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;

/**
 * Read-only Trigger Program V2 virtual machine. Side effects are emitted as planned actions and
 * are never dispatched to a platform adapter.
 */
public final class TriggerSimulator {
    private TriggerSimulator() { }

    public static Result simulate(TriggerProgramV2 program, Request request) {
        Objects.requireNonNull(program, "program");
        request = request == null ? new Request(Map.of(), Map.of(), 0L, Instant.EPOCH) : request;
        Machine machine = new Machine(program, request);
        try {
            machine.initialize(program.declarations());
            machine.run(program.statements(), 0, false);
            return machine.result(Status.COMPLETED, "");
        } catch (SimulationFailure failure) {
            machine.trace(failure.nodeId, "error", failure.getMessage(), Map.of());
            return machine.result(Status.FAILED, failure.getMessage());
        } catch (RuntimeException failure) {
            machine.trace(null, "error", failure.getMessage(), Map.of());
            return machine.result(Status.FAILED, failure.getMessage());
        }
    }

    public record Request(Map<String, Object> event, Map<String, Object> variables, long seed, Instant instant) {
        public Request {
            event = immutableMap(event);
            variables = immutableMap(variables);
            instant = instant == null ? Instant.EPOCH : instant;
        }
    }

    public enum Status { COMPLETED, FAILED }

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

    public record TraceStep(
            long sequence,
            UUID nodeId,
            String kind,
            Object result,
            Map<String, Object> writes,
            int instructions) {
        public TraceStep {
            kind = kind == null ? "" : kind;
            writes = immutableMap(writes);
        }
    }

    public record Result(
            UUID executionId,
            Status status,
            String error,
            Map<String, Object> variables,
            List<PlannedAction> actions,
            List<TraceStep> trace,
            int instructions,
            long seed) {
        public Result {
            variables = immutableMap(variables);
            actions = List.copyOf(actions);
            trace = List.copyOf(trace);
        }
    }

    private enum Signal { NONE, BREAK, CONTINUE, RETURN }

    private static final class Flow {
        private static final Flow NONE = new Flow(Signal.NONE, null);
        private final Signal signal;
        private final Object value;

        private Flow(Signal signal, Object value) {
            this.signal = signal;
            this.value = value;
        }
    }

    private static final class Machine {
        private final UUID executionId = UUID.randomUUID();
        private final TriggerProgramV2 program;
        private final Request request;
        private final Map<String, Object> variables = new LinkedHashMap<>();
        private final Map<String, TriggerProgramV2.FunctionDeclaration> functions = new LinkedHashMap<>();
        private final List<PlannedAction> actions = new ArrayList<>();
        private final List<TraceStep> trace = new ArrayList<>();
        private TriggerExpressionEvaluator evaluator;
        private int instructions;
        private long sequence;

        private Machine(TriggerProgramV2 program, Request request) {
            this.program = program;
            this.request = request;
            this.variables.putAll(request.variables());
            program.functions().forEach(function -> functions.put(function.name(), function));
            rebuildEvaluator();
        }

        private void rebuildEvaluator() {
            evaluator = new TriggerExpressionEvaluator(
                    request.event(), variables, request.seed(), request.instant(),
                    (expression, value) -> trace(expression.nodeId(), "expression", value, Map.of()));
        }

        private void initialize(List<TriggerProgramV2.VariableDeclaration> declarations) {
            for (TriggerProgramV2.VariableDeclaration declaration : declarations) {
                if (!variables.containsKey(declaration.name())) {
                    variables.put(declaration.name(), evaluator.evaluate(declaration.initialValue()));
                }
            }
        }

        private Flow run(List<TriggerProgramV2.Statement> statements, int callDepth, boolean loop) {
            if (callDepth > TriggerProgramValidator.MAX_CALL_DEPTH) {
                throw new SimulationFailure(null, "function call depth exceeds " + TriggerProgramValidator.MAX_CALL_DEPTH);
            }
            for (TriggerProgramV2.Statement statement : statements) {
                instruction(statement.nodeId());
                Flow flow;
                try {
                    flow = execute(statement, callDepth, loop);
                } catch (SimulationFailure failure) {
                    if (statement.kind() != TriggerProgramV2.StatementKind.TRY) throw failure;
                    variables.put("error.message", failure.getMessage());
                    trace(statement.nodeId(), "error_branch", failure.getMessage(), Map.of("error.message", failure.getMessage()));
                    flow = run(statement.elseStatements(), callDepth, loop);
                } catch (RuntimeException failure) {
                    if (statement.kind() != TriggerProgramV2.StatementKind.TRY) {
                        throw new SimulationFailure(statement.nodeId(), safeMessage(failure));
                    }
                    variables.put("error.message", safeMessage(failure));
                    trace(statement.nodeId(), "error_branch", safeMessage(failure),
                            Map.of("error.message", safeMessage(failure)));
                    flow = run(statement.elseStatements(), callDepth, loop);
                }
                if (flow.signal != Signal.NONE) return flow;
            }
            return Flow.NONE;
        }

