Reload scripts in Play Mode!!!

- Dont serialize static fields
- Added some profiling to ScriptEngine
- Removed superseded ScriptFieldMaps
This commit is contained in:
Simon Lübeß
2023-12-24 01:33:48 +01:00
parent d0f0a34247
commit abb119589c
7 changed files with 58 additions and 169 deletions
@@ -560,8 +560,6 @@ namespace GlitchyEditor.EditWindows
className == scriptComponent.ScriptClassName)) className == scriptComponent.ScriptClassName))
{ {
scriptComponent.ScriptClassName = scriptClass.FullName; scriptComponent.ScriptClassName = scriptClass.FullName;
ScriptEngine.CreateScriptFieldMap(entity);
} }
} }
ImGui.EndPopup(); ImGui.EndPopup();
-4
View File
@@ -1003,8 +1003,6 @@ namespace GlitchyEditor
// Clear serialized data, so that we don't waste time deserializing it. // Clear serialized data, so that we don't waste time deserializing it.
ClearPrePlaySerializedData(); ClearPrePlaySerializedData();
OnSceneStop(); OnSceneStop();
ScriptEngine.ClearEntityScriptFields();
} }
/// Creates a new scene and openes it. /// Creates a new scene and openes it.
@@ -1130,8 +1128,6 @@ namespace GlitchyEditor
{ {
CloseCurrentScene(); CloseCurrentScene();
ScriptEngine.ClearEntityScriptFields();
SceneFilePath = scope String(filename); SceneFilePath = scope String(filename);
using (Scene newScene = new Scene()) using (Scene newScene = new Scene())
@@ -79,8 +79,6 @@ public struct ScriptFieldInstance
} }
} }
public typealias ScriptFieldMap = Dictionary<String, ScriptFieldInstance>;
abstract class SharpType : RefCounter abstract class SharpType : RefCounter
{ {
protected String _namespace ~ delete _; protected String _namespace ~ delete _;
+44 -148
View File
@@ -136,16 +136,6 @@ static class ScriptEngine
private set => SetReference!(s_Context, value); private set => SetReference!(s_Context, value);
} }
private static Dictionary<UUID, ScriptFieldMap> _entityFields = new .() ~
{
for (var value in _entityFields.Values)
{
DeleteDictionaryAndKeys!(value);
}
delete _;
};
private static FileSystemWatcher _userAssemblyWatcher ~ delete _; private static FileSystemWatcher _userAssemblyWatcher ~ delete _;
// TODO: This should be a global setting somewhere // TODO: This should be a global setting somewhere
@@ -294,20 +284,11 @@ static class ScriptEngine
_requestingReload = true; _requestingReload = true;
// TODO: Temporary, we want to be able to reload while in play-mode. (+ Editor Scripts will be a thing some day)
if (_entityScriptInstances.Count > 0)
{
Log.EngineLogger.Warning("There are script instances. Skipping assembly reload.");
return;
}
Log.EngineLogger.Info("Script reload requested."); Log.EngineLogger.Info("Script reload requested.");
Application.Instance.InvokeOnMainThread(new () => Application.Instance.InvokeOnMainThread(new () =>
{ {
Log.EngineLogger.Info("Reloading scripts...");
ReloadAssemblies(); ReloadAssemblies();
Log.EngineLogger.Info("Scripts reloaded!");
_requestingReload = false; _requestingReload = false;
_userAssemblyWatcher.StartRaisingEvents(); _userAssemblyWatcher.StartRaisingEvents();
@@ -319,19 +300,10 @@ static class ScriptEngine
_userAssemblyWatcher.StartRaisingEvents(); _userAssemblyWatcher.StartRaisingEvents();
} }
/// Gets rid of all entity fields.
public static void ClearEntityScriptFields()
{
for (var value in _entityFields.Values)
{
DeleteDictionaryAndKeys!(value);
}
_entityFields.Clear();
}
static void LoadScriptAssemblies() static void LoadScriptAssemblies()
{ {
Debug.Profiler.ProfileFunction!();
CreateAppDomain("GlitchyEngineScriptRuntime"); CreateAppDomain("GlitchyEngineScriptRuntime");
(s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled); (s_CoreAssembly, s_CoreAssemblyImage) = LoadAssembly("resources/scripts/ScriptCore.dll", _debuggingEnabled);
@@ -393,6 +365,8 @@ static class ScriptEngine
Context = null; Context = null;
} }
/// Instantiates the entities script components script and runs the constructor.
/// Disposes of and replaces the old instance, if one exists.
public static bool InitializeInstance(Entity entity, ScriptComponent* script) public static bool InitializeInstance(Entity entity, ScriptComponent* script)
{ {
ScriptClass scriptClass = GetScriptClass(script.ScriptClassName); ScriptClass scriptClass = GetScriptClass(script.ScriptClassName);
@@ -471,60 +445,6 @@ static class ScriptEngine
return componentInstance; return componentInstance;
} }
public static void CopyEditorFieldsToInstance(Entity entity, ScriptComponent* script)
{
Log.EngineLogger.AssertDebug(script.Instance != null);
// 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);
for (var (fieldName, field) in fields)
{
// TODO: a litte assertion maybe?
ScriptField scriptField = script.Instance.ScriptClass.Fields[fieldName];
switch (scriptField.FieldType)
{
case .Entity:
// On the C# side we actually differentiate between an Entity and the Script
// in the sense that getting an entity and a script yields two different results (one creates a new Entity-Class instance, the other returns the actual instance).
// But here its just easier to always use the script instance.
// Obviously breaks once we support multiple scripts per entity.
UUID referencedId = field.GetData<UUID>();
MonoObject* referencedEntity = GetOrCreateScriptReferenceInstance(referencedId);
script.Instance.SetFieldValue(scriptField, referencedEntity);
case .Component:
// Get or create entity reference
UUID referencedId = field.GetData<UUID>();
//MonoObject* referencedEntity = GetOrCreateScriptReferenceInstance(referencedId);
MonoType* fieldMonoType = scriptField.GetMonoType();
SharpType componentType = ScriptEngine.GetSharpType(fieldMonoType);
var componentClass = ComponentClasses[componentType.FullName];
//MonoObject* componentInstance = CreateComponentReferenceInstance(componentClass, referencedEntity);
MonoObject* componentInstance = CreateComponentReferenceInstance(componentClass, referencedId);
script.Instance.SetFieldValue(scriptField, componentInstance);
componentType.ReleaseRef();
case .Struct:
// TODO: Handle Structs when copying fields to instance.
case .Class:
// TODO: Handle Class when copying fields to instance.
case .String:
// TODO: Handle String when copying fields to instance.
default:
script.Instance.SetFieldValue(scriptField, field._data);
}
}
}
public static void CopyFieldsToInstance(ScriptComponent* targetScript, ScriptComponent* sourceScript, Dictionary<UUID, UUID> sourceIdToTargetId) public static void CopyFieldsToInstance(ScriptComponent* targetScript, ScriptComponent* sourceScript, Dictionary<UUID, UUID> sourceIdToTargetId)
{ {
Debug.Profiler.ProfileFunction!(); Debug.Profiler.ProfileFunction!();
@@ -661,6 +581,8 @@ static class ScriptEngine
static (MonoAssembly* assembly, MonoImage* image) LoadAssembly(StringView filepath, bool loadPDB = false) static (MonoAssembly* assembly, MonoImage* image) LoadAssembly(StringView filepath, bool loadPDB = false)
{ {
Debug.Profiler.ProfileFunction!();
MonoAssembly* assembly = LoadCSharpAssembly(filepath, loadPDB); MonoAssembly* assembly = LoadCSharpAssembly(filepath, loadPDB);
MonoImage* image = Mono.mono_assembly_get_image(assembly); MonoImage* image = Mono.mono_assembly_get_image(assembly);
@@ -680,6 +602,8 @@ static class ScriptEngine
private static void GetEntitiesFromAssemblies() private static void GetEntitiesFromAssemblies()
{ {
Debug.Profiler.ProfileFunction!();
if (s_AppAssemblyImage == null) if (s_AppAssemblyImage == null)
return; return;
@@ -706,8 +630,11 @@ static class ScriptEngine
} }
} }
} }
private static void GetComponentsFromAssemblies() private static void GetComponentsFromAssemblies()
{ {
Debug.Profiler.ProfileFunction!();
MonoTableInfo* typeDefinitionsTable = Mono.mono_image_get_table_info(s_CoreAssemblyImage, .MONO_TABLE_TYPEDEF); MonoTableInfo* typeDefinitionsTable = Mono.mono_image_get_table_info(s_CoreAssemblyImage, .MONO_TABLE_TYPEDEF);
int32 numTypes = Mono.mono_table_info_get_rows(typeDefinitionsTable); int32 numTypes = Mono.mono_table_info_get_rows(typeDefinitionsTable);
@@ -736,18 +663,41 @@ static class ScriptEngine
{ {
Debug.Profiler.ProfileFunction!(); Debug.Profiler.ProfileFunction!();
Mono.mono_domain_set(s_RootDomain, false); Log.EngineLogger.Info("Reloading script assemblies.");
Dictionary<UUID, SerializedObject> serializedData = scope .();
SerializeScriptInstances(serializedData);
Mono.mono_domain_set(s_RootDomain, true);
Mono.mono_domain_unload(s_AppDomain); Mono.mono_domain_unload(s_AppDomain);
LoadScriptAssemblies(); LoadScriptAssemblies();
// TODO: ScriptFields might get added {
// TODO: ScriptField Types may change after reload! Debug.Profiler.ProfileScope!("Initialize Instances");
// TODO: Scripts may be renamed (probably not detectable (trivially))
// TODO: Reload in play mode // We need to create a new instance for every entity
// Only scripts that were changed should actually be reinstatiated for (let (id, scriptInstance) in _entityScriptInstances)
{
if (Context.GetEntityByID(id) case .Ok(let entity))
{
ScriptComponent* script = entity.GetComponent<ScriptComponent>();
InitializeInstance(entity, script);
}
else
{
Log.EngineLogger.AssertDebug(false, "Entities script was just serialized but the entity doesn't exist anymore.");
}
}
}
DeserializeScriptInstances(serializedData);
ClearDictionaryAndDeleteValues!(serializedData);
Log.EngineLogger.Info("Script assemblies reloaded!");
} }
public static void Shutdown() public static void Shutdown()
@@ -838,58 +788,6 @@ static class ScriptEngine
return new SharpClass(classNamespace, className, Mono.mono_class_get_image(monoClass), scriptType); return new SharpClass(classNamespace, className, Mono.mono_class_get_image(monoClass), scriptType);
} }
public static void CreateScriptFieldMap(Entity entity)
{
Log.EngineLogger.AssertDebug(entity.IsValid);
if (_entityFields.TryGetValue(entity.UUID, var entityFields))
{
ClearDictionaryAndDeleteKeys!(entityFields);
}
else
{
entityFields = new ScriptFieldMap();
_entityFields.Add(entity.UUID, entityFields);
}
let scriptComponent = entity.GetComponent<ScriptComponent>();
ScriptClass scriptClass = GetScriptClass(scriptComponent.ScriptClassName);
Log.EngineLogger.AssertDebug(scriptClass != null);
void AddFieldsToMap(Dictionary<StringView, ScriptField> classFields, StringView baseName)
{
for (let (fieldName, field) in classFields)
{
/*if (field.FieldType == .Struct && field.SharpType != null)
{
AddFieldsToMap(field.SharpType.Fields, scope $"{baseName}{fieldName}.");
}
else
{
entityFields.Add(new $"{baseName}{fieldName}", ScriptFieldInstance(field.FieldType));
}*/
entityFields.Add(new String(fieldName), ScriptFieldInstance(field.FieldType));
}
}
AddFieldsToMap(scriptClass.Fields, "");
}
public static ScriptFieldMap GetScriptFieldMap(Entity entity)
{
Log.EngineLogger.AssertDebug(entity.IsValid);
let uuid = entity.UUID;
// TODO: Entites bekommen noch kein Eintrag hier!!
Log.EngineLogger.AssertDebug(_entityFields.ContainsKey(uuid));
return _entityFields[uuid];
}
/// Returns the script instance or null. /// Returns the script instance or null.
public static MonoObject* GetManagedInstance(UUID entityId) public static MonoObject* GetManagedInstance(UUID entityId)
{ {
@@ -955,11 +853,10 @@ static class ScriptEngine
/// Serializes all script instances /// Serializes all script instances
public static void SerializeScriptInstances(Dictionary<UUID, SerializedObject> allObjects) public static void SerializeScriptInstances(Dictionary<UUID, SerializedObject> allObjects)
{ {
Debug.Profiler.ProfileFunction!();
for (let (id, script) in _entityScriptInstances) for (let (id, script) in _entityScriptInstances)
{ {
if (script.ScriptClass.ClassName != "SerializationTest")
continue;
SerializedObject object = new SerializedObject(allObjects, script.EntityId); SerializedObject object = new SerializedObject(allObjects, script.EntityId);
object.Serialize(script); object.Serialize(script);
} }
@@ -968,11 +865,10 @@ static class ScriptEngine
/// Deserializes the given data into the script instances /// Deserializes the given data into the script instances
public static void DeserializeScriptInstances(Dictionary<UUID, SerializedObject> allObjects) public static void DeserializeScriptInstances(Dictionary<UUID, SerializedObject> allObjects)
{ {
Debug.Profiler.ProfileFunction!();
for (let (id, script) in _entityScriptInstances) for (let (id, script) in _entityScriptInstances)
{ {
if (script.ScriptClass.ClassName != "SerializationTest")
continue;
if (allObjects.TryGetValue(id, let object)) if (allObjects.TryGetValue(id, let object))
{ {
object.Deserialize(script); object.Deserialize(script);
@@ -60,6 +60,8 @@ static class ScriptGlue
public static void RegisterManagedComponents() public static void RegisterManagedComponents()
{ {
Debug.Profiler.ProfileFunction!();
s_AddComponentMethods.Clear(); s_AddComponentMethods.Clear();
s_HasComponentMethods.Clear(); s_HasComponentMethods.Clear();
s_RemoveComponentMethods.Clear(); s_RemoveComponentMethods.Clear();
+6 -12
View File
@@ -209,7 +209,8 @@ class SceneSerializer
// 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)
{ {
let fields = ScriptEngine.GetScriptFieldMap(entity); // TODO: Serialize Script Instance!
/*let fields = ScriptEngine.GetScriptFieldMap(entity);
writer.Identifier("Fields"); writer.Identifier("Fields");
@@ -234,7 +235,7 @@ class SceneSerializer
Serialize.Value(writer, ValueView(fieldInstance.Type.GetBeefType(), &fieldInstance.[Friend]_data), gBonEnv); Serialize.Value(writer, ValueView(fieldInstance.Type.GetBeefType(), &fieldInstance.[Friend]_data), gBonEnv);
} }
} }
} }*/
} }
}); });
} }
@@ -624,7 +625,8 @@ 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); // TODO: We need a new way to Serialize/Deserialize these fields!
/*ScriptEngine.CreateScriptFieldMap(entity);
var fields = ScriptEngine.GetScriptFieldMap(entity); var fields = ScriptEngine.GetScriptFieldMap(entity);
@@ -708,19 +710,11 @@ class SceneSerializer
} }
Try!(reader.ArrayBlockEnd()); Try!(reader.ArrayBlockEnd());
} }*/
} }
return .Ok; return .Ok;
})); }));
/*
SerializeComponent<ScriptComponent>(writer, entity, "ScriptComponent", scope (component) =>
{
Serialize.Value(writer, "ScriptClass", component.ScriptClass?.FullName);
});
*/
default: default:
Log.EngineLogger.AssertDebug(false, "Unknown component type"); Log.EngineLogger.AssertDebug(false, "Unknown component type");
//return .Err; //return .Err;
+6 -1
View File
@@ -453,7 +453,8 @@ public static class EntitySerializer
} }
else if (fieldType.IsClass) else if (fieldType.IsClass)
{ {
// SerializeClass(fieldName, fieldValue, fieldType); //DeserializeClass(fieldName, fieldValue, fieldType);
Log.Error("Class is not yet implemented.");
} }
else else
{ {
@@ -565,6 +566,10 @@ public static class EntitySerializer
public static bool SerializeField(FieldInfo fieldInfo) public static bool SerializeField(FieldInfo fieldInfo)
{ {
// TODO: We want to be able to serialize static fields in the future!
if (fieldInfo.IsStatic)
return false;
var serializeField = fieldInfo.HasCustomAttribute<SerializeFieldAttribute>(); var serializeField = fieldInfo.HasCustomAttribute<SerializeFieldAttribute>();
var dontSerializeField = fieldInfo.HasCustomAttribute<DontSerializeFieldAttribute>(); var dontSerializeField = fieldInfo.HasCustomAttribute<DontSerializeFieldAttribute>();
//var hideField = fieldInfo.HasCustomAttribute<HideInEditorAttribute>(); //var hideField = fieldInfo.HasCustomAttribute<HideInEditorAttribute>();