diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..2530691 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "BazelBuild.vscode-bazel", + "ms-dotnettools.csharp" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5c151f3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.insertSpaces": false, + "files.insertFinalNewline": true, + "[starlark]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "BazelBuild.vscode-bazel" + } +} diff --git a/WORKSPACE b/WORKSPACE index daaee7c..3e6dbf1 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -4,40 +4,40 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( - name = "ecsact", - remote = "git@github.com:seaube/ecsact.git", - commit = "6bce75438e812fd887dbe24e757d2a523993fa26", - shallow_since = "1651763515 -0400", + name = "ecsact_rtb", + commit = "4d4856d697300a1e3c8b2e49b25acb72e9328b9d", + remote = "git@github.com:seaube/ecsact-rtb.git", + # shallow_since = "1657554031 -0700", ) -http_archive( - name = "boost", - strip_prefix = "boost-563e8e0de4eac4b48a02d296581dc2454127608e", - urls = ["https://github.com/bazelboost/boost/archive/563e8e0de4eac4b48a02d296581dc2454127608e.zip"], - sha256 = "c41441a6e9f8038ad626e9f937ddc3675ab896b6c3512eefc6840037b3816d03", -) +load("@ecsact_rtb//:repositories.bzl", "ecsact_rtb_repositories") + +ecsact_rtb_repositories() -load("@boost//:index.bzl", "boost_http_archives") -boost_http_archives() +load("@ecsact_rtb//:workspace.bzl", "ecsact_rtb_workspace") + +ecsact_rtb_workspace() http_archive( name = "bzlws", - strip_prefix = "bzlws-f929e5380f441f50a77776d34a7df8cacdbdf986", - url = "https://github.com/zaucy/bzlws/archive/f929e5380f441f50a77776d34a7df8cacdbdf986.zip", - sha256 = "5bebb821b158b11d81dd25cf031b5b26bae97dbb02025df7d0e41a262b3a030b", + sha256 = "9bc9d6bf1d885992d58a4ad9dc7476a8cd48d672b497707b0ae2c0899c6d369b", + strip_prefix = "bzlws-344801b9b3105bd13e4b51ec9776f04bd5e01972", + url = "https://github.com/zaucy/bzlws/archive/344801b9b3105bd13e4b51ec9776f04bd5e01972.zip", ) load("@bzlws//:repo.bzl", "bzlws_deps") + bzlws_deps() http_archive( name = "rules_7zip", + sha256 = "29ba984e2a7d48540faa839efaf09be4b880d211a93575e7ac87abffc12dbdea", strip_prefix = "rules_7zip-25d3b858a37580dbc1f1ced002e210be15012e2f", urls = ["https://github.com/zaucy/rules_7zip/archive/25d3b858a37580dbc1f1ced002e210be15012e2f.zip"], - sha256 = "29ba984e2a7d48540faa839efaf09be4b880d211a93575e7ac87abffc12dbdea", ) load("@rules_7zip//:setup.bzl", "setup_7zip") + setup_7zip() _nlohmann_json_build_file = """ @@ -54,9 +54,9 @@ cc_library( http_archive( name = "nlohmann_json", - url = "https://github.com/nlohmann/json/releases/download/v3.10.4/include.zip", - sha256 = "62c585468054e2d8e7c2759c0d990fd339d13be988577699366fe195162d16cb", build_file_content = _nlohmann_json_build_file, + sha256 = "62c585468054e2d8e7c2759c0d990fd339d13be988577699366fe195162d16cb", + url = "https://github.com/nlohmann/json/releases/download/v3.10.4/include.zip", ) http_archive( @@ -66,6 +66,7 @@ http_archive( ) load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories") + node_repositories() http_archive( @@ -76,4 +77,18 @@ http_archive( ) load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies") + aspect_bazel_lib_dependencies() + +http_archive( + name = "rules_pkg", + sha256 = "8a298e832762eda1830597d64fe7db58178aa84cd5926d76d5b744d6558941c2", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz", + "https://github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz", + ], +) + +load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") + +rules_pkg_dependencies() diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index b9fc89c..6a14a5f 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -1,16 +1,23 @@ -load("@bzlws//:index.bzl", "bzlws_copy") +load("@bzlws//:index.bzl", "bzlws_copy", "bzlws_extract") package(default_visibility = ["//visibility:public"]) -bzlws_copy( +# bzlws_copy( +# name = "copy_for_dev", +# testonly = True, +# srcs = ["//packages/com.seaube.ecsact:generators"], +# out = "packages/com.seaube.ecsact/generators~.tar", +# force = True, +# metafile_path = "generators~/.copy_for_dev.yml", +# visibility = ["//visibility:private"], +# ) + +bzlws_extract( name = "copy_for_dev", - visibility = ["//visibility:private"], testonly = True, + srcs = ["//packages/com.seaube.ecsact:generators"], + out = "packages/com.seaube.ecsact/generators~", force = True, - out = "packages/com.seaube.ecsact/generators~/{FILENAME}", metafile_path = "generators~/.copy_for_dev.yml", - srcs = [ - "@ecsact//generator/csharp:cli", - "@ecsact//generator/parser_info:cli", - ], + visibility = ["//visibility:private"], ) diff --git a/packages/com.seaube.ecsact/.gitignore b/packages/com.seaube.ecsact/.gitignore index 7e5c0b9..bf96c4f 100644 --- a/packages/com.seaube.ecsact/.gitignore +++ b/packages/com.seaube.ecsact/.gitignore @@ -71,4 +71,4 @@ crashlytics-build.properties /[Aa]ssets/[Ss]treamingAssets/aa/* # Folder that stores codegen executables -# /generators~ +/generators~ diff --git a/packages/com.seaube.ecsact/BUILD.bazel b/packages/com.seaube.ecsact/BUILD.bazel index 3f395b8..5b2ee26 100644 --- a/packages/com.seaube.ecsact/BUILD.bazel +++ b/packages/com.seaube.ecsact/BUILD.bazel @@ -1,27 +1,30 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") -load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load(":pkg.bzl", "pkg_executable") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") -copy_to_directory( - name = "generators~", - replace_prefixes = { - "generator/csharp/": "", - "generator/parser_info/": "", - }, - include_external_repositories = [ - "ecsact", - ], - srcs = [ - "@ecsact//generator/csharp:cli", - "@ecsact//generator/parser_info:cli", - ], +pkg_executable( + name = "ecsact_csharp_codegen_with_runfiles", + bin = "@ecsact//generator/csharp:cli", + path = "ecsact_csharp_codegen", +) + +pkg_executable( + name = "ecsact_parser_info_codegen_with_runfiles", + bin = "@ecsact//generator/parser_info:cli", + path = "ecsact_parser_info_codegen", ) -pkg_npm( - name = "com.seaube.ecsact", - package_name = "com.seaube.ecsact", - deps = [":generators~"], - srcs = glob(["**/*"]), +pkg_executable( + name = "ecsact_rtb_with_runfiles", + bin = "@ecsact_rtb//cli:ecsact-rtb", + path = "ecsact-rtb", ) -alias(name = "pack", actual = "com.seaube.ecsact.pack") -alias(name = "publish", actual = "com.seaube.ecsact.publish") +pkg_tar( + name = "generators", + srcs = [ + ":ecsact_csharp_codegen_with_runfiles", + ":ecsact_parser_info_codegen_with_runfiles", + ":ecsact_rtb_with_runfiles", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/com.seaube.ecsact/Editor/DefaultRunnerEditor.cs b/packages/com.seaube.ecsact/Editor/DefaultRunnerEditor.cs new file mode 100644 index 0000000..794d13e --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/DefaultRunnerEditor.cs @@ -0,0 +1,41 @@ +using UnityEditor; +using UnityEngine; + +namespace Ecsact.Editor { + [CustomEditor(typeof(Ecsact.DefaultRunner))] + public class DefaultRunnerEditor : UnityEditor.Editor { + public override bool RequiresConstantRepaint() { + return Application.isPlaying; + } + + public override void OnInspectorGUI() { + var runner = target as DefaultRunner; + var executionHeatStyle = new GUIStyle(EditorStyles.label); + executionHeatStyle.normal.textColor = Color.green; + + float deltaTimePc = 0f; + if(runner.debugExecutionTimeMs > 0) { + // 0.0 - 1.0 how much the execution time takes up from the delta time + deltaTimePc = + Time.deltaTime / ((float)runner.debugExecutionTimeMs) / 1000f; + + if(deltaTimePc > 0.5) { + executionHeatStyle.normal.textColor = Color.red; + } else if(deltaTimePc > 0.2) { + executionHeatStyle.normal.textColor = Color.yellow; + } + } + + EditorGUILayout.LabelField( + "Execution Count", + $"{runner.debugExecutionCountTotal}" + ); + EditorGUILayout.LabelField( + "Execution Time", + $"{runner.debugExecutionTimeMs}ms " + + $"({deltaTimePc*100:0.0}% of delta time)", + executionHeatStyle + ); + } + } +} diff --git a/packages/com.seaube.ecsact/Editor/DefaultRunnerEditor.cs.meta b/packages/com.seaube.ecsact/Editor/DefaultRunnerEditor.cs.meta new file mode 100644 index 0000000..1ddc3f3 --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/DefaultRunnerEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a250f3f440349154990c57791c722ac2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Editor/Ecsact.Editor.asmdef b/packages/com.seaube.ecsact/Editor/Ecsact.Editor.asmdef index 1dac4c2..88c1311 100644 --- a/packages/com.seaube.ecsact/Editor/Ecsact.Editor.asmdef +++ b/packages/com.seaube.ecsact/Editor/Ecsact.Editor.asmdef @@ -1,19 +1,25 @@ { - "name": "Ecsact.Editor", - "rootNamespace": "", - "references": [ - "Ecsact", - "Unity.EditorCoroutines.Editor" - ], - "includePlatforms": [ - "Editor" - ], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} + "name": "Ecsact.Editor", + "rootNamespace": "", + "references": [ + "GUID:6e7029456009ab94da19a8f93d5e3523", + "GUID:478a2357cc57436488a56e564b08d223", + "GUID:21b0c8d1703a94250bfac916590cea4f", + "GUID:54cb1906aeb4e4264bc1d2aa3818a43f", + "GUID:ea715009a4efd4c6cbc85be3ae097dd3", + "GUID:23f3c6063e13e4fd9addce3606ff4e42", + "GUID:7faa839a710e565409361b9f0c62a4da", + "GUID:2cd956959600cec4a97cd312e2adbdbb" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/packages/com.seaube.ecsact/Editor/EcsactPackagesPostprocessor.cs b/packages/com.seaube.ecsact/Editor/EcsactPackagesPostprocessor.cs index 25d6775..5649e3e 100644 --- a/packages/com.seaube.ecsact/Editor/EcsactPackagesPostprocessor.cs +++ b/packages/com.seaube.ecsact/Editor/EcsactPackagesPostprocessor.cs @@ -3,6 +3,7 @@ using System.IO; using System.Diagnostics; using System.Collections.Generic; +using System.Linq; public class EcsactPackagesPostprocessor : AssetPostprocessor { @@ -59,6 +60,26 @@ static void OnPostprocessAllAssets } } + static void TryImportAssets + ( List assets + ) + { + EditorApplication.delayCall += () => { + if(!Progress.running) { + try { + AssetDatabase.StartAssetEditing(); + foreach(var asset in assets) { + AssetDatabase.ImportAsset(asset); + } + } finally { + AssetDatabase.StopAssetEditing(); + } + } else { + TryImportAssets(assets); + } + }; + } + static void RefreshEcsactCodegen ( List importedPkgs , List deletedPkgs @@ -70,7 +91,7 @@ static void RefreshEcsactCodegen ); var progressId = Progress.Start( - "ECSACT Codegen", + "Ecsact Codegen", "Generating C# files..." ); @@ -97,19 +118,15 @@ static void RefreshEcsactCodegen UnityEngine.Debug.LogError(codegen.StandardError.ReadToEnd()); Progress.Remove(progressId); } else { - Progress.Report(progressId, 0.9f); - - EditorApplication.delayCall += () => { - foreach(var importedPkg in importedPkgs) { - // Import newly created scripts - AssetDatabase.ImportAsset(importedPkg + ".cs"); - } - Progress.Remove(progressId); - }; + Progress.Finish(progressId, Progress.Status.Succeeded); + // Import newly created scripts + // TryImportAssets(importedPkgs.Select(pkg => pkg + ".cs").ToList()); } }; - foreach(var (pkg, pkgPath) in FindEcsactPackages()) { + var packages = FindEcsactPackages().ToList(); + + foreach(var (pkg, pkgPath) in packages) { codegen.StartInfo.Arguments += pkgPath + " "; } @@ -119,6 +136,10 @@ static void RefreshEcsactCodegen foreach(var plugin in GetCodegenPlugins()) { // TODO: Custom codegen plugins } + + EcsactRuntimeBuilder.Build(new EcsactRuntimeBuilder.Options{ + ecsactFiles = packages.Select(item => item.Item2).ToList(), + }); } static IEnumerable GetCodegenPlugins() { diff --git a/packages/com.seaube.ecsact/Editor/EcsactRuntimeBuilder.cs b/packages/com.seaube.ecsact/Editor/EcsactRuntimeBuilder.cs new file mode 100644 index 0000000..63fbeef --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/EcsactRuntimeBuilder.cs @@ -0,0 +1,116 @@ +using UnityEditor; +using UnityEngine; +using System.IO; +using System.Diagnostics; +using System.Collections.Generic; + +public static class EcsactRuntimeBuilder { + + public struct Options { + public List ecsactFiles; + } + + public static void Build + ( Options options + ) + { + if(options.ecsactFiles.Count == 0) { + UnityEngine.Debug.LogError( + "Cannot build ecsact runtime without any ecsact files" + ); + return; + } + + var settings = EcsactSettings.GetOrCreateSettings(); + if(string.IsNullOrWhiteSpace(settings.runtimeBuilderOutputPath)) { + UnityEngine.Debug.LogError( + "Cannot build ecsact runtime without output path" + ); + return; + } + + string runtimeBuilderExecutablePath = Path.GetFullPath( + "Packages/com.seaube.ecsact/generators~/ecsact-rtb.exe" + ); + + var progressId = Progress.Start("Ecsact Runtime Builder"); + + var proc = new Process(); + proc.StartInfo.FileName = runtimeBuilderExecutablePath; + proc.StartInfo.CreateNoWindow = true; + proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + proc.EnableRaisingEvents = true; + proc.StartInfo.Arguments = ""; + proc.StartInfo.RedirectStandardError = true; + proc.StartInfo.RedirectStandardOutput = true; + proc.StartInfo.UseShellExecute = false; + + proc.ErrorDataReceived += (_, ev) => { + var line = ev.Data; + if(!string.IsNullOrWhiteSpace(line)) { + UnityEngine.Debug.LogError(line); + Progress.SetDescription(progressId, line); + } + }; + + proc.OutputDataReceived += (_, ev) => { + var line = ev.Data; + if(!string.IsNullOrWhiteSpace(line)) { + Progress.SetDescription(progressId, line); + UnityEngine.Debug.Log(line); + } + }; + + proc.Exited += (_, _) => { + if(proc.ExitCode != 0) { + UnityEngine.Debug.LogError( + $"ecsact-rtb exited with code {proc.ExitCode}" + ); + Progress.Finish(progressId, Progress.Status.Failed); + } else { + Progress.Finish(progressId, Progress.Status.Succeeded); + } + }; + + foreach(var ecsactFile in options.ecsactFiles) { + proc.StartInfo.Arguments += "\"" + ecsactFile + "\" "; + } + + proc.StartInfo.Arguments += "--output=\""; + proc.StartInfo.Arguments += Path.GetFullPath( + settings.runtimeBuilderOutputPath + ); +#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN + proc.StartInfo.Arguments += ".dll"; +#elif UNITY_EDITOR_LINUX || UNITY_STANDALONE_LINUX + proc.StartInfo.Arguments += ".so"; +#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX + proc.StartInfo.Arguments += ".dylib"; +#elif UNITY_WEBGL + proc.StartInfo.Arguments += ".wasm"; +#endif + proc.StartInfo.Arguments += "\" "; + + if(!string.IsNullOrWhiteSpace(settings.runtimeBuilderCompilerPath)) { + proc.StartInfo.Arguments += "--compiler_path=\""; + proc.StartInfo.Arguments += Path.GetFullPath( + settings.runtimeBuilderCompilerPath + ); + proc.StartInfo.Arguments += "\" "; + } + + proc.StartInfo.Arguments += "--temp_dir="; + proc.StartInfo.Arguments += Path.GetFullPath( + FileUtil.GetUniqueTempPathInProject() + ); + + UnityEngine.Debug.Log(proc.StartInfo.FileName); + UnityEngine.Debug.Log(proc.StartInfo.Arguments); + UnityEngine.Debug.Log($"CWD: {System.IO.Directory.GetCurrentDirectory()}"); + + Progress.Report(progressId, 0.1f); + proc.Start(); + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + } +} diff --git a/packages/com.seaube.ecsact/Editor/EcsactRuntimeBuilder.cs.meta b/packages/com.seaube.ecsact/Editor/EcsactRuntimeBuilder.cs.meta new file mode 100644 index 0000000..394ede3 --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/EcsactRuntimeBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abd5ca5c67fee5447af8ed6740f58af2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Editor/EcsactRuntimeMethodLoadedUI.uxml b/packages/com.seaube.ecsact/Editor/EcsactRuntimeMethodLoadedUI.uxml new file mode 100644 index 0000000..689193b --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/EcsactRuntimeMethodLoadedUI.uxml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/com.seaube.ecsact/Editor/EcsactRuntimeMethodLoadedUI.uxml.meta b/packages/com.seaube.ecsact/Editor/EcsactRuntimeMethodLoadedUI.uxml.meta new file mode 100644 index 0000000..757a8f9 --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/EcsactRuntimeMethodLoadedUI.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: a2a638a8b49ec24468a126f89d509035 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/packages/com.seaube.ecsact/Editor/EcsactSettings.cs b/packages/com.seaube.ecsact/Editor/EcsactSettings.cs index 19dce05..7061280 100644 --- a/packages/com.seaube.ecsact/Editor/EcsactSettings.cs +++ b/packages/com.seaube.ecsact/Editor/EcsactSettings.cs @@ -2,9 +2,11 @@ using UnityEditor; using UnityEngine.UIElements; using UnityEditor.UIElements; -using System.IO; +using System.Linq; using System.Collections.Generic; +#nullable enable + [System.Serializable] class EcsactSettings : ScriptableObject { public const string assetPath = "Assets/Editor/EcsactSettings.asset"; @@ -19,7 +21,11 @@ class EcsactSettings : ScriptableObject { /// will be downloaded and used instead. public string csharpCodegenPluginPath = ""; - internal static EcsactSettings GetOrCreateSettings() { + public string runtimeBuilderOutputPath = "Assets/Plugins/EcsactRuntime.dll"; + + public string runtimeBuilderCompilerPath = ""; + + public static EcsactSettings GetOrCreateSettings() { var settings = AssetDatabase.LoadAssetAtPath(assetPath); if(settings == null) { settings = ScriptableObject.CreateInstance(); @@ -35,11 +41,55 @@ internal static SerializedObject GetSerializedSettings() { } } +[System.Serializable] +class EcsactMethodUIBindings : ScriptableObject { + public string methodName = ""; + public bool methodLoaded = false; +} + static class EcsactSettingsUIElementsRegister { + + internal static void SetupMethodsUI + ( TemplateContainer ui + , string moduleName + , IEnumerable methods + , IEnumerable availableMethods + ) + { + var coreMethodTemplate = ui.Q( + $"{moduleName}-method-template" + ); + + foreach(var method in methods) { + var clone = coreMethodTemplate.templateSource.CloneTree(); + var bindings = + ScriptableObject.CreateInstance(); + bindings.methodName = method; + bindings.methodLoaded = availableMethods.Contains(method); + BindingExtensions.Bind(clone, new SerializedObject(bindings)); + + if(bindings.methodLoaded) { + var missingIcon = clone.Q("method-missing-icon"); + missingIcon.style.display = DisplayStyle.None; + } else { + var availableIcon = clone.Q("method-available-icon"); + availableIcon.style.display = DisplayStyle.None; + } + + coreMethodTemplate.contentContainer.parent.Add(clone); + } + + coreMethodTemplate.contentContainer.style.display = DisplayStyle.None; + } + [SettingsProvider] public static SettingsProvider CreateEcsactSettingsProvider() { + EcsactRuntime? testDefaultRuntime = null; + Editor? runtimeSettingsEditor = null; + Editor? wasmRuntimeSettingsEditor = null; + return new SettingsProvider(EcsactSettings.path, EcsactSettings.scope) { - label = "ECSACT", + label = "Ecsact", activateHandler = (searchContext, rootElement) => { var settings = EcsactSettings.GetSerializedSettings(); var template = AssetDatabase.LoadAssetAtPath( @@ -48,10 +98,98 @@ public static SettingsProvider CreateEcsactSettingsProvider() { var ui = template.Instantiate(); BindingExtensions.Bind(ui, settings); rootElement.Add(ui); + + if(testDefaultRuntime != null) { + EcsactRuntime.Free(testDefaultRuntime); + testDefaultRuntime = null; + } + + var runtimeSettings = EcsactRuntimeSettings.Get(); + testDefaultRuntime = EcsactRuntime.Load( + runtimeSettings.runtimeLibraryPaths + ); + + SetupMethodsUI( + ui, + "async", + EcsactRuntime.Async.methods, + testDefaultRuntime.async.availableMethods + ); + + SetupMethodsUI( + ui, + "core", + EcsactRuntime.Core.methods, + testDefaultRuntime.core.availableMethods + ); + + SetupMethodsUI( + ui, + "dynamic", + EcsactRuntime.Dynamic.methods, + testDefaultRuntime.dynamic.availableMethods + ); + + SetupMethodsUI( + ui, + "meta", + EcsactRuntime.Meta.methods, + testDefaultRuntime.meta.availableMethods + ); + + SetupMethodsUI( + ui, + "serialize", + EcsactRuntime.Serialize.methods, + testDefaultRuntime.serialize.availableMethods + ); + + SetupMethodsUI( + ui, + "static", + EcsactRuntime.Static.methods, + testDefaultRuntime.@static.availableMethods + ); + + SetupMethodsUI( + ui, + "wasm", + EcsactRuntime.Wasm.methods, + testDefaultRuntime.wasm.availableMethods + ); + + var runtimeSettingsContainer = + ui.Q("runtime-settings-container"); + + runtimeSettingsEditor = Editor.CreateEditor(runtimeSettings); + + runtimeSettingsContainer.onGUIHandler = () => { + runtimeSettingsEditor.OnInspectorGUI(); + }; + + var wasmRuntimeSettings = EcsactWasmRuntimeSettings.Get(); + var wasmRuntimeSettingsContainer = + ui.Q("wasm-runtime-settings-container"); + + wasmRuntimeSettingsEditor = Editor.CreateEditor(wasmRuntimeSettings); + + wasmRuntimeSettingsContainer.onGUIHandler = () => { + wasmRuntimeSettingsEditor.OnInspectorGUI(); + }; + }, + deactivateHandler = () => { + if(testDefaultRuntime != null) { + EcsactRuntime.Free(testDefaultRuntime); + testDefaultRuntime = null; + } + + if(runtimeSettingsEditor != null) { + Editor.DestroyImmediate(runtimeSettingsEditor); + runtimeSettingsEditor = null; + } }, keywords = new HashSet(new[] { - "ECSACT", - "ECSACT", + "Ecsact", "ECS", "ECS Plugin", "Plugin", diff --git a/packages/com.seaube.ecsact/Editor/EcsactSettings.uxml b/packages/com.seaube.ecsact/Editor/EcsactSettings.uxml index 01bc04b..f553aff 100644 --- a/packages/com.seaube.ecsact/Editor/EcsactSettings.uxml +++ b/packages/com.seaube.ecsact/Editor/EcsactSettings.uxml @@ -1,8 +1,81 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/com.seaube.ecsact/Editor/EcsactWasmRuntimeSettingsEditor.cs b/packages/com.seaube.ecsact/Editor/EcsactWasmRuntimeSettingsEditor.cs new file mode 100644 index 0000000..f72b3fa --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/EcsactWasmRuntimeSettingsEditor.cs @@ -0,0 +1,112 @@ +using System; +using UnityEditor; +using UnityEngine; +using System.Linq; +using System.Collections.Generic; + +#nullable enable + +[CustomEditor(typeof(EcsactWasmRuntimeSettings))] +public class EcsactWasmRuntimeSettingsEditor : Editor { + + private List? systemLikeTypes; + private Dictionary wasmInfos = new(); + private bool allWasmInfoLoaded = false; + + public override void OnInspectorGUI() { + var settings = target as EcsactWasmRuntimeSettings; + if(settings == null) return; + + if(systemLikeTypes == null) { + systemLikeTypes = Ecsact.Util.GetAllSystemLikeTypes().ToList(); + } + + EditorGUILayout.LabelField( + label: "System Implementations", + style: EditorStyles.boldLabel + ); + + allWasmInfoLoaded = true; + + foreach(Type systemLikeType in systemLikeTypes) { + EditorGUILayout.BeginHorizontal(); + + var systemLikeId = Ecsact.Util.GetSystemID(systemLikeType); + + EditorGUILayout.LabelField(systemLikeType.FullName); + var entry = settings.wasmSystemEntries.Find( + entry => entry.systemId == systemLikeId + ); + if(entry == null) { + settings.wasmSystemEntries.Add(new()); + entry = settings.wasmSystemEntries.Last(); + } + entry.systemId = systemLikeId; + entry.wasmAsset = EditorGUILayout.ObjectField( + obj: entry.wasmAsset, + objType: typeof(WasmAsset), + allowSceneObjects: false + ) as WasmAsset; + + if(entry.wasmAsset != null) { + if(!wasmInfos.ContainsKey(systemLikeId)) { + wasmInfos.Add(systemLikeId, null); + WasmInfo.Load( + AssetDatabase.GetAssetPath(entry.wasmAsset), + loadedWasmInfo => { wasmInfos[systemLikeId] = loadedWasmInfo; }, + () => { wasmInfos.Remove(systemLikeId); }, + settings + ); + } + } + + var wasmInfo = wasmInfos.ContainsKey(systemLikeId) + ? wasmInfos[systemLikeId] + : null; + + if(wasmInfo == null) { + allWasmInfoLoaded = false; + } + + EditorGUI.BeginDisabledGroup(entry.wasmAsset == null); + var dropdownContent = new GUIContent(entry.wasmExportName); + var dropdownPressed = EditorGUILayout.DropdownButton( + dropdownContent, + FocusType.Keyboard + ); + EditorGUI.EndDisabledGroup(); + + if(dropdownPressed && wasmInfo != null) { + var dropdownMenu = new GenericMenu(); + dropdownMenu.allowDuplicateNames = false; + foreach(var export in wasmInfo.exports) { + bool isValidSystemImpl = + export.type == "WASM_EXTERN_FUNC" && + export.results!.Count == 0 && + export.@params!.Count == 1; + + var menuItemContent = new GUIContent(export.name); + + if(isValidSystemImpl) { + dropdownMenu.AddItem( + menuItemContent, + entry.wasmExportName == export.name, + () => { entry.wasmExportName = export.name; } + ); + } else { + dropdownMenu.AddDisabledItem(menuItemContent, false); + } + } + dropdownMenu.ShowAsContext(); + } + + EditorGUILayout.EndHorizontal(); + } + + serializedObject.ApplyModifiedProperties(); + } + + public override bool RequiresConstantRepaint() { + return !allWasmInfoLoaded; + } +} diff --git a/packages/com.seaube.ecsact/Editor/EcsactWasmRuntimeSettingsEditor.cs.meta b/packages/com.seaube.ecsact/Editor/EcsactWasmRuntimeSettingsEditor.cs.meta new file mode 100644 index 0000000..d75fef5 --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/EcsactWasmRuntimeSettingsEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32b5d0323c80fdf4fbd6990ff981a1ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Editor/Importer/EcsactPackage.cs.meta b/packages/com.seaube.ecsact/Editor/Importer/EcsactPackage.cs.meta index 7608653..21eccc3 100644 --- a/packages/com.seaube.ecsact/Editor/Importer/EcsactPackage.cs.meta +++ b/packages/com.seaube.ecsact/Editor/Importer/EcsactPackage.cs.meta @@ -5,7 +5,7 @@ MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 - icon: {instanceID: 0} + icon: {fileID: 2800000, guid: eedabef44d060c54fb86eb832961a281, type: 3} userData: assetBundleName: assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Editor/ecsact-logo-128.png b/packages/com.seaube.ecsact/Editor/ecsact-logo-128.png new file mode 100644 index 0000000..ecd77f7 Binary files /dev/null and b/packages/com.seaube.ecsact/Editor/ecsact-logo-128.png differ diff --git a/packages/com.seaube.ecsact/Editor/ecsact-logo-128.png.meta b/packages/com.seaube.ecsact/Editor/ecsact-logo-128.png.meta new file mode 100644 index 0000000..8a27276 --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/ecsact-logo-128.png.meta @@ -0,0 +1,122 @@ +fileFormatVersion: 2 +guid: eedabef44d060c54fb86eb832961a281 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 0 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Editor/ecsact-logo-256.png b/packages/com.seaube.ecsact/Editor/ecsact-logo-256.png new file mode 100644 index 0000000..aa244ff Binary files /dev/null and b/packages/com.seaube.ecsact/Editor/ecsact-logo-256.png differ diff --git a/packages/com.seaube.ecsact/Editor/ecsact-logo-256.png.meta b/packages/com.seaube.ecsact/Editor/ecsact-logo-256.png.meta new file mode 100644 index 0000000..ac71629 --- /dev/null +++ b/packages/com.seaube.ecsact/Editor/ecsact-logo-256.png.meta @@ -0,0 +1,122 @@ +fileFormatVersion: 2 +guid: 634118fa0a22375438cb8823cebafb9d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 0 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/AsyncRunner.cs b/packages/com.seaube.ecsact/Runtime/AsyncRunner.cs new file mode 100644 index 0000000..6db937a --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/AsyncRunner.cs @@ -0,0 +1,41 @@ +using UnityEngine; + +#nullable enable + +namespace Ecsact { + [AddComponentMenu("")] + public class AsyncRunner : MonoBehaviour { + private static AsyncRunner? instance = null; + private EcsactRuntime? runtimeInstance = null; + + [RuntimeInitializeOnLoadMethod] + private static void OnRuntimeLoad() { + if(instance != null) { + return; + } + + var settings = EcsactRuntimeSettings.Get(); + if(!settings.useAsyncRunner) { + return; + } + + var gameObject = new GameObject("Ecsact Async Runner"); + instance = gameObject.AddComponent(); + DontDestroyOnLoad(gameObject); + } + + void Awake() { + runtimeInstance = EcsactRuntime.GetOrLoadDefault(); + } + + void Update() { + runtimeInstance!.async.FlushEvents(); + } + + void OnDestroy() { + if(this.Equals(instance)) { + instance = null; + } + } + } +} diff --git a/packages/com.seaube.ecsact/Runtime/AsyncRunner.cs.meta b/packages/com.seaube.ecsact/Runtime/AsyncRunner.cs.meta new file mode 100644 index 0000000..d8b5505 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/AsyncRunner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af1b115eb8d72ec42897c763d838ad15 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/DefaultRunner.cs b/packages/com.seaube.ecsact/Runtime/DefaultRunner.cs new file mode 100644 index 0000000..a35d18f --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/DefaultRunner.cs @@ -0,0 +1,74 @@ +using UnityEngine; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#nullable enable + +namespace Ecsact { + [AddComponentMenu("")] + public class DefaultRunner : MonoBehaviour { + private EcsactRuntime? runtimeInstance = null; + + private EcsactRuntimeDefaultRegistry? defReg; + + public Int32 registryId => defReg?.registryId ?? -1; + +#if UNITY_EDITOR + [NonSerialized] + public int debugExecutionCountTotal = 0; + [NonSerialized] + public int debugExecutionTimeMs = 0; +#endif + + [RuntimeInitializeOnLoadMethod] + private static void OnRuntimeLoad() { + var settings = EcsactRuntimeSettings.Get(); + + foreach(var defReg in settings.defaultRegistries) { + if(!defReg.useDefaultRunner) continue; + + var gameObjectName = $"Ecsact Default Runner"; + if(!string.IsNullOrWhiteSpace(defReg.registryName)) { + gameObjectName += " - " + defReg.registryName; + } + + var gameObject = new GameObject(gameObjectName); + var runner = gameObject.AddComponent(); + runner.defReg = defReg; + DontDestroyOnLoad(gameObject); + } + } + + void Awake() { + runtimeInstance = EcsactRuntime.GetOrLoadDefault(); + } + + void Start() { + gameObject.name += $" ({defReg!.registryId})"; + } + + void Update() { + if(defReg == null) return; + UnityEngine.Debug.Assert(defReg.registryId != -1); + +#if UNITY_EDITOR + var executionTimeWatch = Stopwatch.StartNew(); +#endif + + runtimeInstance!.core.ExecuteSystems( + registryId: defReg.registryId, + executionCount: 1, + new EcsactRuntime.ExecutionOptions[]{defReg.executionOptions} + ); + +#if UNITY_EDITOR + executionTimeWatch.Stop(); + debugExecutionTimeMs = (int)executionTimeWatch.ElapsedMilliseconds; + debugExecutionCountTotal += 1; +#endif + + defReg.executionOptions = new EcsactRuntime.ExecutionOptions(); + } + } +} diff --git a/packages/com.seaube.ecsact/Runtime/DefaultRunner.cs.meta b/packages/com.seaube.ecsact/Runtime/DefaultRunner.cs.meta new file mode 100644 index 0000000..84a2855 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/DefaultRunner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 524a4ad0b84b1eb4b80ac980181df7fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/Ecsact.asmdef b/packages/com.seaube.ecsact/Runtime/Ecsact.asmdef index bf8422e..e168f72 100644 --- a/packages/com.seaube.ecsact/Runtime/Ecsact.asmdef +++ b/packages/com.seaube.ecsact/Runtime/Ecsact.asmdef @@ -1,14 +1,18 @@ { - "name": "Ecsact", - "rootNamespace": "", - "references": [], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} + "name": "Ecsact", + "rootNamespace": "", + "references": [ + "GUID:21b0c8d1703a94250bfac916590cea4f", + "GUID:ea715009a4efd4c6cbc85be3ae097dd3", + "GUID:7faa839a710e565409361b9f0c62a4da" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/packages/com.seaube.ecsact/Runtime/Ecsact.cs b/packages/com.seaube.ecsact/Runtime/Ecsact.cs index 1b9790d..9dfdc0a 100644 --- a/packages/com.seaube.ecsact/Runtime/Ecsact.cs +++ b/packages/com.seaube.ecsact/Runtime/Ecsact.cs @@ -15,6 +15,9 @@ public interface Component {} /// ECSACT Action Marker Interface public interface Action {} + /// ECSACT System Marker Interface + public interface System {} + public static class Util { private static Dictionary cachedComponentTypes; @@ -23,7 +26,7 @@ static Util() { } public static bool IsComponent - ( System.Type componentType + ( Type componentType ) { foreach(var i in componentType.GetInterfaces()) { @@ -35,16 +38,42 @@ public static bool IsComponent return false; } + public static bool IsSystem + ( Type systemType + ) + { + foreach(var i in systemType.GetInterfaces()) { + if(i == typeof(Ecsact.System)) { + return true; + } + } + + return false; + } + + public static bool IsAction + ( Type actionType + ) + { + foreach(var i in actionType.GetInterfaces()) { + if(i == typeof(Ecsact.Action)) { + return true; + } + } + + return false; + } + public static object? PtrToComponent - ( IntPtr componentIntPtr - , System.Int32 componentId + ( IntPtr componentIntPtr + , Int32 componentId ) { var componentType = GetComponentType(componentId); if(componentType != null) { if(componentIntPtr == IntPtr.Zero) { - return System.Activator.CreateInstance(componentType); + return Activator.CreateInstance(componentType); } return Marshal.PtrToStructure(componentIntPtr, componentType); } @@ -53,23 +82,23 @@ public static object? PtrToComponent } public static void ComponentToPtr - ( object component - , System.Int32 componentId - , IntPtr componentIntPtr + ( object component + , Int32 componentId + , IntPtr componentIntPtr ) { Marshal.StructureToPtr(component, componentIntPtr, false); } - public static System.Type? GetComponentType - ( System.Int32 componentId + public static Type? GetComponentType + ( Int32 componentId ) { if(cachedComponentTypes.TryGetValue(componentId, out var t)) { return t; } - foreach(var assembly in System.AppDomain.CurrentDomain.GetAssemblies()) { + foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies()) { foreach(var type in assembly.GetTypes()) { if(IsComponent(type)) { var typeComponentId = GetComponentID(type); @@ -87,12 +116,12 @@ public static void ClearComponentTypeCache() { cachedComponentTypes.Clear(); } - public static System.Int32 GetComponentID() where T : Ecsact.Component { + public static Int32 GetComponentID() where T : Ecsact.Component { return GetComponentID(typeof(T)); } - public static System.Int32 GetComponentID - ( System.Type componentType + public static Int32 GetComponentID + ( Type componentType ) { if(!IsComponent(componentType)) { @@ -104,17 +133,28 @@ public static System.Int32 GetComponentID BindingFlags.Static | BindingFlags.Public ); - var componentId = (System.Int32)idField.GetValue(null); + var componentId = (Int32)idField.GetValue(null); cachedComponentTypes[componentId] = componentType; return componentId; } - public static System.Int32 GetActionID() where T : Ecsact.Action { + public static Int32 GetActionID() where T : Ecsact.Action { return GetActionID(typeof(T)); } - public static System.Int32 GetActionID - ( System.Type actionType + public static Int32 GetActionID + ( Type actionType + ) + { + return GetSystemID(actionType); + } + + public static Int32 GetSystemID() where T : Ecsact.System { + return GetSystemID(typeof(T)); + } + + public static Int32 GetSystemID + ( Type actionType ) { var idField = actionType.GetField( @@ -122,7 +162,7 @@ public static System.Int32 GetActionID BindingFlags.Static | BindingFlags.Public ); - return (System.Int32)idField.GetValue(null); + return (Int32)idField.GetValue(null); } public static IEnumerable GetComponentIdPermutations @@ -170,5 +210,14 @@ public static IEnumerable GetComponentIdPermutations UnityEngine.Debug.Log("PERMUTATION END"); } + public static IEnumerable GetAllSystemLikeTypes() { + foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + foreach(var type in assembly.GetTypes()) { + if(Util.IsSystem(type) || Util.IsAction(type)) { + yield return type; + } + } + } + } } } diff --git a/packages/com.seaube.ecsact/Runtime/EcsactRuntime.cs b/packages/com.seaube.ecsact/Runtime/EcsactRuntime.cs new file mode 100644 index 0000000..7a36aaf --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/EcsactRuntime.cs @@ -0,0 +1,1795 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Ecsact { + public enum AsyncError : Int32 { + ConnectionClosed, + ConnectFail, + SocketFail, + StateFail, + StartFail, + InvalidConnectionString, + } +} + +#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN +internal static class NativeLibrary { + [DllImport("Kernel32.dll", + EntryPoint = "LoadLibrary", + CharSet = CharSet.Ansi, + CallingConvention = CallingConvention.Winapi + )] + private static extern IntPtr LoadLibrary + ( [MarshalAs(UnmanagedType.LPStr)] string lpFileName + ); + + [DllImport("Kernel32.dll", + EntryPoint = "FreeLibrary", + CallingConvention = CallingConvention.Winapi + )] + private static extern bool FreeLibrary + ( IntPtr hLibModule + ); + + [DllImport("Kernel32.dll", + EntryPoint = "GetProcAddress", + CharSet = CharSet.Ansi, + CallingConvention = CallingConvention.Winapi + )] + private static extern IntPtr GetProcAddress + ( IntPtr hModule + , [MarshalAs(UnmanagedType.LPStr)] string procName + ); + + public static IntPtr Load + ( string libraryPath + ) + { + return LoadLibrary(libraryPath); + } + + public static void Free + ( IntPtr handle + ) + { + FreeLibrary(handle); + } + + public static bool TryGetExport + ( IntPtr handle + , string name + , out IntPtr address + ) + { + address = GetProcAddress(handle, name); + return address != IntPtr.Zero; + } +} +#endif + +public class EcsactRuntimeMissingMethod : Exception { + public EcsactRuntimeMissingMethod + ( string methodName + ) + : base(methodName) + { + } + + public EcsactRuntimeMissingMethod + ( string methodName + , Exception inner + ) + : base(methodName, inner) + { + } +} + +public class EcsactRuntime { + public static class VisualScriptingEventNames { + public const string AsyncError = "EcsactAsyncErrorEvent"; + public const string AsyncConnectStateChange = "EcsactAsyncConnectStateChange"; + } + + public enum EcsactEvent : Int32 { + InitComponent = 0, + UpdateComponent = 1, + RemoveComponent = 2, + } + + public delegate void EachComponentCallback + ( Int32 componentId + , object componentData + , IntPtr callbackUserData + ); + + public delegate void ComponentEventCallback + ( EcsactEvent ev + , Int32 entityId + , Int32 componentId + , IntPtr componentData + , IntPtr callbackUserData + ); + + public struct ExecutionOptions { + public Int32 addComponentsLength; + public Int32[] addComponentsEntities; + public object[] addComponents; + + public Int32 updateComponentsLength; + public Int32[] updateComponentsEntities; + public object[] updateComponents; + + public Int32 removeComponentsLength; + public Int32[] removeComponentsEntities; + public Int32[] removeComponents; + + public Int32 actionsLength; + public object[] actions; + }; + + public struct ExecutionEventsCollector { + /// + /// invoked after system executions are finished for every component that + /// is new. The component_data is the last value given for the component, + /// not the first. Invocation happens in the calling thread. `event` will + /// always be EcsactEvent.InitComponent + /// + public ComponentEventCallback initCallback; + + /// + /// callbackUserData passed to initCallback + /// + public IntPtr initCallbackUserData; + + /// + /// invoked after system executions are finished for every changed + /// component. Invocation happens in the calling thread. event will + /// always be EcsactEvent.UpdateComponent + /// + public ComponentEventCallback updateCallback; + + /// + /// callbackUserData passed to updateCallback + /// + public IntPtr updateCallbackUserData; + + /// + /// invoked after system executions are finished for every removed + /// component. Invocation happens in the calling thread. event will + /// always be EcsactEvent.RemoveComponent. + /// + public ComponentEventCallback removeCallback; + + /// + /// callbackUserData passed to removeCallback + /// + public IntPtr removeCallbackUserData; + } + + public struct StaticComponentInfo { + public Int32 componentId; + [MarshalAs(UnmanagedType.LPStr)] public string componentName; + public Int32 componentSize; + public ComponentCompareFn componentCompareFn; + [MarshalAs(UnmanagedType.I1)] public bool transient; + } + + public struct StaticSystemInfo { + public Int32 systemId; + public Int32 order; + [MarshalAs(UnmanagedType.LPStr)] public string systemName; + public Int32 parentSystemId; + public Int32 childSystemsCount; + public Int32[] childSystemIds; + public Int32 capabilitiesCount; + public Int32[] capabilityComponents; + public SystemCapability[] capabilities; + public SystemExecutionImpl executionImpl; + } + + public struct StaticActionInfo { + public Int32 actionId; + public Int32 order; + [MarshalAs(UnmanagedType.LPStr)] public string actionName; + public Int32 actionSize; + public ActionCompareFn actionCompareFn; + public Int32 childSystemsCount; + public Int32[] childSystemIds; + public Int32 capabilitiesCount; + public Int32[] capabilityComponents; + public SystemCapability[] capabilities; + public SystemExecutionImpl executionImpl; + } + + public delegate void StaticReloadCallback + ( IntPtr userData + ); + + public enum SystemCapability : Int32 { + Readonly = 1, + Writeonly = 2, + ReadWrite = 3, + OptionalReadonly = 4 | Readonly, + OptionalWriteonly = 4 | Writeonly, + OptionalReadWrite = 4 | ReadWrite, + Include = 8, + Exclude = 16, + Adds = 32 | Exclude, + Removes = 64 | Include, + } + + public enum SystemGenerate : Int32 { + Required = 1, + Optional = 2, + } + + public delegate void SystemExecutionImpl + ( IntPtr context + ); + + public delegate Int32 ActionCompareFn + ( IntPtr firstAction + , IntPtr secondAction + ); + + public delegate Int32 ComponentCompareFn + ( IntPtr firstComponent + , IntPtr secondComponent + ); + + public delegate void AsyncErrorCallback + ( Ecsact.AsyncError err + , Int32 requestId + , IntPtr callbackUserData + ); + + public delegate void AsyncConnectCallback + ( [MarshalAs(UnmanagedType.LPStr)] string connectAddress + , Int32 connectPort + , IntPtr callbackUserData + ); + + public delegate void AsyncActionCommittedCallback + ( Int32 actionId + , object actionData + , Int32 committedTick + , Int32 requestId + , IntPtr callbackUserData + ); + + public struct AsyncEventsCollector { + public AsyncErrorCallback errorCallback; + public IntPtr errorCallbackUserData; + public AsyncConnectCallback connectCallback; + public IntPtr connectCallbackUserData; + public AsyncActionCommittedCallback actionCommittedCallback; + public IntPtr actionCommittedCallbackUserData; + } + + private static EcsactRuntime? defaultInstance; + + public static EcsactRuntime GetOrLoadDefault() { + if(defaultInstance == null) { + var settings = EcsactRuntimeSettings.Get(); + defaultInstance = Load(settings.runtimeLibraryPaths); + + if(defaultInstance != null) { + foreach(var defReg in settings.defaultRegistries) { + defReg.registryId = defaultInstance.core.CreateRegistry( + defReg.registryName + ); + } + } + } + + if(defaultInstance == null) { +#if UNITY_EDITOR + UnityEditor.EditorApplication.isPlaying = false; + var okQuit = UnityEditor.EditorUtility.DisplayDialog( + title: "Failed to load default ecsact runtime", + message: "Please check your ecsact runtime settings", + ok: "Ok Quit", + cancel: "Continue Anyways" + ); + + if(okQuit) { + UnityEditor.EditorApplication.isPlaying = false; + } + UnityEngine.Application.Quit(1); +#else + UnityEngine.Debug.LogError("Failed to load default ecsact runtime"); + UnityEngine.Application.Quit(1); +#endif + throw new Exception("Failed to load default ecsact runtime"); + } + + return defaultInstance; + } + + public static void SetDefault + ( EcsactRuntime? runtime + ) + { + if(defaultInstance != null) { + Free(defaultInstance); + } + defaultInstance = runtime; + } + + private IntPtr[]? _libs; + private Core? _core; + private Async? _async; + private Dynamic? _dynamic; + private Meta? _meta; + private Serialize? _serialize; + private Static? _static; + private Wasm? _wasm; + + public class Async { + internal List _availableMethods = new(); + public static string[] methods => new string[]{ + "ecsact_async_connect", + "ecsact_async_disconnect", + "ecsact_async_execute_action_at", + "ecsact_async_execute_action", + "ecsact_async_flush_events", + }; + + public IEnumerable availableMethods => _availableMethods; + + internal delegate void ecsact_async_execute_action_delegate + ( Int32 actionId + , IntPtr actionData + ); + internal ecsact_async_execute_action_delegate? ecsact_async_execute_action; + + internal delegate void ecsact_async_execute_action_at_delegate + ( Int32 actionId + , IntPtr actionData + , Int32 tick + ); + internal ecsact_async_execute_action_at_delegate? ecsact_async_execute_action_at; + + internal delegate void ecsact_async_flush_events_delegate + ( in ExecutionEventsCollector executionEventsCollector + , in AsyncEventsCollector asyncEventsCollector + ); + internal ecsact_async_flush_events_delegate? ecsact_async_flush_events; + + internal delegate void ecsact_async_connect_delegate + ( [MarshalAs(UnmanagedType.LPStr)] string connectionString + ); + internal ecsact_async_connect_delegate? ecsact_async_connect; + + internal delegate void ecsact_async_disconnect_delegate + ( + ); + internal ecsact_async_disconnect_delegate? ecsact_async_disconnect; + + public delegate void ErrorCallback + ( Ecsact.AsyncError err + , Int32 requestId + ); + + public delegate void ConnectCallback + ( string connectAddress + , Int32 connectPort + ); + + private AsyncEventsCollector _asyncEvs; + private List _errCallbacks = new(); + private List _connectCallbacks = new(); + private EcsactRuntime _owner; + + internal Async + ( EcsactRuntime owner + ) + { + _owner = owner; + _asyncEvs = new AsyncEventsCollector{ + actionCommittedCallback = OnActionCommittedHandler, + actionCommittedCallbackUserData = IntPtr.Zero, + errorCallback = OnErrorHandler, + errorCallbackUserData = IntPtr.Zero, + connectCallback = OnConnectHandler, + connectCallbackUserData = IntPtr.Zero, + }; + } + + [AOT.MonoPInvokeCallback(typeof(AsyncActionCommittedCallback))] + private static void OnActionCommittedHandler + ( Int32 actionId + , object actionData + , Int32 committedTick + , Int32 requestId + , IntPtr callbackUserData + ) + { + var self = (GCHandle.FromIntPtr(callbackUserData).Target as Async)!; + } + + [AOT.MonoPInvokeCallback(typeof(AsyncErrorCallback))] + private static void OnErrorHandler + ( Ecsact.AsyncError err + , Int32 requestId + , IntPtr callbackUserData + ) + { + var self = (GCHandle.FromIntPtr(callbackUserData).Target as Async)!; + foreach(var cb in self._errCallbacks) { + cb(err, requestId); + } + } + + [AOT.MonoPInvokeCallback(typeof(AsyncConnectCallback))] + private static void OnConnectHandler + ( [MarshalAs(UnmanagedType.LPStr)] string connectAddress + , Int32 connectPort + , IntPtr callbackUserData + ) + { + var self = (GCHandle.FromIntPtr(callbackUserData).Target as Async)!; + foreach(var cb in self._connectCallbacks) { + cb(connectAddress, connectPort); + } + } + + public Action OnError + ( ErrorCallback callback + ) + { + _errCallbacks.Add(callback); + + return () => { + _errCallbacks.Remove(callback); + }; + } + + public Action OnConnect + ( ConnectCallback callback + ) + { + _connectCallbacks.Add(callback); + + return () => { + _connectCallbacks.Remove(callback); + }; + } + + public void Connect + ( string connectionString + ) + { + if(ecsact_async_connect == null) { + throw new EcsactRuntimeMissingMethod("ecsact_async_connect"); + } + ecsact_async_connect(connectionString); + } + + public void FlushEvents() { + if(ecsact_async_flush_events == null) { + throw new EcsactRuntimeMissingMethod("ecsact_async_flush_events"); + } + + var selfPinned = GCHandle.Alloc(this, GCHandleType.Pinned); + var ownerPinned = GCHandle.Alloc(_owner, GCHandleType.Pinned); + try { + var selfIntPtr = GCHandle.ToIntPtr(selfPinned); + var ownerIntPtr = GCHandle.ToIntPtr(ownerPinned); + _owner._execEvs.initCallbackUserData = ownerIntPtr; + _owner._execEvs.updateCallbackUserData = ownerIntPtr; + _owner._execEvs.removeCallbackUserData = ownerIntPtr; + _asyncEvs.errorCallbackUserData = selfIntPtr; + _asyncEvs.connectCallbackUserData = selfIntPtr; + _asyncEvs.actionCommittedCallbackUserData = selfIntPtr; + ecsact_async_flush_events(in _owner._execEvs, in _asyncEvs); + } finally { + selfPinned.Free(); + ownerPinned.Free(); + } + } + } + + public class Core { + internal List _availableMethods = new(); + public static string[] methods => new string[]{ + "ecsact_add_component", + "ecsact_clear_registry", + "ecsact_count_components", + "ecsact_count_entities", + "ecsact_create_entity", + "ecsact_create_registry", + "ecsact_destroy_entity", + "ecsact_destroy_registry", + "ecsact_each_component", + "ecsact_ensure_entity", + "ecsact_entity_exists", + "ecsact_execute_systems", + "ecsact_get_component", + "ecsact_get_components", + "ecsact_get_entities", + "ecsact_has_component", + "ecsact_remove_component", + "ecsact_update_component", + }; + + public IEnumerable availableMethods => _availableMethods; + + internal delegate Int32 ecsact_create_registry_delegate + ( string registryName + ); + internal ecsact_create_registry_delegate? ecsact_create_registry; + + internal delegate void ecsact_destroy_registry_delegate + ( Int32 registryId + ); + internal ecsact_destroy_registry_delegate? ecsact_destroy_registry; + + internal delegate void ecsact_clear_registry_delegate + ( Int32 registryId + ); + internal ecsact_clear_registry_delegate? ecsact_clear_registry; + + internal delegate Int32 ecsact_create_entity_delegate + ( Int32 registryId + ); + internal ecsact_create_entity_delegate? ecsact_create_entity; + + internal delegate void ecsact_ensure_entity_delegate + ( Int32 registryId + , Int32 entityId + ); + internal ecsact_ensure_entity_delegate? ecsact_ensure_entity; + + internal delegate bool ecsact_entity_exists_delegate + ( Int32 registryId + , Int32 entityId + ); + internal ecsact_entity_exists_delegate? ecsact_entity_exists; + + internal delegate void ecsact_destroy_entity_delegate + ( Int32 registryId + , Int32 entityId + ); + internal ecsact_destroy_entity_delegate? ecsact_destroy_entity; + + internal delegate Int32 ecsact_count_entities_delegate + ( Int32 registryId + ); + internal ecsact_count_entities_delegate? ecsact_count_entities; + + internal delegate void ecsact_get_entities_delegate + ( Int32 registryId + , Int32 maxEntitiesCount + , out Int32[] outEntities + , out Int32 outEntitiesCount + ); + internal ecsact_get_entities_delegate? ecsact_get_entities; + + internal delegate void ecsact_add_component_delegate + ( Int32 registryId + , Int32 entityId + , Int32 componentId + , IntPtr componentData + ); + internal ecsact_add_component_delegate? ecsact_add_component; + + internal delegate bool ecsact_has_component_delegate + ( Int32 registryId + , Int32 entityId + , Int32 componentId + ); + internal ecsact_has_component_delegate? ecsact_has_component; + + internal delegate IntPtr ecsact_get_component_delegate + ( Int32 registryId + , Int32 entityId + , Int32 componentId + ); + internal ecsact_get_component_delegate? ecsact_get_component; + + internal delegate void ecsact_each_component_delegate + ( Int32 registryId + , Int32 entityId + , EachComponentCallback callback + , IntPtr callbackUserData + ); + internal ecsact_each_component_delegate? ecsact_each_component; + + internal delegate Int32 ecsact_count_components_delegate + ( Int32 registryId + , Int32 entityId + ); + internal ecsact_count_components_delegate? ecsact_count_components; + + internal delegate void ecsact_get_components_delegate + ( Int32 registryId + , Int32 entityId + , Int32 maxComponentsCount + , out Int32[] outComponentIds + , out IntPtr[] outComponentsData + , out Int32 outComponentsCount + ); + internal ecsact_get_components_delegate? ecsact_get_components; + + internal delegate void ecsact_update_component_delegate + ( Int32 registryId + , Int32 entityId + , Int32 componentId + , object componentData + ); + internal ecsact_update_component_delegate? ecsact_update_component; + + internal delegate void ecsact_remove_component_delegate + ( Int32 registryId + , Int32 entityId + , Int32 componentId + ); + internal ecsact_remove_component_delegate? ecsact_remove_component; + + internal delegate void ecsact_component_event_callback + ( EcsactEvent ev + , Int32 entityId + , Int32 componentId + , object componentData + , IntPtr callbackUserData + ); + internal delegate void ecsact_execute_systems_delegate + ( Int32 registryId + , Int32 executionCount + , ExecutionOptions[] executionOptionsList + , in ExecutionEventsCollector eventsCollector + ); + internal ecsact_execute_systems_delegate? ecsact_execute_systems; + + private EcsactRuntime _owner; + + internal Core + ( EcsactRuntime owner + ) + { + _owner = owner; + } + + public Int32 CreateRegistry + ( string registryName + ) + { + if(ecsact_create_registry == null) { + throw new EcsactRuntimeMissingMethod("ecsact_create_registry"); + } + + return ecsact_create_registry(registryName); + } + + public void DestroyRegistry + ( Int32 registryId + ) + { + if(ecsact_destroy_registry == null) { + throw new EcsactRuntimeMissingMethod("ecsact_destroy_registry"); + } + + ecsact_destroy_registry(registryId); + } + + public void ClearRegistry + ( Int32 registryId + ) + { + if(ecsact_clear_registry == null) { + throw new EcsactRuntimeMissingMethod("ecsact_clear_registry"); + } + + ecsact_clear_registry(registryId); + } + + public Int32 CreateEntity + ( Int32 registryId + ) + { + if(ecsact_create_entity == null) { + throw new EcsactRuntimeMissingMethod("ecsact_create_entity"); + } + + return ecsact_create_entity(registryId); + } + + public void EnsureEntity + ( Int32 registryId + , Int32 entityId + ) + { + if(ecsact_ensure_entity == null) { + throw new EcsactRuntimeMissingMethod("ecsact_ensure_entity"); + } + + ecsact_ensure_entity(registryId, entityId); + } + + public bool EntityExists + ( Int32 registryId + , Int32 entityId + ) + { + if(ecsact_entity_exists == null) { + throw new EcsactRuntimeMissingMethod("ecsact_entity_exists"); + } + + return ecsact_entity_exists(registryId, entityId); + } + + public void DestroyEntity + ( Int32 registryId + , Int32 entityId + ) + { + if(ecsact_destroy_entity == null) { + throw new EcsactRuntimeMissingMethod("ecsact_destroy_entity"); + } + + ecsact_destroy_entity(registryId, entityId); + } + + public Int32 CountEntities + ( Int32 registryId + ) + { + if(ecsact_count_entities == null) { + throw new EcsactRuntimeMissingMethod("ecsact_count_entities"); + } + + return ecsact_count_entities(registryId); + } + + public void GetEntities + ( Int32 registryId + , Int32 maxEntitiesCount + , out Int32[] outEntities + , out Int32 outEntitiesCount + ) + { + if(ecsact_get_entities == null) { + throw new EcsactRuntimeMissingMethod("ecsact_get_entities"); + } + + ecsact_get_entities( + registryId, + maxEntitiesCount, + out outEntities, + out outEntitiesCount + ); + } + + public Int32[] GetEntities + ( Int32 registryId + ) + { + var entitiesCount = CountEntities(registryId); + var entities = new Int32[entitiesCount]; + + GetEntities( + registryId, + entitiesCount, + out entities, + out entitiesCount + ); + + return entities; + } + + public void AddComponent + ( Int32 registryId + , Int32 entityId + , C component + ) where C : Ecsact.Component + { + if(ecsact_add_component == null) { + throw new EcsactRuntimeMissingMethod("ecsact_add_component"); + } + + var componentId = Ecsact.Util.GetComponentID(); + var componentPtr = Marshal.AllocHGlobal(Marshal.SizeOf(component)); + + try { + Marshal.StructureToPtr(component, componentPtr, false); + ecsact_add_component( + registryId, + entityId, + componentId, + componentPtr + ); + } finally { + Marshal.FreeHGlobal(componentPtr); + } + } + + public bool HasComponent + ( Int32 registryId + , Int32 entityId + , Int32 componentId + ) + { + if(ecsact_has_component == null) { + throw new EcsactRuntimeMissingMethod("ecsact_has_component"); + } + + return ecsact_has_component(registryId, entityId, componentId); + } + + public IntPtr GetComponent + ( Int32 registryId + , Int32 entityId + , Int32 componentId + ) + { + if(ecsact_get_component == null) { + throw new EcsactRuntimeMissingMethod("ecsact_get_component"); + } + + return ecsact_get_component(registryId, entityId, componentId); + } + + public Int32 CountComponents + ( Int32 registryId + , Int32 entityId + ) + { + if(ecsact_count_components == null) { + throw new EcsactRuntimeMissingMethod("ecsact_count_components"); + } + + return ecsact_count_components(registryId, entityId); + } + + public void GetComponents + ( Int32 registryId + , Int32 entityId + , Int32 maxComponentsCount + , out Int32[] outComponentIds + , out IntPtr[] outComponentsData + , out Int32 outComponentsCount + ) + { + if(ecsact_get_components == null) { + throw new EcsactRuntimeMissingMethod("ecsact_get_components"); + } + + ecsact_get_components( + registryId, + entityId, + maxComponentsCount, + out outComponentIds, + out outComponentsData, + out outComponentsCount + ); + } + + public void EachComponent + ( Int32 registryId + , Int32 entityId + , EachComponentCallback callback + , IntPtr callbackUserData + ) + { + if(ecsact_each_component == null) { + throw new EcsactRuntimeMissingMethod("ecsact_each_component"); + } + + ecsact_each_component( + registryId, + entityId, + callback, + callbackUserData + ); + } + + public void UpdateComponent + ( Int32 registryId + , Int32 entityId + , Int32 componentId + , object componentData + ) + { + if(ecsact_update_component == null) { + throw new EcsactRuntimeMissingMethod("ecsact_update_component"); + } + + ecsact_update_component( + registryId, + entityId, + componentId, + componentData + ); + } + + public void RemoveComponent + ( Int32 registryId + , Int32 entityId + , Int32 componentId + ) + { + if(ecsact_remove_component == null) { + throw new EcsactRuntimeMissingMethod("ecsact_remove_component"); + } + + ecsact_remove_component(registryId, entityId, componentId); + } + + public void ExecuteSystems + ( Int32 registryId + , Int32 executionCount + , ExecutionOptions[] executionOptionsList + ) + { + if(ecsact_execute_systems == null) { + throw new EcsactRuntimeMissingMethod("ecsact_execute_systems"); + } + + var ownerPinned = GCHandle.Alloc(_owner, GCHandleType.Pinned); + + try { + var ownerIntPtr = GCHandle.ToIntPtr(ownerPinned); + _owner._execEvs.initCallbackUserData = ownerIntPtr; + _owner._execEvs.updateCallbackUserData = ownerIntPtr; + _owner._execEvs.removeCallbackUserData = ownerIntPtr; + ecsact_execute_systems( + registryId, + executionCount, + executionOptionsList, + in _owner._execEvs + ); + } finally { + ownerPinned.Free(); + } + } + } + + public class Dynamic { + internal List _availableMethods = new(); + public static string[] methods => new string[]{ + "ecsact_add_system_capability", + "ecsact_add_system_generate_component_set", + "ecsact_create_action", + "ecsact_create_component", + "ecsact_create_system", + "ecsact_create_variant", + "ecsact_destroy_component", + "ecsact_destroy_variant", + "ecsact_register_action", + "ecsact_register_component", + "ecsact_register_system", + "ecsact_remove_system_capability", + "ecsact_resize_action", + "ecsact_resize_component", + "ecsact_set_system_execution_impl", + "ecsact_system_execution_context_action", + "ecsact_system_execution_context_add", + "ecsact_system_execution_context_generate", + "ecsact_system_execution_context_get", + "ecsact_system_execution_context_has", + "ecsact_system_execution_context_id", + "ecsact_system_execution_context_parent", + "ecsact_system_execution_context_remove", + "ecsact_system_execution_context_same", + "ecsact_update_system_capability", + }; + + public IEnumerable availableMethods => _availableMethods; + + internal delegate void ecsact_system_execution_context_action_delegate + ( IntPtr context + , IntPtr outActionData + ); + internal ecsact_system_execution_context_action_delegate? ecsact_system_execution_context_action; + + internal delegate void ecsact_system_execution_context_add_delegate + ( IntPtr context + , Int32 componentId + , object componentData + ); + internal ecsact_system_execution_context_add_delegate? ecsact_system_execution_context_add; + + internal delegate void ecsact_system_execution_context_remove_delegate + ( IntPtr context + , Int32 componentId + ); + internal ecsact_system_execution_context_remove_delegate? ecsact_system_execution_context_remove; + + internal delegate void ecsact_system_execution_context_get_delegate + ( IntPtr context + , Int32 componentId + , out object outComponentData + ); + internal ecsact_system_execution_context_get_delegate? ecsact_system_execution_context_get; + + internal delegate void ecsact_system_execution_context_update_delegate + ( IntPtr context + , Int32 componentId + , object componentData + ); + internal ecsact_system_execution_context_update_delegate? ecsact_system_execution_context_update; + + internal delegate bool ecsact_system_execution_context_has_delegate + ( IntPtr context + , Int32 componentId + ); + internal ecsact_system_execution_context_has_delegate? ecsact_system_execution_context_has; + + internal delegate void ecsact_system_execution_context_generate_delegate + ( IntPtr context + , Int32 componentCount + , Int32[] componentIds + , object[] componentsData + ); + internal ecsact_system_execution_context_generate_delegate? ecsact_system_execution_context_generate; + + internal delegate IntPtr ecsact_system_execution_context_parent_delegate + ( IntPtr context + ); + internal ecsact_system_execution_context_parent_delegate? ecsact_system_execution_context_parent; + + internal delegate bool ecsact_system_execution_context_same_delegate + ( IntPtr firstContext + , IntPtr secondContext + ); + internal ecsact_system_execution_context_same_delegate? ecsact_system_execution_context_same; + + internal delegate void ecsact_create_system_delegate + ( [MarshalAs(UnmanagedType.LPStr)] string systemName + , Int32 parentSystemId + , Int32[] capabilityComponentIds + , SystemCapability[] capabilities + , Int32 capabilitiesCount + , SystemExecutionImpl executionImpl + ); + internal ecsact_create_system_delegate? ecsact_create_system; + + internal delegate void ecsact_set_system_execution_impl_delegate + ( Int32 systemId + , SystemExecutionImpl executionImpl + ); + internal ecsact_set_system_execution_impl_delegate? ecsact_set_system_execution_impl; + + internal delegate void ecsact_create_action_delegate + ( [MarshalAs(UnmanagedType.LPStr)] string actionName + , Int32 actionSize + , ActionCompareFn actionCompareFn + , Int32[] capabilityComponentIds + , SystemCapability[] capabilities + , Int32 capabilitiesCount + , SystemExecutionImpl executionImpl + ); + internal ecsact_create_action_delegate? ecsact_create_action; + + internal delegate void ecsact_resize_action_delegate + ( Int32 actionId + , Int32 newActionSize + , ActionCompareFn newActionCompareFn + ); + internal ecsact_resize_action_delegate? ecsact_resize_action; + + internal delegate void ecsact_create_component_delegate + ( [MarshalAs(UnmanagedType.LPStr)] string componentName + , Int32 componentSize + , ComponentCompareFn componentCompareFn + ); + internal ecsact_create_component_delegate? ecsact_create_component; + + internal delegate void ecsact_resize_component_delegate + ( Int32 componentId + , Int32 newComponentSize + , ComponentCompareFn newComponentCompareFn + ); + internal ecsact_resize_component_delegate? ecsact_resize_component; + + internal delegate void ecsact_destroy_component_delegate + ( Int32 componentId + ); + internal ecsact_destroy_component_delegate? ecsact_destroy_component; + + internal delegate void ecsact_add_system_capability_delegate + ( Int32 systemId + , Int32 componentId + , SystemCapability systemCapability + ); + internal ecsact_add_system_capability_delegate? ecsact_add_system_capability; + + internal delegate void ecsact_update_system_capability_delegate + ( Int32 systemId + , Int32 componentId + , SystemCapability systemCapability + ); + internal ecsact_update_system_capability_delegate? ecsact_update_system_capability; + + internal delegate void ecsact_remove_system_capability_delegate + ( Int32 systemId + , Int32 componentId + ); + internal ecsact_remove_system_capability_delegate? ecsact_remove_system_capability; + + internal delegate void ecsact_add_system_generate_component_set_delegate + ( Int32 systemId + , Int32 componentsCount + , Int32[] componentIds + , SystemGenerate[] componentGenerateFlags + ); + internal ecsact_add_system_generate_component_set_delegate? ecsact_add_system_generate_component_set; + + internal delegate void ecsact_register_component_delegate + ( Int32 registryId + , Int32 componentId + ); + internal ecsact_register_component_delegate? ecsact_register_component; + + internal delegate void ecsact_register_system_delegate + ( Int32 registryId + , Int32 systemId + ); + internal ecsact_register_system_delegate? ecsact_register_system; + + internal delegate void ecsact_register_action_delegate + ( Int32 registryId + , Int32 actionId + ); + internal ecsact_register_action_delegate? ecsact_register_action; + + internal delegate Int32 ecsact_system_execution_context_id_delegate + ( IntPtr context + ); + internal ecsact_system_execution_context_id_delegate? ecsact_system_execution_context_id; + } + + public class Meta { + internal List _availableMethods = new(); + public static string[] methods => new string[]{ + "ecsact_meta_action_name", + "ecsact_meta_action_size", + "ecsact_meta_component_name", + "ecsact_meta_component_size", + "ecsact_meta_registry_name", + "ecsact_meta_system_capabilities_count", + "ecsact_meta_system_capabilities", + "ecsact_meta_system_name", + }; + + public IEnumerable availableMethods => _availableMethods; + + [return: MarshalAs(UnmanagedType.LPStr)] + internal delegate string ecsact_meta_registry_name_delegate + ( Int32 registryId + ); + internal ecsact_meta_registry_name_delegate? ecsact_meta_registry_name; + + internal delegate Int32 ecsact_meta_component_size_delegate + ( Int32 componentId + ); + internal ecsact_meta_component_size_delegate? ecsact_meta_component_size; + + [return: MarshalAs(UnmanagedType.LPStr)] + internal delegate string ecsact_meta_component_name_delegate + ( Int32 componentId + ); + internal ecsact_meta_component_name_delegate? ecsact_meta_component_name; + + internal delegate Int32 ecsact_meta_action_size_delegate + ( Int32 actionId + ); + internal ecsact_meta_action_size_delegate? ecsact_meta_action_size; + + [return: MarshalAs(UnmanagedType.LPStr)] + internal delegate string ecsact_meta_action_name_delegate + ( Int32 actionId + ); + internal ecsact_meta_action_name_delegate? ecsact_meta_action_name; + + [return: MarshalAs(UnmanagedType.LPStr)] + internal delegate string ecsact_meta_system_name_delegate + ( Int32 systemId + ); + internal ecsact_meta_system_name_delegate? ecsact_meta_system_name; + + internal delegate Int32 ecsact_meta_system_capabilities_count_delegate + ( Int32 systemId + ); + internal ecsact_meta_system_capabilities_count_delegate? ecsact_meta_system_capabilities_count; + + internal delegate void ecsact_meta_system_capabilities_delegate + ( Int32 systemId + , out Int32[] outCapabilityComponentIds + , out SystemCapability[] outCapabilities + ); + internal ecsact_meta_system_capabilities_delegate? ecsact_meta_system_capabilities; + } + + public class Serialize { + internal List _availableMethods = new(); + public static string[] methods => new string[]{ + "ecsact_deserialize_action", + "ecsact_deserialize_component", + "ecsact_serialize_action_size", + "ecsact_serialize_action", + "ecsact_serialize_component_size", + "ecsact_serialize_component", + }; + + public IEnumerable availableMethods => _availableMethods; + + internal delegate Int32 ecsact_serialize_action_size_delegate + ( Int32 actionId + ); + internal ecsact_serialize_action_size_delegate? ecsact_serialize_action_size; + + internal delegate Int32 ecsact_serialize_component_size_delegate + ( Int32 componentId + ); + internal ecsact_serialize_component_size_delegate? ecsact_serialize_component_size; + + internal delegate void ecsact_serialize_action_delegate + ( Int32 actionId + , IntPtr actionData + , IntPtr outBytes + ); + internal ecsact_serialize_action_delegate? ecsact_serialize_action; + + internal delegate void ecsact_serialize_component_delegate + ( Int32 componentId + , object inComponentData + , IntPtr outBytes + ); + internal ecsact_serialize_component_delegate? ecsact_serialize_component; + + internal delegate void ecsact_deserialize_action_delegate + ( Int32 actionId + , IntPtr inBytes + , IntPtr outActionData + ); + internal ecsact_deserialize_action_delegate? ecsact_deserialize_action; + + internal delegate void ecsact_deserialize_component_delegate + ( Int32 componentId + , IntPtr inBytes + , out object outComponentData + ); + internal ecsact_deserialize_component_delegate? ecsact_deserialize_component; + } + + public class Static { + internal List _availableMethods = new(); + public static string[] methods => new string[]{ + "ecsact_static_actions", + "ecsact_static_components", + "ecsact_static_off_reload", + "ecsact_static_on_reload", + "ecsact_static_systems", + }; + + public IEnumerable availableMethods => _availableMethods; + + internal delegate void ecsact_static_components_delegate + ( out StaticComponentInfo[] outComponents + , out Int32 outComponentsCount + ); + internal ecsact_static_components_delegate? ecsact_static_components; + + internal delegate void ecsact_static_systems_delegate + ( out StaticSystemInfo[] outSystems + , out Int32 outSystemsCount + ); + internal ecsact_static_systems_delegate? ecsact_static_systems; + + internal delegate void ecsact_static_actions_delegate + ( out StaticActionInfo[] outActions + , out Int32 outActionsCount + ); + internal ecsact_static_actions_delegate? ecsact_static_actions; + + internal delegate void ecsact_static_on_reload_delegate + ( StaticReloadCallback callback + , IntPtr callbackUserData + ); + internal ecsact_static_on_reload_delegate? ecsact_static_on_reload; + + internal delegate void ecsact_static_off_reload_delegate + ( StaticReloadCallback callback + ); + internal ecsact_static_off_reload_delegate? ecsact_static_off_reload; + } + + // TODO(zaucy): This is misplaced here. It should be organized somewhere else + // maybe in another package + // ecsactsi_* fns + public class Wasm { + public enum Error { + Ok, + OpenFail, + ReadFail, + CompileFail, + InstantiateFail, + ExportNotFound, + ExportInvalid, + GuestImportUnknown, + } + + internal List _availableMethods = new(); + public static string[] methods => new string[]{ + "ecsactsi_wasm_load", + "ecsactsi_wasm_load_file", + "ecsactsi_wasm_set_trap_handler", + }; + + public IEnumerable availableMethods => _availableMethods; + + internal delegate void ecsactsi_wasm_load_delegate + ( sbyte[] wasmData + , Int32 wasmDataSize + , Int32 systemsCount + , Int32[] systmIds + , string[] wasmExports + ); + + internal ecsactsi_wasm_load_delegate? ecsactsi_wasm_load; + + internal delegate void ecsactsi_wasm_load_file_delegate + ( [MarshalAs(UnmanagedType.LPStr)] string wasmFilePath + , Int32 systemsCount + , Int32[] systmIds + , string[] wasmExports + ); + + internal ecsactsi_wasm_load_file_delegate? ecsactsi_wasm_load_file; + + internal delegate void ecsactsi_wasm_trap_handler + ( Int32 systemId + , [MarshalAs(UnmanagedType.LPStr)] string trapMessage + ); + + internal delegate void ecsactsi_wasm_set_trap_handler_delegate + ( ecsactsi_wasm_trap_handler handler + ); + + internal ecsactsi_wasm_set_trap_handler_delegate? ecsactsi_wasm_set_trap_handler; + + public void Load + ( byte[] wasmData + , Int32 systemId + , string exportName + ) + { + if(ecsactsi_wasm_load == null) { + throw new EcsactRuntimeMissingMethod("ecsactsi_wasm_load"); + } + + var systemIds = new Int32[]{systemId}; + var exportNames = new string[]{exportName}; + + ecsactsi_wasm_load( + (sbyte[]) (Array) wasmData, + wasmData.Length, + 1, + systemIds, + exportNames + ); + } + } + + public Core core => _core!; + public Async async => _async!; + public Dynamic dynamic => _dynamic!; + public Meta meta => _meta!; + public Serialize serialize => _serialize!; + public Static @static => _static!; + public Wasm wasm => _wasm!; + + [AOT.MonoPInvokeCallback(typeof(Wasm.ecsactsi_wasm_trap_handler))] + private static void DefaultWasmTrapHandler + ( Int32 systemId + , [MarshalAs(UnmanagedType.LPStr)] string trapMessage + ) + { + UnityEngine.Debug.LogError( + $"[Wasm Trap (systemId={systemId})] {trapMessage}" + ); + } + + private static void LoadDelegate + ( IntPtr lib + , string name + , out D? outDelegate + , List availableMethods + ) where D : Delegate + { + IntPtr addr; + if(NativeLibrary.TryGetExport(lib, name, out addr)) { + outDelegate = Marshal.GetDelegateForFunctionPointer(addr); + if(outDelegate != null) { + availableMethods.Add(name); + } else { + UnityEngine.Debug.LogError( + $"{name} is not a function in runtime library" + ); + } + } else { + outDelegate = null; + } + } + + public static EcsactRuntime Load + ( IEnumerable libraryPaths + ) + { + var runtime = new EcsactRuntime(); + runtime._core = new Core(runtime); + runtime._async = new Async(runtime); + runtime._dynamic = new Dynamic(); + runtime._meta = new Meta(); + runtime._serialize = new Serialize(); + runtime._static = new Static(); + runtime._wasm = new Wasm(); + runtime._libs = + libraryPaths.Select(path => NativeLibrary.Load(path)).ToArray(); + + foreach(var lib in runtime._libs) { + // Load async methods + LoadDelegate(lib, "ecsact_async_execute_action", out runtime._async.ecsact_async_execute_action, runtime._async._availableMethods); + LoadDelegate(lib, "ecsact_async_execute_action_at", out runtime._async.ecsact_async_execute_action_at, runtime._async._availableMethods); + LoadDelegate(lib, "ecsact_async_flush_events", out runtime._async.ecsact_async_flush_events, runtime._async._availableMethods); + LoadDelegate(lib, "ecsact_async_connect", out runtime._async.ecsact_async_connect, runtime._async._availableMethods); + LoadDelegate(lib, "ecsact_async_disconnect", out runtime._async.ecsact_async_disconnect, runtime._async._availableMethods); + + // Load core methods + LoadDelegate(lib, "ecsact_create_registry", out runtime._core.ecsact_create_registry, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_destroy_registry", out runtime._core.ecsact_destroy_registry, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_clear_registry", out runtime._core.ecsact_clear_registry, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_create_entity", out runtime._core.ecsact_create_entity, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_ensure_entity", out runtime._core.ecsact_ensure_entity, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_entity_exists", out runtime._core.ecsact_entity_exists, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_destroy_entity", out runtime._core.ecsact_destroy_entity, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_count_entities", out runtime._core.ecsact_count_entities, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_get_entities", out runtime._core.ecsact_get_entities, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_add_component", out runtime._core.ecsact_add_component, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_has_component", out runtime._core.ecsact_has_component, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_get_component", out runtime._core.ecsact_get_component, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_each_component", out runtime._core.ecsact_each_component, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_count_components", out runtime._core.ecsact_count_components, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_get_components", out runtime._core.ecsact_get_components, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_update_component", out runtime._core.ecsact_update_component, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_remove_component", out runtime._core.ecsact_remove_component, runtime._core._availableMethods); + LoadDelegate(lib, "ecsact_execute_systems", out runtime._core.ecsact_execute_systems, runtime._core._availableMethods); + + // Load dynamic methods + LoadDelegate(lib, "ecsact_system_execution_context_action", out runtime._dynamic.ecsact_system_execution_context_action, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_add", out runtime._dynamic.ecsact_system_execution_context_add, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_remove", out runtime._dynamic.ecsact_system_execution_context_remove, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_update", out runtime._dynamic.ecsact_system_execution_context_update, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_get", out runtime._dynamic.ecsact_system_execution_context_get, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_has", out runtime._dynamic.ecsact_system_execution_context_has, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_generate", out runtime._dynamic.ecsact_system_execution_context_generate, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_parent", out runtime._dynamic.ecsact_system_execution_context_parent, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_same", out runtime._dynamic.ecsact_system_execution_context_same, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_create_system", out runtime._dynamic.ecsact_create_system, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_set_system_execution_impl", out runtime._dynamic.ecsact_set_system_execution_impl, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_create_action", out runtime._dynamic.ecsact_create_action, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_resize_action", out runtime._dynamic.ecsact_resize_action, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_create_component", out runtime._dynamic.ecsact_create_component, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_resize_component", out runtime._dynamic.ecsact_resize_component, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_destroy_component", out runtime._dynamic.ecsact_destroy_component, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_add_system_capability", out runtime._dynamic.ecsact_add_system_capability, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_update_system_capability", out runtime._dynamic.ecsact_update_system_capability, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_remove_system_capability", out runtime._dynamic.ecsact_remove_system_capability, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_add_system_generate_component_set", out runtime._dynamic.ecsact_add_system_generate_component_set, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_register_component", out runtime._dynamic.ecsact_register_component, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_register_system", out runtime._dynamic.ecsact_register_system, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_register_action", out runtime._dynamic.ecsact_register_action, runtime._dynamic._availableMethods); + LoadDelegate(lib, "ecsact_system_execution_context_id", out runtime._dynamic.ecsact_system_execution_context_id, runtime._dynamic._availableMethods); + + // Load meta methods + LoadDelegate(lib, "ecsact_meta_registry_name", out runtime._meta.ecsact_meta_registry_name, runtime._meta._availableMethods); + LoadDelegate(lib, "ecsact_meta_component_size", out runtime._meta.ecsact_meta_component_size, runtime._meta._availableMethods); + LoadDelegate(lib, "ecsact_meta_component_name", out runtime._meta.ecsact_meta_component_name, runtime._meta._availableMethods); + LoadDelegate(lib, "ecsact_meta_action_size", out runtime._meta.ecsact_meta_action_size, runtime._meta._availableMethods); + LoadDelegate(lib, "ecsact_meta_action_name", out runtime._meta.ecsact_meta_action_name, runtime._meta._availableMethods); + LoadDelegate(lib, "ecsact_meta_system_name", out runtime._meta.ecsact_meta_system_name, runtime._meta._availableMethods); + LoadDelegate(lib, "ecsact_meta_system_capabilities_count", out runtime._meta.ecsact_meta_system_capabilities_count, runtime._meta._availableMethods); + LoadDelegate(lib, "ecsact_meta_system_capabilities", out runtime._meta.ecsact_meta_system_capabilities, runtime._meta._availableMethods); + + // Load serialize methods + LoadDelegate(lib, "ecsact_serialize_action_size", out runtime._serialize.ecsact_serialize_action_size, runtime._serialize._availableMethods); + LoadDelegate(lib, "ecsact_serialize_component_size", out runtime._serialize.ecsact_serialize_component_size, runtime._serialize._availableMethods); + LoadDelegate(lib, "ecsact_serialize_action", out runtime._serialize.ecsact_serialize_action, runtime._serialize._availableMethods); + LoadDelegate(lib, "ecsact_serialize_component", out runtime._serialize.ecsact_serialize_component, runtime._serialize._availableMethods); + LoadDelegate(lib, "ecsact_deserialize_action", out runtime._serialize.ecsact_deserialize_action, runtime._serialize._availableMethods); + LoadDelegate(lib, "ecsact_deserialize_component", out runtime._serialize.ecsact_deserialize_component, runtime._serialize._availableMethods); + + // Load static methods + LoadDelegate(lib, "ecsact_static_components", out runtime._static.ecsact_static_components, runtime._static._availableMethods); + LoadDelegate(lib, "ecsact_static_systems", out runtime._static.ecsact_static_systems, runtime._static._availableMethods); + LoadDelegate(lib, "ecsact_static_actions", out runtime._static.ecsact_static_actions, runtime._static._availableMethods); + LoadDelegate(lib, "ecsact_static_on_reload", out runtime._static.ecsact_static_on_reload, runtime._static._availableMethods); + LoadDelegate(lib, "ecsact_static_off_reload", out runtime._static.ecsact_static_off_reload, runtime._static._availableMethods); + + // Load system implementation wasm methods + LoadDelegate(lib, "ecsactsi_wasm_load", out runtime._wasm.ecsactsi_wasm_load, runtime._wasm._availableMethods); + LoadDelegate(lib, "ecsactsi_wasm_load_file", out runtime._wasm.ecsactsi_wasm_load_file, runtime._wasm._availableMethods); + LoadDelegate(lib, "ecsactsi_wasm_set_trap_handler", out runtime._wasm.ecsactsi_wasm_set_trap_handler, runtime._wasm._availableMethods); + } + + if(runtime._wasm.ecsactsi_wasm_set_trap_handler != null) { + runtime._wasm.ecsactsi_wasm_set_trap_handler(DefaultWasmTrapHandler); + } + + return runtime; + } + + public static void Free + ( EcsactRuntime runtime + ) + { + if(runtime._libs != null) { + foreach(var lib in runtime._libs) { + NativeLibrary.Free(lib); + } + + runtime._libs = null; + } + } + + /// Init Component Untyped Callback + private delegate void InitCompUtCb + ( Int32 entityId + , object component + ); + + /// Update Component Untyped Callback + private delegate void UpCompUtCb + ( Int32 entityId + , object component + ); + + /// Remove Component Untyped Callback + private delegate void RmvCompUtCb + ( Int32 entityId + , object component + ); + + private List _initAnyCompCbs = new(); + private List _updateAnyCompCbs = new(); + private List _removeAnyCompCbs = new(); + private Dictionary> _initCompCbs = new(); + private Dictionary> _updateCompCbs = new(); + private Dictionary> _removeCompCbs = new(); + internal ExecutionEventsCollector _execEvs; + + private EcsactRuntime() { + _execEvs = new ExecutionEventsCollector{ + initCallback = OnInitComponentHandler, + initCallbackUserData = IntPtr.Zero, + updateCallback = OnUpdateComponentHandler, + updateCallbackUserData = IntPtr.Zero, + removeCallback = OnRemoveComponentHandler, + removeCallbackUserData = IntPtr.Zero, + }; + } + + ~EcsactRuntime() { + Free(this); + } + + public delegate void InitComponentCallback + ( Int32 entityId + , Int32 componentId + , object component + ); + + public Action OnInitComponent + ( InitComponentCallback callback + ) + { + _initAnyCompCbs.Add(callback); + return () => { + _initAnyCompCbs.Remove(callback); + }; + } + + public delegate void InitComponentCallback + ( Int32 entityId + , ComponentT component + ) where ComponentT : Ecsact.Component; + + /// Adds a callback for when component init event is fired. + /// Action that clears callback upon invocation. + public Action OnInitComponent + ( InitComponentCallback callback + ) where ComponentT : Ecsact.Component + { + var compId = Ecsact.Util.GetComponentID()!; + + if(!_initCompCbs.TryGetValue(compId, out var callbacks)) { + callbacks = new(); + _initCompCbs.Add(compId, callbacks); + } + + InitCompUtCb cb = (entityId, comp) => { + callback(entityId, (ComponentT)comp); + }; + + callbacks.Add(cb); + + return () => { + if(_initCompCbs.TryGetValue(compId, out var callbacks)) { + callbacks.Remove(cb); + } + }; + } + + public delegate void UpdateComponentCallback + ( Int32 entityId + , Int32 componentId + , object component + ); + + public Action OnUpdateComponent + ( UpdateComponentCallback callback + ) + { + _updateAnyCompCbs.Add(callback); + return () => { + _updateAnyCompCbs.Remove(callback); + }; + } + + public delegate void UpdateComponentCallback + ( Int32 entityId + , ComponentT component + ) where ComponentT : Ecsact.Component; + + public Action OnUpdateComponent + ( UpdateComponentCallback callback + ) where ComponentT : Ecsact.Component + { + var compId = Ecsact.Util.GetComponentID()!; + + if(!_updateCompCbs.TryGetValue(compId, out var callbacks)) { + callbacks = new(); + _updateCompCbs.Add(compId, callbacks); + } + + UpCompUtCb cb = (entityId, comp) => { + callback(entityId, (ComponentT)comp); + }; + + callbacks.Add(cb); + + return () => { + if(_updateCompCbs.TryGetValue(compId, out var callbacks)) { + callbacks.Remove(cb); + } + }; + } + + public delegate void RemoveComponentCallback + ( Int32 entityId + , Int32 componentId + , object component + ); + + public Action OnRemoveComponent + ( RemoveComponentCallback callback + ) + { + _removeAnyCompCbs.Add(callback); + return () => { + _removeAnyCompCbs.Remove(callback); + }; + } + + public delegate void RemoveComponentCallback + ( Int32 entityId + , ComponentT component + ) where ComponentT : Ecsact.Component; + + public Action OnRemoveComponent + ( RemoveComponentCallback callback + ) where ComponentT : Ecsact.Component + { + var compId = Ecsact.Util.GetComponentID()!; + + if(!_removeCompCbs.TryGetValue(compId, out var callbacks)) { + callbacks = new(); + _removeCompCbs.Add(compId, callbacks); + } + + RmvCompUtCb cb = (entityId, comp) => { + callback(entityId, (ComponentT)comp); + }; + + callbacks.Add(cb); + + return () => { + if(_removeCompCbs.TryGetValue(compId, out var callbacks)) { + callbacks.Remove(cb); + } + }; + } + + + [AOT.MonoPInvokeCallback(typeof(ComponentEventCallback))] + private static void OnInitComponentHandler + ( EcsactEvent ev + , Int32 entityId + , Int32 componentId + , IntPtr componentData + , IntPtr callbackUserData + ) + { + UnityEngine.Debug.Assert(ev == EcsactEvent.InitComponent); + + var self = (GCHandle.FromIntPtr(callbackUserData).Target as EcsactRuntime)!; + var component = Ecsact.Util.PtrToComponent(componentData, componentId); + + if(self._initCompCbs.TryGetValue(componentId, out var cbs)) { + foreach(var cb in cbs) { + cb(entityId, component); + } + } + + foreach(var cb in self._initAnyCompCbs) { + cb(entityId, componentId, component); + } + } + + [AOT.MonoPInvokeCallback(typeof(ComponentEventCallback))] + private static void OnUpdateComponentHandler + ( EcsactEvent ev + , Int32 entityId + , Int32 componentId + , IntPtr componentData + , IntPtr callbackUserData + ) + { + UnityEngine.Debug.Assert(ev == EcsactEvent.UpdateComponent); + + var self = (GCHandle.FromIntPtr(callbackUserData).Target as EcsactRuntime)!; + var component = Ecsact.Util.PtrToComponent(componentData, componentId); + + if(self._updateCompCbs.TryGetValue(componentId, out var cbs)) { + foreach(var cb in cbs) { + cb(entityId, component); + } + } + + foreach(var cb in self._updateAnyCompCbs) { + cb(entityId, componentId, component); + } + } + + [AOT.MonoPInvokeCallback(typeof(ComponentEventCallback))] + private static void OnRemoveComponentHandler + ( EcsactEvent ev + , Int32 entityId + , Int32 componentId + , IntPtr componentData + , IntPtr callbackUserData + ) + { + UnityEngine.Debug.Assert(ev == EcsactEvent.RemoveComponent); + + var self = (GCHandle.FromIntPtr(callbackUserData).Target as EcsactRuntime)!; + var component = Ecsact.Util.PtrToComponent(componentData, componentId); + + if(self._removeCompCbs.TryGetValue(componentId, out var cbs)) { + foreach(var cb in cbs) { + cb(entityId, component); + } + } + + foreach(var cb in self._removeAnyCompCbs) { + cb(entityId, componentId, component); + } + } + +} diff --git a/packages/com.seaube.ecsact/Runtime/EcsactRuntime.cs.meta b/packages/com.seaube.ecsact/Runtime/EcsactRuntime.cs.meta new file mode 100644 index 0000000..c0b7df9 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/EcsactRuntime.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb87bc4710ab9b74396157c9995c9957 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/EcsactRuntimeSettings.cs b/packages/com.seaube.ecsact/Runtime/EcsactRuntimeSettings.cs new file mode 100644 index 0000000..1b05171 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/EcsactRuntimeSettings.cs @@ -0,0 +1,63 @@ +using UnityEngine; +using System.Collections.Generic; +using System.IO; +using System; +#if UNITY_EDITOR +using UnityEditor; +#endif + +#nullable enable + +[System.Serializable] +public class EcsactRuntimeDefaultRegistry { + [Tooltip("Name given to registry. For display and debug purposes only")] + public string registryName = ""; + [Tooltip( + "Create game object at startup with the Ecsact.DefaultRunner script for " + + "this registry." + )] + public bool useDefaultRunner = true; + public EcsactRuntime.ExecutionOptions executionOptions; + public Int32 registryId { get; internal set; } = -1; +} + +[System.Serializable] +public class EcsactRuntimeSettings : ScriptableObject { + private static EcsactRuntimeSettings? _instance; + + public const string resourcePath = "Settings/EcsactRuntimeSettings"; + public const string assetPath = "Assets/Resources/" + resourcePath + ".asset"; + + public bool useAsyncRunner = true; + public bool useVisualScriptingEvents = true; + [Tooltip( + "List of ecsact registries that are created automatically when " + + "the game loads." + )] + public List defaultRegistries = new(); + public List runtimeLibraryPaths = new(); + + public static EcsactRuntimeSettings Get() { + if(_instance != null) { + return _instance; + } + +#if UNITY_EDITOR + _instance = AssetDatabase.LoadAssetAtPath(assetPath); + if(_instance == null) { + _instance = ScriptableObject.CreateInstance(); + Directory.CreateDirectory(Path.GetDirectoryName(assetPath)); + AssetDatabase.CreateAsset(_instance, assetPath); + AssetDatabase.SaveAssetIfDirty(_instance); + } +#else + _instance = Resources.Load(resourcePath) as EcsactRuntimeSettings; +#endif + + if(_instance == null) { + throw new Exception("Failed to load ecsact runtime settings"); + } + + return _instance; + } +} diff --git a/packages/com.seaube.ecsact/Runtime/EcsactRuntimeSettings.cs.meta b/packages/com.seaube.ecsact/Runtime/EcsactRuntimeSettings.cs.meta new file mode 100644 index 0000000..eae513e --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/EcsactRuntimeSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c06d71d460aeb804fa9b6550d0ff0218 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/EcsactUnitySync.cs b/packages/com.seaube.ecsact/Runtime/EcsactUnitySync.cs index 39d4474..0729b1e 100644 --- a/packages/com.seaube.ecsact/Runtime/EcsactUnitySync.cs +++ b/packages/com.seaube.ecsact/Runtime/EcsactUnitySync.cs @@ -26,7 +26,7 @@ namespace Ecsact.UnitySync { public interface IRequired where T : Ecsact.Component {} public interface IOnInitEntity { - void OnInitEntity(System.Int32 entityId); + void OnInitEntity(Int32 entityId); } public interface IOnInitComponent where T : Ecsact.Component { @@ -374,8 +374,8 @@ public static IEnumerable GetInterfaces } } - public static IEnumerable GetInitComponentIds - ( System.Type type + public static IEnumerable GetInitComponentIds + ( Type type ) { if(onInitComponentsMap.TryGetValue(type, out var compIds)) { @@ -385,8 +385,8 @@ public static IEnumerable GetInitComponentIds } } - public static IEnumerable GetRemoveComponentIds - ( System.Type type + public static IEnumerable GetRemoveComponentIds + ( Type type ) { if(onInitComponentsMap.TryGetValue(type, out var compIds)) { @@ -396,8 +396,8 @@ public static IEnumerable GetRemoveComponentIds } } - public static IEnumerable GetRequiredComponentIds - ( System.Type type + public static IEnumerable GetRequiredComponentIds + ( Type type ) { if(requiredComponentsMap.TryGetValue(type, out var compIds)) { @@ -555,7 +555,7 @@ private static void RegisterOnRemoveComponentInterface private static void AddKnownComponentId - ( System.Int32 componentId + ( Int32 componentId ) { if(knownComponentIds.Add(componentId)) { diff --git a/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeLoader.cs b/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeLoader.cs new file mode 100644 index 0000000..5212cc7 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeLoader.cs @@ -0,0 +1,36 @@ +using System; +using UnityEngine; + +namespace Ecsact { + public static class EcsactWasmRuntimeLoader { + [RuntimeInitializeOnLoadMethod] + public static void OnLoad() { + var settings = EcsactWasmRuntimeSettings.Get(); + if(!settings.useDefaultLoader) { + return; + } + + var defaultRuntime = EcsactRuntime.GetOrLoadDefault(); + // defaultRuntime.wasm. + + foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + foreach(var type in assembly.GetTypes()) { + if(Util.IsSystem(type) || Util.IsAction(type)) { + Debug.Log($"System {type.Name} ID is {Util.GetSystemID(type)}"); + } + } + } + + foreach(var entry in settings.wasmSystemEntries) { + if(string.IsNullOrWhiteSpace(entry.wasmExportName)) continue; + + Debug.Log($"Loading {entry.wasmExportName}"); + defaultRuntime.wasm.Load( + entry.wasmAsset.bytes, + entry.systemId, + entry.wasmExportName + ); + } + } + } +} diff --git a/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeLoader.cs.meta b/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeLoader.cs.meta new file mode 100644 index 0000000..21dc2c2 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: daba126f39fdfdd4381f0b63dff1369c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeSettings.cs b/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeSettings.cs new file mode 100644 index 0000000..fd50391 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeSettings.cs @@ -0,0 +1,53 @@ +using UnityEngine; +using System.Collections.Generic; +using System.IO; +using System; +#if UNITY_EDITOR +using UnityEditor; +#endif + +#nullable enable + +[System.Serializable] +public class EcsactWasmRuntimeSettings : ScriptableObject { + [System.Serializable] + public class SystemMapEntry { + public Int32 systemId = -1; + public WasmAsset? wasmAsset; + public string wasmExportName = ""; + } + + private static EcsactWasmRuntimeSettings? _instance; + + public const string resourcePath = "Settings/EcsactWasmRuntimeSettings"; + public const string assetPath = "Assets/Resources/" + resourcePath + ".asset"; + + public bool useDefaultLoader = true; + public List wasmSystemEntries = new(); + + public static EcsactWasmRuntimeSettings Get() { + if(_instance != null) { + return _instance; + } + +#if UNITY_EDITOR + _instance = AssetDatabase.LoadAssetAtPath( + assetPath + ); + if(_instance == null) { + _instance = ScriptableObject.CreateInstance(); + Directory.CreateDirectory(Path.GetDirectoryName(assetPath)); + AssetDatabase.CreateAsset(_instance, assetPath); + AssetDatabase.SaveAssetIfDirty(_instance); + } +#else + _instance = Resources.Load(resourcePath) as EcsactWasmRuntimeSettings; +#endif + + if(_instance == null) { + throw new Exception("Failed to load ecsact wasm runtime settings"); + } + + return _instance; + } +} diff --git a/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeSettings.cs.meta b/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeSettings.cs.meta new file mode 100644 index 0000000..78b295c --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/EcsactWasmRuntimeSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61f6048043c761b47b4e0be2b0a0e30e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/EntityGameObjectPool.cs b/packages/com.seaube.ecsact/Runtime/EntityGameObjectPool.cs index 65efbc2..610cef6 100644 --- a/packages/com.seaube.ecsact/Runtime/EntityGameObjectPool.cs +++ b/packages/com.seaube.ecsact/Runtime/EntityGameObjectPool.cs @@ -14,12 +14,12 @@ namespace Ecsact.UnitySync { public class EntityGameObjectPool : ScriptableObject { public abstract class EntitySource { public abstract object GetComponent - ( System.Int32 entityId - , System.Int32 componentId + ( Int32 entityId + , Int32 componentId ); public abstract bool HasComponent - ( System.Int32 entityId - , System.Int32 componentId + ( Int32 entityId + , Int32 componentId ); } @@ -58,7 +58,7 @@ public Scene? targetScene { get => _targetScene; set { if(_rootGameObject != null) { - throw new System.ArgumentException( + throw new ArgumentException( "EntityGameObjectPool.targetScene may not be set if " + "EntityGameObjectPool.rootGameObject is set." ); @@ -80,7 +80,7 @@ public GameObject? rootGameObject { get => _rootGameObject; set { if(_targetScene != null) { - throw new System.ArgumentException( + throw new ArgumentException( "EntityGameObjectPool.rootGameObject may not be set if " + "EntityGameObjectPool.targetScene is set." ); @@ -112,7 +112,7 @@ void OnDisable() { } public GameObject? GetEntityGameObject - ( System.Int32 entityId + ( Int32 entityId ) { if(entityGameObjects.Count > entityId) { @@ -135,8 +135,8 @@ public void Clear() { } public void InitComponent - ( System.Int32 entityId - , in T component + ( Int32 entityId + , in T component ) where T : Ecsact.Component { InitComponent( @@ -147,9 +147,9 @@ public void InitComponent } public void InitComponent - ( System.Int32 entityId - , System.Int32 componentId - , in object component + ( Int32 entityId + , Int32 componentId + , in object component ) { EnsureEntityLists(entityId); @@ -216,8 +216,8 @@ in component } public void UpdateComponent - ( System.Int32 entityId - , in T component + ( Int32 entityId + , in T component ) where T : Ecsact.Component { UpdateComponent( @@ -228,9 +228,9 @@ public void UpdateComponent } public void UpdateComponent - ( System.Int32 entityId - , System.Int32 componentId - , in object component + ( Int32 entityId + , Int32 componentId + , in object component ) { var gameObject = GetEntityGameObject(entityId); @@ -244,8 +244,8 @@ in component } public void RemoveComponent - ( System.Int32 entityId - , in T component + ( Int32 entityId + , in T component ) where T : Ecsact.Component { var compObj = (object)component; @@ -257,9 +257,9 @@ in compObj } public void RemoveComponent - ( System.Int32 entityId - , System.Int32 componentId - , in object component + ( Int32 entityId + , Int32 componentId + , in object component ) { if(entityGameObjects[entityId] != null) { @@ -333,7 +333,7 @@ in component } private GameObject EnsureEntityGameObject - ( System.Int32 entityId + ( Int32 entityId ) { GameObject? gameObject = entityGameObjects[entityId]; @@ -351,7 +351,7 @@ private GameObject EnsureEntityGameObject } private void EnsureEntityLists - ( System.Int32 entityId + ( Int32 entityId ) { var capacity = Math.Max(entityId + 1, entityComponentIds.Count); diff --git a/packages/com.seaube.ecsact/Runtime/VisualScripting.meta b/packages/com.seaube.ecsact/Runtime/VisualScripting.meta new file mode 100644 index 0000000..f31bbcd --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScripting.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 79e56f1c5516a1349abd4c1c3e659eb4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectEvent.cs b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectEvent.cs new file mode 100644 index 0000000..76642b0 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectEvent.cs @@ -0,0 +1,50 @@ +using Unity.VisualScripting; +using UnityEngine; +using System; + +namespace Ecsact.VisualScripting { + public class AsyncConnectEventData { + public string connectAddress; + public Int32 connectPort; + } + + [UnitTitle("On Connect")] + [UnitCategory("Ecsact\\Async")] + [UnitSurtitle("Ecsact / Async")] + public class AsyncConnectEvent : EventUnit { + public const string eventName = "EcsactAsyncConnectEvent"; + + [PortLabel("Connect Address")] + [DoNotSerialize] + public ValueOutput connectAddressOutput { get; private set; } + + [PortLabel("Connect port")] + [DoNotSerialize] + public ValueOutput connectPortOutput { get; private set; } + + protected override bool register => true; + + public override EventHook GetHook + ( GraphReference reference + ) + { + return new EventHook(eventName); + } + + protected override void Definition() { + base.Definition(); + + connectAddressOutput = ValueOutput(nameof(connectAddressOutput)); + connectPortOutput = ValueOutput(nameof(connectPortOutput)); + } + + protected override void AssignArguments + ( Flow flow + , AsyncConnectEventData data + ) + { + flow.SetValue(connectAddressOutput, data.connectAddress); + flow.SetValue(connectPortOutput, data.connectPort); + } + } +} diff --git a/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectEvent.cs.meta b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectEvent.cs.meta new file mode 100644 index 0000000..97cbf8e --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 758219e01a2eabe4c8056cface8bfd5a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectNode.cs b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectNode.cs new file mode 100644 index 0000000..e042399 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectNode.cs @@ -0,0 +1,33 @@ +using Unity.VisualScripting; +using UnityEngine; + +namespace Ecsact.VisualScripting { + [UnitTitle("Connect")] + [UnitCategory("Ecsact\\Async")] + [UnitSurtitle("Ecsact / Async")] + public class AsyncConnectNode : Unit { + [PortLabelHidden] + [DoNotSerialize] + public ControlInput controlInput; + + [PortLabelHidden] + [DoNotSerialize] + public ControlOutput controlOutput; + + [DoNotSerialize] + public ValueInput connectionStringInput; + + protected override void Definition() { + connectionStringInput = ValueInput("connectionString"); + controlOutput = ControlOutput("controlOutput"); + controlInput = ControlInput("controlInput", flow => { + var connectUri = flow.GetValue(connectionStringInput); + EcsactRuntime.GetOrLoadDefault().async.Connect(connectUri); + return controlOutput; + }); + + Requirement(connectionStringInput, controlInput); + Succession(controlInput, controlOutput); + } + } +} diff --git a/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectNode.cs.meta b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectNode.cs.meta new file mode 100644 index 0000000..137d168 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncConnectNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd5ecdb22ce6e1c43855635c294d0c62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncErrorEvent.cs b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncErrorEvent.cs new file mode 100644 index 0000000..058be7b --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncErrorEvent.cs @@ -0,0 +1,51 @@ +using Unity.VisualScripting; +using UnityEngine; +using System; + +namespace Ecsact.VisualScripting { + [Serializable] + public class AsyncErrorEventData { + public Ecsact.AsyncError error; + public Int32 requestId; + } + + [UnitTitle("On Error")] + [UnitCategory("Ecsact\\Async")] + [UnitSurtitle("Ecsact / Async")] + public class AsyncErrorEvent : EventUnit { + public const string eventName = "EcsactAsyncErrorEvent"; + + [PortLabel("Error")] + [DoNotSerialize] + public ValueOutput errorOutput { get; private set; } + + [PortLabel("Request ID")] + [DoNotSerialize] + public ValueOutput requestIdOutput { get; private set; } + + protected override bool register => true; + + public override EventHook GetHook + ( GraphReference reference + ) + { + return new EventHook(eventName); + } + + protected override void Definition() { + base.Definition(); + + errorOutput = ValueOutput(nameof(errorOutput)); + requestIdOutput = ValueOutput(nameof(requestIdOutput)); + } + + protected override void AssignArguments + ( Flow flow + , AsyncErrorEventData data + ) + { + flow.SetValue(errorOutput, data.error); + flow.SetValue(requestIdOutput, data.requestId); + } + } +} diff --git a/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncErrorEvent.cs.meta b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncErrorEvent.cs.meta new file mode 100644 index 0000000..17a9f50 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScripting/AsyncErrorEvent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 096ed9de536db6041b724f6153225f26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/Runtime/VisualScriptingEvents.cs b/packages/com.seaube.ecsact/Runtime/VisualScriptingEvents.cs new file mode 100644 index 0000000..3a390e9 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScriptingEvents.cs @@ -0,0 +1,49 @@ +using UnityEngine; +using Unity.VisualScripting; +using System; +using System.Collections.Generic; + +#nullable enable + +namespace Ecsact { + [AddComponentMenu("")] + public class VisualScriptingEvents : MonoBehaviour { + private static VisualScriptingEvents? instance = null; + private EcsactRuntime? runtimeInstance = null; + + [RuntimeInitializeOnLoadMethod] + private static void OnRuntimeLoad() { + if(instance != null) { + return; + } + + var settings = EcsactRuntimeSettings.Get(); + if(!settings.useVisualScriptingEvents) { + return; + } + + var gameObject = new GameObject("Ecsact Visual Scripting Events"); + instance = gameObject.AddComponent(); + DontDestroyOnLoad(gameObject); + } + + List diposeCallbacks = new(); + + void OnEnable() { + runtimeInstance = EcsactRuntime.GetOrLoadDefault(); + // diposeCallbacks.Add(runtimeInstance!.@async.OnError((err, reqId) => { + // Unity.VisualScripting.EventBus.Trigger( + // EcsactRuntime.VisualScriptingEventNames.AsyncError, + // err + // ); + // })); + } + + void OnDisable() { + foreach(var diposeCb in diposeCallbacks) { + diposeCb(); + } + diposeCallbacks.Clear(); + } + } +} diff --git a/packages/com.seaube.ecsact/Runtime/VisualScriptingEvents.cs.meta b/packages/com.seaube.ecsact/Runtime/VisualScriptingEvents.cs.meta new file mode 100644 index 0000000..b277d95 --- /dev/null +++ b/packages/com.seaube.ecsact/Runtime/VisualScriptingEvents.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f5b22bc46ad84f04aa3c02e7aae89e0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/com.seaube.ecsact/package.json b/packages/com.seaube.ecsact/package.json index 26deab7..39de995 100644 --- a/packages/com.seaube.ecsact/package.json +++ b/packages/com.seaube.ecsact/package.json @@ -1,14 +1,15 @@ { "name": "com.seaube.ecsact", "version": "0.2.2", - "description": "ECSACT Integration", - "displayName": "ECSACT Integration", + "description": "Ecsact Integration", + "displayName": "Ecsact Integration", "unity": "2021.2", "author": { "name" : "Seaube" }, "dependencies": { - "com.unity.editorcoroutines": "1.0.0" + "com.unity.editorcoroutines": "1.0.0", + "com.unity.visualscripting": "1.7.8" }, "repository": "https://github.com/seaube/ecsact-unity" } diff --git a/packages/com.seaube.ecsact/pkg.bzl b/packages/com.seaube.ecsact/pkg.bzl new file mode 100644 index 0000000..b9acf53 --- /dev/null +++ b/packages/com.seaube.ecsact/pkg.bzl @@ -0,0 +1,100 @@ +load("@rules_pkg//:providers.bzl", "PackageFilegroupInfo", "PackageFilesInfo", "PackageSymlinkInfo") + +def _runfile_path(workspace_name, file): + path = file.short_path + return path[len("../"):] if path.startswith("../") else "%s/%s" % (workspace_name, path) + +def _runfiles_pkg_files(workspace_name, runfiles): + files = {} + for file in runfiles.files.to_list(): + files[_runfile_path(workspace_name, file)] = file + for file in runfiles.symlinks.to_list(): + files[file.path] = "%s/%s" % (workspace_name, files.target_file) + for file in runfiles.root_symlinks.to_list(): + files[file.path] = files.target_file + + return PackageFilesInfo( + dest_src_map = files, + attributes = {"mode": "0755"}, + ) + +def _pkg_runfiles_impl(ctx): + runfiles = ctx.attr.runfiles[DefaultInfo] + label = ctx.label + workspace_name = ctx.workspace_name + + runfiles_files = _runfiles_pkg_files(workspace_name, runfiles.default_runfiles) + + pkg_filegroup_info = PackageFilegroupInfo( + pkg_dirs = [], + pkg_files = [(runfiles_files, label)], + pkg_symlinks = [], + ) + + default_info = DefaultInfo(files = depset(runfiles_files.dest_src_map.values())) + + return [default_info, pkg_filegroup_info] + +pkg_runfiles = rule( + implementation = _pkg_runfiles_impl, + attrs = { + "runfiles": attr.label( + doc = "Runfiles.", + mandatory = True, + ), + }, + provides = [PackageFilegroupInfo], +) + +def _pkg_executable_impl(ctx): + bin = ctx.attr.bin[DefaultInfo] + bin_executable = ctx.executable.bin + bin_executable_ext = bin_executable.extension + if bin_executable_ext: + bin_executable_ext = "." + bin_executable_ext + + path = ctx.attr.path + bin_executable_ext + label = ctx.label + workspace_name = ctx.workspace_name + + runfiles_files = _runfiles_pkg_files(workspace_name, bin.default_runfiles) + + dest_src_map = {"%s.runfiles/%s" % (path, p): file for p, file in runfiles_files.dest_src_map.items()} + + runfiles_files = PackageFilesInfo( + dest_src_map = dest_src_map, + attributes = runfiles_files.attributes, + ) + + executable_symlink = PackageSymlinkInfo( + attributes = {"mode": "0755"}, + destination = path, + target = "%s.runfiles/%s" % (path, _runfile_path(workspace_name, bin_executable)), + ) + + pkg_filegroup_info = PackageFilegroupInfo( + pkg_dirs = [], + pkg_files = [(runfiles_files, label)], + pkg_symlinks = [(executable_symlink, label)], + ) + + default_info = DefaultInfo(files = depset(runfiles_files.dest_src_map.values())) + + return [default_info, pkg_filegroup_info] + +pkg_executable = rule( + implementation = _pkg_executable_impl, + attrs = { + "bin": attr.label( + doc = "Executable.", + executable = True, + cfg = "target", + mandatory = True, + ), + "path": attr.string( + doc = "Packaged path of executable. (Runfiles tree will be at .runfiles.)", + mandatory = True, + ), + }, + provides = [PackageFilegroupInfo], +) diff --git a/packages/com.seaube.ecsact/pkg.bzl.meta b/packages/com.seaube.ecsact/pkg.bzl.meta new file mode 100644 index 0000000..0beb30f --- /dev/null +++ b/packages/com.seaube.ecsact/pkg.bzl.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9189d70ae7e8b41459e74b436f859333 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: