No CoreCLR... for now

This commit is contained in:
Simon Lübeß
2023-07-24 14:15:59 +02:00
parent 62c85e9e1f
commit 499ca0311c
13 changed files with 701 additions and 216 deletions
@@ -15,32 +15,39 @@ public class Camera : Entity
public float DontFollowRadius = 3.0f; public float DontFollowRadius = 3.0f;
void OnUpdate(float deltaTime) void OnCreate()
{ {
Entity player = FindEntityWithName("Player"); Log.Info("Camera Create");
}
if (player != null) public void OnUpdate(float deltaTime)
{ {
float2 playerPosition = player.Transform.Translation.XY; 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) // float distance = length(distanceVector);
{
neededMovement = distanceVector - (distanceVector / distance) * DontFollowRadius;
}
Transform.Translation = new float3(cameraPosition + neededMovement, DistanceFromPlayer); // float2 neededMovement = float2.Zero;
}
else // if (distance > DontFollowRadius)
{ // {
Log.Error("Player not found!"); // neededMovement = distanceVector - (distanceVector / distance) * DontFollowRadius;
} // }
// Transform.Translation = new float3(cameraPosition + neededMovement, DistanceFromPlayer);
//}
//else
//{
// Log.Error("Player not found!");
//}
} }
} }
@@ -3,6 +3,7 @@
"Start Editor": { "Start Editor": {
"commandName": "Executable", "commandName": "Executable",
"executablePath": "D:\\Development\\Projects\\Beef\\GlitchyEngine\\build\\Debug_Win64\\GlitchyEditor\\GlitchyEditor.exe", "executablePath": "D:\\Development\\Projects\\Beef\\GlitchyEngine\\build\\Debug_Win64\\GlitchyEditor\\GlitchyEditor.exe",
"commandLineArgs": "\"content\\Scenes\\physics2D.scene\"",
"workingDirectory": "D:\\Development\\Projects\\Beef\\GlitchyEngine\\GlitchyEditor", "workingDirectory": "D:\\Development\\Projects\\Beef\\GlitchyEngine\\GlitchyEditor",
"nativeDebugging": true "nativeDebugging": true
} }
@@ -1,25 +1,15 @@
{ {
"runtimeTarget": { "runtimeTarget": {
"name": ".NETStandard,Version=v2.0/", "name": ".NETCoreApp,Version=v7.0",
"signature": "" "signature": ""
}, },
"compilationOptions": {}, "compilationOptions": {},
"targets": { "targets": {
".NETStandard,Version=v2.0": {}, ".NETCoreApp,Version=v7.0": {
".NETStandard,Version=v2.0/": {
"ScriptCore/1.0.0": { "ScriptCore/1.0.0": {
"dependencies": {
"NETStandard.Library": "2.0.3"
},
"runtime": { "runtime": {
"ScriptCore.dll": {} "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", "type": "project",
"serviceable": false, "serviceable": false,
"sha512": "" "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"
} }
} }
} }
@@ -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"
]
}
}
@@ -0,0 +1,10 @@
{
"runtimeOptions": {
"tfm": "net7.0",
"rollForward": "LatestMinor",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "7.0.0"
}
}
}
@@ -453,7 +453,8 @@ namespace GlitchyEditor.EditWindows
StringView search = StringView(); 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)) if (ImGui.Button(scriptLabel))
ImGui.OpenPopup("SelectScript"); ImGui.OpenPopup("SelectScript");
@@ -479,12 +480,14 @@ namespace GlitchyEditor.EditWindows
scriptComponent.ScriptClassName = scriptClass.FullName; scriptComponent.ScriptClassName = scriptClass.FullName;
ScriptEngine.CreateScriptFieldMap(entity); //ScriptEngine.CreateScriptFieldMap(entity);
} }
} }
ImGui.EndPopup(); ImGui.EndPopup();
} }
/*
ScriptClass scriptClass = ScriptEngine.GetScriptClass(scriptComponent.ScriptClassName); ScriptClass scriptClass = ScriptEngine.GetScriptClass(scriptComponent.ScriptClassName);
/*T GetFieldValue<T>() /*T GetFieldValue<T>()
@@ -707,6 +710,8 @@ namespace GlitchyEditor.EditWindows
} }
} }
} }
*/
} }
private static void LabelColumn(StringView label) private static void LabelColumn(StringView label)
+23
View File
@@ -70,5 +70,28 @@ namespace GlitchyEngine
delete dictionary; 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;
}
}
} }
} }
+37 -4
View File
@@ -1,6 +1,7 @@
using NetHostBeef; using NetHostBeef;
using System; using System;
using System.Interop; using System.Interop;
using GlitchyEngine.Core;
namespace GlitchyEngine.Scripting; namespace GlitchyEngine.Scripting;
@@ -19,6 +20,15 @@ static class CoreClrHelper
static LoadScriptAssemblyFunc _loadScriptAssembly; static LoadScriptAssemblyFunc _loadScriptAssembly;
static function void() _unloadAssemblies; 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) public static void Init(StringView coreAssemblyPath)
{ {
LoadHostFxr(); LoadHostFxr();
@@ -77,11 +87,15 @@ static class CoreClrHelper
static void PrepareFunction() static void PrepareFunction()
{ {
GetFunctionPointerUnmanagedCallersOnly<LoadScriptAssemblyFunc>("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly", GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly", out _loadScriptAssembly);
out _loadScriptAssembly);
GetFunctionPointerUnmanagedCallersOnly<function void()>("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadAssemblies", GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadAssemblies", out _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<uint8> appAssemblyData, Span<uint8> pdbData) public static void LoadAppAssembly(Span<uint8> appAssemblyData, Span<uint8> pdbData)
@@ -94,6 +108,25 @@ static class CoreClrHelper
_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<T>(StringView typeName, StringView methodName, StringView delegateName, out T outDelegate) where T: operator explicit void* public static int GetFunctionPointer<T>(StringView typeName, StringView methodName, StringView delegateName, out T outDelegate) where T: operator explicit void*
{ {
void* funPtr = null; void* funPtr = null;
+190 -124
View File
@@ -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 static class ScriptEngine
{ {
private static MonoDomain* s_RootDomain; private static MonoDomain* s_RootDomain;
@@ -65,17 +132,11 @@ static class ScriptEngine
private static Dictionary<StringView, SharpType> _sharpClasses = new .() ~ DeleteDictionaryAndReleaseValues!(_); private static Dictionary<StringView, SharpType> _sharpClasses = new .() ~ DeleteDictionaryAndReleaseValues!(_);
private static Dictionary<StringView, ScriptClass> _entityScripts = new .() ~ DeleteDictionaryAndReleaseValues!(_); private static Dictionary<StringView, SexyScriptClass> _entityScripts = new .() ~ DeleteDictionaryAndValues!(_);
private static Dictionary<UUID, ScriptInstance> _entityScriptInstances = new .() ~ { private static Dictionary<UUID, SexyScriptInstance> _entityScriptInstances = new .() ~ DeleteDictionaryAndValues!(_);
for (var entry in _)
{
entry.value?.ReleaseRef();
}
delete _;
}
public static Dictionary<StringView, ScriptClass> EntityClasses => _entityScripts; public static Dictionary<StringView, SexyScriptClass> EntityClasses => _entityScripts;
public static Scene Context => s_Context; public static Scene Context => s_Context;
@@ -102,10 +163,13 @@ static class ScriptEngine
private static String _coreAssemblyPath = "resources/scripts/ScriptCore.dll"; private static String _coreAssemblyPath = "resources/scripts/ScriptCore.dll";
private static String _appAssemblyPath = "SandboxProject/Assets/Scripts/bin/Sandbox.dll"; private static String _appAssemblyPath = "SandboxProject/Assets/Scripts/bin/Sandbox.dll";
public static void Init() public static void Init()
{ {
CoreClrHelper.Init(_coreAssemblyPath); CoreClrHelper.Init(_coreAssemblyPath);
GetFunctions();
//ScriptGlue.Init(); //ScriptGlue.Init();
LoadScriptAssembly(); LoadScriptAssembly();
@@ -150,17 +214,6 @@ static class ScriptEngine
_userAssemblyWatcher.StartRaisingEvents(); _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() static void LoadScriptAssembly()
{ {
List<uint8> data = new List<uint8>(1024); List<uint8> data = new List<uint8>(1024);
@@ -178,11 +231,27 @@ static class ScriptEngine
delete data; delete data;
delete pdbData; delete pdbData;
//GetEntitiesFromAssemblies(); GetEntitiesFromAssemblies();
//ScriptGlue.RegisterManagedComponents(); //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) public static void SetContext(Scene scene)
{ {
SetReference!(s_Context, scene); SetReference!(s_Context, scene);
@@ -190,37 +259,54 @@ static class ScriptEngine
public static void OnRuntimeStop() 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(); entry.value.ReleaseRef();
} }
_entityScriptInstances.Clear(); _entityScriptInstances.Clear();
*/
SetContext(null); 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) public static bool InitializeInstance(Entity entity, ScriptComponent* script)
{ {
ScriptClass scriptClass = GetScriptClass(script.ScriptClassName); SexyScriptClass scriptClass = GetScriptClass(script.ScriptClassName);
if (scriptClass == null) if (scriptClass == null)
return false; return false;
script.Instance = new ScriptInstance(scriptClass); ScriptFunctionPointers functions = CoreClrHelper.CreateScriptInstance(entity.UUID, scriptClass.FullName);
script.Instance..ReleaseRef();
_entityScriptInstances[entity.UUID] = script.Instance..AddRef(); script.Instance = new SexyScriptInstance(scriptClass, functions);//new ScriptInstance(scriptClass);
// script.Instance..ReleaseRef();
script.Instance.Instantiate(entity.UUID); _entityScriptInstances[entity.UUID] = script.Instance;
CopyEditorFieldsToInstance(entity, script); // CopyEditorFieldsToInstance(entity, script);
return true; return true;
} }
private static void CopyEditorFieldsToInstance(Entity entity, ScriptComponent* script) 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 // however the UUID is the same, so we get the correct field map
let fields = GetScriptFieldMap(entity); let fields = GetScriptFieldMap(entity);
@@ -231,82 +317,71 @@ static class ScriptEngine
ScriptField scriptField = script.Instance.ScriptClass.Fields[fieldName]; ScriptField scriptField = script.Instance.ScriptClass.Fields[fieldName];
script.Instance.SetFieldValue(scriptField, field._data); script.Instance.SetFieldValue(scriptField, field._data);
} }*/
} }
private static MonoAssembly* LoadCSharpAssembly(StringView assemblyPath, bool loadPDB = false) struct ScriptClassInfo
{ {
List<uint8> data = new List<uint8>(1024); public char8* Name;
public Guid Guid;
File.ReadAll(assemblyPath, data); public ScriptMethods Methods;
// 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<uint8> pdbData = new List<uint8>(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);
} }
private static void GetEntitiesFromAssemblies() private static void GetEntitiesFromAssemblies()
{ {
CoreClrHelper.GetScriptClasses(let data, let entryCount);
Span<ScriptClassInfo> scriptClasses = .((.)data, entryCount);
List<SexyScriptClass> newClasses = scope .();
for (var entry in _entityScripts) 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(); entry.value.ReleaseRef();
} }
@@ -351,28 +426,7 @@ static class ScriptEngine
Log.EngineLogger.Info($"Added entity \"{entityScript.FullName}\""); 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() public static void Shutdown()
@@ -434,6 +488,9 @@ static class ScriptEngine
public static void CreateScriptFieldMap(Entity entity) public static void CreateScriptFieldMap(Entity entity)
{ {
Log.EngineLogger.Error("Not implemented");
/*
Log.EngineLogger.AssertDebug(entity.IsValid); Log.EngineLogger.AssertDebug(entity.IsValid);
if (_entityFields.TryGetValue(entity.UUID, var entityFields)) if (_entityFields.TryGetValue(entity.UUID, var entityFields))
@@ -455,11 +512,13 @@ static class ScriptEngine
for (let (fieldName, field) in scriptClass.Fields) for (let (fieldName, field) in scriptClass.Fields)
{ {
entityFields.Add(new String(fieldName), ScriptFieldInstance(field.FieldType)); entityFields.Add(new String(fieldName), ScriptFieldInstance(field.FieldType));
} }*/
} }
public static ScriptFieldMap GetScriptFieldMap(Entity entity) public static ScriptFieldMap GetScriptFieldMap(Entity entity)
{ {
ThrowUnimplemented();
Log.EngineLogger.AssertDebug(entity.IsValid); Log.EngineLogger.AssertDebug(entity.IsValid);
let uuid = entity.UUID; let uuid = entity.UUID;
@@ -472,16 +531,23 @@ static class ScriptEngine
public static MonoObject* GetManagedInstance(UUID entityId) public static MonoObject* GetManagedInstance(UUID entityId)
{ {
if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance)) ThrowUnimplemented();
return scriptInstance.MonoInstance;
/*if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance))
return scriptInstance.MonoInstance;*/
return null; return null;
} }
public static ScriptClass GetScriptClass(StringView name) public static SexyScriptClass GetScriptClass(StringView name)
{ {
EntityClasses.TryGetValue(name, let scriptClass); EntityClasses.TryGetValue(name, let scriptClass);
return scriptClass; return scriptClass;
} }
public static void InvokeOnUpdate(UUID entityId, float deltaTime)
{
_InvokeOnUpdate(entityId, deltaTime);
}
} }
@@ -509,7 +509,7 @@ namespace GlitchyEngine.World
{ {
private String _scriptClassName = null; private String _scriptClassName = null;
private ScriptInstance _instance = null; private SexyScriptInstance _instance = null;
public StringView ScriptClassName public StringView ScriptClassName
{ {
@@ -523,22 +523,24 @@ namespace GlitchyEngine.World
} }
} }
public ScriptInstance Instance public SexyScriptInstance Instance
{ {
[Inline] [Inline]
get => _instance; get => _instance;
[Inline] [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 public void Dispose() mut
{ {
delete _scriptClassName; delete _scriptClassName;
ReleaseRefAndNullify!(_instance); //ReleaseRefAndNullify!(_instance);
} }
} }
} }
+5 -5
View File
@@ -272,15 +272,15 @@ namespace GlitchyEngine.World
{ {
if (!script.IsCreated) if (!script.IsCreated)
{ {
if (!script.IsInitialized)
ScriptEngine.InitializeInstance(Entity(entity, this), script); ScriptEngine.InitializeInstance(Entity(entity, this), script);
if (mode.HasFlag(.Runtime)) //if (mode.HasFlag(.Runtime))
script.Instance.InvokeOnCreate(); //script.Instance.InvokeOnCreate();
} }
if (mode.HasFlag(.Runtime)) if (mode.HasFlag(.Runtime) && script.Instance.ScriptClass.Methods.HasFlag(.OnUpdate))
script.Instance.InvokeOnUpdate(gameTime.DeltaTime); ScriptEngine.InvokeOnUpdate(Entity(entity, this).UUID, gameTime.DeltaTime);
//script.Instance.InvokeOnUpdate(gameTime.DeltaTime);
} }
} }
+20 -15
View File
@@ -191,7 +191,9 @@ class SceneSerializer
{ {
Serialize.Value(writer, "ScriptClass", component.ScriptClassName); 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 // TODO: Thats not a good check, I think. At least we know the script class is valid
if (ScriptEngine.GetScriptClass(component.ScriptClassName) != null) 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 // 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 :) // 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()); Try!(reader.EntryEnd());
@@ -613,44 +615,47 @@ class SceneSerializer
// Allocate a string on the stack, because the dictionary uses a string as key // Allocate a string on the stack, because the dictionary uses a string as key
String fieldNameString = scope .(fieldName); String fieldNameString = scope .(fieldName);
if (fields.ContainsKey(fieldNameString)) //if (fields.ContainsKey(fieldNameString))
{ //{
var field = ref fields[fieldNameString]; //var field = ref fields[fieldNameString];
Result<StringView> fieldTypeName = reader.Type(); Result<StringView> 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}\")"); Log.EngineLogger.Error($"Failed to read field type for field \"{fieldName}\" in script \"{component.ScriptClassName}\" of entity {entity.UUID} (\"{entity.Name}\")");
reader.FileEntrySkip(1); reader.FileEntrySkip(1);
dontRemoveComma = true; dontRemoveComma = true;
continue; continue;
} }*/
Result<ScriptFieldType> fieldType = Enum.Parse<ScriptFieldType>(fieldTypeName, true); Result<ScriptFieldType> fieldType = Enum.Parse<ScriptFieldType>(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}\")"); 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); reader.FileEntrySkip(1);
dontRemoveComma = true; dontRemoveComma = true;
continue; continue;
} }*/
void* data = &field.[Friend]_data; //void* data = &field.[Friend]_data;
if (Deserialize.Value(reader, ValueView(field.Type.GetBeefType(), data), gBonEnv) case .Err) uint8[128] data;
//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}\")"); Log.EngineLogger.Error($"Failed to deserialize data for field: \"{fieldName}\" in script \"{component.ScriptClassName}\" of entity {entity.UUID} (\"{entity.Name}\")");
reader.FileEntrySkip(1); reader.FileEntrySkip(1);
dontRemoveComma = true; dontRemoveComma = true;
continue; continue;
} }
} /*}
else else
{ {
Log.EngineLogger.Error($"Script \"{component.ScriptClassName}\" doesn't have a field with name \"{fieldName}\". (Entity {entity.UUID} (\"{entity.Name}\"))"); Log.EngineLogger.Error($"Script \"{component.ScriptClassName}\" doesn't have a field with name \"{fieldName}\". (Entity {entity.UUID} (\"{entity.Name}\"))");
} }*/
} }
Try!(reader.ArrayBlockEnd()); Try!(reader.ArrayBlockEnd());
+354 -6
View File
@@ -1,11 +1,18 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Mime;
using System.Net.Security; using System.Net.Security;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Text;
using GlitchyEngine.Core; using GlitchyEngine.Core;
using GlitchyEngine.Math; using GlitchyEngine.Math;
@@ -95,7 +102,7 @@ internal static class ScriptGlue
LoadAssembly(assemblyStream, pdbStream); LoadAssembly(assemblyStream, pdbStream);
} }
private static void LoadAssembly(Stream assemblyStream, Stream? pdbStream)//(string path) private static void LoadAssembly(Stream assemblyStream, Stream? pdbStream)
{ {
try try
{ {
@@ -107,6 +114,8 @@ internal static class ScriptGlue
// Test the loaded assembly // Test the loaded assembly
_appAssembly.GetType("Sandbox.TestClass")?.GetMethod("InternalTest")?.Invoke(null, null); _appAssembly.GetType("Sandbox.TestClass")?.GetMethod("InternalTest")?.Invoke(null, null);
AssemblyLoadContext.EnterContextualReflection(_appAssembly);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -116,13 +125,352 @@ internal static class ScriptGlue
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
public static void UnloadAssemblies() public static void UnloadAssemblies()
{
UnloadContext();
}
private static void UnloadContext()
{ {
_scriptAssemblyContext?.Unload(); _scriptAssemblyContext?.Unload();
_scriptAssemblyContext = null; _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<UUID, (Entity Entity, Type Type)> _entityScripts = new();
private static Dictionary<Type, MethodInfo> _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<Action>(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()));
}
/// <summary>
/// Create delegate by methodinfo in target
/// </summary>
/// <param name="method">method info</param>
/// <param name="target">A instance of the object which contains the method where will be execute</param>
/// <returns>delegate or null</returns>
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<string, Type> _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<string, Type>();
}
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<Delegate>(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);
}
}
} }