using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Unity.VisualScripting; using UnityEngine; using XLua; namespace LuaFormula { /// /// Lua言語版Formulaノード /// [UnitTitle("Lua Formula")] [UnitShortTitle("Lua Formula")] [UnitCategory("LuaFormula")] public class LuaFormula : Unit, IMultiInputUnit { /// /// 実行するLuaコードを入力します。 /// /// 入力データポートにつなげたノードのデータは、 /// Lua上ではAにつなげたものはグローバル変数A Bにつなげたものはグローバル変数Bに格納されます。 /// [Inspectable, InspectorTextArea, UnitHeaderInspectable] public string LuaCode { get; private set; } /// /// Luaコード実行の結果、エラーや例外が発生しなかった場合はTrueになります。 /// [DoNotSerialize] public ValueOutput Success { get; private set; } /// /// Luaコード実行の結果、エラーや例外が発生した場合に例外の文字列が格納されます。 /// 発生しなかった場合は空文字列になります。 /// [DoNotSerialize] public ValueOutput ErrorMessage { get; private set; } /// /// Luaコード内でグローバル変数Rに代入した値が格納されます。 /// ただし、res変数内にテーブルを代入するとLuaTable型が格納され、 /// そのままではVisual Scripting内でデータを扱えないので注意してください。 /// [DoNotSerialize] public ValueOutput Result { get; private set; } /// /// 入力データポートに接続したデータを次のLuaFormulaノードでも使えるようにするかどうか設定します。 /// チェックをつけると入力データポート関係の変数A~Iの内容が終了後も保持されます。 /// /// ※Luaコード内でA~Iの変数の内容を変更した場合はその変更も保持されます。 /// ※変数を保持していても次のLuaFormulaノードで入力データポートにノードを接続した場合は接続したノードデータに上書きされます。 /// [Serialize] [Inspectable(order = int.MaxValue)] [InspectorExpandTooltip] public bool cacheArguments { get; set; } /// /// 入力データポートの数を指定できます。 /// 接続したノードのデータはLua環境にA~Iのグローバル変数として格納されます。 /// 0~9の間で設定できます。 /// [DoNotSerialize] [Inspectable, UnitHeaderInspectable("Inputs")] public virtual int inputCount { get { return _inputCount; } set { _inputCount = Mathf.Clamp(value, minInputCount, maxInputCount); } } [DoNotSerialize] public ReadOnlyCollection multiInputs { get; protected set; } [SerializeAs(nameof(inputCount))] private int _inputCount = 0; [DoNotSerialize] protected virtual int maxInputCount => 9; [DoNotSerialize] protected virtual int minInputCount => 0; [DoNotSerialize, PortLabelHidden] public ControlInput InputTrigger { get; private set; } [DoNotSerialize, PortLabelHidden] public ControlOutput OutputTrigger { get; private set; } private bool _isSuccess; private string _errorMessage; private object _resultValue; protected override void Definition() { var mi = new List(); multiInputs = mi.AsReadOnly(); for (var i = 0; i < inputCount; i++) { mi.Add(ValueInput(((char)('a' + i)).ToString())); } foreach (var input in multiInputs) { input.AllowsNull(); } InputTrigger = ControlInput(nameof(InputTrigger), Execute); OutputTrigger = ControlOutput(nameof(OutputTrigger)); Success = ValueOutput(nameof(Success), _ => _isSuccess); ErrorMessage = ValueOutput(nameof(ErrorMessage), _ => _errorMessage); Result = ValueOutput(nameof(Result), _ => _resultValue); /* foreach (var input in multiInputs) { Requirement(input, Success); Requirement(input, ErrorMessage); Requirement(input, Result); } */ Succession(InputTrigger, OutputTrigger); } private ControlOutput Execute(Flow flow) { try { LuaEnvironment.prepare(); LuaEnvironment.luaenv.Global.Set("R", null); for (var i = 0; i < inputCount; i++) { try { LuaEnvironment.luaenv.Global.Set(((char)('A' + i)).ToString(), flow.GetValue(multiInputs[i])); } catch (MissingValuePortInputException) { //LuaEnvironment.luaenv.Global.Set(((char)('A' + i)).ToString(), null); } } LuaEnvironment.luaenv.DoString(LuaCode); if (!cacheArguments) { for (var i = 0; i < maxInputCount; i++) { LuaEnvironment.luaenv.Global.Set(((char)('A' + i)).ToString(), null); } } _isSuccess = true; _errorMessage = ""; _resultValue = LuaEnvironment.luaenv.Global.Get("R"); } catch (Exception e) { #if UNITY_EDITOR Debug.LogError(e.ToString()); #endif _isSuccess = false; _errorMessage = e.ToString(); _resultValue = null; } return OutputTrigger; } } /// /// xLua用ヘルパー関数群 /// public static class LuaEnvironment { public static LuaEnv luaenv = null; /// /// Luaの初期化(初期化済みの場合は何もしない) /// public static void init() { if (luaenv == null) { luaenv = new LuaEnv(); luaenv.DoString(@" LF = { IsUnityEditor = false, --Log出力用の文字列を生成 _LogString = function(msg) local fdata = debug.getinfo(3) local parent = fdata.name local fpath = fdata.source if string.sub(fpath, 1, 1) == '@' then fpath = string.sub(fpath, 2) end if parent == nil then parent = 'Global' end if not LF.IsUseCustomLoader and fpath ~= 'chunk' then fpath = 'Assets/Resources/'..fpath..'.txt' end return tostring(msg)..'\n'..parent..' (at '..fpath..':'..fdata.currentline..')'; end; --Debug.Logラッパー(呼び出し元のLuaコードのファイル名と行位置も出力します) Log = function(msg) if LF.IsUnityEditor then CS.UnityEngine.Debug.Log(LF._LogString(msg)) end end; --Debug.LogWarningラッパー(呼び出し元のLuaコードのファイル名と行位置も出力します) LogWarning = function(msg) if LF.IsUnityEditor then CS.UnityEngine.Debug.LogWarning(LF._LogString(msg)) end end; --Debug.LogErrorラッパー(呼び出し元のLuaコードのファイル名と行位置も出力します) LogError = function(msg) if LF.IsUnityEditor then CS.UnityEngine.Debug.LogError(LF._LogString(msg)) end end; --テーブル以外の変数を文字列に変換 _SerializeBaseType = function(key, value) local prestr local convstr if type(key) == 'number' then prestr = '' else prestr = '['..string.format('%q', key)..']=' end if type(value) == 'number' then convstr = tostring(value) elseif type(value) == 'string' then convstr = string.format('%q', value) elseif type(value) == 'boolean' then convstr = value and 'true' or 'false' else if type(key) == 'number' then return '""""' else return '' end end return prestr..convstr end; --テーブルの内容を列挙して文字列に変換(多次元、循環参照対応) _SerializeTable = function(cycle, key, value) local tablestr = '' local valstring = '' local cskip = true if type(value) == 'table' then if cycle[tostring(value)] then tablestr = tablestr..LF._SerializeBaseType(key, nil) else cycle[tostring(value)] = key tablestr = tablestr..'{' for k,v in pairs(value) do valstring = LF._SerializeTable(cycle, k, v) if valstring ~= '' then if cskip then cskip = false else tablestr = tablestr..', ' end tablestr = tablestr..valstring end end tablestr = tablestr..'}' end else tablestr = tablestr..LF._SerializeBaseType(key, value) end return tablestr; end; --変数を文字列にシリアライズ Serialize = function(value) cycle = {} return LF._SerializeTable(cycle, '', value) end; --シリアライズした文字列を変数にデシリアライズ Deserialize = function(objname, str) assert(load(objname..'='..str))() end; } "); LuaTable LF = luaenv.Global.Get("LF"); #if UNITY_EDITOR LF.Set("IsUnityEditor", true); #else LF.Set("IsUnityEditor", false); #endif } } /// /// Luaの初期化・準備(初期化済みの場合はGCを実行) /// public static void prepare() { if (luaenv == null) { init(); } else { luaenv.Tick(); } } /// /// 文字列をLuaコードとして実行 /// /// 実行するLuaコード public static void ExecuteString(string code) { if (luaenv == null) return; luaenv.DoString(code); } /// /// Luaモジュールを読み込む /// /// 読み込むLuaファイルのパス public static void RequireModule(string modPath) { ExecuteString($"require '{modPath}'"); } /// /// 変数名から簡易シリアライズ /// /// ※簡易的なシリアライズ処理のため、複雑なテーブルの場合上手くいかない可能性があります。 ///  また、関数や循環参照となるテーブル等は無視されシリアライズされません。 ///  ちゃんとしたい場合はjson.luaなどを導入してJSONでやり取りすることをおすすめします。 /// /// シリアライズする数名 /// シリアライズされた文字列 public static string Serialize(string name) { ExecuteString($"LF_result = LF.Serialize({name})"); string ret = luaenv.Global.Get("LF_result"); return ret; } /// /// LuaTableから簡易シリアライズ /// /// シリアライズするLuaTableオブジェクト /// シリアライズされた文字列 public static string Serialize(LuaTable table) { luaenv.Global.Set("LF_intable", table); ExecuteString("LF_result = LF.Serialize(LF_intable)"); string ret = luaenv.Global.Get("LF_result"); return ret; } /// /// 簡易シリアライズ文字列を指定した文字列の変数にデシリアライズ /// /// ※単純にLuaコードとして実行することでデシリアライズしているため、 ///  不正な文字列を渡すと思わぬ動作を引き起こすことがあります。 ///  ちゃんとしたい場合はjson.luaなどを導入してJSONでやり取りすることをおすすめします。 /// /// デシリアライズされたテーブルを代入する変数名 /// シリアライズされている文字列 public static void DeSerialize(string name, string data) { luaenv.Global.Set("LF_instring", data); ExecuteString($"LF.Deserialize('{name}', LF_instring)"); } /// /// Luaグローバル変数に値をセットする /// /// 値をセットする変数名 /// セットする値 public static void GlobalSet(string key, object dat) { if (luaenv == null) return; luaenv.Global.Set(key, dat); } /// /// Luaグローバル変数の値を取得 /// /// ※この関数にテーブルが設定されている変数を指定するとLuaTable型の値が戻るためそのままではC#では扱えません。 ///  リストやハッシュになっている値を取得するにはGlobalGetListやGlobalGetDictionary関数を使います。 ///   /// /// 値を取得する変数名 /// 指定した変数に設定されていた値、テーブルだった場合はLuaTableオブジェクト public static object GlobalGet(string key) { if (luaenv == null) return null; return luaenv.Global.Get(key); } /// /// Luaグローバル変数の内容をリストとして取得 /// /// ※2次元以上のリストには対応していません。リスト内のリストやハッシュはLuaTableオブジェクトになります。 /// /// Listを取得する変数名 /// 指定した変数に設定されていたリスト public static List GlobalGetList(string key) { if (luaenv == null) return null; return luaenv.Global.Get>(key); } /// /// Luaグローバル変数の内容をハッシュとして取得 /// /// ※2次元以上のハッシュには対応していません。ハッシュ内のリストやハッシュはLuaTableオブジェクトになります。 /// /// ハッシュを取得する変数名 /// 指定した変数に設定されていたハッシュ public static Dictionary GlobalGetDictionary(string key) { if (luaenv == null) return null; return luaenv.Global.Get>(key); } /// /// LuaTableに値を設定 /// /// 値をセットするLuaTableオブジェクト /// 値をセットするLuaTableオブジェクト内の変数名 /// セットする値 public static void TableSet(LuaTable table, string key, object dat) { table.Set(key, dat); } /// /// LuaTableの値を取得 /// /// ※この関数にテーブルが設定されているLuaTableを渡すとLuaTable型の値が戻るためそのままではC#では扱えません。 ///  リストやハッシュになっている値を取得するにはTableGetListやTableGetDictionary関数を使います。 ///   /// /// 値を取得するLuaTableオブジェクト /// 値を取得する変数名 /// 指定した変数に設定されていた値、テーブルだった場合はLuaTableオブジェクト public static object TableGet(LuaTable table, string key) { return table.Get(key); } /// /// LuaTableのリストを取得 /// /// ※2次元以上のリストには対応していません。リスト内のリストやハッシュはLuaTableオブジェクトになります。 /// /// 値をセットするLuaTableオブジェクト /// 値をセットするLuaTableオブジェクト内の変数名 /// 指定した変数に設定されていたリスト public static List TableGetList(LuaTable table, string key) { return table.Get>(key); } /// /// LuaTableのハッシュを取得 /// /// ※2次元以上のハッシュには対応していません。ハッシュ内のリストやハッシュはLuaTableオブジェクトになります。 /// /// 値をセットするLuaTableオブジェクト /// 値をセットするLuaTableオブジェクト内の変数名 /// 指定した変数に設定されていたハッシュ public static Dictionary TableGetDictionary(LuaTable table, string key) { return table.Get>(key); } } }