        private Flow execute(TriggerProgramV2.Statement statement, int callDepth, boolean loop) {
            return switch (statement.kind()) {
                case ACTION -> action(statement);
                case SET -> set(statement);
                case IF -> branch(statement, callDepth, loop);
                case SWITCH -> switchBranch(statement, callDepth, loop);
                case REPEAT -> repeat(statement, callDepth);
                case WHILE -> whileLoop(statement, callDepth);
                case FOREACH -> foreach(statement, callDepth);
                case BREAK -> {
                    if (!loop) throw new SimulationFailure(statement.nodeId(), "break used outside a loop");
                    yield new Flow(Signal.BREAK, null);
                }
                case CONTINUE -> {
                    if (!loop) throw new SimulationFailure(statement.nodeId(), "continue used outside a loop");
                    yield new Flow(Signal.CONTINUE, null);
                }
                case CALL -> call(statement, callDepth, loop);
                case RETURN -> new Flow(Signal.RETURN,
                        statement.expression() == null ? null : evaluator.evaluate(statement.expression()));
                case TRY -> run(statement.statements(), callDepth, loop);
            };
        }

        private Flow action(TriggerProgramV2.Statement statement) {
            Map<String, Object> parameters = evaluateInputs(statement.inputs());
            String key = executionId + ":" + statement.nodeId();
            actions.add(new PlannedAction(statement.nodeId(), statement.name(), parameters, key));
            trace(statement.nodeId(), "planned_action", statement.name(), parameters);
            return Flow.NONE;
        }

        private Flow set(TriggerProgramV2.Statement statement) {
            Object value = evaluator.evaluate(statement.expression());
            variables.put(statement.name(), value);
            Map<String, Object> writes = new LinkedHashMap<>();
            writes.put(statement.name(), value);
            trace(statement.nodeId(), "variable_write", value, writes);
            return Flow.NONE;
        }

        private Flow branch(TriggerProgramV2.Statement statement, int callDepth, boolean loop) {
            boolean matched = evaluator.evaluateBoolean(statement.expression());
            trace(statement.nodeId(), "branch", matched, Map.of());
            return run(matched ? statement.statements() : statement.elseStatements(), callDepth, loop);
        }

        private Flow switchBranch(TriggerProgramV2.Statement statement, int callDepth, boolean loop) {
            Object selector = evaluator.evaluate(statement.expression());
            for (TriggerProgramV2.SwitchCase branch : statement.cases()) {
                Object match = evaluator.evaluate(branch.match());
                if (Objects.equals(selector, match)
                        || selector instanceof Number && match instanceof Number
                        && Double.compare(((Number) selector).doubleValue(), ((Number) match).doubleValue()) == 0) {
                    trace(branch.nodeId(), "switch_case", match, Map.of());
                    return run(branch.statements(), callDepth, loop);
                }
            }
            trace(statement.nodeId(), "switch_default", selector, Map.of());
            return run(statement.elseStatements(), callDepth, loop);
        }

        private Flow repeat(TriggerProgramV2.Statement statement, int callDepth) {
            long count = integral(evaluator.evaluate(statement.expression()), statement.nodeId());
            if (count < 0 || count > TriggerProgramValidator.MAX_LOOP_ITERATIONS) {
                throw new SimulationFailure(statement.nodeId(), "repeat count exceeds loop budget");
            }
            for (long index = 0; index < count; index++) {
                variables.put("loop.index", index);
                Flow flow = run(statement.statements(), callDepth, true);
                if (flow.signal == Signal.BREAK) break;
                if (flow.signal == Signal.RETURN) return flow;
            }
            return Flow.NONE;
        }

