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
+23
View File
@@ -70,5 +70,28 @@ namespace GlitchyEngine
delete dictionary;
}
}
public static mixin ClearDictionaryAndDeleteValues(var dictionary)
{
if (dictionary != null)
{
for (var value in dictionary)
delete value.value;
delete dictionary;
}
}
public static mixin ClearDictionaryAndDeleteKeysAndValues(var dictionary)
{
if (dictionary != null)
{
for (var value in dictionary)
{
delete value.key;
delete value.value;
}
delete dictionary;
}
}
}
}
+37 -4
View File
@@ -1,6 +1,7 @@
using NetHostBeef;
using System;
using System.Interop;
using GlitchyEngine.Core;
namespace GlitchyEngine.Scripting;
@@ -19,6 +20,15 @@ static class CoreClrHelper
static LoadScriptAssemblyFunc _loadScriptAssembly;
static function void() _unloadAssemblies;
// public static unsafe void GetScriptClasses(void** outBuffer, long* length)
private function void GetScriptClassesFunc(void** outBuffer, int64* length);
static GetScriptClassesFunc _getScriptClasses;
static function void() _freeScriptClassNames;
//public static unsafe ScriptFunctions CreateScriptInstance(UUID entityId, byte* scriptClassName)
private function ScriptFunctionPointers CreateScriptInstanceFunc(UUID entityId, char8* scriptClassName);
static CreateScriptInstanceFunc _createScriptInstance;
public static void Init(StringView coreAssemblyPath)
{
LoadHostFxr();
@@ -77,11 +87,15 @@ static class CoreClrHelper
static void PrepareFunction()
{
GetFunctionPointerUnmanagedCallersOnly<LoadScriptAssemblyFunc>("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly",
out _loadScriptAssembly);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly", out _loadScriptAssembly);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadAssemblies", out _unloadAssemblies);
GetFunctionPointerUnmanagedCallersOnly<function void()>("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadAssemblies",
out _unloadAssemblies);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "GetScriptClasses", out _getScriptClasses);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "FreeScriptClassNames", out _freeScriptClassNames);
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "CreateScriptInstance", out _createScriptInstance);
}
public static void LoadAppAssembly(Span<uint8> appAssemblyData, Span<uint8> pdbData)
@@ -92,6 +106,25 @@ static class CoreClrHelper
public static void UnloadAssemblies()
{
_unloadAssemblies();
}
/// Gets an array of script class infos. Use FreeScriptClassNames to release the buffer.
public static void GetScriptClasses(out void* outBuffer, out int64 entryCount)
{
outBuffer = null;
entryCount = 0;
_getScriptClasses(&outBuffer, &entryCount);
}
/// Releases the buffer created by GetScriptClasses
public static void FreeScriptClassNames()
{
_freeScriptClassNames();
}
public static ScriptFunctionPointers CreateScriptInstance(UUID entityId, StringView scriptName)
{
return _createScriptInstance(entityId, scriptName.Ptr);
}
public static int GetFunctionPointer<T>(StringView typeName, StringView methodName, StringView delegateName, out T outDelegate) where T: operator explicit void*
+195 -129
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
{
private static MonoDomain* s_RootDomain;
@@ -65,17 +132,11 @@ static class ScriptEngine
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 .() ~ {
for (var entry in _)
{
entry.value?.ReleaseRef();
}
delete _;
}
private static Dictionary<UUID, SexyScriptInstance> _entityScriptInstances = new .() ~ DeleteDictionaryAndValues!(_);
public static Dictionary<StringView, ScriptClass> EntityClasses => _entityScripts;
public static Dictionary<StringView, SexyScriptClass> EntityClasses => _entityScripts;
public static Scene Context => s_Context;
@@ -101,11 +162,14 @@ static class ScriptEngine
private static String _coreAssemblyPath = "resources/scripts/ScriptCore.dll";
private static String _appAssemblyPath = "SandboxProject/Assets/Scripts/bin/Sandbox.dll";
public static void Init()
{
CoreClrHelper.Init(_coreAssemblyPath);
GetFunctions();
//ScriptGlue.Init();
LoadScriptAssembly();
@@ -150,17 +214,6 @@ static class ScriptEngine
_userAssemblyWatcher.StartRaisingEvents();
}
/*static void LoadScriptAssemblies()
{
CreateAppDomain("GlitchyEngineScriptRuntime");
(s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled);
(s_AppAssembly, s_AppAssemblyImage) = LoadAssembly("SandboxProject/Assets/Scripts/bin/Sandbox.dll", _debuggingEnabled);
GetEntitiesFromAssemblies();
ScriptGlue.RegisterManagedComponents();
}*/
static void LoadScriptAssembly()
{
List<uint8> data = new List<uint8>(1024);
@@ -178,11 +231,27 @@ static class ScriptEngine
delete data;
delete pdbData;
//GetEntitiesFromAssemblies();
GetEntitiesFromAssemblies();
//ScriptGlue.RegisterManagedComponents();
}
public static void ReloadAssemblies()
{
Debug.Profiler.ProfileFunction!();
CoreClrHelper.UnloadAssemblies();
LoadScriptAssembly();
// TODO: ScriptFields might get added
// TODO: ScriptField Types may change after reload!
// TODO: Scripts may be renamed (probably not detectable (trivially))
// TODO: Reload in play mode
// Only scripts that were changed should actually be reinstatiated
}
public static void SetContext(Scene scene)
{
SetReference!(s_Context, scene);
@@ -190,37 +259,54 @@ static class ScriptEngine
public static void OnRuntimeStop()
{
for (var entry in _entityScriptInstances)
ThrowUnimplemented();
// TODO: Also check, if something has to happen on the C# side!
ClearDictionaryAndDeleteValues!(_entityScriptInstances);
/*for (var entry in _entityScriptInstances)
{
entry.value.ReleaseRef();
}
_entityScriptInstances.Clear();
*/
SetContext(null);
}
private function void InvokeOnUpdateFunc(UUID entityId, float deltaTime);
private static InvokeOnUpdateFunc _InvokeOnUpdate;
private static void GetFunctions()
{
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "InvokeOnUpdate", out _InvokeOnUpdate);
}
public static bool InitializeInstance(Entity entity, ScriptComponent* script)
{
ScriptClass scriptClass = GetScriptClass(script.ScriptClassName);
SexyScriptClass scriptClass = GetScriptClass(script.ScriptClassName);
if (scriptClass == null)
return false;
ScriptFunctionPointers functions = CoreClrHelper.CreateScriptInstance(entity.UUID, scriptClass.FullName);
script.Instance = new ScriptInstance(scriptClass);
script.Instance..ReleaseRef();
script.Instance = new SexyScriptInstance(scriptClass, functions);//new ScriptInstance(scriptClass);
// script.Instance..ReleaseRef();
_entityScriptInstances[entity.UUID] = script.Instance;
_entityScriptInstances[entity.UUID] = script.Instance..AddRef();
script.Instance.Instantiate(entity.UUID);
CopyEditorFieldsToInstance(entity, script);
// CopyEditorFieldsToInstance(entity, script);
return true;
}
private static void CopyEditorFieldsToInstance(Entity entity, ScriptComponent* script)
{
// Technically the map is for a different entity (namely the editor-entity),
ThrowUnimplemented();
/*// Technically the map is for a different entity (namely the editor-entity),
// however the UUID is the same, so we get the correct field map
let fields = GetScriptFieldMap(entity);
@@ -231,82 +317,71 @@ static class ScriptEngine
ScriptField scriptField = script.Instance.ScriptClass.Fields[fieldName];
script.Instance.SetFieldValue(scriptField, field._data);
}
}*/
}
private static MonoAssembly* LoadCSharpAssembly(StringView assemblyPath, bool loadPDB = false)
struct ScriptClassInfo
{
List<uint8> data = new List<uint8>(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<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);
public char8* Name;
public Guid Guid;
public ScriptMethods Methods;
}
private static void GetEntitiesFromAssemblies()
{
CoreClrHelper.GetScriptClasses(let data, let entryCount);
Span<ScriptClassInfo> scriptClasses = .((.)data, entryCount);
List<SexyScriptClass> newClasses = scope .();
for (var entry in _entityScripts)
{
bool remove = true;
// We can only rely on guids.
for (var newEntry in scriptClasses)
{
if (entry.value.Guid == newEntry.Guid)
{
remove = false;
if (!String.Equals(entry.value.FullName.CStr(), newEntry.Name))
{
@entry.Remove();
entry.value.FullName.Set(StringView(newEntry.Name));
}
newClasses.Add(entry.value);
}
}
if (remove)
{
@entry.Remove();
delete entry.value;
}
}
for (var entry in scriptClasses)
{
SexyScriptClass scriptClass = new .(StringView(entry.Name), entry.Guid, entry.Methods);
newClasses.Add(scriptClass);
}
for (var scriptClass in newClasses)
{
_entityScripts.Add(scriptClass.FullName, scriptClass);
}
CoreClrHelper.FreeScriptClassNames();
// TODO: Cleanup
/*for (var entry in _entityScripts)
{
entry.value.ReleaseRef();
}
@@ -351,28 +426,7 @@ static class ScriptEngine
Log.EngineLogger.Info($"Added entity \"{entityScript.FullName}\"");
}
}
}
public static void ReloadAssemblies()
{
Debug.Profiler.ProfileFunction!();
CoreClrHelper.UnloadAssemblies();
LoadScriptAssembly();
//Mono.mono_domain_set(s_RootDomain, false);
//Mono.mono_domain_unload(s_AppDomain);
//LoadScriptAssemblies();
// TODO: ScriptFields might get added
// TODO: ScriptField Types may change after reload!
// TODO: Scripts may be renamed (probably not detectable (trivially))
// TODO: Reload in play mode
// Only scripts that were changed should actually be reinstatiated
}*/
}
public static void Shutdown()
@@ -434,6 +488,9 @@ static class ScriptEngine
public static void CreateScriptFieldMap(Entity entity)
{
Log.EngineLogger.Error("Not implemented");
/*
Log.EngineLogger.AssertDebug(entity.IsValid);
if (_entityFields.TryGetValue(entity.UUID, var entityFields))
@@ -455,11 +512,13 @@ static class ScriptEngine
for (let (fieldName, field) in scriptClass.Fields)
{
entityFields.Add(new String(fieldName), ScriptFieldInstance(field.FieldType));
}
}*/
}
public static ScriptFieldMap GetScriptFieldMap(Entity entity)
{
ThrowUnimplemented();
Log.EngineLogger.AssertDebug(entity.IsValid);
let uuid = entity.UUID;
@@ -472,16 +531,23 @@ static class ScriptEngine
public static MonoObject* GetManagedInstance(UUID entityId)
{
if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance))
return scriptInstance.MonoInstance;
ThrowUnimplemented();
/*if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance))
return scriptInstance.MonoInstance;*/
return null;
}
public static ScriptClass GetScriptClass(StringView name)
public static SexyScriptClass GetScriptClass(StringView name)
{
EntityClasses.TryGetValue(name, let scriptClass);
return scriptClass;
}
public static void InvokeOnUpdate(UUID entityId, float deltaTime)
{
_InvokeOnUpdate(entityId, deltaTime);
}
}
@@ -509,7 +509,7 @@ namespace GlitchyEngine.World
{
private String _scriptClassName = null;
private ScriptInstance _instance = null;
private SexyScriptInstance _instance = null;
public StringView ScriptClassName
{
@@ -523,22 +523,24 @@ namespace GlitchyEngine.World
}
}
public ScriptInstance Instance
public SexyScriptInstance Instance
{
[Inline]
get => _instance;
[Inline]
set mut => SetReference!(_instance, value);
set mut => _instance = value;//SetReference!(_instance, value);
}
public bool IsInitialized => _instance?.IsInitialized ?? false;
// public bool IsInitialized => _instance?.IsInitialized ?? false;
public bool IsCreated => _instance?.IsCreated ?? false;
// public bool IsCreated => _instance?.IsCreated ?? false;
public bool IsCreated => _instance != null;
public void Dispose() mut
{
delete _scriptClassName;
ReleaseRefAndNullify!(_instance);
//ReleaseRefAndNullify!(_instance);
}
}
}
}
+7 -7
View File
@@ -260,7 +260,7 @@ namespace GlitchyEngine.World
script.Instance._entity = Entity(entity, this);
script.Instance.[Friend]OnCreate();
}
script.Instance.[Friend]OnUpdate(gameTime);
}
/*}
@@ -272,15 +272,15 @@ namespace GlitchyEngine.World
{
if (!script.IsCreated)
{
if (!script.IsInitialized)
ScriptEngine.InitializeInstance(Entity(entity, this), script);
ScriptEngine.InitializeInstance(Entity(entity, this), script);
if (mode.HasFlag(.Runtime))
script.Instance.InvokeOnCreate();
//if (mode.HasFlag(.Runtime))
//script.Instance.InvokeOnCreate();
}
if (mode.HasFlag(.Runtime))
script.Instance.InvokeOnUpdate(gameTime.DeltaTime);
if (mode.HasFlag(.Runtime) && script.Instance.ScriptClass.Methods.HasFlag(.OnUpdate))
ScriptEngine.InvokeOnUpdate(Entity(entity, this).UUID, gameTime.DeltaTime);
//script.Instance.InvokeOnUpdate(gameTime.DeltaTime);
}
}
+20 -15
View File
@@ -191,7 +191,9 @@ class SceneSerializer
{
Serialize.Value(writer, "ScriptClass", component.ScriptClassName);
//if (component.HasScript)
ThrowUnimplemented();
/*//if (component.HasScript)
// TODO: Thats not a good check, I think. At least we know the script class is valid
if (ScriptEngine.GetScriptClass(component.ScriptClassName) != null)
{
@@ -221,7 +223,7 @@ class SceneSerializer
}
}
}
}
}*/
});
}
@@ -579,9 +581,9 @@ class SceneSerializer
// But that is a bug for me to rediscover in the distant future, so in case this bug occurred and it took ages for you to
// figure out what happened: You are welcome :)
ScriptEngine.CreateScriptFieldMap(entity);
//ScriptEngine.CreateScriptFieldMap(entity);
var fields = ScriptEngine.GetScriptFieldMap(entity);
//var fields = ScriptEngine.GetScriptFieldMap(entity);
Try!(reader.EntryEnd());
@@ -613,44 +615,47 @@ class SceneSerializer
// Allocate a string on the stack, because the dictionary uses a string as key
String fieldNameString = scope .(fieldName);
if (fields.ContainsKey(fieldNameString))
{
var field = ref fields[fieldNameString];
//if (fields.ContainsKey(fieldNameString))
//{
//var field = ref fields[fieldNameString];
Result<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}\")");
reader.FileEntrySkip(1);
dontRemoveComma = true;
continue;
}
}*/
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}\")");
reader.FileEntrySkip(1);
dontRemoveComma = true;
continue;
}
}*/
void* data = &field.[Friend]_data;
//void* data = &field.[Friend]_data;
uint8[128] data;
if (Deserialize.Value(reader, ValueView(field.Type.GetBeefType(), data), gBonEnv) case .Err)
//if (Deserialize.Value(reader, ValueView(field.Type.GetBeefType(), data), gBonEnv) case .Err)
if (Deserialize.Value(reader, ValueView(fieldType.Value.GetBeefType(), &data), gBonEnv) case .Err)
{
Log.EngineLogger.Error($"Failed to deserialize data for field: \"{fieldName}\" in script \"{component.ScriptClassName}\" of entity {entity.UUID} (\"{entity.Name}\")");
reader.FileEntrySkip(1);
dontRemoveComma = true;
continue;
}
}
/*}
else
{
Log.EngineLogger.Error($"Script \"{component.ScriptClassName}\" doesn't have a field with name \"{fieldName}\". (Entity {entity.UUID} (\"{entity.Name}\"))");
}
}*/
}
Try!(reader.ArrayBlockEnd());