XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
package com.xfestudio.xfeservermanager.core.trigger;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * Resumable, platform-neutral Trigger Program V2 VM. It evaluates pure control flow and yields
 * bounded side effects to the owning platform; no Minecraft/JVM object enters persisted state.
 */
public final class TriggerProgramExecutor {
    private TriggerProgramExecutor() { }

    public enum Status { YIELDED, WAITING, COMPLETED, FAILED }

    public record Effect(UUID nodeId, String type, Map<String, Object> parameters,
                         String idempotencyKey) {
        public Effect { parameters = immutable(parameters); }
    }

    public record CallFrame(int returnPc, Map<String, Object> callerVariables,
                            Map<String, String> outTargets, String returnTarget,
                            int handlerDepth) {
        public CallFrame {
            callerVariables = immutable(callerVariables);
            outTargets = Map.copyOf(outTargets == null ? Map.of() : outTargets);
            returnTarget = returnTarget == null ? "" : returnTarget;
        }
    }

    public record LoopState(String kind, long index, long count, List<Object> values, String variable) {
        public LoopState {
            kind = Objects.requireNonNull(kind, "kind");
            values = List.copyOf(values == null ? List.of() : values);
            variable = variable == null ? "" : variable;
        }
    }

    /** Entire continuation required to restart against the pinned trigger revision. */
    public record State(
            UUID executionId,
            long seed,
            String instant,
            Map<String, Object> event,
            Map<String, Object> variables,
            int pc,
            int instructions,
            long randomCounter,
            Map<String, Long> effectCounters,
            List<CallFrame> callStack,
            Map<String, LoopState> loops,
            List<Integer> handlers) {
        public State {
            executionId = executionId == null ? UUID.randomUUID() : executionId;
            instant = instant == null || instant.isBlank() ? Instant.EPOCH.toString() : instant;
            event = immutable(event);
            variables = immutable(variables);
            effectCounters = Map.copyOf(effectCounters == null ? Map.of() : effectCounters);
            callStack = List.copyOf(callStack == null ? List.of() : callStack);
            loops = Map.copyOf(loops == null ? Map.of() : loops);
            handlers = List.copyOf(handlers == null ? List.of() : handlers);
            if (pc < 0 || instructions < 0 || randomCounter < 0) {
                throw new IllegalArgumentException("VM counters must not be negative");
            }
        }
    }

    public record Result(Status status, State state, List<Effect> effects,
                         String error, UUID errorNodeId) {
        public Result {
            Objects.requireNonNull(status, "status");
            Objects.requireNonNull(state, "state");
            effects = List.copyOf(effects == null ? List.of() : effects);
            error = error == null ? "" : error;
        }
    }

    public static State start(TriggerProgramV2 program, Map<String, Object> event,
                              Map<String, Object> suppliedVariables, long seed, Instant instant) {
        Objects.requireNonNull(program, "program");
        Map<String, Object> variables = new LinkedHashMap<>(
                suppliedVariables == null ? Map.of() : suppliedVariables);
        TriggerExpressionEvaluator evaluator = new TriggerExpressionEvaluator(
                event, variables, seed, instant, null, 0L);
        for (TriggerProgramV2.VariableDeclaration declaration : program.declarations()) {
            if (!variables.containsKey(declaration.name())) {
                variables.put(declaration.name(), declaration.initialValue() == null
                        ? null : evaluator.evaluate(declaration.initialValue()));
            }
        }
        return new State(UUID.randomUUID(), seed, (instant == null ? Instant.EPOCH : instant).toString(),
                event, variables, 0, 0, evaluator.randomCounter(), Map.of(), List.of(), Map.of(), List.of());
    }

    /** Runs until completion, a wait action, or the side-effect allowance is consumed. */
    public static Result resume(TriggerProgramV2 program, State persisted, int maximumEffects) {
        Objects.requireNonNull(program, "program");
        Objects.requireNonNull(persisted, "state");
        if (maximumEffects < 1 || maximumEffects > 64) {
            throw new IllegalArgumentException("maximumEffects must be between 1 and 64");
        }
        Program bytecode = Compiler.compile(program);
        Machine machine = new Machine(bytecode, persisted);
        return machine.run(maximumEffects);
    }

    private enum Op {
        ACTION, SET, JUMP, JUMP_FALSE, SWITCH, REPEAT_INIT, REPEAT_NEXT,
        WHILE_GUARD, FOREACH_INIT, FOREACH_NEXT, CALL, RETURN,
        PUSH_HANDLER, POP_HANDLER, END
    }

    private static final class Instruction {
        private final Op op;
        private final TriggerProgramV2.Statement statement;
        private int target;
        private int alternate;
        private Map<UUID, Integer> cases = Map.of();

        private Instruction(Op op, TriggerProgramV2.Statement statement) {
            this.op = op;
            this.statement = statement;
        }
    }

    private record Program(List<Instruction> instructions,
                           Map<String, Integer> functions,
                           Map<String, TriggerProgramV2.FunctionDeclaration> declarations) { }

    private record LoopTargets(int continuePc, List<Instruction> breaks) { }

    private static final class Compiler {
        private final TriggerProgramV2 source;
        private final List<Instruction> instructions = new ArrayList<>();
        private final Map<String, Integer> functions = new LinkedHashMap<>();
        private final Map<String, TriggerProgramV2.FunctionDeclaration> declarations = new LinkedHashMap<>();

        private Compiler(TriggerProgramV2 source) { this.source = source; }

        private static Program compile(TriggerProgramV2 source) {
            Compiler compiler = new Compiler(source);
            source.functions().forEach(value -> compiler.declarations.put(value.name(), value));
            compiler.statements(source.statements(), null);
            compiler.add(Op.END, null);
            for (TriggerProgramV2.FunctionDeclaration function : source.functions()) {
                compiler.functions.put(function.name(), compiler.instructions.size());
                compiler.statements(function.statements(), null);
                compiler.add(Op.RETURN, null);
            }
            return new Program(List.copyOf(compiler.instructions), Map.copyOf(compiler.functions),
                    Map.copyOf(compiler.declarations));
        }

        private void statements(List<TriggerProgramV2.Statement> values, LoopTargets loop) {
            for (TriggerProgramV2.Statement statement : values) statement(statement, loop);
        }

        private void statement(TriggerProgramV2.Statement statement, LoopTargets loop) {
            switch (statement.kind()) {
                case ACTION -> add(Op.ACTION, statement);
                case SET -> add(Op.SET, statement);
                case IF -> branch(statement, loop);
                case SWITCH -> switchStatement(statement, loop);
                case REPEAT -> repeat(statement);
                case WHILE -> whileStatement(statement);
                case FOREACH -> foreach(statement);
                case BREAK -> {
                    if (loop == null) throw new IllegalArgumentException("break used outside a loop");
                    Instruction jump = add(Op.JUMP, statement);
                    loop.breaks().add(jump);
                }
                case CONTINUE -> {
                    if (loop == null) throw new IllegalArgumentException("continue used outside a loop");
                    Instruction jump = add(Op.JUMP, statement);
                    jump.target = loop.continuePc();
                }
                case CALL -> add(Op.CALL, statement);
                case RETURN -> add(Op.RETURN, statement);
                case TRY -> tryStatement(statement, loop);
            }
        }

        private void branch(TriggerProgramV2.Statement statement, LoopTargets loop) {
            Instruction condition = add(Op.JUMP_FALSE, statement);
            statements(statement.statements(), loop);
            Instruction end = add(Op.JUMP, statement);
            condition.target = instructions.size();
            statements(statement.elseStatements(), loop);
            end.target = instructions.size();
        }

