diff --git a/GlitchyEditor/SandboxProject/Assets/Scripts/Camera.cs b/GlitchyEditor/SandboxProject/Assets/Scripts/Camera.cs index 9ad2926..da9b2f6 100644 --- a/GlitchyEditor/SandboxProject/Assets/Scripts/Camera.cs +++ b/GlitchyEditor/SandboxProject/Assets/Scripts/Camera.cs @@ -15,32 +15,39 @@ public class Camera : Entity public float DontFollowRadius = 3.0f; - void OnUpdate(float deltaTime) + void OnCreate() { - Entity player = FindEntityWithName("Player"); + Log.Info("Camera Create"); + } - if (player != null) - { - float2 playerPosition = player.Transform.Translation.XY; + public void OnUpdate(float deltaTime) + { + Log.Info($"Camera Update {deltaTime}"); - float2 cameraPosition = Transform.Translation.XY; + //Entity player = FindEntityWithName("Player"); - float2 distanceVector = playerPosition - cameraPosition; + //if (player != null) + //{ + // float2 playerPosition = player.Transform.Translation.XY; - float distance = length(distanceVector); + // float2 cameraPosition = Transform.Translation.XY; - float2 neededMovement = float2.Zero; + // float2 distanceVector = playerPosition - cameraPosition; - if (distance > DontFollowRadius) - { - neededMovement = distanceVector - (distanceVector / distance) * DontFollowRadius; - } + // float distance = length(distanceVector); - Transform.Translation = new float3(cameraPosition + neededMovement, DistanceFromPlayer); - } - else - { - Log.Error("Player not found!"); - } + // float2 neededMovement = float2.Zero; + + // if (distance > DontFollowRadius) + // { + // neededMovement = distanceVector - (distanceVector / distance) * DontFollowRadius; + // } + + // Transform.Translation = new float3(cameraPosition + neededMovement, DistanceFromPlayer); + //} + //else + //{ + // Log.Error("Player not found!"); + //} } } diff --git a/GlitchyEditor/SandboxProject/Assets/Scripts/Properties/launchSettings.json b/GlitchyEditor/SandboxProject/Assets/Scripts/Properties/launchSettings.json index 6bc43e2..ca5a8c8 100644 --- a/GlitchyEditor/SandboxProject/Assets/Scripts/Properties/launchSettings.json +++ b/GlitchyEditor/SandboxProject/Assets/Scripts/Properties/launchSettings.json @@ -3,6 +3,7 @@ "Start Editor": { "commandName": "Executable", "executablePath": "D:\\Development\\Projects\\Beef\\GlitchyEngine\\build\\Debug_Win64\\GlitchyEditor\\GlitchyEditor.exe", + "commandLineArgs": "\"content\\Scenes\\physics2D.scene\"", "workingDirectory": "D:\\Development\\Projects\\Beef\\GlitchyEngine\\GlitchyEditor", "nativeDebugging": true } diff --git a/GlitchyEditor/resources/scripts/ScriptCore.deps.json b/GlitchyEditor/resources/scripts/ScriptCore.deps.json index 8dbf87a..1cc0c17 100644 --- a/GlitchyEditor/resources/scripts/ScriptCore.deps.json +++ b/GlitchyEditor/resources/scripts/ScriptCore.deps.json @@ -1,25 +1,15 @@ { "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", + "name": ".NETCoreApp,Version=v7.0", "signature": "" }, "compilationOptions": {}, "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { + ".NETCoreApp,Version=v7.0": { "ScriptCore/1.0.0": { - "dependencies": { - "NETStandard.Library": "2.0.3" - }, "runtime": { "ScriptCore.dll": {} } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "NETStandard.Library/2.0.3": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } } } }, @@ -28,20 +18,6 @@ "type": "project", "serviceable": false, "sha512": "" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", - "path": "netstandard.library/2.0.3", - "hashPath": "netstandard.library.2.0.3.nupkg.sha512" } } } \ No newline at end of file diff --git a/GlitchyEditor/resources/scripts/ScriptCore.runtimeconfig.dev.json b/GlitchyEditor/resources/scripts/ScriptCore.runtimeconfig.dev.json new file mode 100644 index 0000000..8033ea6 --- /dev/null +++ b/GlitchyEditor/resources/scripts/ScriptCore.runtimeconfig.dev.json @@ -0,0 +1,9 @@ +{ + "runtimeOptions": { + "additionalProbingPaths": [ + "C:\\Users\\Simon\\.dotnet\\store\\|arch|\\|tfm|", + "C:\\Users\\Simon\\.nuget\\packages", + "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" + ] + } +} \ No newline at end of file diff --git a/GlitchyEditor/resources/scripts/ScriptCore.runtimeconfig.json b/GlitchyEditor/resources/scripts/ScriptCore.runtimeconfig.json new file mode 100644 index 0000000..3f75640 --- /dev/null +++ b/GlitchyEditor/resources/scripts/ScriptCore.runtimeconfig.json @@ -0,0 +1,10 @@ +{ + "runtimeOptions": { + "tfm": "net7.0", + "rollForward": "LatestMinor", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "7.0.0" + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index a74885f..25638d4 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -453,7 +453,8 @@ namespace GlitchyEditor.EditWindows StringView search = StringView(); - char8* scriptLabel = scriptComponent.Instance?.ScriptClass.FullName.ToScopeCStr!() ?? "Select Script..."; + //char8* scriptLabel = scriptComponent.Instance?.ScriptClass.FullName.ToScopeCStr!() ?? "Select Script..."; + char8* scriptLabel = scriptComponent.Instance?.ScriptClass.FullName.CStr() ?? "Select Script..."; if (ImGui.Button(scriptLabel)) ImGui.OpenPopup("SelectScript"); @@ -478,13 +479,15 @@ namespace GlitchyEditor.EditWindows //ScriptEngine.InitializeInstance(entity, scriptComponent); scriptComponent.ScriptClassName = scriptClass.FullName; - - ScriptEngine.CreateScriptFieldMap(entity); + + //ScriptEngine.CreateScriptFieldMap(entity); } } ImGui.EndPopup(); } + /* + ScriptClass scriptClass = ScriptEngine.GetScriptClass(scriptComponent.ScriptClassName); /*T GetFieldValue() @@ -707,6 +710,8 @@ namespace GlitchyEditor.EditWindows } } } + + */ } private static void LabelColumn(StringView label) diff --git a/GlitchyEngine/src/Helper.bf b/GlitchyEngine/src/Helper.bf index 6f7fcd8..fd2a79f 100644 --- a/GlitchyEngine/src/Helper.bf +++ b/GlitchyEngine/src/Helper.bf @@ -70,5 +70,28 @@ namespace GlitchyEngine delete dictionary; } } + + public static mixin ClearDictionaryAndDeleteValues(var dictionary) + { + if (dictionary != null) + { + for (var value in dictionary) + delete value.value; + delete dictionary; + } + } + + public static mixin ClearDictionaryAndDeleteKeysAndValues(var dictionary) + { + if (dictionary != null) + { + for (var value in dictionary) + { + delete value.key; + delete value.value; + } + delete dictionary; + } + } } } diff --git a/GlitchyEngine/src/Scripting/CoreClrHelper.bf b/GlitchyEngine/src/Scripting/CoreClrHelper.bf index 69446fe..b0d8213 100644 --- a/GlitchyEngine/src/Scripting/CoreClrHelper.bf +++ b/GlitchyEngine/src/Scripting/CoreClrHelper.bf @@ -1,6 +1,7 @@ using NetHostBeef; using System; using System.Interop; +using GlitchyEngine.Core; namespace GlitchyEngine.Scripting; @@ -19,6 +20,15 @@ static class CoreClrHelper static LoadScriptAssemblyFunc _loadScriptAssembly; static function void() _unloadAssemblies; + // public static unsafe void GetScriptClasses(void** outBuffer, long* length) + private function void GetScriptClassesFunc(void** outBuffer, int64* length); + static GetScriptClassesFunc _getScriptClasses; + static function void() _freeScriptClassNames; + + //public static unsafe ScriptFunctions CreateScriptInstance(UUID entityId, byte* scriptClassName) + private function ScriptFunctionPointers CreateScriptInstanceFunc(UUID entityId, char8* scriptClassName); + static CreateScriptInstanceFunc _createScriptInstance; + public static void Init(StringView coreAssemblyPath) { LoadHostFxr(); @@ -77,11 +87,15 @@ static class CoreClrHelper static void PrepareFunction() { - GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly", - out _loadScriptAssembly); + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly", out _loadScriptAssembly); + + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadAssemblies", out _unloadAssemblies); - GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadAssemblies", - out _unloadAssemblies); + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "GetScriptClasses", out _getScriptClasses); + + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "FreeScriptClassNames", out _freeScriptClassNames); + + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "CreateScriptInstance", out _createScriptInstance); } public static void LoadAppAssembly(Span appAssemblyData, Span pdbData) @@ -92,6 +106,25 @@ static class CoreClrHelper public static void UnloadAssemblies() { _unloadAssemblies(); + } + + /// Gets an array of script class infos. Use FreeScriptClassNames to release the buffer. + public static void GetScriptClasses(out void* outBuffer, out int64 entryCount) + { + outBuffer = null; + entryCount = 0; + _getScriptClasses(&outBuffer, &entryCount); + } + + /// Releases the buffer created by GetScriptClasses + public static void FreeScriptClassNames() + { + _freeScriptClassNames(); + } + + public static ScriptFunctionPointers CreateScriptInstance(UUID entityId, StringView scriptName) + { + return _createScriptInstance(entityId, scriptName.Ptr); } public static int GetFunctionPointer(StringView typeName, StringView methodName, StringView delegateName, out T outDelegate) where T: operator explicit void* diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index 8277169..e6e262b 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -47,6 +47,73 @@ static sealed class ScriptEngineHelper } } +public enum ScriptMethods : uint32 +{ + None = 0, + OnCreate = 0x1, + OnUpdate = 0x2, + OnDestroy = 0x4, +} + +class SexyScriptClass +{ + public String FullName ~ delete _; + public StringView Name; + public Guid Guid; + public ScriptMethods Methods; + + public this(StringView fullName, Guid guid, ScriptMethods methods) + { + FullName = new String(fullName); + Guid = guid; + + int lastDotIndex = FullName.LastIndexOf('.'); + + if (lastDotIndex == -1) + Name = FullName; + else + Name = FullName.Substring(lastDotIndex + 1); + + Methods = methods; + } +} + +class SexyScriptInstance +{ + public SexyScriptClass ScriptClass; + + public ScriptFunctionPointers Functions; + + public this(SexyScriptClass scriptClass, ScriptFunctionPointers functions) + { + ScriptClass = scriptClass; + Functions = functions; + } + + public void InvokeOnCreate() + { + Functions.OnCreateMethod(); + } + + /*public void InvokeOnUpdate(float deltaTime) + { + CoreClrHelper.InvokeOnUpdate(float deltaTime); + //Functions.OnUpdateMethod(deltaTime); + }*/ + + public void InvokeOnDestroy() + { + Functions.OnDestroyMethod(); + } +} + +public struct ScriptFunctionPointers +{ + public function void() OnCreateMethod; + public function void(float) OnUpdateMethod; + public function void() OnDestroyMethod; +} + static class ScriptEngine { private static MonoDomain* s_RootDomain; @@ -65,17 +132,11 @@ static class ScriptEngine private static Dictionary _sharpClasses = new .() ~ DeleteDictionaryAndReleaseValues!(_); - private static Dictionary _entityScripts = new .() ~ DeleteDictionaryAndReleaseValues!(_); + private static Dictionary _entityScripts = new .() ~ DeleteDictionaryAndValues!(_); - private static Dictionary _entityScriptInstances = new .() ~ { - for (var entry in _) - { - entry.value?.ReleaseRef(); - } - delete _; - } + private static Dictionary _entityScriptInstances = new .() ~ DeleteDictionaryAndValues!(_); - public static Dictionary EntityClasses => _entityScripts; + public static Dictionary EntityClasses => _entityScripts; public static Scene Context => s_Context; @@ -101,11 +162,14 @@ static class ScriptEngine private static String _coreAssemblyPath = "resources/scripts/ScriptCore.dll"; private static String _appAssemblyPath = "SandboxProject/Assets/Scripts/bin/Sandbox.dll"; - + + public static void Init() { CoreClrHelper.Init(_coreAssemblyPath); - + + GetFunctions(); + //ScriptGlue.Init(); LoadScriptAssembly(); @@ -150,17 +214,6 @@ static class ScriptEngine _userAssemblyWatcher.StartRaisingEvents(); } - /*static void LoadScriptAssemblies() - { - CreateAppDomain("GlitchyEngineScriptRuntime"); - (s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled); - (s_AppAssembly, s_AppAssemblyImage) = LoadAssembly("SandboxProject/Assets/Scripts/bin/Sandbox.dll", _debuggingEnabled); - - GetEntitiesFromAssemblies(); - - ScriptGlue.RegisterManagedComponents(); - }*/ - static void LoadScriptAssembly() { List data = new List(1024); @@ -178,11 +231,27 @@ static class ScriptEngine delete data; delete pdbData; - //GetEntitiesFromAssemblies(); + GetEntitiesFromAssemblies(); //ScriptGlue.RegisterManagedComponents(); } + public static void ReloadAssemblies() + { + Debug.Profiler.ProfileFunction!(); + + CoreClrHelper.UnloadAssemblies(); + LoadScriptAssembly(); + + // TODO: ScriptFields might get added + // TODO: ScriptField Types may change after reload! + // TODO: Scripts may be renamed (probably not detectable (trivially)) + + // TODO: Reload in play mode + // Only scripts that were changed should actually be reinstatiated + } + + public static void SetContext(Scene scene) { SetReference!(s_Context, scene); @@ -190,37 +259,54 @@ static class ScriptEngine public static void OnRuntimeStop() { - for (var entry in _entityScriptInstances) + ThrowUnimplemented(); + + // TODO: Also check, if something has to happen on the C# side! + + ClearDictionaryAndDeleteValues!(_entityScriptInstances); + + /*for (var entry in _entityScriptInstances) { entry.value.ReleaseRef(); } _entityScriptInstances.Clear(); - + */ SetContext(null); } + private function void InvokeOnUpdateFunc(UUID entityId, float deltaTime); + + private static InvokeOnUpdateFunc _InvokeOnUpdate; + + private static void GetFunctions() + { + CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "InvokeOnUpdate", out _InvokeOnUpdate); + } + public static bool InitializeInstance(Entity entity, ScriptComponent* script) { - ScriptClass scriptClass = GetScriptClass(script.ScriptClassName); - + SexyScriptClass scriptClass = GetScriptClass(script.ScriptClassName); + if (scriptClass == null) return false; + + ScriptFunctionPointers functions = CoreClrHelper.CreateScriptInstance(entity.UUID, scriptClass.FullName); - script.Instance = new ScriptInstance(scriptClass); - script.Instance..ReleaseRef(); + script.Instance = new SexyScriptInstance(scriptClass, functions);//new ScriptInstance(scriptClass); + // script.Instance..ReleaseRef(); + + _entityScriptInstances[entity.UUID] = script.Instance; - _entityScriptInstances[entity.UUID] = script.Instance..AddRef(); - - script.Instance.Instantiate(entity.UUID); - - CopyEditorFieldsToInstance(entity, script); + // CopyEditorFieldsToInstance(entity, script); return true; } private static void CopyEditorFieldsToInstance(Entity entity, ScriptComponent* script) { - // Technically the map is for a different entity (namely the editor-entity), + ThrowUnimplemented(); + + /*// Technically the map is for a different entity (namely the editor-entity), // however the UUID is the same, so we get the correct field map let fields = GetScriptFieldMap(entity); @@ -231,82 +317,71 @@ static class ScriptEngine ScriptField scriptField = script.Instance.ScriptClass.Fields[fieldName]; script.Instance.SetFieldValue(scriptField, field._data); - } + }*/ } - private static MonoAssembly* LoadCSharpAssembly(StringView assemblyPath, bool loadPDB = false) + struct ScriptClassInfo { - List data = new List(1024); - - File.ReadAll(assemblyPath, data); - - // NOTE: We can't use this image for anything other than loading the assembly because this image doesn't have a reference to the assembly - MonoImageOpenStatus status = .ImageInvalid; - MonoImage* image = Mono.mono_image_open_from_data_full(data.Ptr, (.)data.Count, true, &status, false); - - delete data; - - if (status != .Ok) - { - char8* errorMessage = Mono.mono_image_strerror(status); - - Log.EngineLogger.Error($"Failed to load C# Assembly: \"{StringView(errorMessage)}\""); - - return null; - } - - if (loadPDB) - { - String pdbPath = scope .(); - Path.ChangeExtension(assemblyPath, ".pdb", pdbPath); - - if (File.Exists(pdbPath)) - { - Log.EngineLogger.Trace($"Loading PDB \"{pdbPath}\"..."); - - List pdbData = new List(1024); - - let result = File.ReadAll(pdbPath, pdbData); - - if (result case .Err(let error)) - { - Log.EngineLogger.Error("Failed to load PDB file ({error})."); - } - - Mono.mono_debug_open_image_from_memory(image, pdbData.Ptr, (int32)pdbData.Count); - - delete pdbData; - } - else - { - Log.EngineLogger.Warning($"Debugging enabled but no PDB-File found \"{assemblyPath}\"."); - } - } - - MonoAssembly* assembly = Mono.mono_assembly_load_from_full(image, assemblyPath.ToScopeCStr!(), &status, 0); - - Mono.mono_image_close(image); - - return assembly; - } - - static void CreateAppDomain(StringView name) - { - s_AppDomain = Mono.mono_domain_create_appdomain(name.ToScopeCStr!(), null); - Mono.mono_domain_set(s_AppDomain, true); - } - - static (MonoAssembly* assembly, MonoImage* image) LoadAssembly(StringView filepath, bool loadPDB = false) - { - MonoAssembly* assembly = LoadCSharpAssembly(filepath, loadPDB); - MonoImage* image = Mono.mono_assembly_get_image(assembly); - - return (assembly, image); + public char8* Name; + public Guid Guid; + public ScriptMethods Methods; } private static void GetEntitiesFromAssemblies() { + CoreClrHelper.GetScriptClasses(let data, let entryCount); + + Span scriptClasses = .((.)data, entryCount); + + List newClasses = scope .(); + for (var entry in _entityScripts) + { + bool remove = true; + + // We can only rely on guids. + for (var newEntry in scriptClasses) + { + if (entry.value.Guid == newEntry.Guid) + { + remove = false; + + if (!String.Equals(entry.value.FullName.CStr(), newEntry.Name)) + { + @entry.Remove(); + + entry.value.FullName.Set(StringView(newEntry.Name)); + } + + newClasses.Add(entry.value); + } + } + + if (remove) + { + @entry.Remove(); + + delete entry.value; + } + } + + for (var entry in scriptClasses) + { + SexyScriptClass scriptClass = new .(StringView(entry.Name), entry.Guid, entry.Methods); + + newClasses.Add(scriptClass); + } + + for (var scriptClass in newClasses) + { + _entityScripts.Add(scriptClass.FullName, scriptClass); + } + + CoreClrHelper.FreeScriptClassNames(); + + // TODO: Cleanup + + /*for (var entry in _entityScripts) { entry.value.ReleaseRef(); } @@ -351,28 +426,7 @@ static class ScriptEngine Log.EngineLogger.Info($"Added entity \"{entityScript.FullName}\""); } - } - } - - public static void ReloadAssemblies() - { - Debug.Profiler.ProfileFunction!(); - - CoreClrHelper.UnloadAssemblies(); - LoadScriptAssembly(); - - //Mono.mono_domain_set(s_RootDomain, false); - - //Mono.mono_domain_unload(s_AppDomain); - - //LoadScriptAssemblies(); - - // TODO: ScriptFields might get added - // TODO: ScriptField Types may change after reload! - // TODO: Scripts may be renamed (probably not detectable (trivially)) - - // TODO: Reload in play mode - // Only scripts that were changed should actually be reinstatiated + }*/ } public static void Shutdown() @@ -434,6 +488,9 @@ static class ScriptEngine public static void CreateScriptFieldMap(Entity entity) { + Log.EngineLogger.Error("Not implemented"); + + /* Log.EngineLogger.AssertDebug(entity.IsValid); if (_entityFields.TryGetValue(entity.UUID, var entityFields)) @@ -455,11 +512,13 @@ static class ScriptEngine for (let (fieldName, field) in scriptClass.Fields) { entityFields.Add(new String(fieldName), ScriptFieldInstance(field.FieldType)); - } + }*/ } public static ScriptFieldMap GetScriptFieldMap(Entity entity) { + ThrowUnimplemented(); + Log.EngineLogger.AssertDebug(entity.IsValid); let uuid = entity.UUID; @@ -472,16 +531,23 @@ static class ScriptEngine public static MonoObject* GetManagedInstance(UUID entityId) { - if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance)) - return scriptInstance.MonoInstance; + ThrowUnimplemented(); + + /*if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance)) + return scriptInstance.MonoInstance;*/ return null; } - public static ScriptClass GetScriptClass(StringView name) + public static SexyScriptClass GetScriptClass(StringView name) { EntityClasses.TryGetValue(name, let scriptClass); return scriptClass; } + + public static void InvokeOnUpdate(UUID entityId, float deltaTime) + { + _InvokeOnUpdate(entityId, deltaTime); + } } \ No newline at end of file diff --git a/GlitchyEngine/src/World/Components/Components.bf b/GlitchyEngine/src/World/Components/Components.bf index c1f4f5c..f6c01ab 100644 --- a/GlitchyEngine/src/World/Components/Components.bf +++ b/GlitchyEngine/src/World/Components/Components.bf @@ -509,7 +509,7 @@ namespace GlitchyEngine.World { private String _scriptClassName = null; - private ScriptInstance _instance = null; + private SexyScriptInstance _instance = null; public StringView ScriptClassName { @@ -523,22 +523,24 @@ namespace GlitchyEngine.World } } - public ScriptInstance Instance + public SexyScriptInstance Instance { [Inline] get => _instance; [Inline] - set mut => SetReference!(_instance, value); + set mut => _instance = value;//SetReference!(_instance, value); } - public bool IsInitialized => _instance?.IsInitialized ?? false; + // public bool IsInitialized => _instance?.IsInitialized ?? false; - public bool IsCreated => _instance?.IsCreated ?? false; + // public bool IsCreated => _instance?.IsCreated ?? false; + + public bool IsCreated => _instance != null; public void Dispose() mut { delete _scriptClassName; - ReleaseRefAndNullify!(_instance); + //ReleaseRefAndNullify!(_instance); } } -} \ No newline at end of file +} diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index e9fb7cf..6b7b9de 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -260,7 +260,7 @@ namespace GlitchyEngine.World script.Instance._entity = Entity(entity, this); script.Instance.[Friend]OnCreate(); } - + script.Instance.[Friend]OnUpdate(gameTime); } /*} @@ -272,15 +272,15 @@ namespace GlitchyEngine.World { if (!script.IsCreated) { - if (!script.IsInitialized) - ScriptEngine.InitializeInstance(Entity(entity, this), script); + ScriptEngine.InitializeInstance(Entity(entity, this), script); - if (mode.HasFlag(.Runtime)) - script.Instance.InvokeOnCreate(); + //if (mode.HasFlag(.Runtime)) + //script.Instance.InvokeOnCreate(); } - if (mode.HasFlag(.Runtime)) - script.Instance.InvokeOnUpdate(gameTime.DeltaTime); + if (mode.HasFlag(.Runtime) && script.Instance.ScriptClass.Methods.HasFlag(.OnUpdate)) + ScriptEngine.InvokeOnUpdate(Entity(entity, this).UUID, gameTime.DeltaTime); + //script.Instance.InvokeOnUpdate(gameTime.DeltaTime); } } diff --git a/GlitchyEngine/src/World/SceneSerializer.bf b/GlitchyEngine/src/World/SceneSerializer.bf index 1aaab08..949d383 100644 --- a/GlitchyEngine/src/World/SceneSerializer.bf +++ b/GlitchyEngine/src/World/SceneSerializer.bf @@ -191,7 +191,9 @@ class SceneSerializer { Serialize.Value(writer, "ScriptClass", component.ScriptClassName); - //if (component.HasScript) + ThrowUnimplemented(); + + /*//if (component.HasScript) // TODO: Thats not a good check, I think. At least we know the script class is valid if (ScriptEngine.GetScriptClass(component.ScriptClassName) != null) { @@ -221,7 +223,7 @@ class SceneSerializer } } } - } + }*/ }); } @@ -579,9 +581,9 @@ class SceneSerializer // But that is a bug for me to rediscover in the distant future, so in case this bug occurred and it took ages for you to // figure out what happened: You are welcome :) - ScriptEngine.CreateScriptFieldMap(entity); + //ScriptEngine.CreateScriptFieldMap(entity); - var fields = ScriptEngine.GetScriptFieldMap(entity); + //var fields = ScriptEngine.GetScriptFieldMap(entity); Try!(reader.EntryEnd()); @@ -613,44 +615,47 @@ class SceneSerializer // Allocate a string on the stack, because the dictionary uses a string as key String fieldNameString = scope .(fieldName); - if (fields.ContainsKey(fieldNameString)) - { - var field = ref fields[fieldNameString]; + //if (fields.ContainsKey(fieldNameString)) + //{ + //var field = ref fields[fieldNameString]; Result fieldTypeName = reader.Type(); - if (fieldTypeName case .Err) + /*if (fieldTypeName case .Err) { Log.EngineLogger.Error($"Failed to read field type for field \"{fieldName}\" in script \"{component.ScriptClassName}\" of entity {entity.UUID} (\"{entity.Name}\")"); reader.FileEntrySkip(1); dontRemoveComma = true; continue; - } + }*/ Result fieldType = Enum.Parse(fieldTypeName, true); - if ((fieldType case .Err) || (fieldType != field.Type)) + /*if ((fieldType case .Err) || (fieldType != field.Type)) { Log.EngineLogger.Error($"Unexpected field type (\"{fieldTypeName}\" instead of \"{field.Type}\" for field: \"{fieldName}\" in script \"{component.ScriptClassName}\" of entity {entity.UUID} (\"{entity.Name}\")"); reader.FileEntrySkip(1); dontRemoveComma = true; continue; - } + }*/ - void* data = &field.[Friend]_data; + //void* data = &field.[Friend]_data; + + uint8[128] data; - if (Deserialize.Value(reader, ValueView(field.Type.GetBeefType(), data), gBonEnv) case .Err) + //if (Deserialize.Value(reader, ValueView(field.Type.GetBeefType(), data), gBonEnv) case .Err) + if (Deserialize.Value(reader, ValueView(fieldType.Value.GetBeefType(), &data), gBonEnv) case .Err) { Log.EngineLogger.Error($"Failed to deserialize data for field: \"{fieldName}\" in script \"{component.ScriptClassName}\" of entity {entity.UUID} (\"{entity.Name}\")"); reader.FileEntrySkip(1); dontRemoveComma = true; continue; } - } + /*} else { Log.EngineLogger.Error($"Script \"{component.ScriptClassName}\" doesn't have a field with name \"{fieldName}\". (Entity {entity.UUID} (\"{entity.Name}\"))"); - } + }*/ } Try!(reader.ArrayBlockEnd()); diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index ec1c70a..3b47015 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -1,11 +1,18 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Net.Mime; using System.Net.Security; using System.Reflection; +using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; +using System.Text; using GlitchyEngine.Core; using GlitchyEngine.Math; @@ -95,7 +102,7 @@ internal static class ScriptGlue LoadAssembly(assemblyStream, pdbStream); } - private static void LoadAssembly(Stream assemblyStream, Stream? pdbStream)//(string path) + private static void LoadAssembly(Stream assemblyStream, Stream? pdbStream) { try { @@ -107,6 +114,8 @@ internal static class ScriptGlue // Test the loaded assembly _appAssembly.GetType("Sandbox.TestClass")?.GetMethod("InternalTest")?.Invoke(null, null); + + AssemblyLoadContext.EnterContextualReflection(_appAssembly); } catch (Exception e) { @@ -116,13 +125,352 @@ internal static class ScriptGlue [UnmanagedCallersOnly] public static void UnloadAssemblies() - { - UnloadContext(); - } - - private static void UnloadContext() { _scriptAssemblyContext?.Unload(); _scriptAssemblyContext = null; } + + struct ScriptClassInfo + { + public byte[] Name; + public Guid Guid; + } + + private static NativeScriptClassInfo[]? _unsafeClasses; + + struct NativeScriptClassInfo + { + public IntPtr Name; + public Guid Guid; + public ScriptMethods Methods; + } + + [Flags] + public enum ScriptMethods + { + None = 0, + OnCreate = 0x1, + OnUpdate = 0x2, + OnDestroy = 0x4, + } + + + [UnmanagedCallersOnly] + public static unsafe void GetScriptClasses(void** outBuffer, long* length) + { + AssemblyLoadContext.EnterContextualReflection(_appAssembly); + + Debug.Assert(_appAssembly != null); + + Internal_FreeScriptClassNames(); + + var types = _appAssembly.GetTypes(); + + List<(string Name, Guid Guid, ScriptMethods Methods)> scriptClasses = new(); + + _updateMethods.Clear(); + + foreach (var type in types) + { + if (type.IsSubclassOf(typeof(Entity))) + { + string? name = type.FullName; + + if (name == null) + continue; + + Guid guid = type.GUID; + + ScriptMethods methods = ScriptMethods.None; + + MethodInfo? onUpdateMethod = type.GetMethod("OnUpdate"); + if (onUpdateMethod != null) + { + _updateMethods[type] = onUpdateMethod; + + methods |= ScriptMethods.OnUpdate; + } + + scriptClasses.Add((name, guid, methods)); + } + } + + _unsafeClasses = new NativeScriptClassInfo[scriptClasses.Count]; + + for (int i = 0; i < _unsafeClasses.Length; i++) + { + _unsafeClasses[i] = new NativeScriptClassInfo() + { + Guid = scriptClasses[i].Guid, + Name = Marshal.StringToCoTaskMemUTF8(scriptClasses[i].Name), + Methods = scriptClasses[i].Methods + }; + } + + *outBuffer = (void*)Marshal.UnsafeAddrOfPinnedArrayElement(_unsafeClasses, 0); + *length = _unsafeClasses.Length; + } + + [UnmanagedCallersOnly] + public static void FreeScriptClassNames() + { + Internal_FreeScriptClassNames(); + } + + internal static void Internal_FreeScriptClassNames() + { + if (_unsafeClasses == null) + return; + + foreach (NativeScriptClassInfo info in _unsafeClasses) + { + Marshal.FreeCoTaskMem(info.Name); + } + + _unsafeClasses = null; + } + + private static Dictionary _entityScripts = new(); + private static Dictionary _updateMethods = new(); + + public struct ScriptFunctions + { + public IntPtr OnCreateMethod; + public IntPtr OnUpdateMethod; + public IntPtr OnDestroyMethod; + } + + // Define delegate with the same signature as your method + delegate void OnCreateMethodDelegate(); + + // Define delegate with the same signature as your method + delegate void OnUpdateMethodDelegate(float deltaTime); + + [UnmanagedCallersOnly] + public static void InvokeOnUpdate(UUID entityId, float deltaTime) + { + AssemblyLoadContext.EnterContextualReflection(_appAssembly); + + Debug.Assert(_entityScripts.ContainsKey(entityId)); + + (Entity Entity, Type Type) entity = _entityScripts[entityId]; + + entity.Type.GetMethod("OnUpdate", BindingFlags.NonPublic | BindingFlags.Instance) + .Invoke(entity.Entity, new object?[]{deltaTime }); + + _updateMethods[entity.Type].Invoke(entity.Entity, BindingFlags.NonPublic | BindingFlags.Instance, null, + new object[] { deltaTime }, null); + } + + [UnmanagedCallersOnly] + public static unsafe ScriptFunctions CreateScriptInstance(UUID entityId, byte* scriptClassName) + { + AssemblyLoadContext.EnterContextualReflection(_appAssembly); + + Debug.Assert(_appAssembly != null); + + string? typeName = Marshal.PtrToStringUTF8((IntPtr)scriptClassName); + + Debug.Assert(typeName != null); + + Type? scriptType = _appAssembly.GetType(typeName); + + Debug.Assert(scriptType != null, "Script class Type not found."); + + //Entity? scriptInstance = Activator.CreateInstance(scriptType, BindingFlags.Instance | BindingFlags.NonPublic, null, entityId) as Entity; + + // Get the private constructor + ConstructorInfo constructor = scriptType.BaseType.GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new Type[] { typeof(UUID) }, // replace this with the types of your constructor parameters + null); + + // Call the constructor to create an instance + Entity? scriptInstance = constructor?.Invoke(new object[] { entityId }) as Entity; + + ScriptFunctions functions = new(); + + _entityScripts.Add(entityId, (scriptInstance!, scriptType)); + + //MethodInfo? onCreateMethod = scriptType.GetMethod("OnCreate", BindingFlags.Instance | BindingFlags.NonPublic); + //if (onCreateMethod != null) + //{ + // var v = MethodHelpers.GetFunctionPointerForNativeCode(onCreateMethod, null); + //} + //if (onCreateMethod != null) + //{ + // //Delegate del = CreateDelegateWithTarget(onCreateMethod, scriptInstance); + + // //var createDelegate = onCreateMethod.CreateDelegate(scriptInstance); + // //var createDelegate = onCreateMethod.CreateDelegate(typeof(OnCreateMethodDelegate), scriptInstance); + // //onCreateMethod.CreateDelegate(scriptInstance); + + // //functions.OnCreateMethod = Marshal.GetFunctionPointerForDelegate(createDelegate); + //} + + //MethodInfo? onUpdateMethod = scriptType.GetMethod("OnUpdate", BindingFlags.Instance | BindingFlags.NonPublic); + //if (onUpdateMethod != null) + //{ + // // Create the delegate from your method and instance + // OnUpdateMethodDelegate onUpdateDelegate = (OnUpdateMethodDelegate)Delegate.CreateDelegate(typeof(OnUpdateMethodDelegate), scriptInstance, onUpdateMethod); + + // // Get the function pointer from your delegate + // functions.OnUpdateMethod = Marshal.GetFunctionPointerForDelegate(onUpdateDelegate); + //} + // //functions.OnUpdateMethod = Marshal.GetFunctionPointerForDelegate((float deltaTime) => onUpdateMethod.Invoke(scriptInstance, new object?[]{ deltaTime })); + + //MethodInfo? onDestroyMethod = scriptType.GetMethod("OnUpdate", BindingFlags.Instance | BindingFlags.NonPublic); + //if (onDestroyMethod != null) + // functions.OnDestroyMethod = Marshal.GetFunctionPointerForDelegate(() => onDestroyMethod.Invoke(scriptInstance, null)); + + return functions; + } + + static Delegate CreateDelegate(MethodInfo method) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + if (!method.IsStatic) + { + throw new ArgumentException("The provided method must be static.", nameof(method)); + } + + if (method.IsGenericMethod) + { + throw new ArgumentException("The provided method must not be generic.", nameof(method)); + } + + return method.CreateDelegate(Expression.GetDelegateType( + (from parameter in method.GetParameters() select parameter.ParameterType) + .Concat(new[] { method.ReturnType }) + .ToArray())); + } + + /// + /// Create delegate by methodinfo in target + /// + /// method info + /// A instance of the object which contains the method where will be execute + /// delegate or null + public static Delegate? CreateDelegateWithTarget(MethodInfo? method, object? target) + { + if (method is null || + target is null) + return null; + + //if (method.IsStatic) + // return null; + + if (method.IsGenericMethod) + return null; + + return method.CreateDelegate(Expression.GetDelegateType( + (from parameter in method.GetParameters() select parameter.ParameterType) + .Concat(new[] { method.ReturnType }) + .ToArray()), target); + } + + internal static class MethodHelpers + { + private const string DelegateTypesAssemblyName = "JitDelegateTypes"; + + private static ModuleBuilder _modBuilder; + + private static ConcurrentDictionary<(string, object), Delegate> _delegatesCache; + private static ConcurrentDictionary _delegateTypesCache; + + static MethodHelpers() + { + AssemblyBuilder asmBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(DelegateTypesAssemblyName), AssemblyBuilderAccess.Run); + + _modBuilder = asmBuilder.DefineDynamicModule(DelegateTypesAssemblyName); + + _delegatesCache = new ConcurrentDictionary<(string, object), Delegate>(); + _delegateTypesCache = new ConcurrentDictionary(); + } + + public static IntPtr GetFunctionPointerForNativeCode(MethodInfo meth, object instance = null) + { + string funcName = GetFullName(meth); + + Delegate dlg = _delegatesCache.GetOrAdd((funcName, instance), (_) => + { + Type[] parameters = meth.GetParameters().Select(x => x.ParameterType).ToArray(); + + Type delegateType = GetDelegateType(parameters, meth.ReturnType); + + return Delegate.CreateDelegate(delegateType, instance, meth); + }); + + return Marshal.GetFunctionPointerForDelegate(dlg); + } + + private static string GetFullName(MethodInfo meth) + { + return $"{meth.DeclaringType.FullName}.{meth.Name}"; + } + + private static Type GetDelegateType(Type[] parameters, Type returnType) + { + string key = GetFunctionSignatureKey(parameters, returnType); + + return _delegateTypesCache.GetOrAdd(key, (_) => MakeDelegateType(parameters, returnType, key)); + } + + private const MethodAttributes CtorAttributes = + MethodAttributes.RTSpecialName | + MethodAttributes.HideBySig | + MethodAttributes.Public; + + private const MethodImplAttributes ImplAttributes = + MethodImplAttributes.Runtime | + MethodImplAttributes.Managed; + + private const MethodAttributes InvokeAttributes = + MethodAttributes.Public | + MethodAttributes.HideBySig | + MethodAttributes.NewSlot | + MethodAttributes.Virtual; + + private const TypeAttributes DelegateTypeAttributes = + TypeAttributes.Class | + TypeAttributes.Public | + TypeAttributes.Sealed | + TypeAttributes.AnsiClass | + TypeAttributes.AutoClass; + + private static readonly Type[] _delegateCtorSignature = { typeof(object), typeof(IntPtr) }; + + private static Type MakeDelegateType(Type[] parameters, Type returnType, string name) + { + TypeBuilder builder = _modBuilder.DefineType(name, DelegateTypeAttributes, typeof(MulticastDelegate)); + + builder.DefineConstructor(CtorAttributes, CallingConventions.Standard, _delegateCtorSignature).SetImplementationFlags(ImplAttributes); + + builder.DefineMethod("Invoke", InvokeAttributes, returnType, parameters).SetImplementationFlags(ImplAttributes); + + return builder.CreateTypeInfo(); + } + + private static string GetFunctionSignatureKey(Type[] parameters, Type returnType) + { + string sig = GetTypeName(returnType); + + foreach (Type type in parameters) + { + sig += '_' + GetTypeName(type); + } + + return sig; + } + + private static string GetTypeName(Type type) + { + return type.FullName.Replace(".", string.Empty); + } + } }