diff --git a/.gitmodules b/.gitmodules index d0bc96a..ae1321c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "vendor/ImGui.NET"] path = vendor/ImGui.NET url = https://github.com/aharabada/ImGui.NET.git +[submodule "vendor/NetHostBeef"] + path = vendor/NetHostBeef + url = https://github.com/aharabada/NetHostBeef.git diff --git a/BeefSpace.toml b/BeefSpace.toml index 8fe8227..870a749 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -1,7 +1,7 @@ FileVersion = 1 -Projects = {GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}, bon = {Path = "GlitchyEngine/vendor/bon"}, box2d-beef = {Path = "GlitchyEngine/vendor/box2D"}, "Beef.Linq" = {Path = "GlitchyEngine/vendor/Beef.Linq/src"}, ScriptCore = {Path = "ScriptCore"}} +Projects = {GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}, bon = {Path = "GlitchyEngine/vendor/bon"}, box2d-beef = {Path = "GlitchyEngine/vendor/box2D"}, "Beef.Linq" = {Path = "GlitchyEngine/vendor/Beef.Linq/src"}, ScriptCore = {Path = "ScriptCore"}, NetHostBeef = {Path = "GlitchyEngine/vendor/NetHostBeef/NetHostBeef"}} Locked = ["ImGui"] -WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper", "ScriptCore"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq"], Beef = ["Beefy2D", "corlib", "MiniZ"]} +WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper", "ScriptCore"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq", "NetHostBeef"], Beef = ["Beefy2D", "corlib", "MiniZ"]} [Workspace] StartupProject = "GlitchyEditor" diff --git a/GlitchyEditor/src/EditorApp.bf b/GlitchyEditor/src/EditorApp.bf index 6a3fe77..5d30ab4 100644 --- a/GlitchyEditor/src/EditorApp.bf +++ b/GlitchyEditor/src/EditorApp.bf @@ -10,6 +10,7 @@ using GlitchyEditor.Platform.Windows; using GlitchyEditor.Multithreading; using System.Threading; using GlitchyEditor.Platform; +using System.Diagnostics; namespace GlitchyEditor { @@ -25,6 +26,11 @@ namespace GlitchyEditor public this(String[] args) { + while(!Debug.IsDebuggerPresent) + { + Thread.Sleep(10); + } + Log.ClientLogger = new EditorLogger(); Log.EngineLogger = new EditorLogger() { IsEngineLogger = true }; diff --git a/GlitchyEditor/src/EditorLogger.bf b/GlitchyEditor/src/EditorLogger.bf index 6f89395..db2e89e 100644 --- a/GlitchyEditor/src/EditorLogger.bf +++ b/GlitchyEditor/src/EditorLogger.bf @@ -139,7 +139,7 @@ public class EditorLogger : Logger } else if (args.Count > 0 && messageOrigin != null) { - Editor.Instance.LogWindow.Log(timestamp, level, message, new .() {IsEngineMessage = IsEngineLogger, MessageOrigin = messageOrigin}); + Editor.Instance.LogWindow.Log(timestamp, level, message, new .() {IsEngineMessage = IsEngineLogger, MessageOrigin = new MessageOrigin(messageOrigin.FileName, messageOrigin.LineNumber)}); } else { diff --git a/GlitchyEngine/BeefProj.toml b/GlitchyEngine/BeefProj.toml index babd331..406d8eb 100644 --- a/GlitchyEngine/BeefProj.toml +++ b/GlitchyEngine/BeefProj.toml @@ -1,5 +1,5 @@ FileVersion = 1 -Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*", box2d-beef = "*", "Beef.Linq" = "*"} +Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*", box2d-beef = "*", "Beef.Linq" = "*", NetHostBeef = "*"} [Project] Name = "GlitchyEngine" diff --git a/GlitchyEngine/src/Scripting/CoreClrHelper.bf b/GlitchyEngine/src/Scripting/CoreClrHelper.bf new file mode 100644 index 0000000..17ac918 --- /dev/null +++ b/GlitchyEngine/src/Scripting/CoreClrHelper.bf @@ -0,0 +1,185 @@ +using NetHostBeef; +using System; +using System.Interop; +using GlitchyEngine.Core; + +namespace GlitchyEngine.Scripting; + +public struct ScriptFunctionPointers +{ + public function void() OnCreate; + public function void(float) OnUpdate; + public function void() OnDestroy; +} + +static class CoreClrHelper +{ + static HostFxr.InitializeForDotnetCommandLineFn HostFxr_Init; + static HostFxr.GetRuntimeDelegateFn HostFxr_GetDelegate; + static HostFxr.CloseFn HostFxr_Close; + + static CoreClr.LoadAssemblyAndGetFunctionPointerFn LoadAssemblyAndGetFunctionPointerFn; + static CoreClr.GetFunctionPointerFn GetFunctionPointerFn; + + private function void LoadScriptAssemblyFunc(uint8* appData, int64 appLength, uint8* pdbData, int64 pdbLength); + + //static function void(char8* path) LoadScriptAssembly; + 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 void CreateScriptInstanceFunc(UUID entityId, char8* scriptClassName); + static CreateScriptInstanceFunc _createScriptInstance; + + public static ScriptFunctionPointers _entityScriptFunctions; + + public static void Init(StringView coreAssemblyPath) + { + LoadHostFxr(); + InitAndStartRuntime(coreAssemblyPath); + + PrepareFunction(); + } + + static void LoadHostFxr() + { + char_t[256] buffer = ?; + c_size bufferSize = buffer.Count; + int rc = NetHost.get_hostfxr_path(&buffer, &bufferSize, null); + + Log.EngineLogger.AssertDebug(rc == 0, "Failed to get HostFxr path."); + + // Load hostfxr and get desired exports + void* lib = NetHostHelper.LoadLibrary(&buffer); + HostFxr_Init = NetHostHelper.GetExport(lib, "hostfxr_initialize_for_dotnet_command_line"); + HostFxr_GetDelegate = NetHostHelper.GetExport(lib, "hostfxr_get_runtime_delegate"); + HostFxr_Close = NetHostHelper.GetExport(lib, "hostfxr_close"); + + Log.EngineLogger.AssertDebug(HostFxr_Init != null && HostFxr_GetDelegate != null && HostFxr_Close != null, "Retrieving at least one HostFxr function failed."); + } + + static void InitAndStartRuntime(StringView coreAssembly) + { + char_t* ptr = NetHostHelper.GetScopedRawPtr!(coreAssembly); + + char_t*[2] args = char_t*[]( + ptr, + null + ); + + HostFxr.Handle cxt = null; + int rc = HostFxr_Init(1, &args, null, &cxt); + + if (rc != 0 || cxt == null) + { + Log.EngineLogger.Error($"Failed to initialize HostFxr: {rc}"); + HostFxr_Close(cxt); + } + + Log.EngineLogger.Assert(rc == 0, "Failed to initialize HostFxr."); + + rc = HostFxr_GetDelegate(cxt, .LoadAssemblyAndGetFunctionPointer, (void**)&LoadAssemblyAndGetFunctionPointerFn); + + Log.EngineLogger.Assert(rc == 0, scope $"Get delegate failed: {rc}. (LoadAssemblyAndGetFunctionPointer)"); + + rc = HostFxr_GetDelegate(cxt, .GetFunctionPointer, (void**)&GetFunctionPointerFn); + + Log.EngineLogger.Assert(rc == 0, scope $"Get delegate failed: {rc}. (GetFunctionPointer)"); + + HostFxr_Close(cxt); + } + + static void PrepareFunction() + { + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly", out _loadScriptAssembly); + + 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); + + InitEntityFunctions(); + } + + + static void InitEntityFunctions() + { + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "InvokeEntityOnCreate", out _entityScriptFunctions.OnCreate); + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "InvokeEntityOnUpdate", out _entityScriptFunctions.OnUpdate); + GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "InvokeEntityOnDestroy", out _entityScriptFunctions.OnDestroy); + } + + public static void LoadAppAssembly(Span appAssemblyData, Span pdbData) + { + _loadScriptAssembly(appAssemblyData.Ptr, appAssemblyData.Length, pdbData.Ptr, pdbData.Length); + } + + 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 void CreateScriptInstance(UUID entityId, StringView scriptName) + { + _createScriptInstance(entityId, scriptName.Ptr); + } + + public static int GetFunctionPointer(StringView typeName, StringView methodName, StringView delegateName, out T outDelegate) where T: operator explicit void* + { + void* funPtr = null; + + int rc = GetFunctionPointerFn(NetHostHelper.GetScopedRawPtr!(typeName), + NetHostHelper.GetScopedRawPtr!(methodName), NetHostHelper.GetScopedRawPtr!(delegateName), + null, null, out funPtr); + + outDelegate = (T)funPtr; + + return rc; + } + + public static int GetFunctionPointerDefaultDelegate(StringView typeName, StringView methodName, out CoreClr.DefaultEntryPoint outDelegate) + { + void* funPtr = null; + + int rc = GetFunctionPointerFn(NetHostHelper.GetScopedRawPtr!(typeName), + NetHostHelper.GetScopedRawPtr!(methodName), null, null, null, out funPtr); + + outDelegate = (CoreClr.DefaultEntryPoint)funPtr; + + return rc; + } + + public static int GetFunctionPointerUnmanagedCallersOnly(StringView typeName, StringView methodName, out T outDelegate) where T: operator explicit void* + { + void* funPtr = null; + + int rc = GetFunctionPointerFn(NetHostHelper.GetScopedRawPtr!(typeName), + NetHostHelper.GetScopedRawPtr!(methodName), CoreClr.UNMANAGEDCALLERSONLY_METHOD, null, null, out funPtr); + + outDelegate = (T)funPtr; + + return rc; + } +} \ No newline at end of file diff --git a/GlitchyEngine/src/Scripting/EntityEditorWrapper.bf b/GlitchyEngine/src/Scripting/EntityEditorWrapper.bf index 6d28ae0..88aaf42 100644 --- a/GlitchyEngine/src/Scripting/EntityEditorWrapper.bf +++ b/GlitchyEngine/src/Scripting/EntityEditorWrapper.bf @@ -1,37 +1,28 @@ using System; using Mono; using GlitchyEngine.Core; +using System.Diagnostics; namespace GlitchyEngine.Scripting; using internal GlitchyEngine.Scripting; -class EntityEditorWrapper : ScriptClass +class EntityEditorWrapper : NewScriptClass { - function void ShowEntityEditorFunc(MonoObject* scriptInstance, MonoException** exception); + function void ShowEntityEditorFunc(UUID entityId); private ShowEntityEditorFunc _showEntityEditorFunc; [AllowAppend] - public this(StringView classNamespace, StringView className, MonoImage* image) : base(classNamespace, className, image) + public this() : base(FullName, .Empty, .None, false) { - _showEntityEditorFunc = (ShowEntityEditorFunc)GetMethodThunk("ShowEntityEditor", 1); - - if (_showEntityEditorFunc == null) - { - Log.EngineLogger.Error("Entity editor has no show entity editor func."); - } + CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "ShowEntityEditor", out _showEntityEditorFunc); + + Debug.Assert(_showEntityEditorFunc != null); } - public void ShowEntityEditor(ScriptInstance instance, UUID entityId) + public void ShowEntityEditor(NewScriptInstance instance, UUID entityId) { - MonoException* exception = null; - - _showEntityEditorFunc(instance.MonoInstance, &exception); - - if (exception != null) - { - ScriptEngine.HandleMonoException(exception, entityId); - } + _showEntityEditorFunc(entityId); } } diff --git a/GlitchyEngine/src/Scripting/NewScriptClass.bf b/GlitchyEngine/src/Scripting/NewScriptClass.bf new file mode 100644 index 0000000..3e31fe1 --- /dev/null +++ b/GlitchyEngine/src/Scripting/NewScriptClass.bf @@ -0,0 +1,98 @@ +using System; +using GlitchyEngine.Core; +using static GlitchyEngine.Scripting.ScriptEngine; +namespace GlitchyEngine.Scripting; + +class NewScriptClass +{ + public String FullName ~ delete _; + public StringView Name; + public Guid Guid; + public ScriptMethods Methods; + + public bool RunInEditMode; + + public this(StringView fullName, Guid guid, ScriptMethods methods, bool runInEditMode = false) + { + FullName = new String(fullName); + Guid = guid; + + int lastDotIndex = FullName.LastIndexOf('.'); + + if (lastDotIndex == -1) + Name = FullName; + else + Name = FullName.Substring(lastDotIndex + 1); + + Methods = methods; + + RunInEditMode = runInEditMode; + } +} + +using internal GlitchyEngine.Scripting; + +class NewScriptInstance : RefCounter +{ + private NewScriptClass _scriptClass; + + private UUID _entityId; + + private bool _isCreated = false; + + public NewScriptClass ScriptClass => _scriptClass; + + /// Gets whether or not the instance has ben initialized. + public bool IsInitialized {get; private set;}; + + /// Gets whether or not the Create-Method of this instance has been called before. + public bool IsCreated => _isCreated; + + public UUID EntityId => _entityId; + + public this(UUID entityId, NewScriptClass scriptClass) + { + Log.EngineLogger.AssertDebug(scriptClass != null); + + _entityId = entityId; + _scriptClass = scriptClass; + } + + public ~this() + { + Destroy(); + } + + public void Instantiate() + { + CoreClrHelper.CreateScriptInstance(_entityId, _scriptClass.FullName); + IsInitialized = true; + } + + public void InvokeOnCreate() + { + if (_scriptClass.Methods.HasFlag(.OnCreate)) + CoreClrHelper._entityScriptFunctions.OnCreate(); + } + + public void InvokeOnUpdate(float deltaTime) + { + if (_scriptClass.Methods.HasFlag(.OnUpdate)) + CoreClrHelper._entityScriptFunctions.OnUpdate(deltaTime); + } + + public void Destroy() + { + if (!IsInitialized) + return; + + if (_scriptClass.Methods.HasFlag(.OnDestroy) && (ScriptEngine.ApplicationInfo.IsInPlayMode || ScriptClass.RunInEditMode)) + { + CoreClrHelper._entityScriptFunctions.OnDestroy(); + } + + IsInitialized = false; + + ScriptEngine.UnregisterScriptInstance(_entityId); + } +} \ No newline at end of file diff --git a/GlitchyEngine/src/Scripting/ScriptClass.bf b/GlitchyEngine/src/Scripting/ScriptClass.bf index 6276f90..f77acc3 100644 --- a/GlitchyEngine/src/Scripting/ScriptClass.bf +++ b/GlitchyEngine/src/Scripting/ScriptClass.bf @@ -81,8 +81,8 @@ class ScriptClass : SharpClass // TODO: This is only relevant for the editor! MonoCustomAttrInfo* attributes = Mono.mono_custom_attrs_from_class(_monoClass); - if (attributes != null && ScriptEngine.Classes.RunInEditModeAttribute != null) - _runInEditMode = Mono.mono_custom_attrs_has_attr(attributes, ScriptEngine.Classes.RunInEditModeAttribute._monoClass); + //if (attributes != null && ScriptEngine.Classes.RunInEditModeAttribute != null) + // _runInEditMode = Mono.mono_custom_attrs_has_attr(attributes, ScriptEngine.Classes.RunInEditModeAttribute._monoClass); } private MonoMethod* FindConstructor() @@ -148,8 +148,8 @@ class ScriptClass : SharpClass if (_onCollisionEnter2D != null) { - MonoObject* monoObject = ScriptEngine.Classes.Collision2D.BoxValue(collision); - _onCollisionEnter2D(instance, monoObject, &exception); + //MonoObject* monoObject = ScriptEngine.Classes.Collision2D.BoxValue(collision); + //_onCollisionEnter2D(instance, monoObject, &exception); } } @@ -159,8 +159,8 @@ class ScriptClass : SharpClass if (_onCollisionEnter2D != null) { - MonoObject* monoObject = ScriptEngine.Classes.Collision2D.BoxValue(collision); - _onCollisionLeave2D(instance, monoObject, &exception); + //MonoObject* monoObject = ScriptEngine.Classes.Collision2D.BoxValue(collision); + //_onCollisionLeave2D(instance, monoObject, &exception); } } diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index f7c7026..128bdfd 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -15,31 +15,31 @@ using internal GlitchyEngine.Scripting; class EngineClasses { // TODO: Not all of these are actually ScriptClasses (actually none of them are...) - - private ScriptClass s_ComponentRoot; - private ScriptClass s_EntityRoot; - private ScriptClass s_EngineObject; + + private NewScriptClass s_EngineObject; + private NewScriptClass s_ComponentRoot; + private NewScriptClass s_EntityRoot; private EntityEditorWrapper s_EntityEditor; private EntitySerializerWrapper s_EntitySerializer; - private ScriptClass s_SerializationContext; + //private NewScriptClass s_SerializationContext; - private ScriptClass s_Collision2D; + private NewScriptClass s_Collision2D; - private ScriptClass s_RunInEditModeAttribute; - - public ScriptClass ComponentRoot => s_ComponentRoot; - public ScriptClass EntityRoot => s_EntityRoot; - public ScriptClass EngineObject => s_EngineObject; + private NewScriptClass s_RunInEditModeAttribute; + + public NewScriptClass EngineObject => s_EngineObject; + public NewScriptClass ComponentRoot => s_ComponentRoot; + public NewScriptClass EntityRoot => s_EntityRoot; public EntityEditorWrapper EntityEditor => s_EntityEditor; public EntitySerializerWrapper EntitySerializer => s_EntitySerializer; - public ScriptClass Collision2D => s_Collision2D; + public NewScriptClass Collision2D => s_Collision2D; - public ScriptClass RunInEditModeAttribute => s_RunInEditModeAttribute; + public NewScriptClass RunInEditModeAttribute => s_RunInEditModeAttribute; public ~this() { @@ -48,37 +48,37 @@ class EngineClasses internal void ReleaseAndNullify() { - ReleaseRefAndNullify!(s_EngineObject); - ReleaseRefAndNullify!(s_EntityRoot); - ReleaseRefAndNullify!(s_ComponentRoot); + DeleteAndNullify!(s_EngineObject); + DeleteAndNullify!(s_EntityRoot); + DeleteAndNullify!(s_ComponentRoot); - ReleaseRefAndNullify!(s_EntityEditor); + DeleteAndNullify!(s_EntityEditor); ReleaseRefAndNullify!(s_EntitySerializer); - ReleaseRefAndNullify!(s_SerializationContext); + //ReleaseRefAndNullify!(s_SerializationContext); - ReleaseRefAndNullify!(s_Collision2D); + DeleteAndNullify!(s_Collision2D); - ReleaseRefAndNullify!(s_RunInEditModeAttribute); + DeleteAndNullify!(s_RunInEditModeAttribute); } - internal void LoadClasses(MonoImage* image) + internal void LoadClasses() { ReleaseAndNullify(); - s_EngineObject = new ScriptClass("GlitchyEngine.Core", "EngineObject", image); - s_EntityRoot = new ScriptClass("GlitchyEngine", "Entity", image); - s_ComponentRoot = new ScriptClass("GlitchyEngine.Core", "Component", image); + s_EngineObject = new NewScriptClass("GlitchyEngine.Core.EngineObject", .Empty, .None); + s_EntityRoot = new NewScriptClass("GlitchyEngine.Core.Entity", .Empty, .None); + s_ComponentRoot = new NewScriptClass("GlitchyEngine.Core.Component", .Empty, .None); // Editor classes - s_EntityEditor = new EntityEditorWrapper("GlitchyEngine.Editor", "EntityEditor", image); + s_EntityEditor = new EntityEditorWrapper(); - s_EntitySerializer = new EntitySerializerWrapper("GlitchyEngine.Serialization", "EntitySerializer", image); + //s_EntitySerializer = new EntitySerializerWrapper("GlitchyEngine.Serialization", "EntitySerializer"); - s_Collision2D = new ScriptClass("GlitchyEngine.Physics", "Collision2D", image); + s_Collision2D = new NewScriptClass("GlitchyEngine.Physics.Collision2D", .Empty, .None); // Attributes - s_RunInEditModeAttribute = new ScriptClass("GlitchyEngine.Editor", "RunInEditModeAttribute", image); + s_RunInEditModeAttribute = new NewScriptClass("GlitchyEngine.Editor.RunInEditModeAttribute", .Empty, .None); } } @@ -99,9 +99,9 @@ static class ScriptEngine public static EngineClasses Classes => _classes; - private static Dictionary _entityScripts = new .() ~ DeleteDictionaryAndReleaseValues!(_); + private static Dictionary _entityScripts = new .() ~ DeleteDictionaryAndValues!(_); - internal static Dictionary _entityScriptInstances = new .() ~ { + internal static Dictionary _entityScriptInstances = new .() ~ { for (var entry in _) { entry.value?.ReleaseRef(); @@ -109,7 +109,7 @@ static class ScriptEngine delete _; } - public static Dictionary EntityClasses => _entityScripts; + public static Dictionary EntityClasses => _entityScripts; /// Gets or sets the current scene context for the runtime. public static Scene Context @@ -189,7 +189,8 @@ static class ScriptEngine public static void Init() { - InitMono(); + InitRuntime(); + //InitMono(); LoadScriptAssemblies(); } @@ -202,7 +203,17 @@ static class ScriptEngine ReloadAssemblies(); } - static void InitMono() + static void InitRuntime() + { + // TODO: We currently have the utility methods (e.g. assembly loading) in ScriptCore, + // the problem is, that CoreCLR currently can't unload the initial assembly. + // this means that we cannot easily reload changes to ScriptCore. + // Since this basic infrastructure shouldn't really ever change we could make a tiny + // library with only that enabling us to reload script core. + CoreClrHelper.Init("resources/scripts/ScriptCore.dll"); + } + + /*static void InitMono() { Mono.mono_set_assemblies_path("mono/lib/4.5"); @@ -226,7 +237,7 @@ static class ScriptEngine } Mono.mono_thread_set_main(Mono.mono_thread_current()); - } + }*/ static bool _requestingReload = false; @@ -278,26 +289,27 @@ static class ScriptEngine ScriptGlue.Init(); - CreateAppDomain("GlitchyEngineScriptRuntime"); - (s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled); - + // TODO: Check if files exist if (File.Exists(_appAssemblyPath)) - (s_AppAssembly, s_AppAssemblyImage) = LoadAssembly(_appAssemblyPath, _debuggingEnabled); - else { - s_AppAssembly = null; - s_AppAssemblyImage = null; + List data = new:ScopedAlloc! List(1024); + File.ReadAll(_appAssemblyPath, data); + + List pdbData = new:ScopedAlloc! List(1024); + + String pdbPath = scope .(); + Path.ChangeExtension(_appAssemblyPath, ".pdb", pdbPath); + + File.ReadAll(pdbPath, pdbData); + + CoreClrHelper.LoadAppAssembly(data, pdbData); + + GetEntitiesFromAssemblies(); + + //ScriptGlue.RegisterManagedComponents(); } - Classes.LoadClasses(s_CoreAssemblyImage); - - ClearDictionaryAndReleaseValues!(_entityScripts); - - GetEntitiesFromAssemblies(); - - ScriptGlue.RegisterManagedComponents(); - - InitAssemblyWatcher(); + //InitAssemblyWatcher(); } /// Starts the script runtime and sets the context scene. @@ -338,14 +350,16 @@ static class ScriptEngine /// Disposes of and replaces the old instance, if one exists. public static bool InitializeInstance(Entity entity, ScriptComponent* script) { - ScriptClass scriptClass = GetScriptClass(script.ScriptClassName); + Log.EngineLogger.Error($"{Compiler.CallerMemberName} not updated yet."); + + NewScriptClass scriptClass = GetScriptClass(script.ScriptClassName); if (scriptClass == null) return false; UUID entityId = entity.UUID; - script.Instance = new ScriptInstance(entityId, scriptClass); + script.Instance = new NewScriptInstance(entityId, scriptClass); script.Instance..ReleaseRef(); if (_entityScriptInstances.TryGetValue(entityId, let currentInstance)) @@ -353,7 +367,7 @@ static class ScriptEngine _entityScriptInstances[entityId] = script.Instance..AddRef(); - script.Instance.Instantiate(entityId); + script.Instance.Instantiate(); return true; } @@ -381,109 +395,50 @@ static class ScriptEngine _entityScriptInstances.Remove(entityId); } - private static MonoAssembly* LoadCSharpAssembly(StringView assemblyPath, bool loadPDB = false) + public enum ScriptMethods : uint32 { - Debug.Profiler.ProfileFunction!(); - - 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; + None = 0, + OnCreate = 0x1, + OnUpdate = 0x2, + OnDestroy = 0x4, } - static void CreateAppDomain(StringView name) + struct ScriptClassInfo { - 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) - { - Debug.Profiler.ProfileFunction!(); - - 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; + public bool RunInEditMode; } private static void GetEntitiesFromAssemblies() { Debug.Profiler.ProfileFunction!(); - if (s_AppAssemblyImage == null) - return; + // TODO: We should probably verify, that all required types actually exist. + Classes.LoadClasses(); - MonoTableInfo* typeDefinitionsTable = Mono.mono_image_get_table_info(s_AppAssemblyImage, .MONO_TABLE_TYPEDEF); - int32 numTypes = Mono.mono_table_info_get_rows(typeDefinitionsTable); + CoreClrHelper.GetScriptClasses(let data, let entryCount); + + Span scriptClasses = .((.)data, entryCount); + + List newClasses = scope .(); - for (int32 i = 0; i < numTypes; i++) - { - int32[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_SIZE] cols = .(); - Mono.mono_metadata_decode_row(typeDefinitionsTable, i, (.)&cols, (.)SOME_RANDOM_ENUM.MONO_TYPEDEF_SIZE); + ClearDictionaryAndDeleteValues!(_entityScripts); - char8* nameSpace = Mono.mono_metadata_string_heap(s_AppAssemblyImage, (.)cols[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_NAMESPACE]); - char8* name = Mono.mono_metadata_string_heap(s_AppAssemblyImage, (.)cols[(.)SOME_RANDOM_ENUM.MONO_TYPEDEF_NAME]); - - MonoClass* monoClass = Mono.mono_class_from_name(s_AppAssemblyImage, nameSpace, name); - - // Check if it is an entity - if (monoClass != null && Mono.mono_class_is_subclass_of(monoClass, Classes.EntityRoot.[Friend]_monoClass, false)) - { - ScriptClass entityScript = new ScriptClass(StringView(nameSpace), StringView(name), s_AppAssemblyImage); - _entityScripts.Add(entityScript.FullName, entityScript); + for (var entry in scriptClasses) + { + NewScriptClass scriptClass = new .(StringView(entry.Name), entry.Guid, entry.Methods, entry.RunInEditMode); - Log.EngineLogger.Info($"Added entity \"{entityScript.FullName}\""); - } - } + newClasses.Add(scriptClass); + } + + for (var scriptClass in newClasses) + { + _entityScripts.Add(scriptClass.FullName, scriptClass); + } + + CoreClrHelper.FreeScriptClassNames(); } public static void ReloadAssemblies() @@ -492,20 +447,20 @@ static class ScriptEngine Log.EngineLogger.Info("Reloading script assemblies."); + ScriptInstanceSerializer contextSerializer = scope .(); contextSerializer.SerializeScriptInstances(); - Mono.mono_domain_set(s_RootDomain, true); - - Mono.mono_domain_unload(s_AppDomain); + // TODO: Unload script assemblies, remove class handles, fire unload events? LoadScriptAssemblies(); { Debug.Profiler.ProfileScope!("Initialize Instances"); - // We need to create a new instance for every entity + Log.EngineLogger.Error("Recreating and copying instances (ReloadAssemblies) not updated yet."); + /*// We need to create a new instance for every entity for (let (id, scriptInstance) in _entityScriptInstances) { if (Context.GetEntityByID(id) case .Ok(let entity)) @@ -517,7 +472,7 @@ static class ScriptEngine { Log.EngineLogger.AssertDebug(false, "Entities script was just serialized but the entity doesn't exist anymore."); } - } + }*/ } contextSerializer.DeserializeScriptInstances(); @@ -527,25 +482,25 @@ static class ScriptEngine public static void Shutdown() { - Mono.mono_domain_set(s_RootDomain, false); + /*Mono.mono_domain_set(s_RootDomain, false); Mono.mono_domain_unload(s_AppDomain); s_AppDomain = null; Mono.mono_jit_cleanup(s_RootDomain); - s_RootDomain = null; + s_RootDomain = null;*/ } /// Returns the script instance or null. public static MonoObject* GetManagedInstance(UUID entityId) { - if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance)) - return scriptInstance.MonoInstance; + //if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance)) + // return scriptInstance.MonoInstance; return null; } - public static ScriptClass GetScriptClass(StringView name) + public static NewScriptClass GetScriptClass(StringView name) { EntityClasses.TryGetValue(name, let scriptClass); diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index e8a4a27..0536811 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -14,7 +14,10 @@ using GlitchyEngine.Editor; using GlitchyEngine.World.Components; using GlitchyEngine.Content; using GlitchyEngine.Renderer; +using System.Diagnostics; +using System.IO; using static GlitchyEngine.Renderer.Text.FontRenderer; +using System.Linq; namespace GlitchyEngine.Scripting; @@ -39,22 +42,250 @@ class MessageOrigin } } +struct RegisterCallAttribute : Attribute +{ + public String MethodName; + + public this(String methodName) + { + MethodName = methodName; + } +} + +struct TypeTranslationTemplate +{ + public String StartCode; + public String EndCode; + public String PrettyCSharpParamType; + + public this(String startCode, String endCode, String prettyCSharpParamType) + { + StartCode = startCode; + EndCode = endCode; + PrettyCSharpParamType = prettyCSharpParamType; + } +} + +[AttributeUsage(.Struct)] +struct EngineFunctionsGeneratorAttribute : Attribute, IComptimeTypeApply +{ + private static readonly Dictionary BeefToCsharpTypeMap = new .() + { + ("int32", "int"), + ("char16*", "char*"), + ("char8*", "byte*"), + ("GlitchyEngine.Events.MouseButton", "GlitchyEngine.MouseButton") + }; + + // {0}... Out name, + // {1}... In name + private static readonly Dictionary BeefTypeToCSharpWrapperTemplate = new .() + { + ("char8*", .("byte* {0} = (byte*)Marshal.StringToCoTaskMemUTF8({1});", "Marshal.FreeCoTaskMem((IntPtr){0});", "string")), + ("char16*", .("fixed (char* {0} = {1})\n{{", "}}", "string")), + }; + + private static readonly Dictionary CSharpTypeToReturnValueTemplate = new .() + { + ("void", .("", "", "void")), + }; + + private static String GetCSharpInterfaceType(String beefType) + { + if (BeefToCsharpTypeMap.TryGetValueAlt(beefType, let csharpType)) + { + return csharpType; + } + + return beefType; + } + + private static TypeTranslationTemplate GetCSharpWrapperTemplate(String beefType) + { + if (BeefTypeToCSharpWrapperTemplate.TryGetValueAlt(beefType, let template)) + { + return template; + } + + return .("", "", GetCSharpInterfaceType(beefType)); + } + + private static TypeTranslationTemplate GetCSharpReturnValueTemplate(String beefType) + { + if (CSharpTypeToReturnValueTemplate.TryGetValueAlt(beefType, let template)) + { + return template; + } + + return .("var returnValue = ", "return returnValue;", GetCSharpInterfaceType(beefType)); + } + + private static void GenerateCSharpFunctionPointer(MethodInfo method, String outString) + { + String parameters = new String(); + + for (int i < method.ParamCount) + { + if (i != 0) + parameters.Append(", "); + + String typeHolder = scope String(); + + method.GetParamType(i).ToString(typeHolder); + + StringView csharpType = GetCSharpInterfaceType(typeHolder); + + parameters.AppendF($"{csharpType}"); + } + + String csharpFunctionPointer = scope $" public delegate* unmanaged[Cdecl]<{parameters}{(parameters.IsEmpty ? "" : ", ")}{method.ReturnType}> {method.Name};\n"; + outString.Append(csharpFunctionPointer); + } + + private static void GenerateCSharpWrapperMethod(MethodInfo method, String outString) + { + String wrapperParameters = new .(); + + String callArguments = new .(); + + String translation = new .(); + String cleanup = new .(); + + for (int i < method.ParamCount) + { + String beefParameterType = scope String(); + method.GetParamType(i).ToString(beefParameterType); + + TypeTranslationTemplate template = GetCSharpWrapperTemplate(beefParameterType); + + StringView paramName = method.GetParamName(i); + + // Generate the parameter for the wrapper head. + { + if (i != 0) + wrapperParameters.Append(", "); + + wrapperParameters.AppendF($"{template.PrettyCSharpParamType} {paramName}"); + } + + { + if (i != 0) + callArguments.Append(", "); + + String translatedParamName = new String(paramName); + + if (!template.StartCode.IsEmpty) + { + translatedParamName.Append("Converted"); + + callArguments.AppendF($"{translatedParamName}"); + + translation.AppendF(template.StartCode, translatedParamName, paramName); + translation.Append('\n'); + } + else + { + callArguments.AppendF($"{paramName}"); + } + + if (!template.EndCode.IsEmpty) + { + cleanup.AppendF(template.EndCode, translatedParamName, paramName); + cleanup.Append('\n'); + } + } + } + + String returnTypeName = new .(); + method.ReturnType.ToString(returnTypeName); + String csharpReturnType = GetCSharpInterfaceType(returnTypeName); + TypeTranslationTemplate template = GetCSharpReturnValueTemplate(csharpReturnType); + + outString.AppendF($""" + internal static unsafe {template.PrettyCSharpParamType} NEW_{method.Name}({wrapperParameters}) + {{ + {translation} + {template.StartCode}_engineFunctions.{method.Name}({callArguments}); + + {cleanup} + {template.EndCode} + }} + + + """); + } + + [Comptime] + public void ApplyToType(Type self) + { + String csharpEngineFunctionsStruct = new String(""" + using System; + using System.Runtime.InteropServices; + using GlitchyEngine; + using GlitchyEngine.Core; + + namespace GlitchyEngine; + + internal unsafe partial struct EngineFunctions + { + + """); + + String csharpScriptGlue = new String(""" + + + internal static partial class ScriptGlue + { + + """); + + for (MethodInfo methodInfo in typeof(ScriptGlue).GetMethods(.Static | .NonPublic)) + { + if (methodInfo.GetCustomAttribute() case .Ok(let attribute)) + { + String parameters = new String(); + + for (int i < methodInfo.ParamCount) + { + if (i != 0) + parameters.Append(", "); + + parameters.AppendF($"{methodInfo.GetParamType(i)}"); + } + + String line = scope $"public function {methodInfo.ReturnType}({parameters}) {methodInfo.Name};\n"; + + Compiler.EmitTypeBody(self, line); + + GenerateCSharpFunctionPointer(methodInfo, csharpEngineFunctionsStruct); + + GenerateCSharpWrapperMethod(methodInfo, csharpScriptGlue); + } + } + + csharpEngineFunctionsStruct.Append('}'); + csharpScriptGlue.Append('}'); + + csharpEngineFunctionsStruct.Append(csharpScriptGlue); + + if (File.WriteAllText("../ScriptCore/ScriptGlue.gen.cs", csharpEngineFunctionsStruct) case .Err) + { + Runtime.FatalError("Failed to write file"); + } + } +} + +[EngineFunctionsGenerator] +struct EngineFunctions +{ +} + static class ScriptGlue { private static Dictionary s_AddComponentMethods = new .() ~ delete _; private static Dictionary s_HasComponentMethods = new .() ~ delete _; private static Dictionary s_RemoveComponentMethods = new .() ~ delete _; - struct RegisterCallAttribute : Attribute - { - public String MethodName; - - public this(String methodName) - { - MethodName = methodName; - } - } - /* Adding this attribute to a method will log method entry and returned Result errors */ [AttributeUsage(.Method)] struct RegisterMethodAttribute : Attribute, IOnMethodInit @@ -62,27 +293,47 @@ static class ScriptGlue [Comptime] public void OnMethodInit(MethodInfo method, Self* prev) { + String functionContent = new .(); + + int i = 0; + for (var methodInfo in typeof(ScriptGlue).GetMethods(.Static)) { if (methodInfo.GetCustomAttribute() case .Ok(let attribute)) { - String functionType = scope $"{methodInfo.ReturnType}({methodInfo.GetParamsDecl(.. scope .())})"; - - String line = scope $"RegisterCall(\"{attribute.MethodName}\", => {methodInfo.Name});\n"; - - Compiler.EmitMethodEntry(method, line); + functionContent.AppendF($"functions.{methodInfo.Name} = => {methodInfo.Name};\n"); + //functionContent.AppendF($"functions[{i}] = (void*)( => {methodInfo.Name});\n"); + i++; } } + + functionContent.Insert(0, scope $"EngineFunctions functions = .();\n"); + + functionContent.Append("_setEngineFunctions(&functions);"); + + Compiler.EmitMethodEntry(method, functionContent); } } + public static Event OnRegisterNativeCalls ~ _.Dispose(); + /*private function void SetEngineFunctions(EngineFunctions* engineFunctions); + private static SetEngineFunctions _setEngineFunctions;*/ + + private function void SetEngineFunctions(EngineFunctions* engineFunctions); + private static SetEngineFunctions _setEngineFunctions; + public static void Init() { + if (_setEngineFunctions == null) + { + CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "SetEngineFunctions", out _setEngineFunctions); + } + RegisterCalls(); - RegisterMathFunctions(); + //RegisterMathFunctions(); } public static void RegisterManagedComponents() @@ -103,11 +354,18 @@ static class ScriptGlue RegisterComponent("GlitchyEngine.Graphics.MeshRenderer"); } - [RegisterMethod] private static void RegisterCalls() { + FillEngineFunctions(); + OnRegisterNativeCalls.Invoke(); } + + [RegisterMethod] + private static void FillEngineFunctions() + { + + } private static void RegisterComponent(StringView cSharpClassName = "") where T : struct, new { @@ -248,7 +506,7 @@ static class ScriptGlue #region Log - [RegisterCall("ScriptGlue::Log_LogMessage")] + /*[RegisterCall("ScriptGlue::Log_LogMessage")] static void Log_LogMessage(int32 logLevel, MonoString* message, MonoString* fileName, int lineNumber) { char8* utfMessage = Mono.mono_string_to_utf8(message); @@ -275,6 +533,30 @@ static class ScriptGlue } Mono.mono_free(utfMessage); + }*/ + + [RegisterCall("ScriptGlue::Log_LogMessage")] + [CallingConvention(.Cdecl)] + static void Log_LogMessage(int32 logLevel, char16* messagePtr, char16* fileNamePtr, int lineNumber) + { + String escapedMessage = new:ScopedAlloc! String(messagePtr); + + // Why exactly do we have to replace these? Can we get away without copying the string above? + escapedMessage.Replace("{", "{{"); + escapedMessage.Replace("}", "}}"); + + if (fileNamePtr != null) + { + String fileName = new:ScopedAlloc! String(fileNamePtr); + + MessageOrigin messageOrigin = scope MessageOrigin(fileName, lineNumber); + + Log.ClientLogger.Log((LogLevel)logLevel, escapedMessage, messageOrigin); + } + else + { + Log.ClientLogger.Log((LogLevel)logLevel, escapedMessage); + } } [RegisterCall("ScriptGlue::Log_LogException")] @@ -457,8 +739,8 @@ static class ScriptGlue scriptComponent = entity.GetComponent(); } - if (scriptComponent.Instance != null) - ScriptEngine.Context.DestroyScriptDeferred(scriptComponent.Instance, false); + //if (scriptComponent.Instance != null) + // ScriptEngine.Context.DestroyScriptDeferred(scriptComponent.Instance, false); scriptComponent.Instance = null; @@ -470,7 +752,8 @@ static class ScriptGlue // TODO: this returns false, if no script with ScriptClassName exists, we have to handle this case correctly I think. ScriptEngine.InitializeInstance(entity, scriptComponent); - return scriptComponent.Instance.MonoInstance; + //return scriptComponent.Instance.MonoInstance; + return null; } [RegisterCall("ScriptGlue::Entity_RemoveScript")] @@ -478,7 +761,7 @@ static class ScriptGlue { ScriptComponent* scriptComponent = GetComponentSafe(entityId); - ScriptEngine.Context.DestroyScriptDeferred(scriptComponent.Instance, true); + //ScriptEngine.Context.DestroyScriptDeferred(scriptComponent.Instance, true); } [RegisterCall("ScriptGlue::Entity_GetName")] @@ -653,7 +936,7 @@ static class ScriptGlue } [Packed] - struct AxisAngle + public struct AxisAngle { public float3 Axis; public float Angle; @@ -1014,8 +1297,8 @@ static class ScriptGlue innerRadius = circleRenderer.InnerRadius; } - [RegisterCall("ScriptGlue::CircleRenderer_GetInnerRadius")] - static void CircleRenderer_GetInnerRadius(UUID entityId, float innerRadius) + [RegisterCall("ScriptGlue::CircleRenderer_SetInnerRadius")] + static void CircleRenderer_SetInnerRadius(UUID entityId, float innerRadius) { CircleRendererComponent* circleRenderer = GetComponentSafe(entityId); circleRenderer.InnerRadius = innerRadius; @@ -1085,8 +1368,8 @@ static class ScriptGlue #region TextRenderer - [RegisterCall("ScriptGlue::TextRenderer_SetIsRichText")] - static bool TextRenderer_SetIsRichText(UUID entityId) + [RegisterCall("ScriptGlue::TextRenderer_GetIsRichText")] + static bool TextRenderer_GetIsRichText(UUID entityId) { return GetComponentSafe(entityId).IsRichText; } @@ -1419,7 +1702,7 @@ static class ScriptGlue } [RegisterCall("ScriptGlue::Asset_SetIdentifier")] - static void Asset_GetIdentifier(UUID assetId, MonoString* text) + static void Asset_SetIdentifier(UUID assetId, MonoString* text) { ThrowNotImplementedException("Asset.GetIdentifier is not implemented."); diff --git a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf index 55cc1d2..0272cb4 100644 --- a/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf +++ b/GlitchyEngine/src/Scripting/ScriptInstanceSerializer.bf @@ -23,12 +23,12 @@ public class ScriptInstanceSerializer public void Clear() { ClearDictionaryAndDeleteValues!(_serializedData); - ScriptEngine.Classes.EntitySerializer.DestroySerializationContext(this); + //ScriptEngine.Classes.EntitySerializer.DestroySerializationContext(this); } public void Init() { - ScriptEngine.Classes.EntitySerializer.CreateSerializationContext(this); + //ScriptEngine.Classes.EntitySerializer.CreateSerializationContext(this); } /// Serializes all script instances that are currently managed by the ScriptEngine. @@ -36,7 +36,11 @@ public class ScriptInstanceSerializer { Debug.Profiler.ProfileFunction!(); - Init(); + Log.EngineLogger.Error("SerializeScriptInstances not implemented."); + + return; + + /*Init(); for (let (id, scriptInstance) in ScriptEngine._entityScriptInstances) { @@ -46,7 +50,7 @@ public class ScriptInstanceSerializer for (let (name, scriptClass) in ScriptEngine.EntityClasses) { SerializeStaticScriptClassFields(scriptClass); - } + }*/ } /// Serializes the given script instance. @@ -67,8 +71,12 @@ public class ScriptInstanceSerializer public void DeserializeScriptInstances() { Debug.Profiler.ProfileFunction!(); + + Log.EngineLogger.Error("SerializeScriptInstances not implemented."); - Init(); + return; + + /*Init(); for (let (id, script) in ScriptEngine._entityScriptInstances) { @@ -78,7 +86,7 @@ public class ScriptInstanceSerializer for (let (name, scriptClass) in ScriptEngine.EntityClasses) { DeserializeStaticScriptClassFields(scriptClass); - } + }*/ } /// Deserializes the data into the given script instance, if there is data available. diff --git a/GlitchyEngine/src/World/Components/Components.bf b/GlitchyEngine/src/World/Components/Components.bf index de47225..d5d4755 100644 --- a/GlitchyEngine/src/World/Components/Components.bf +++ b/GlitchyEngine/src/World/Components/Components.bf @@ -674,7 +674,7 @@ namespace GlitchyEngine.World { private String _scriptClassName = null; - private ScriptInstance _instance = null; + private NewScriptInstance _instance = null; public StringView ScriptClassName { @@ -688,7 +688,7 @@ namespace GlitchyEngine.World } } - public ScriptInstance Instance + public NewScriptInstance Instance { [Inline] get => _instance; diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index c9e6550..f6ebcec 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -394,9 +394,10 @@ namespace GlitchyEngine.World collision.OtherEntity = colliderEntityB.UUID; collision.Rigidbody = rigidbodyEntityA.UUID; collision.OtherRigidbody = rigidbodyEntityB.UUID; - - scriptOfColliderA?.Instance?.InvokeOnCollisionEnter2D(collision); - scriptOfRigidbodyA?.Instance?.InvokeOnCollisionEnter2D(collision); + + // TODO: + //scriptOfColliderA?.Instance?.InvokeOnCollisionEnter2D(collision); + //scriptOfRigidbodyA?.Instance?.InvokeOnCollisionEnter2D(collision); } bool fireEventB = colliderEntityB.TryGetComponent(let scriptOfColliderB); @@ -410,8 +411,8 @@ namespace GlitchyEngine.World collision.Rigidbody = rigidbodyEntityB.UUID; collision.OtherRigidbody = rigidbodyEntityA.UUID; - scriptOfColliderB?.Instance?.InvokeOnCollisionEnter2D(collision); - scriptOfRigidbodyB?.Instance?.InvokeOnCollisionEnter2D(collision); + //scriptOfColliderB?.Instance?.InvokeOnCollisionEnter2D(collision); + //scriptOfRigidbodyB?.Instance?.InvokeOnCollisionEnter2D(collision); } }; _contactListener.endContactCallback = (contact, userData) => { @@ -446,8 +447,8 @@ namespace GlitchyEngine.World collision.Rigidbody = rigidbodyEntityA.UUID; collision.OtherRigidbody = rigidbodyEntityB.UUID; - scriptOfColliderA?.Instance?.InvokeOnCollisionLeave2D(collision); - scriptOfRigidbodyA?.Instance?.InvokeOnCollisionLeave2D(collision); + //scriptOfColliderA?.Instance?.InvokeOnCollisionLeave2D(collision); + //scriptOfRigidbodyA?.Instance?.InvokeOnCollisionLeave2D(collision); } bool fireEventB = colliderEntityB.TryGetComponent(let scriptOfColliderB); @@ -461,8 +462,8 @@ namespace GlitchyEngine.World collision.Rigidbody = rigidbodyEntityB.UUID; collision.OtherRigidbody = rigidbodyEntityA.UUID; - scriptOfColliderB?.Instance?.InvokeOnCollisionLeave2D(collision); - scriptOfRigidbodyB?.Instance?.InvokeOnCollisionLeave2D(collision); + //scriptOfColliderB?.Instance?.InvokeOnCollisionLeave2D(collision); + //scriptOfRigidbodyB?.Instance?.InvokeOnCollisionLeave2D(collision); } }; @@ -992,13 +993,13 @@ namespace GlitchyEngine.World if (sourceScript.Instance != null) { - scriptSerializer.SerializeScriptInstance(sourceScript.Instance); + //scriptSerializer.SerializeScriptInstance(sourceScript.Instance); // Initializes the created instance // TODO: this returns false, if no script with ScriptClassName exists, we have to handle this case correctly I think. ScriptEngine.InitializeInstance(copy, targetScript); - newScripts.Add((original.UUID, targetScript.Instance)); + //newScripts.Add((original.UUID, targetScript.Instance)); } } diff --git a/GlitchyEngine/vendor/NetHostBeef b/GlitchyEngine/vendor/NetHostBeef new file mode 160000 index 0000000..aeb0995 --- /dev/null +++ b/GlitchyEngine/vendor/NetHostBeef @@ -0,0 +1 @@ +Subproject commit aeb099576d0b75dbcfbde0d864986fc8bcca13bf diff --git a/ScriptCore/Entity.cs b/ScriptCore/Entity.cs index 948aad1..0b09d00 100644 --- a/ScriptCore/Entity.cs +++ b/ScriptCore/Entity.cs @@ -414,12 +414,18 @@ public class Entity : EngineObject return new Entity(newEntityId); } - // Will be executed once after the entity has be created. - // void OnCreate(); + /// + /// Will be executed once after the entity has be created. + /// + protected internal virtual void OnCreate() { } - // Will be executed every frame. - // void OnUpdate(GameTime); + /// + /// Is called once every frame. + /// + protected internal virtual void OnUpdate(float deltaTime) { } - // Will be executed once when the entity is being destroyed. - // void OnDestroy(); + /// + /// Will be executed once when the entity is being destroyed. + /// + protected internal virtual void OnDestroy() { } } diff --git a/ScriptCore/Extensions/TypeExtension.cs b/ScriptCore/Extensions/TypeExtension.cs index f689acd..d9bab2b 100644 --- a/ScriptCore/Extensions/TypeExtension.cs +++ b/ScriptCore/Extensions/TypeExtension.cs @@ -148,7 +148,7 @@ public static class TypeExtension string name = fullName.TrimEnd(']').ToString(); // Non generic type, easy! - foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse()) + foreach (Assembly assembly in ((IEnumerable)AppDomain.CurrentDomain.GetAssemblies()).Reverse()) { Type? type = assembly.GetType(name); diff --git a/ScriptCore/Math/Half.cs b/ScriptCore/Math/Half.cs index 3fb5586..da978f0 100644 --- a/ScriptCore/Math/Half.cs +++ b/ScriptCore/Math/Half.cs @@ -1,284 +1,284 @@ -using System; -using System.Runtime.CompilerServices; - -namespace GlitchyEngine.Math; - -/// -/// Represents a 16-bit (half-precision) floating point number. (aka. IEEE 754 half-precision binary floating-point (binary16)) -/// -/// -/// Even though it is possible, it's not recommended to perform large calculations using this type directly because most operators will simply cast the operands to and cast the result back to . -/// If you need to do larger calculations consider casting to once and cast the result back to afterwards. This will not only have better performance, but will also increase the accuracy of the result. -/// -public struct Half : IComparable , IComparable, IConvertible, IEquatable, IFormattable -{ - public static readonly Half MinValue = new(-65504); // Should be 0xFBFF - public static readonly Half MaxValue = new(65504); // Should be 0x7BFF - - // The numbers for Infinity, NaN and Zero need to be hardcoded as binaries, - // because the conversion itself relies on them. - - public static readonly Half PositiveInfinity = new(0x7C00); - public static readonly Half NegativeInfinity = new(0xFC00); - public static readonly Half NaN = new(0x7CFF); - - public static readonly Half Zero = new(0x0000); - public static readonly Half NegativeZero = new(0x8000); - - private ushort _data; - - public bool IsNegative => IsNegative_Impl(this); - - public bool IsFinite => IsFinite_Impl(this); - public bool IsInfinity => IsInfinity_Impl(this); - public bool IsPositiveInfinity => _data == PositiveInfinity._data; - public bool IsNegativeInfinity => _data == NegativeInfinity._data; - public bool IsNaN => IsNan_Impl(this); - - public bool IsSubnormal => IsSubnormal_Impl(this); - - public Half(float value) - { - FromFloat32(value, out Half halfValue); - this = halfValue; - } - - private Half(ushort data) - { - _data = data; - } - - public static explicit operator float(Half value) - { - ToFloat32(value, out float floatValue); - return floatValue; - } - - public static explicit operator Half(float value) - { - FromFloat32(value, out Half halfValue); - return halfValue; - } - - /// Converts the numeric value of this instance to its equivalent string representation. - /// The string representation of the value of this instance. - public override string ToString() => ((float)this).ToString(); - - public int CompareTo(object obj) - { - if (obj == null) - return 1; - - if (obj is not Half value) - throw new ArgumentException($"Object must be of type {typeof(Half)}"); - - return CompareTo(value); - } - - public int CompareTo(Half other) - { - return ((float)this).CompareTo((float)other); - } - - public TypeCode GetTypeCode() => TypeCode.Object; - - public bool ToBoolean(IFormatProvider provider) => Convert.ToBoolean((float)this, provider); - - public byte ToByte(IFormatProvider provider) => Convert.ToByte((float)this, provider); - - public char ToChar(IFormatProvider provider) => Convert.ToChar((float)this, provider); - - public DateTime ToDateTime(IFormatProvider provider) => Convert.ToDateTime((float)this, provider); - - public decimal ToDecimal(IFormatProvider provider) => Convert.ToDecimal((float)this, provider); - - public double ToDouble(IFormatProvider provider) => Convert.ToDouble((float)this, provider); - - public short ToInt16(IFormatProvider provider) => Convert.ToInt16((float)this, provider); - - public int ToInt32(IFormatProvider provider) => Convert.ToInt32((float)this, provider); - - public long ToInt64(IFormatProvider provider) => Convert.ToInt64((float)this, provider); - - public sbyte ToSByte(IFormatProvider provider) => Convert.ToSByte((float)this, provider); - - public float ToSingle(IFormatProvider provider) => Convert.ToSingle((float)this, provider); - - /// Converts the numeric value of this instance to its equivalent string representation using the specified culture-specific format information. - /// An object that supplies culture-specific formatting information. - /// The string representation of the value of this instance as specified by provider. - public string ToString(IFormatProvider provider) => ((float)this).ToString(provider); - - /// Converts the numeric value of this instance to its equivalent string representation, using the specified format. - /// A numeric format string. - /// The string representation of the value of this instance as specified by format. - /// format is invalid. - public string ToString(string format) => ((float)this).ToString(format); - - /// Converts the numeric value of this instance to its equivalent string representation using the specified format and culture-specific format information. - /// A numeric format string. - /// An object that supplies culture-specific formatting information. - /// The string representation of the value of this instance as specified by format and provider. - public string ToString(string format, IFormatProvider provider) => ((float)this).ToString(format, provider); - - public object ToType(Type conversionType, IFormatProvider provider) => ((IConvertible)(float)this).ToType(conversionType, provider); - - public ushort ToUInt16(IFormatProvider provider) => Convert.ToUInt16((float)this, provider); - - public uint ToUInt32(IFormatProvider provider) => Convert.ToUInt32((float)this, provider); - - public ulong ToUInt64(IFormatProvider provider) => Convert.ToUInt64((float)this, provider); - - public bool Equals(Half other) - { - return _data == other._data; - } - - public override bool Equals(object obj) - { - return obj is Half other && Equals(other); - } - - public override int GetHashCode() - { - return _data.GetHashCode(); - } - - #region Operators - - public static bool operator ==(Half left, Half right) => left._data == right._data; - - public static bool operator !=(Half left, Half right) => left._data != right._data; - - public static bool operator <(Half left, Half right) - { - LessThan_Impl(left, right, out bool result); - return result; - } - - public static bool operator <=(Half left, Half right) - { - LessThanOrEqual_Impl(left, right, out bool result); - return result; - } - public static bool operator >(Half left, Half right) - { - GreaterThan_Impl(left, right, out bool result); - return result; - } - - public static bool operator >=(Half left, Half right) - { - GreaterThanOrEqual_Impl(left, right, out bool result); - return result; - } - - public static Half operator +(Half value) => value; - - public static Half operator -(Half value) - { - Negate_Impl(value, out Half result); - return result; - } - - public static Half operator +(Half left, Half right) - { - Add_Impl(left, right, out Half result); - return result; - } - - public static Half operator -(Half left, Half right) - { - Subtract_Impl(left, right, out Half result); - return result; - } - - public static Half operator *(Half left, Half right) - { - Multiply_Impl(left, right, out Half result); - return result; - } - - public static Half operator /(Half left, Half right) - { - Divide_Impl(left, right, out Half result); - return result; - } - - public static Half operator %(Half left, Half right) - { - Modulo_Impl(left, right, out Half result); - return result; - } - - public static Half operator ++(Half value) - { - Increment_Impl(value, out Half result); - return result; - } - - public static Half operator --(Half value) - { - Decrement_Impl(value, out Half result); - return result; - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void LessThan_Impl(Half left, Half right, out bool result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void LessThanOrEqual_Impl(Half left, Half right, out bool result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GreaterThan_Impl(Half left, Half right, out bool result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GreaterThanOrEqual_Impl(Half left, Half right, out bool result); - - // Externe Methoden hier als Platzhalter - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void Add_Impl(Half left, Half right, out Half result); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void Subtract_Impl(Half left, Half right, out Half result); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void Multiply_Impl(Half left, Half right, out Half result); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void Divide_Impl(Half left, Half right, out Half result); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void Modulo_Impl(Half left, Half right, out Half result); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void Negate_Impl(Half value, out Half result); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void Increment_Impl(Half value, out Half result); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void Decrement_Impl(Half value, out Half result); - - #endregion - - #region Bindings - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void FromFloat32(float value, out Half halfValue); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void ToFloat32(Half value, out float floatValue); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool IsNegative_Impl(Half self); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool IsFinite_Impl(Half self); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool IsInfinity_Impl(Half self); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool IsNan_Impl(Half self); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool IsSubnormal_Impl(Half self); - - #endregion -} +// using System; +// using System.Runtime.CompilerServices; +// +// namespace GlitchyEngine.Math; +// +// /// +// /// Represents a 16-bit (half-precision) floating point number. (aka. IEEE 754 half-precision binary floating-point (binary16)) +// /// +// /// +// /// Even though it is possible, it's not recommended to perform large calculations using this type directly because most operators will simply cast the operands to and cast the result back to . +// /// If you need to do larger calculations consider casting to once and cast the result back to afterwards. This will not only have better performance, but will also increase the accuracy of the result. +// /// +// public struct Half : IComparable , IComparable, IConvertible, IEquatable, IFormattable +// { +// public static readonly Half MinValue = new(-65504); // Should be 0xFBFF +// public static readonly Half MaxValue = new(65504); // Should be 0x7BFF +// +// // The numbers for Infinity, NaN and Zero need to be hardcoded as binaries, +// // because the conversion itself relies on them. +// +// public static readonly Half PositiveInfinity = new(0x7C00); +// public static readonly Half NegativeInfinity = new(0xFC00); +// public static readonly Half NaN = new(0x7CFF); +// +// public static readonly Half Zero = new(0x0000); +// public static readonly Half NegativeZero = new(0x8000); +// +// private ushort _data; +// +// public bool IsNegative => IsNegative_Impl(this); +// +// public bool IsFinite => IsFinite_Impl(this); +// public bool IsInfinity => IsInfinity_Impl(this); +// public bool IsPositiveInfinity => _data == PositiveInfinity._data; +// public bool IsNegativeInfinity => _data == NegativeInfinity._data; +// public bool IsNaN => IsNan_Impl(this); +// +// public bool IsSubnormal => IsSubnormal_Impl(this); +// +// public Half(float value) +// { +// FromFloat32(value, out Half halfValue); +// this = halfValue; +// } +// +// private Half(ushort data) +// { +// _data = data; +// } +// +// public static explicit operator float(Half value) +// { +// ToFloat32(value, out float floatValue); +// return floatValue; +// } +// +// public static explicit operator Half(float value) +// { +// FromFloat32(value, out Half halfValue); +// return halfValue; +// } +// +// /// Converts the numeric value of this instance to its equivalent string representation. +// /// The string representation of the value of this instance. +// public override string ToString() => ((float)this).ToString(); +// +// public int CompareTo(object obj) +// { +// if (obj == null) +// return 1; +// +// if (obj is not Half value) +// throw new ArgumentException($"Object must be of type {typeof(Half)}"); +// +// return CompareTo(value); +// } +// +// public int CompareTo(Half other) +// { +// return ((float)this).CompareTo((float)other); +// } +// +// public TypeCode GetTypeCode() => TypeCode.Object; +// +// public bool ToBoolean(IFormatProvider provider) => Convert.ToBoolean((float)this, provider); +// +// public byte ToByte(IFormatProvider provider) => Convert.ToByte((float)this, provider); +// +// public char ToChar(IFormatProvider provider) => Convert.ToChar((float)this, provider); +// +// public DateTime ToDateTime(IFormatProvider provider) => Convert.ToDateTime((float)this, provider); +// +// public decimal ToDecimal(IFormatProvider provider) => Convert.ToDecimal((float)this, provider); +// +// public double ToDouble(IFormatProvider provider) => Convert.ToDouble((float)this, provider); +// +// public short ToInt16(IFormatProvider provider) => Convert.ToInt16((float)this, provider); +// +// public int ToInt32(IFormatProvider provider) => Convert.ToInt32((float)this, provider); +// +// public long ToInt64(IFormatProvider provider) => Convert.ToInt64((float)this, provider); +// +// public sbyte ToSByte(IFormatProvider provider) => Convert.ToSByte((float)this, provider); +// +// public float ToSingle(IFormatProvider provider) => Convert.ToSingle((float)this, provider); +// +// /// Converts the numeric value of this instance to its equivalent string representation using the specified culture-specific format information. +// /// An object that supplies culture-specific formatting information. +// /// The string representation of the value of this instance as specified by provider. +// public string ToString(IFormatProvider provider) => ((float)this).ToString(provider); +// +// /// Converts the numeric value of this instance to its equivalent string representation, using the specified format. +// /// A numeric format string. +// /// The string representation of the value of this instance as specified by format. +// /// format is invalid. +// public string ToString(string format) => ((float)this).ToString(format); +// +// /// Converts the numeric value of this instance to its equivalent string representation using the specified format and culture-specific format information. +// /// A numeric format string. +// /// An object that supplies culture-specific formatting information. +// /// The string representation of the value of this instance as specified by format and provider. +// public string ToString(string format, IFormatProvider provider) => ((float)this).ToString(format, provider); +// +// public object ToType(Type conversionType, IFormatProvider provider) => ((IConvertible)(float)this).ToType(conversionType, provider); +// +// public ushort ToUInt16(IFormatProvider provider) => Convert.ToUInt16((float)this, provider); +// +// public uint ToUInt32(IFormatProvider provider) => Convert.ToUInt32((float)this, provider); +// +// public ulong ToUInt64(IFormatProvider provider) => Convert.ToUInt64((float)this, provider); +// +// public bool Equals(Half other) +// { +// return _data == other._data; +// } +// +// public override bool Equals(object obj) +// { +// return obj is Half other && Equals(other); +// } +// +// public override int GetHashCode() +// { +// return _data.GetHashCode(); +// } +// +// #region Operators +// +// public static bool operator ==(Half left, Half right) => left._data == right._data; +// +// public static bool operator !=(Half left, Half right) => left._data != right._data; +// +// public static bool operator <(Half left, Half right) +// { +// LessThan_Impl(left, right, out bool result); +// return result; +// } +// +// public static bool operator <=(Half left, Half right) +// { +// LessThanOrEqual_Impl(left, right, out bool result); +// return result; +// } +// public static bool operator >(Half left, Half right) +// { +// GreaterThan_Impl(left, right, out bool result); +// return result; +// } +// +// public static bool operator >=(Half left, Half right) +// { +// GreaterThanOrEqual_Impl(left, right, out bool result); +// return result; +// } +// +// public static Half operator +(Half value) => value; +// +// public static Half operator -(Half value) +// { +// Negate_Impl(value, out Half result); +// return result; +// } +// +// public static Half operator +(Half left, Half right) +// { +// Add_Impl(left, right, out Half result); +// return result; +// } +// +// public static Half operator -(Half left, Half right) +// { +// Subtract_Impl(left, right, out Half result); +// return result; +// } +// +// public static Half operator *(Half left, Half right) +// { +// Multiply_Impl(left, right, out Half result); +// return result; +// } +// +// public static Half operator /(Half left, Half right) +// { +// Divide_Impl(left, right, out Half result); +// return result; +// } +// +// public static Half operator %(Half left, Half right) +// { +// Modulo_Impl(left, right, out Half result); +// return result; +// } +// +// public static Half operator ++(Half value) +// { +// Increment_Impl(value, out Half result); +// return result; +// } +// +// public static Half operator --(Half value) +// { +// Decrement_Impl(value, out Half result); +// return result; +// } +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void LessThan_Impl(Half left, Half right, out bool result); +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void LessThanOrEqual_Impl(Half left, Half right, out bool result); +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void GreaterThan_Impl(Half left, Half right, out bool result); +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void GreaterThanOrEqual_Impl(Half left, Half right, out bool result); +// +// // Externe Methoden hier als Platzhalter +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void Add_Impl(Half left, Half right, out Half result); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void Subtract_Impl(Half left, Half right, out Half result); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void Multiply_Impl(Half left, Half right, out Half result); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void Divide_Impl(Half left, Half right, out Half result); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void Modulo_Impl(Half left, Half right, out Half result); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void Negate_Impl(Half value, out Half result); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void Increment_Impl(Half value, out Half result); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void Decrement_Impl(Half value, out Half result); +// +// #endregion +// +// #region Bindings +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void FromFloat32(float value, out Half halfValue); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern void ToFloat32(Half value, out float floatValue); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern bool IsNegative_Impl(Half self); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern bool IsFinite_Impl(Half self); +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern bool IsInfinity_Impl(Half self); +// +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern bool IsNan_Impl(Half self); +// [MethodImpl(MethodImplOptions.InternalCall)] +// private static extern bool IsSubnormal_Impl(Half self); +// +// #endregion +// } diff --git a/ScriptCore/Math/Matrices/FloatMatrices.cs b/ScriptCore/Math/Matrices/FloatMatrices.cs index 5a9ddd9..10fa377 100644 --- a/ScriptCore/Math/Matrices/FloatMatrices.cs +++ b/ScriptCore/Math/Matrices/FloatMatrices.cs @@ -1,4 +1,5 @@ -using GlitchyEngine.Math.Attributes; +using System; +using GlitchyEngine.Math.Attributes; namespace GlitchyEngine.Math; diff --git a/ScriptCore/Math/Matrices/HalfMatrices.cs b/ScriptCore/Math/Matrices/HalfMatrices.cs index c7276c4..5009613 100644 --- a/ScriptCore/Math/Matrices/HalfMatrices.cs +++ b/ScriptCore/Math/Matrices/HalfMatrices.cs @@ -1,4 +1,5 @@ -using GlitchyEngine.Math.Attributes; +using System; +using GlitchyEngine.Math.Attributes; namespace GlitchyEngine.Math; diff --git a/ScriptCore/ScriptCore.csproj b/ScriptCore/ScriptCore.csproj index 00f2a53..2badb55 100644 --- a/ScriptCore/ScriptCore.csproj +++ b/ScriptCore/ScriptCore.csproj @@ -1,27 +1,30 @@  - - netstandard2.0 - latest - - true - GlitchyEngine - true - true - enable - + net9.0 + latest + + + true + GlitchyEngine + true + true + enable + + true + true + true + + + - - + - - - + \ No newline at end of file diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index f2948ac..595d1ff 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -1,8 +1,19 @@ 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.Numerics; +using System.Reflection; +using System.Reflection.Emit; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Loader; using GlitchyEngine.Core; using GlitchyEngine.Editor; +using GlitchyEngine.Extensions; using GlitchyEngine.Graphics; using GlitchyEngine.Graphics.Text; using GlitchyEngine.Math; @@ -11,16 +22,470 @@ using GlitchyEngine.Serialization; namespace GlitchyEngine; +[StructLayout(LayoutKind.Sequential)] +internal unsafe partial struct EngineFunctions +{ + public delegate* unmanaged[Cdecl] Log_LogMessage; +} + /// /// All methods in here are glued to the ScriptGlue.bf in the engine. /// TODO: This could be auto-generated fairly easily /// -internal static class ScriptGlue +internal static partial class ScriptGlue { +#region Script Glueing infrastructure + + static ScriptGlue() + { + NativeLibrary.SetDllImportResolver(typeof(ScriptGlue).Assembly, ImportResolver); + } + + private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (libraryName == "__Internal") + { + // Lade die aktuelle exe selbst + return NativeLibrary.Load(Process.GetCurrentProcess().MainModule.FileName); + } + + return IntPtr.Zero; + } + + + + private static EngineFunctions _engineFunctions; + + [UnmanagedCallersOnly] + public static unsafe void SetEngineFunctions(EngineFunctions* engineFunctions) + { + _engineFunctions = *engineFunctions; + + Log.Info("Yeah"); + } + + private static AssemblyLoadContext? _scriptAssemblyContext; + + private static Assembly? _appAssembly; + + [UnmanagedCallersOnly] + public static unsafe void LoadScriptAssembly(byte* assemblyData, long assemblyLength, byte* pdbData, long pdbLength) + { + using UnmanagedMemoryStream assemblyStream = new(assemblyData, assemblyLength); + + using UnmanagedMemoryStream? pdbStream = (pdbData != null) ? new UnmanagedMemoryStream(pdbData, pdbLength) : null; + + LoadAssembly(assemblyStream, pdbStream); + } + + private static void LoadAssembly(Stream assemblyStream, Stream? pdbStream) + { + try + { + _scriptAssemblyContext ??= new AssemblyLoadContext("ScriptContext", true); + + _appAssembly = _scriptAssemblyContext.LoadFromStream(assemblyStream, pdbStream); + + Debug.Assert(_appAssembly != null); + } + catch (Exception e) + { + Console.WriteLine($"Fehler: {e}"); + } + } + + [UnmanagedCallersOnly] + public static void UnloadAssemblies() + { + _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; + public bool RunInEditMode; + } + + [Flags] + public enum ScriptMethods + { + None = 0, + OnCreate = 0x1, + OnUpdate = 0x2, + OnDestroy = 0x4 + } + + [UnmanagedCallersOnly] + public static unsafe void GetScriptClasses(void** outBuffer, long* length) + { + using var contextualReflection = AssemblyLoadContext.EnterContextualReflection(_appAssembly); + + Debug.Assert(_appAssembly != null); + + Internal_FreeScriptClassNames(); + + var types = _appAssembly.GetTypes(); + + List<(string Name, Guid Guid, ScriptMethods AvailableMethods, bool runInEditMode)> scriptClasses = new(); + + 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; + + static ScriptMethods HasMethod(Type type, string methodName, ScriptMethods methodFlag) + { + MethodInfo? methodInfo = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + + return methodInfo != null ? methodFlag : ScriptMethods.None; + } + + methods |= HasMethod(type, nameof(Entity.OnCreate), ScriptMethods.OnCreate); + methods |= HasMethod(type, nameof(Entity.OnUpdate), ScriptMethods.OnUpdate); + methods |= HasMethod(type, nameof(Entity.OnDestroy), ScriptMethods.OnDestroy); + + bool runInEditMode = type.HasCustomAttribute(); + + scriptClasses.Add((name, guid, methods, runInEditMode)); + } + } + + _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].AvailableMethods, + RunInEditMode = scriptClasses[i].runInEditMode + }; + } + + *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(); + + [UnmanagedCallersOnly] + public static void ShowEntityEditor(UUID entityId) + { + (Entity entity, Type type) = _entityScripts[entityId]; + EntityEditor.ShowEntityEditor(entity); + } + + [UnmanagedCallersOnly] + public static void InvokeEntityOnCreate(UUID entityId) + { + try + { + (Entity entity, Type type) = _entityScripts[entityId]; + entity.OnCreate(); + } + catch (Exception e) + { + Console.WriteLine(e); + // TODO: Log exceptions to console + } + } + + [UnmanagedCallersOnly] + public static void InvokeEntityOnUpdate(UUID entityId, float deltaTime) + { + // using var _ = AssemblyLoadContext.EnterContextualReflection(_appAssembly); // TODO: Check if reflection works correctly in entities + + (Entity entity, Type type) = _entityScripts[entityId]; + entity.OnUpdate(deltaTime); + } + + [UnmanagedCallersOnly] + public static void InvokeEntityOnDestroy(UUID entityId, float deltaTime) + { + (Entity entity, Type type) = _entityScripts[entityId]; + entity.OnDestroy(); + } + + [UnmanagedCallersOnly] + public static unsafe void CreateScriptInstance(UUID entityId, byte* scriptClassName) + { + using var _ = 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 = ActivatorExtension.CreateEngineObject(scriptType, entityId) as Entity; + + // + // // Get the constructor + // ConstructorInfo? constructor = scriptType.GetConstructor( + // BindingFlags.Instance | BindingFlags.Public, + // null, + // [], + // null); + // + // Debug.Assert(constructor != null, "Script class constructor not found."); + // + // // Call the constructor to create an instance + // Entity? scriptInstance = constructor?.Invoke(null) as Entity; + Debug.Assert(scriptInstance != null, "Failed to create script instance."); + // + // if (scriptInstance != null) + // scriptInstance._uuid = entityId; + + //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); + } + } + +#endregion + + #region Log - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void Log_LogMessage(Log.LogLevel logLevel, string message, string filePath, int line); + + internal static unsafe void Log_LogMessage(Log.LogLevel logLevel, string message, string filePath, int line) + { + // unsafe + // { + // byte* mess = (byte*)Marshal.StringToCoTaskMemUTF8(message); + // + // fixed (char* messagePtr = message) + // fixed (char* filePathPtr = filePath) + // { + // _engineFunctions.Log_LogMessage(logLevel, messagePtr, filePathPtr, line); + // } + // + // Marshal.FreeCoTaskMem((IntPtr)mess); + // } + } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/ScriptCore/Serialization/DeserializationObject.cs b/ScriptCore/Serialization/DeserializationObject.cs index b490cc3..86613f2 100644 --- a/ScriptCore/Serialization/DeserializationObject.cs +++ b/ScriptCore/Serialization/DeserializationObject.cs @@ -148,7 +148,7 @@ public class DeserializationObject } [StructLayout(LayoutKind.Explicit)] - private struct DataHelper + public struct DataHelper { [StructLayout(LayoutKind.Sequential)] public unsafe struct StringView diff --git a/vendor/NetHostBeef b/vendor/NetHostBeef new file mode 160000 index 0000000..aeb0995 --- /dev/null +++ b/vendor/NetHostBeef @@ -0,0 +1 @@ +Subproject commit aeb099576d0b75dbcfbde0d864986fc8bcca13bf