Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
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;
    }
}