Start Websocket
This commit is contained in:
@@ -2,6 +2,7 @@ package io.github.skippyall.minions.gui;
|
||||
|
||||
import eu.pb4.sgui.api.elements.GuiElementBuilder;
|
||||
import eu.pb4.sgui.api.gui.SimpleGui;
|
||||
import io.github.skippyall.minions.program.value.ValueType;
|
||||
import io.github.skippyall.minions.registration.MinionComponentTypes;
|
||||
import io.github.skippyall.minions.registration.MinionRegistries;
|
||||
import io.github.skippyall.minions.gui.input.Result;
|
||||
@@ -129,20 +130,22 @@ public class InstructionGui {
|
||||
gui.open();
|
||||
}
|
||||
|
||||
public static CompletableFuture<ValueSupplierType<MinionRuntime>> selectArgumentType(ServerPlayerEntity player, MinionFakePlayer minion, ConfiguredInstruction<MinionRuntime> instruction) {
|
||||
public static CompletableFuture<ValueSupplierType<MinionRuntime>> selectArgumentType(ServerPlayerEntity player, MinionFakePlayer minion, ValueType<?> valueType, ConfiguredInstruction<MinionRuntime> instruction) {
|
||||
CompletableFuture<ValueSupplierType<MinionRuntime>> future = new CompletableFuture<>();
|
||||
SimpleGui gui = new InstructionBoundSimpleGui(ScreenHandlerType.GENERIC_9X3, player, minion, instruction);
|
||||
for (ValueSupplierType<MinionRuntime> type : MinionRegistries.VALUE_SUPPLIER_TYPES) {
|
||||
gui.addSlot(new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_SUPPLIER_TYPES, type, player.getRegistryManager()))
|
||||
.setCallback(() -> future.complete(type))
|
||||
);
|
||||
if(type.isConfigurable(player, valueType, minion)) {
|
||||
gui.addSlot(new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_SUPPLIER_TYPES, type, player.getRegistryManager()))
|
||||
.setCallback(() -> future.complete(type))
|
||||
);
|
||||
}
|
||||
}
|
||||
gui.open();
|
||||
return future;
|
||||
}
|
||||
|
||||
public static <T> void configureTypeAndValue(String name, ConfiguredInstruction<MinionRuntime> instruction, Parameter<T> parameter, MinionFakePlayer minion, ServerPlayerEntity player) {
|
||||
selectArgumentType(player, minion, instruction)
|
||||
selectArgumentType(player, minion, parameter.type(), instruction)
|
||||
.thenApply(type -> type.openConfiguration(player, parameter.type(), null)
|
||||
.thenAccept(newArgument -> {
|
||||
instruction.getArguments().setArgument(parameter, newArgument);
|
||||
|
||||
@@ -3,17 +3,17 @@ package io.github.skippyall.minions.listener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
public class ListenerManager<T> implements Iterable<T> {
|
||||
protected final List<T> listeners;
|
||||
protected final Set<T> listeners;
|
||||
|
||||
public ListenerManager() {
|
||||
this(new CopyOnWriteArrayList<>());
|
||||
this(new CopyOnWriteArraySet<>());
|
||||
}
|
||||
|
||||
protected ListenerManager(List<T> listeners) {
|
||||
protected ListenerManager(Set<T> listeners) {
|
||||
this.listeners = listeners;
|
||||
}
|
||||
|
||||
@@ -23,34 +23,15 @@ public class ListenerManager<T> implements Iterable<T> {
|
||||
|
||||
public void removeListener(T listener) {
|
||||
listeners.remove(listener);
|
||||
onRemove(listener);
|
||||
}
|
||||
|
||||
protected void onRemove(T listener) {}
|
||||
public boolean hasListener(T listener) {
|
||||
return listeners.contains(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<T> iterator() {
|
||||
return new Iterator<>() {
|
||||
final Iterator<T> backing = listeners.iterator();
|
||||
T last;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return backing.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
last = backing.next();
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
backing.remove();
|
||||
onRemove(last);
|
||||
}
|
||||
};
|
||||
return listeners.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,7 +9,8 @@ import net.minecraft.util.Identifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
public class SerializableListenerManager<T extends SerializableListenerManager.SerializableListener> extends ListenerManager<T> {
|
||||
private final Registry<Codec<? extends T>> registry;
|
||||
@@ -18,7 +19,7 @@ public class SerializableListenerManager<T extends SerializableListenerManager.S
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public SerializableListenerManager(Registry<Codec<? extends T>> registry, List<T> listeners) {
|
||||
private SerializableListenerManager(Registry<Codec<? extends T>> registry, Set<T> listeners) {
|
||||
super(listeners);
|
||||
this.registry = registry;
|
||||
}
|
||||
@@ -28,7 +29,7 @@ public class SerializableListenerManager<T extends SerializableListenerManager.S
|
||||
listener -> listener.getCodecId().map(registry::get).orElse(Codec.unit(null)),
|
||||
codec -> codec.fieldOf("data")
|
||||
).listOf().xmap(
|
||||
list -> new SerializableListenerManager<>(registry, new CopyOnWriteArrayList<>(list)),
|
||||
list -> new SerializableListenerManager<>(registry, new CopyOnWriteArraySet<>(list)),
|
||||
manager -> {
|
||||
List<T> serializableListeners = new ArrayList<>();
|
||||
for(T listener : manager.listeners) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.github.skippyall.minions.minion;
|
||||
|
||||
import io.github.skippyall.minions.program.instruction.ConfiguredInstructionListener;
|
||||
import io.github.skippyall.minions.registration.MinionRegistries;
|
||||
import io.github.skippyall.minions.Minions;
|
||||
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
|
||||
@@ -16,10 +17,12 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class MinionRuntime implements InstructionRuntime<MinionRuntime> {
|
||||
private final MinionFakePlayer minion;
|
||||
private final Map<String, ConfiguredInstruction<MinionRuntime>> configuredInstructions = new HashMap<>();
|
||||
private final Map<Integer, ConfiguredInstruction<MinionRuntime>> webSocketInstructions = new ConcurrentHashMap<>();
|
||||
|
||||
public MinionRuntime(MinionFakePlayer minion) {
|
||||
this.minion = minion;
|
||||
@@ -33,6 +36,10 @@ public class MinionRuntime implements InstructionRuntime<MinionRuntime> {
|
||||
for (ConfiguredInstruction<MinionRuntime> instruction : configuredInstructions.values()) {
|
||||
instruction.tick(this);
|
||||
}
|
||||
|
||||
for(ConfiguredInstruction<MinionRuntime> instruction : webSocketInstructions.values()) {
|
||||
instruction.tick(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableInstructionType(InstructionType<MinionRuntime> instructionType) {
|
||||
@@ -49,6 +56,11 @@ public class MinionRuntime implements InstructionRuntime<MinionRuntime> {
|
||||
instruction.updatePauseStatus(this);
|
||||
}
|
||||
}
|
||||
for(ConfiguredInstruction<MinionRuntime> instruction : webSocketInstructions.values()) {
|
||||
if(instruction.getInstruction() == instructionType) {
|
||||
instruction.updatePauseStatus(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,6 +114,16 @@ public class MinionRuntime implements InstructionRuntime<MinionRuntime> {
|
||||
}
|
||||
}
|
||||
|
||||
public void addWebSocketInstruction(int id, ConfiguredInstruction<MinionRuntime> instruction) {
|
||||
webSocketInstructions.put(id, instruction);
|
||||
instruction.addListener(new ConfiguredInstructionListener() {
|
||||
@Override
|
||||
public void onStop(ConfiguredInstruction<?> instruction) {
|
||||
webSocketInstructions.remove(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void save(WriteView view) {
|
||||
WriteView.ListView list = view.getList("configuredInstructions");
|
||||
for (Map.Entry<String, ConfiguredInstruction<MinionRuntime>> instruction : configuredInstructions.entrySet()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.github.skippyall.minions.program.consumer;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
|
||||
import io.github.skippyall.minions.program.InstructionRuntime;
|
||||
import io.github.skippyall.minions.program.value.ValueType;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
@@ -8,8 +9,12 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public abstract class ValueConsumerType<R extends InstructionRuntime<R>> {
|
||||
public abstract <T> Codec<? extends ValueConsumer<T,R>> getCodec(ValueType<T> type);
|
||||
public interface ValueConsumerType<R extends InstructionRuntime<R>> {
|
||||
<T> Codec<? extends ValueConsumer<T,R>> getCodec(ValueType<T> type);
|
||||
|
||||
public abstract <T> CompletableFuture<? extends ValueConsumer<T,R>> openConfiguration(ServerPlayerEntity player, ValueType<T> valueType, @Nullable ValueConsumer<T,R> previous);
|
||||
default boolean isConfigurable(ServerPlayerEntity player, ValueType<?> valueType, MinionFakePlayer minion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
<T> CompletableFuture<? extends ValueConsumer<T,R>> openConfiguration(ServerPlayerEntity player, ValueType<T> valueType, @Nullable ValueConsumer<T,R> previous);
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ public class ConfiguredInstruction<R extends InstructionRuntime<R>> {
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
private ConfiguredInstruction(InstructionType<R> instruction, ValueSupplierList<R> arguments, ValueConsumerList<R> valueConsumers, @Nullable InstructionExecution<R> execution) {
|
||||
public ConfiguredInstruction(InstructionType<R> instruction, ValueSupplierList<R> arguments, ValueConsumerList<R> valueConsumers, @Nullable InstructionExecution<R> execution) {
|
||||
this.instruction = instruction;
|
||||
this.arguments = arguments;
|
||||
this.valueConsumers = valueConsumers;
|
||||
|
||||
+5
-6
@@ -8,10 +8,6 @@ import net.minecraft.storage.WriteView;
|
||||
|
||||
/**
|
||||
* Responsible for executing instructions.
|
||||
* When an instruction is executed:
|
||||
* <li>A new instance is created using the factory</li>
|
||||
* <li>{@link InstructionExecution#readArguments(ValueSupplierList, R) readFromParameters} is called</li>
|
||||
* <li>{@link InstructionExecution#start(R) start} is called</li>
|
||||
*/
|
||||
public interface InstructionExecution<R extends InstructionRuntime<R>> {
|
||||
/**
|
||||
@@ -26,18 +22,21 @@ public interface InstructionExecution<R extends InstructionRuntime<R>> {
|
||||
default void tick(R runtime) {}
|
||||
|
||||
/**
|
||||
* Called every tick to determine if the execution of this instruction should be stopped.
|
||||
* Called before and after {@code tick} to determine if the execution of this instruction should be stopped.
|
||||
* @return <code>true</code> if the instruction is done, <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isDone(R runtime);
|
||||
|
||||
/**
|
||||
* Called when the instruction is paused. This freezes the instruction in its current state.
|
||||
*/
|
||||
default void pause(R runtime) {}
|
||||
|
||||
default void resume(R runtime) {}
|
||||
|
||||
/**
|
||||
* Stops this execution. Is called when isDone returns true, but it may also be called before that.
|
||||
* This should undo changes to the minion unless they are supposed to be permanent.
|
||||
* This should undo temporary changes to the minion.
|
||||
*
|
||||
* @param runtime The runtime that was executing this instruction.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class FixedValueSupplierType<R extends InstructionRuntime<R>> extends ValueSupplierType<R> {
|
||||
public class FixedValueSupplierType<R extends InstructionRuntime<R>> implements ValueSupplierType<R> {
|
||||
@Override
|
||||
public <T> Codec<FixedValueSupplier<T,R>> getCodec(ValueType<T> valueType) {
|
||||
return valueType.codec().xmap(value -> new FixedValueSupplier<>(this, valueType, value), FixedValueSupplier::getValue);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.github.skippyall.minions.program.supplier;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
|
||||
import io.github.skippyall.minions.program.InstructionRuntime;
|
||||
import io.github.skippyall.minions.program.value.ValueType;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
@@ -8,8 +9,12 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public abstract class ValueSupplierType<R extends InstructionRuntime<R>> {
|
||||
public abstract <T> Codec<? extends ValueSupplier<T,R>> getCodec(ValueType<T> type);
|
||||
public interface ValueSupplierType<R extends InstructionRuntime<R>> {
|
||||
<T> Codec<? extends ValueSupplier<T,R>> getCodec(ValueType<T> type);
|
||||
|
||||
public abstract <T> CompletableFuture<? extends ValueSupplier<T,R>> openConfiguration(ServerPlayerEntity player, ValueType<T> valueType, @Nullable ValueSupplier<T,R> previous);
|
||||
default boolean isConfigurable(ServerPlayerEntity player, ValueType<?> valueType, MinionFakePlayer minion) {
|
||||
return true;
|
||||
}
|
||||
|
||||
<T> CompletableFuture<? extends ValueSupplier<T,R>> openConfiguration(ServerPlayerEntity player, ValueType<T> valueType, @Nullable ValueSupplier<T,R> previous);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public class MinionRegistration {
|
||||
MinionListeners.register();
|
||||
SkinProviders.register();
|
||||
SpecialAbilities.register();
|
||||
ValueConsumers.register();
|
||||
ValueSuppliers.register();
|
||||
ValueTypes.register();
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.github.skippyall.minions.registration;
|
||||
|
||||
import io.github.skippyall.minions.Minions;
|
||||
import io.github.skippyall.minions.minion.MinionRuntime;
|
||||
import io.github.skippyall.minions.program.consumer.ValueConsumerType;
|
||||
import io.github.skippyall.minions.websocket.WebsocketValueConsumer;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
public class ValueConsumers {
|
||||
public static final WebsocketValueConsumer.Type WEBSOCKET = register("websocket", new WebsocketValueConsumer.Type());
|
||||
|
||||
public static <T extends ValueConsumerType<MinionRuntime>> T register(String id, T type) {
|
||||
return Registry.register(MinionRegistries.VALUE_CONSUMER_TYPES, Identifier.of(Minions.MOD_ID, id), type);
|
||||
}
|
||||
|
||||
public static void register() {}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.github.skippyall.minions.websocket;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class Authenticator extends ChannelInboundHandlerAdapter {
|
||||
public static final AttributeKey<Boolean> AUTHENTICATED = AttributeKey.newInstance("authenticated");
|
||||
public static final AttributeKey<UUID> MINION = AttributeKey.newInstance("minion");
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if(!ctx.channel().hasAttr(AUTHENTICATED) && msg instanceof HttpMessage message) {
|
||||
String header = message.headers().get(HttpHeaderNames.AUTHORIZATION);
|
||||
if (header.startsWith("Bearer ")) {
|
||||
String key = header.substring("Bearer ".length()).trim();
|
||||
if (WebsocketServer.keyToMinion.containsKey(key)) {
|
||||
ctx.channel().attr(AUTHENTICATED).set(true);
|
||||
ctx.channel().attr(MINION).set(WebsocketServer.keyToMinion.get(key));
|
||||
} else {
|
||||
ctx.channel().attr(AUTHENTICATED).set(false);
|
||||
}
|
||||
} else {
|
||||
ctx.channel().attr(AUTHENTICATED).set(false);
|
||||
}
|
||||
}
|
||||
|
||||
if(ctx.channel().hasAttr(AUTHENTICATED) && ctx.channel().attr(AUTHENTICATED).get() == Boolean.TRUE) {
|
||||
super.channelRead(ctx, msg);
|
||||
} else {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.github.skippyall.minions.websocket;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonDecoder extends MessageToMessageDecoder<WebSocketFrame> {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) {
|
||||
if(msg instanceof TextWebSocketFrame text) {
|
||||
out.add(JsonParser.parseString(text.text()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.github.skippyall.minions.websocket;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonEncoder extends MessageToMessageEncoder<JsonObject> {
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, JsonObject msg, List<Object> out) {
|
||||
out.add(msg.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.github.skippyall.minions.websocket;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class MessageHandler extends SimpleChannelInboundHandler<JsonObject> {
|
||||
private final WebsocketServer server;
|
||||
|
||||
public MessageHandler(WebsocketServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelActive(ctx);
|
||||
server.handlers.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelInactive(ctx);
|
||||
server.handlers.remove(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, JsonObject msg) throws Exception {
|
||||
UUID minion = ctx.channel().attr(Authenticator.MINION).get();
|
||||
MinionWebsocketManager.get(minion).handleMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
|
||||
|
||||
} else {
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package io.github.skippyall.minions.websocket;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import io.github.skippyall.minions.minion.MinionRuntime;
|
||||
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
|
||||
import io.github.skippyall.minions.program.consumer.ValueConsumerList;
|
||||
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
|
||||
import io.github.skippyall.minions.program.instruction.ConfiguredInstructionListener;
|
||||
import io.github.skippyall.minions.program.instruction.InstructionType;
|
||||
import io.github.skippyall.minions.program.supplier.FixedValueSupplier;
|
||||
import io.github.skippyall.minions.program.supplier.Parameter;
|
||||
import io.github.skippyall.minions.program.supplier.ValueSupplierList;
|
||||
import io.github.skippyall.minions.registration.MinionRegistries;
|
||||
import io.github.skippyall.minions.registration.ValueSuppliers;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class MinionWebsocketManager {
|
||||
private static Map<UUID, MinionWebsocketManager> managers;
|
||||
|
||||
private int currentRequestId = 0;
|
||||
private final Map<Integer, WebsocketRequest> requests = new ConcurrentHashMap<>();
|
||||
private MinionFakePlayer minion;
|
||||
|
||||
public MinionWebsocketManager(MinionFakePlayer minion) {
|
||||
this.minion = minion;
|
||||
}
|
||||
|
||||
public static MinionWebsocketManager get(UUID minion) {
|
||||
return managers.get(minion);
|
||||
}
|
||||
|
||||
public WebsocketRequest getRequest(int id) {
|
||||
return requests.get(id);
|
||||
}
|
||||
|
||||
public void handleMessage(JsonObject msg) {
|
||||
String type = msg.get("type").getAsString();
|
||||
if(type.equals("run_instruction")) {
|
||||
onRunInstruction(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRunInstruction(JsonObject msg) {
|
||||
Identifier id = Identifier.tryParse(msg.get("instruction").getAsString());
|
||||
InstructionType<MinionRuntime> instructionType = MinionRegistries.INSTRUCTION_TYPES.get(id);
|
||||
|
||||
if(instructionType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ValueSupplierList<MinionRuntime> valueSuppliers = new ValueSupplierList<>();
|
||||
JsonObject arguments = msg.get("arguments").getAsJsonObject();
|
||||
for(String argumentName : arguments.keySet()) {
|
||||
JsonElement argument = arguments.get(argumentName);
|
||||
|
||||
Parameter<?> parameter = null;
|
||||
for(Parameter<?> otherParameter : instructionType.getParameters()) {
|
||||
if(otherParameter.name().equals(argumentName)) {
|
||||
parameter = otherParameter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(parameter != null) {
|
||||
addArgument(valueSuppliers, argument, parameter);
|
||||
}
|
||||
}
|
||||
currentRequestId++;
|
||||
int requestId = currentRequestId;
|
||||
|
||||
ValueConsumerList<MinionRuntime> valueConsumers = new ValueConsumerList<>();
|
||||
for(Parameter<?> parameter : instructionType.getReturnParameters()) {
|
||||
valueConsumers.setValueConsumer(parameter, new WebsocketValueConsumer<>(parameter.type(), parameter.name(), requestId));
|
||||
}
|
||||
|
||||
ConfiguredInstruction<MinionRuntime> instruction = new ConfiguredInstruction<>(instructionType, valueSuppliers, new ValueConsumerList<>(), null);
|
||||
}
|
||||
|
||||
public <T> void addArgument(ValueSupplierList<MinionRuntime> valueSuppliers, JsonElement element, Parameter<T> parameter) {
|
||||
if(parameter.type() != null) {
|
||||
Optional<T> value = parameter.type().codec().decode(JsonOps.INSTANCE, element).map(Pair::getFirst).result();
|
||||
if(value.isPresent()) {
|
||||
valueSuppliers.setArgument(parameter, new FixedValueSupplier<>(ValueSuppliers.FIXED_VALUE_SUPPLIER_TYPE, parameter.type(), value.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class WebsocketRequest implements ConfiguredInstructionListener {
|
||||
private JsonObject returnObject = null;
|
||||
private ConfiguredInstruction<MinionRuntime> instruction;
|
||||
|
||||
public WebsocketRequest(ConfiguredInstruction<MinionRuntime> instruction) {
|
||||
this.instruction = instruction;
|
||||
instruction.addListener(this);
|
||||
}
|
||||
|
||||
public void acceptReturnValue(String key, JsonElement value) {
|
||||
if(returnObject == null) {
|
||||
returnObject = new JsonObject();
|
||||
}
|
||||
returnObject.add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package io.github.skippyall.minions.websocket;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WebsocketServer {
|
||||
public static final Map<String, UUID> keyToMinion = new HashMap<>();
|
||||
|
||||
private final NioEventLoopGroup group = new NioEventLoopGroup();
|
||||
private Channel channel;
|
||||
List<MessageHandler> handlers;
|
||||
|
||||
private String host;
|
||||
private int port;
|
||||
private SslContext sslCtx;
|
||||
|
||||
public void start() {
|
||||
channel = new ServerBootstrap()
|
||||
.group(group)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.handler(new LoggingHandler(LogLevel.INFO))
|
||||
.childHandler(new WebSocketServerInitializer(sslCtx, this))
|
||||
.bind(host, port)
|
||||
.syncUninterruptibly()
|
||||
.channel();
|
||||
}
|
||||
|
||||
public void stop() throws InterruptedException {
|
||||
if(channel != null) {
|
||||
channel.close().sync();
|
||||
}
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
|
||||
public static class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
private static final int MAX_CONTENT_LENGTH = 65536;
|
||||
|
||||
private final SslContext sslCtx;
|
||||
private final WebsocketServer server;
|
||||
|
||||
public WebSocketServerInitializer(SslContext sslCtx, WebsocketServer server) {
|
||||
this.sslCtx = sslCtx;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
if (sslCtx != null) {
|
||||
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
|
||||
}
|
||||
pipeline.addLast(new HttpServerCodec())
|
||||
.addLast(new HttpObjectAggregator(MAX_CONTENT_LENGTH))
|
||||
.addLast(new Authenticator())
|
||||
.addLast(new WebSocketServerCompressionHandler(MAX_CONTENT_LENGTH))
|
||||
.addLast(new WebSocketServerProtocolHandler("/", null, true))
|
||||
.addLast(new JsonDecoder())
|
||||
.addLast(new JsonEncoder())
|
||||
.addLast(new MessageHandler(server));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.github.skippyall.minions.websocket;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import io.github.skippyall.minions.Minions;
|
||||
import io.github.skippyall.minions.minion.MinionRuntime;
|
||||
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
|
||||
import io.github.skippyall.minions.program.consumer.ValueConsumer;
|
||||
import io.github.skippyall.minions.program.consumer.ValueConsumerType;
|
||||
import io.github.skippyall.minions.program.value.ValueType;
|
||||
import io.github.skippyall.minions.registration.ValueConsumers;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class WebsocketValueConsumer<T> implements ValueConsumer<T, MinionRuntime> {
|
||||
private final ValueType<T> valueType;
|
||||
private final String key;
|
||||
private final int id;
|
||||
|
||||
public WebsocketValueConsumer(ValueType<T> valueType, String key, int id) {
|
||||
this.valueType = valueType;
|
||||
this.key = key;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(T value, MinionRuntime runtime) {
|
||||
DataResult<JsonElement> encoded = valueType.codec().encodeStart(JsonOps.INSTANCE, value);
|
||||
if(encoded.hasResultOrPartial()) {
|
||||
MinionWebsocketManager.get(runtime.getMinion().getUuid()).getRequest(id).acceptReturnValue(key, encoded.getPartialOrThrow());
|
||||
}
|
||||
encoded.ifError(error -> Minions.LOGGER.error("Error while encoding value for websocket: {}", error.message()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueType<T> getValueType() {
|
||||
return valueType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueConsumerType<MinionRuntime> getType() {
|
||||
return ValueConsumers.WEBSOCKET;
|
||||
}
|
||||
|
||||
public static class Type implements ValueConsumerType<MinionRuntime> {
|
||||
@Override
|
||||
public <T> Codec<? extends ValueConsumer<T, MinionRuntime>> getCodec(ValueType<T> type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable(ServerPlayerEntity player, ValueType<?> valueType, MinionFakePlayer minion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> CompletableFuture<? extends ValueConsumer<T, MinionRuntime>> openConfiguration(ServerPlayerEntity player, ValueType<T> valueType, @Nullable ValueConsumer<T, MinionRuntime> previous) {
|
||||
return CompletableFuture.failedFuture(new UnsupportedOperationException());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user