        private void switchStatement(TriggerProgramV2.Statement statement, LoopTargets loop) {
            Instruction selector = add(Op.SWITCH, statement);
            Map<UUID, Integer> targets = new LinkedHashMap<>();
            List<Instruction> ends = new ArrayList<>();
            for (TriggerProgramV2.SwitchCase branch : statement.cases()) {
                targets.put(branch.nodeId(), instructions.size());
                statements(branch.statements(), loop);
                ends.add(add(Op.JUMP, statement));
            }
            selector.alternate = instructions.size();
            statements(statement.elseStatements(), loop);
            int end = instructions.size();
            ends.forEach(value -> value.target = end);
            selector.cases = Map.copyOf(targets);
        }

        private void repeat(TriggerProgramV2.Statement statement) {
            Instruction initial = add(Op.REPEAT_INIT, statement);
            int body = instructions.size();
            Instruction nextMarker = new Instruction(Op.REPEAT_NEXT, statement);
            LoopTargets loop = new LoopTargets(-1, new ArrayList<>());
            statements(statement.statements(), loop);
            int next = instructions.size();
            nextMarker.target = body;
            instructions.add(nextMarker);
            int end = instructions.size();
            initial.target = end;
            loop.breaks().forEach(value -> value.target = end);
            patchContinues(body, next, end);
        }

        private void whileStatement(TriggerProgramV2.Statement statement) {
            int guardPc = instructions.size();
            Instruction guard = add(Op.WHILE_GUARD, statement);
            LoopTargets loop = new LoopTargets(guardPc, new ArrayList<>());
            statements(statement.statements(), loop);
            Instruction back = add(Op.JUMP, statement);
            back.target = guardPc;
            int end = instructions.size();
            guard.target = end;
            loop.breaks().forEach(value -> value.target = end);
        }

        private void foreach(TriggerProgramV2.Statement statement) {
            Instruction initial = add(Op.FOREACH_INIT, statement);
            int body = instructions.size();
            LoopTargets loop = new LoopTargets(-1, new ArrayList<>());
            statements(statement.statements(), loop);
            int next = instructions.size();
            Instruction advance = add(Op.FOREACH_NEXT, statement);
            advance.target = body;
            int end = instructions.size();
            initial.target = end;
            loop.breaks().forEach(value -> value.target = end);
            patchContinues(body, next, end);
        }

        private void patchContinues(int from, int next, int end) {
            for (int index = from; index < next; index++) {
                Instruction value = instructions.get(index);
                if (value.op == Op.JUMP && value.statement != null
                        && value.statement.kind() == TriggerProgramV2.StatementKind.CONTINUE
                        && value.target < 0) value.target = next;
            }
        }

        private void tryStatement(TriggerProgramV2.Statement statement, LoopTargets loop) {
            Instruction push = add(Op.PUSH_HANDLER, statement);
            statements(statement.statements(), loop);
            add(Op.POP_HANDLER, statement);
            Instruction end = add(Op.JUMP, statement);
            push.target = instructions.size();
            statements(statement.elseStatements(), loop);
            end.target = instructions.size();
        }

        private Instruction add(Op op, TriggerProgramV2.Statement statement) {
            Instruction value = new Instruction(op, statement);
            value.target = -1;
            value.alternate = -1;
            instructions.add(value);
            return value;
        }
    }

    private static final class Machine {
        private final Program program;
        private final UUID executionId;
        private final long seed;
        private final String instant;
        private final Map<String, Object> event;
        private final Map<String, Object> variables;
        private final Map<String, Long> effectCounters;
        private final List<CallFrame> callStack;
        private final Map<String, LoopState> loops;
        private final List<Integer> handlers;
        private final List<Effect> effects = new ArrayList<>();
        private int pc;
        private int instructions;
        private TriggerExpressionEvaluator evaluator;

