Repository URL to install this package:
|
Version:
2023.12.1 ▾
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Antlr4.Runtime;
using Antlr4.Runtime.Tree;
using Gs2.Core.Model;
using Gs2.Gs2StateMachine.Model;
using Gs2.Unity.Gs2StateMachine.Local.Model;
using Gs2.Unity.Gs2StateMachine.Local.Parser;
using Gs2.Unity.Gs2StateMachine.Model;
using Gs2.Unity.Util;
using Gs2.Util.LitJson;
using Gs2.Util.WebSocketSharp;
using UnityEngine;
using XLua;
namespace Gs2.Unity.Gs2StateMachine.Local
{
public class ActiveStateMachine
{
private GameSession _gameSession;
private StateMachine[] _stateMachines;
private EzStackEntry[] _stacks;
private Dictionary<string, MapVariableValue> _variables = new Dictionary<string, MapVariableValue>();
private ActiveStateMachineStatus _status;
private string _lastError;
private long _transitionCount;
public MapVariableValue CurrentVariables => this._variables[CurrentStack().StateMachineName];
public MapVariableValue Variables(
string stateMachineName
) {
return this._variables[stateMachineName];
}
public ActiveStateMachineStatus Status => this._status;
public string LastError => this._lastError;
private ActiveStateMachine(
GameSession gameSession,
StateMachine[] stateMachines,
string entryStateMachineName
) {
this._stateMachines = stateMachines;
{
this._gameSession = gameSession;
var stateMachine = StateMachine(entryStateMachineName);
this._stacks = new[] {
new EzStackEntry {
StateMachineName = stateMachine.Name,
TaskName = stateMachine.EntryPoint
},
};
}
foreach (var stateMachine in stateMachines) {
this._variables[stateMachine.Name] = new MapVariableValue(new Dictionary<string, IVariableValue>());
}
}
private ActiveStateMachine(
GameSession gameSession,
StateMachine[] stateMachines,
EzStackEntry[] stacks,
EzVariable[] variables,
ActiveStateMachineStatus status,
string lastError,
long transitionCount
) {
this._stateMachines = stateMachines;
this._gameSession = gameSession;
this._stacks = stacks;
this._variables = new Dictionary<string, MapVariableValue>();
foreach (var variable in variables) {
this._variables[variable.StateMachineName] = MapVariableValue.FromJson(JsonMapper.ToObject(variable.Value));
}
this._status = status;
this._lastError = lastError;
this._transitionCount = transitionCount;
}
public static ActiveStateMachine Setup(
GameSession gameSession,
string input,
string entryStateMachineName
) {
var lexer = new StateMachineLexer(new AntlrInputStream(input));
var stream = new CommonTokenStream(lexer);
var parser = new StateMachineParser(stream);
var lexerErrorListener = new ConsoleErrorListener<int>();
var parserErrorListener = new BaseErrorListener();
lexer.RemoveErrorListeners();
lexer.AddErrorListener(lexerErrorListener);
parser.RemoveErrorListeners();
parser.AddErrorListener(parserErrorListener);
var listener = new Listener();
ParseTreeWalker.Default.Walk(listener, parser.stateMachines());
return new ActiveStateMachine(gameSession, listener.StateMachines, entryStateMachineName);
}
public static ActiveStateMachine Setup(
GameSession gameSession,
EzStatus status
) {
if (status.EnableSpeculativeExecution != "enable") {
throw new InvalidOperationException("State machines that do not have speculative execution enabled cannot be executed locally.");
}
var lexer = new StateMachineLexer(new AntlrInputStream(status.StateMachineDefinition));
var stream = new CommonTokenStream(lexer);
var parser = new StateMachineParser(stream);
var lexerErrorListener = new ConsoleErrorListener<int>();
var parserErrorListener = new BaseErrorListener();
lexer.RemoveErrorListeners();
lexer.AddErrorListener(lexerErrorListener);
parser.RemoveErrorListeners();
parser.AddErrorListener(parserErrorListener);
var listener = new Listener();
ParseTreeWalker.Default.Walk(listener, parser.stateMachines());
return new ActiveStateMachine(
gameSession,
listener.StateMachines,
status.Stacks.ToArray(),
status.Variables.ToArray(),
ActiveStateMachineStatusExt.FromString(status.Status),
status.LastError,
status.TransitionCount
);
}
public void Start(
MapVariableValue parameters
) {
Invoke(parameters);
}
private EzStackEntry CurrentStack() {
if (this._stacks.Length == 0) {
throw new InvalidOperationException("stack is empty");
}
return this._stacks[this._stacks.Length-1];
}
private StateMachine StateMachine(
string stateMachineName
) {
return this._stateMachines.FirstOrDefault(v => v.Name == stateMachineName);
}
private StateMachine CurrentStateMachine() {
var currentStateMachine = CurrentStack();
return StateMachine(currentStateMachine.StateMachineName);
}
private ITask CurrentTask() {
var currentStack = CurrentStack();
return CurrentStateMachine().Task(currentStack.TaskName);
}
private byte[] ComputeHash(
SpeculativeExecutor.SpeculativeExecutor transaction
) {
var data = new SortedDictionary<string, object>();
foreach (var element in this._variables) {
data[element.Key] = element.Value.ToDictionary();
}
var data2 = new SortedDictionary<string, object> {
["variables"] = data,
["consumeActions"] = transaction.ConsumeActions.Select(v => new SortedDictionary<string, object> {
["action"] = v.Action,
["request"] = v.Request,
}).ToArray(),
["acquireActions"] = transaction.AcquireActions.Select(v => new SortedDictionary<string, object> {
["action"] = v.Action,
["request"] = v.Request,
}).ToArray(),
["transitionCount"] = this._transitionCount,
};
var json = JsonMapper.ToJson(data2);
return SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(json));
}
private void Invoke(
MapVariableValue parameters
) {
var task = CurrentTask();
if (task is ScriptTask scriptTask) {
var env = new LuaEnv();
try {
var transaction = new SpeculativeExecutor.SpeculativeExecutor(env);
var args = env.NewTable();
args.Set("result", env.NewTable());
args.Set("params", parameters.ToLuaTable(env));
args.Set("userId", this._gameSession.AccessToken.UserId);
args.Set("variables", CurrentVariables.ToLuaTable(env));
env.Global.Set("args", args);
env.Global.Set("transaction", transaction.Function());
env.DoString(scriptTask.Payload);
var result = env.Global.Get<LuaTable>("result");
var emitEvent = result.Get<string>("event");
var emitParameters = result.Get<LuaTable>("params");
var updatedVariables = result.Get<LuaTable>("updatedVariables");
if (emitEvent == null) {
InnerEmit("Error", new MapVariableValue(new Dictionary<string, IVariableValue> {
["reason"] = new StringVariableValue("event not defined"),
}));
return;
}
if (emitParameters == null) {
emitParameters = env.NewTable();
}
if (updatedVariables == null) {
updatedVariables = env.NewTable();
}
this._variables[CurrentStack().StateMachineName] = MapVariableValue.FromLuaTable(updatedVariables);
OnIssueTransaction?.Invoke(transaction.ConsumeActions.ToArray(), transaction.AcquireActions.ToArray());
OnChangeState?.Invoke(task.Name(), ComputeHash(transaction));
transaction.Flush();
InnerEmit(emitEvent, MapVariableValue.FromLuaTable(emitParameters));
}
catch (Exception e) {
Debug.LogError(e.Message);
InnerEmit("Error", new MapVariableValue(new Dictionary<string, IVariableValue> {
["reason"] = new StringVariableValue(e.Message),
}));
}
finally {
env.Dispose();
}
}
}
private void InnerEmit(
string eventName,
MapVariableValue parameters
) {
this._transitionCount++;
if (this._transitionCount > 1000) {
throw new InvalidOperationException("The maximum number of state transitions has been reached");
}
var stateMachine = CurrentStateMachine();
var task = CurrentTask();
var transition = stateMachine.Transition(
task.Name(),
eventName
);
if (transition == null) return;
var next = stateMachine.Task(transition.Destination);
var currentStack = CurrentStack();
if (next is SubStateMachineTask subStateMachineTask) {
this._stacks[this._stacks.Length-1] = new EzStackEntry {
StateMachineName = currentStack.StateMachineName,
TaskName = next.Name(),
};
var subStateMachine = StateMachine(subStateMachineTask.Using);
this._stacks = this._stacks.Concat(new[] {
new EzStackEntry {
StateMachineName = subStateMachine.Name,
TaskName = subStateMachine.EntryPoint,
},
}).ToArray();
var newParameters = new Dictionary<string, IVariableValue>();
foreach (var parameterMapping in subStateMachineTask.InParameters) {
newParameters[parameterMapping.To] = this._variables[currentStack.StateMachineName].Get(parameterMapping.From);
}
this._status = ActiveStateMachineStatus.Running;
Invoke(new MapVariableValue(newParameters));
}
else {
this._stacks = this._stacks.SubArray(0, this._stacks.Length - 1);
if (next.Type() == TaskType.Pass && this._stacks.Length > 0) {
var returnStack = CurrentStack();
next = CurrentTask();
if (next is SubStateMachineTask subStateMachineTask2) {
foreach (var parameterMapping in subStateMachineTask2.OutParameters) {
this._variables[returnStack.StateMachineName].Set(parameterMapping.To, this._variables[currentStack.StateMachineName].Get(parameterMapping.From));
}
this._variables.Remove(currentStack.StateMachineName);
InnerEmit("Pass", parameters);
}
} else if (next.Type() == TaskType.Error && this._stacks.Length > 0) {
if (parameters.Get("reason") is StringVariableValue) {
this._lastError = parameters.Get("reason").String();
}
else {
this._lastError = $"{currentStack.StateMachineName}.{currentStack.TaskName}";
}
InnerEmit("Error", parameters);
}
else {
this._stacks = this._stacks.Concat(new[] {
new EzStackEntry {
StateMachineName = currentStack.StateMachineName,
TaskName = next.Name(),
},
}).ToArray();
if (next.Type() == TaskType.Pass) {
this._status = ActiveStateMachineStatus.Pass;
}
if (next.Type() == TaskType.Wait) {
this._status = ActiveStateMachineStatus.Wait;
}
if (next.Type() == TaskType.Error) {
if (parameters.Get("reason") is StringVariableValue) {
this._lastError = parameters.Get("reason").String();
}
else {
this._lastError = $"{currentStack.StateMachineName}.{currentStack.TaskName}";
}
this._status = ActiveStateMachineStatus.Error;
}
if (next.Type() == TaskType.Script) {
this._status = ActiveStateMachineStatus.Running;
Invoke(parameters);
}
}
}
}
public void Emit(
string eventName,
MapVariableValue parameters
) {
OnEmit?.Invoke(eventName, parameters);
InnerEmit(eventName, parameters);
}
public delegate void EmitHandler(string eventName, MapVariableValue parameters);
public event EmitHandler OnEmit;
public delegate void ChangeStateHandler(string taskName, byte[] hash);
public event ChangeStateHandler OnChangeState;
public delegate void IssueTransactionHandler(ConsumeAction[] consumeActions, AcquireAction[] acquireActions);
public event IssueTransactionHandler OnIssueTransaction;
}
}