        private Flow whileLoop(TriggerProgramV2.Statement statement, int callDepth) {
            for (int count = 0; evaluator.evaluateBoolean(statement.expression()); count++) {
                if (count >= TriggerProgramValidator.MAX_LOOP_ITERATIONS) {
                    throw new SimulationFailure(statement.nodeId(), "while loop exceeds loop budget");
                }
                Flow flow = run(statement.statements(), callDepth, true);
                if (flow.signal == Signal.BREAK) break;
                if (flow.signal == Signal.RETURN) return flow;
            }
            return Flow.NONE;
        }

        private Flow foreach(TriggerProgramV2.Statement statement, int callDepth) {
            Object source = evaluator.evaluate(statement.expression());
            Collection<?> values;
            if (source instanceof Collection<?> collection) values = collection;
            else if (source instanceof Map<?, ?> map) values = map.entrySet();
            else throw new SimulationFailure(statement.nodeId(), "foreach requires a collection");
            if (values.size() > TriggerProgramValidator.MAX_LOOP_ITERATIONS) {
                throw new SimulationFailure(statement.nodeId(), "foreach exceeds loop budget");
            }
            int index = 0;
            for (Object value : values) {
                variables.put(statement.name(), value);
                variables.put("loop.index", (long) index++);
                Flow flow = run(statement.statements(), callDepth, true);
                if (flow.signal == Signal.BREAK) break;
                if (flow.signal == Signal.RETURN) return flow;
            }
            return Flow.NONE;
        }

        private Flow call(TriggerProgramV2.Statement statement, int callDepth, boolean loop) {
            TriggerProgramV2.FunctionDeclaration function = functions.get(statement.name());
            if (function == null) throw new SimulationFailure(statement.nodeId(), "unknown function: " + statement.name());
            Map<String, Object> caller = new LinkedHashMap<>(variables);
            Map<String, Object> arguments = new LinkedHashMap<>();
            Map<String, TriggerExpression> references = new LinkedHashMap<>();
            // Evaluate all IN values in the caller scope before parameter names can shadow them.
            for (TriggerProgramV2.Parameter parameter : function.parameters()) {
                TriggerExpression input = statement.inputs().get(parameter.name());
                if (parameter.mode() != TriggerProgramV2.ParameterMode.OUT) {
                    if (input == null) throw new SimulationFailure(statement.nodeId(), "missing function input: " + parameter.name());
                    arguments.put(parameter.name(), evaluator.evaluate(input));
                }
                if (parameter.mode() != TriggerProgramV2.ParameterMode.IN) {
                    if (input == null || input.kind() != TriggerExpression.Kind.REFERENCE) {
                        throw new SimulationFailure(statement.nodeId(), "OUT/INOUT input must be a variable reference: " + parameter.name());
                    }
                    references.put(parameter.name(), input);
                }
            }
            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(), evaluator.evaluate(local.initialValue()));
            }
            Flow functionFlow = run(function.statements(), callDepth + 1, false);
            Map<String, Object> callee = new LinkedHashMap<>(variables);
            variables.clear();
            variables.putAll(caller);
            references.forEach((parameter, reference) -> variables.put(
                    referenceName(reference.name()), callee.get(parameter)));
            if (statement.expression() != null && statement.expression().kind() == TriggerExpression.Kind.REFERENCE) {
                variables.put(referenceName(statement.expression().name()), functionFlow.value);
            }
            trace(statement.nodeId(), "function_call", functionFlow.value, Map.of());
            return Flow.NONE;
        }

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

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

        private void instruction(UUID nodeId) {
            if (++instructions > TriggerProgramValidator.MAX_INSTRUCTIONS) {
                throw new SimulationFailure(nodeId, "instruction budget exceeded");
            }
        }

        private void trace(UUID nodeId, String kind, Object value, Map<String, Object> writes) {
            trace.add(new TraceStep(++sequence, nodeId, kind, value, writes, instructions));
        }

        private Result result(Status status, String error) {
            return new Result(executionId, status, error == null ? "" : error,
                    variables, actions, trace, instructions, request.seed());
        }

        private static long integral(Object value, UUID nodeId) {
            if (value instanceof Number number) return number.longValue();
            try {
                return Long.parseLong(String.valueOf(value));
            } catch (NumberFormatException invalid) {
                throw new SimulationFailure(nodeId, "loop count must be an integer");
            }
        }

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

    private static final class SimulationFailure extends RuntimeException {
        private final UUID nodeId;

        private SimulationFailure(UUID nodeId, String message) {
            super(message);
            this.nodeId = nodeId;
        }
    }

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