        private Machine(Program program, State state) {
            this.program = program;
            executionId = state.executionId();
            seed = state.seed();
            instant = state.instant();
            event = new LinkedHashMap<>(state.event());
            variables = new LinkedHashMap<>(state.variables());
            effectCounters = new LinkedHashMap<>(state.effectCounters());
            callStack = new ArrayList<>(state.callStack());
            loops = new LinkedHashMap<>(state.loops());
            handlers = new ArrayList<>(state.handlers());
            pc = state.pc();
            instructions = state.instructions();
            rebuildEvaluator(state.randomCounter());
        }

        private void rebuildEvaluator(long counter) {
            evaluator = new TriggerExpressionEvaluator(event, variables, seed, Instant.parse(instant), null, counter);
        }

        private Result run(int maximumEffects) {
            UUID nodeId = null;
            try {
                while (pc < program.instructions().size()) {
                    Instruction instruction = program.instructions().get(pc);
                    nodeId = instruction.statement == null ? null : instruction.statement.nodeId();
                    if (++instructions > TriggerProgramValidator.MAX_INSTRUCTIONS) {
                        throw new IllegalStateException("instruction budget exceeded");
                    }
                    try {
                        Status status = execute(instruction);
                        if (status != null) return result(status, "", null);
                        if (effects.size() >= maximumEffects) return result(Status.YIELDED, "", null);
                    } catch (RuntimeException failure) {
                        if (handlers.isEmpty()) throw failure;
                        pc = handlers.remove(handlers.size() - 1);
                        variables.put("error.message", message(failure));
                        rebuildEvaluator(evaluator.randomCounter());
                    }
                }
                return result(Status.COMPLETED, "", null);
            } catch (RuntimeException failure) {
                return result(Status.FAILED, message(failure), nodeId);
            }
        }

        private Status execute(Instruction instruction) {
            TriggerProgramV2.Statement statement = instruction.statement;
            switch (instruction.op) {
                case ACTION -> {
                    Map<String, Object> parameters = evaluateInputs(statement.inputs());
                    long invocation = effectCounters.merge(statement.nodeId().toString(), 1L, Long::sum) - 1L;
                    effects.add(new Effect(statement.nodeId(), statement.name(), parameters,
                            executionId + ":" + statement.nodeId() + ":" + invocation));
                    pc++;
                    if (statement.name().equals("wait")) return Status.WAITING;
                }
                case SET -> {
                    variables.put(referenceName(statement.name()), evaluator.evaluate(statement.expression()));
                    pc++;
                }
                case JUMP -> pc = instruction.target;
                case JUMP_FALSE -> pc = evaluator.evaluateBoolean(statement.expression())
                        ? pc + 1 : instruction.target;
                case SWITCH -> selectSwitch(instruction);
                case REPEAT_INIT -> repeatInitial(instruction);
                case REPEAT_NEXT -> repeatNext(instruction);
                case WHILE_GUARD -> whileGuard(instruction);
                case FOREACH_INIT -> foreachInitial(instruction);
                case FOREACH_NEXT -> foreachNext(instruction);
                case CALL -> call(instruction);
                case RETURN -> returned(instruction);
                case PUSH_HANDLER -> { handlers.add(instruction.target); pc++; }
                case POP_HANDLER -> { if (!handlers.isEmpty()) handlers.remove(handlers.size() - 1); pc++; }
                case END -> { return Status.COMPLETED; }
            }
            return null;
        }

        private void selectSwitch(Instruction instruction) {
            Object selector = evaluator.evaluate(instruction.statement.expression());
            for (TriggerProgramV2.SwitchCase branch : instruction.statement.cases()) {
                if (equal(selector, evaluator.evaluate(branch.match()))) {
                    pc = instruction.cases.get(branch.nodeId());
                    return;
                }
            }
            pc = instruction.alternate;
        }

        private void repeatInitial(Instruction instruction) {
            long count = integral(evaluator.evaluate(instruction.statement.expression()));
            if (count < 0 || count > TriggerProgramValidator.MAX_LOOP_ITERATIONS) {
                throw new IllegalArgumentException("repeat count exceeds loop budget");
            }
            String key = instruction.statement.nodeId().toString();
            if (count == 0) { loops.remove(key); pc = instruction.target; return; }
            loops.put(key, new LoopState("repeat", 0, count, List.of(), "loop.index"));
            variables.put("loop.index", 0L);
            pc++;
        }

        private void repeatNext(Instruction instruction) {
            String key = instruction.statement.nodeId().toString();
            LoopState state = requiredLoop(key);
            long next = state.index() + 1;
            if (next >= state.count()) { loops.remove(key); pc++; return; }
            loops.put(key, new LoopState(state.kind(), next, state.count(), state.values(), state.variable()));
            variables.put("loop.index", next);
            pc = instruction.target;
        }

        private void whileGuard(Instruction instruction) {
            String key = instruction.statement.nodeId().toString();
            if (!evaluator.evaluateBoolean(instruction.statement.expression())) {
                loops.remove(key); pc = instruction.target; return;
            }
            long count = loops.containsKey(key) ? loops.get(key).count() + 1 : 1;
            if (count > TriggerProgramValidator.MAX_LOOP_ITERATIONS) {
                throw new IllegalStateException("while loop exceeds loop budget");
            }
            loops.put(key, new LoopState("while", 0, count, List.of(), ""));
            pc++;
        }

        private void foreachInitial(Instruction instruction) {
            Object source = evaluator.evaluate(instruction.statement.expression());
            List<Object> values = collection(source);
            if (values.size() > TriggerProgramValidator.MAX_LOOP_ITERATIONS) {
                throw new IllegalArgumentException("foreach exceeds loop budget");
            }
            String key = instruction.statement.nodeId().toString();
            if (values.isEmpty()) { loops.remove(key); pc = instruction.target; return; }
            loops.put(key, new LoopState("foreach", 0, values.size(), values, instruction.statement.name()));
            variables.put(referenceName(instruction.statement.name()), values.get(0));
            variables.put("loop.index", 0L);
            pc++;
        }

        private void foreachNext(Instruction instruction) {
            String key = instruction.statement.nodeId().toString();
            LoopState state = requiredLoop(key);
            long next = state.index() + 1;
            if (next >= state.values().size()) { loops.remove(key); pc++; return; }
            loops.put(key, new LoopState(state.kind(), next, state.count(), state.values(), state.variable()));
            variables.put(referenceName(state.variable()), state.values().get((int) next));
            variables.put("loop.index", next);
            pc = instruction.target;
        }

        private void call(Instruction instruction) {
            TriggerProgramV2.FunctionDeclaration function = program.declarations().get(instruction.statement.name());
            Integer entry = program.functions().get(instruction.statement.name());
            if (function == null || entry == null) throw new IllegalArgumentException(
                    "unknown function: " + instruction.statement.name());
            if (callStack.size() >= TriggerProgramValidator.MAX_CALL_DEPTH) {
                throw new IllegalStateException("function call depth exceeded");
            }
            Map<String, Object> caller = new LinkedHashMap<>(variables);
            Map<String, Object> arguments = evaluateInputs(instruction.statement.inputs());
            Map<String, String> out = new LinkedHashMap<>();
            for (TriggerProgramV2.Parameter parameter : function.parameters()) {
                TriggerExpression input = instruction.statement.inputs().get(parameter.name());
                if (parameter.mode() != TriggerProgramV2.ParameterMode.IN) {
                    out.put(parameter.name(), referenceName(input.name()));
                }
            }
            String returnTarget = instruction.statement.expression() == null ? ""
                    : referenceName(instruction.statement.expression().name());
            callStack.add(new CallFrame(pc + 1, caller, out, returnTarget, handlers.size()));
            variables.clear();
            variables.putAll(caller);
            for (TriggerProgramV2.Parameter parameter : function.parameters()) {
                variables.put(parameter.name(), parameter.mode() == TriggerProgramV2.ParameterMode.OUT
                        ? null : arguments.get(parameter.name()));
            }
            for (TriggerProgramV2.VariableDeclaration local : function.locals()) {
                variables.put(local.name(), local.initialValue() == null ? null
                        : evaluator.evaluate(local.initialValue()));
            }
            rebuildEvaluator(evaluator.randomCounter());
            pc = entry;
        }

        private void returned(Instruction instruction) {
            Object value = instruction.statement == null || instruction.statement.expression() == null
                    ? null : evaluator.evaluate(instruction.statement.expression());
            if (callStack.isEmpty()) { pc = program.instructions().size(); return; }
            CallFrame frame = callStack.remove(callStack.size() - 1);
            Map<String, Object> callee = new LinkedHashMap<>(variables);
            variables.clear();
            variables.putAll(frame.callerVariables());
            frame.outTargets().forEach((parameter, target) -> variables.put(target, callee.get(parameter)));
            if (!frame.returnTarget().isBlank()) variables.put(frame.returnTarget(), value);
            while (handlers.size() > frame.handlerDepth()) handlers.remove(handlers.size() - 1);
            rebuildEvaluator(evaluator.randomCounter());
            pc = frame.returnPc();
        }

        private Map<String, Object> evaluateInputs(Map<String, TriggerExpression> inputs) {
            Map<String, Object> values = new LinkedHashMap<>();
            inputs.forEach((name, value) -> values.put(name, evaluator.evaluate(value)));
            return values;
        }

        private LoopState requiredLoop(String key) {
            LoopState state = loops.get(key);
            if (state == null) throw new IllegalStateException("loop continuation is missing");
            return state;
        }

        private Result result(Status status, String error, UUID errorNode) {
            State state = new State(executionId, seed, instant, event, variables, pc, instructions,
                    evaluator.randomCounter(), effectCounters, callStack, loops, handlers);
            return new Result(status, state, effects, error, errorNode);
        }
    }

    private static String referenceName(String value) {
        if (value == null) return "";
        return value.startsWith("var.") || value.startsWith("global.") || value.startsWith("local.")
                ? value.substring(value.indexOf('.') + 1) : value;
    }

    private static long integral(Object value) {
        if (value instanceof Number number) {
            double exact = number.doubleValue();
            if (!Double.isFinite(exact) || exact != Math.rint(exact)) {
                throw new IllegalArgumentException("loop count must be an integer");
            }
            return number.longValue();
        }
        try { return Long.parseLong(String.valueOf(value)); }
        catch (NumberFormatException invalid) {
            throw new IllegalArgumentException("loop count must be an integer", invalid);
        }
    }

    private static boolean equal(Object left, Object right) {
        if (left instanceof Number a && right instanceof Number b) {
            return Double.compare(a.doubleValue(), b.doubleValue()) == 0;
        }
        return Objects.equals(left, right);
    }

    private static List<Object> collection(Object source) {
        if (source instanceof Collection<?> collection) return new ArrayList<>(collection);
        if (source instanceof Map<?, ?> map) {
            List<Object> result = new ArrayList<>();
            map.forEach((key, value) -> {
                Map<String, Object> entry = new LinkedHashMap<>();
                entry.put("key", key);
                entry.put("value", value);
                result.add(java.util.Collections.unmodifiableMap(entry));
            });
            return result;
        }
        throw new IllegalArgumentException("foreach requires a collection");
    }

    private static String message(RuntimeException failure) {
        return failure.getMessage() == null ? failure.getClass().getSimpleName() : failure.getMessage();
    }

    private static Map<String, Object> immutable(Map<String, ?> values) {
        if (values == null || values.isEmpty()) return Map.of();
        Map<String, Object> copy = new LinkedHashMap<>();
        values.forEach(copy::put);
        return java.util.Collections.unmodifiableMap(copy);
    }
}