diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index 39cbbdd..313eb01 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -464,7 +464,7 @@ namespace GlitchyEditor.EditWindows search = StringView(&buffer); - for (let (className, script) in ScriptEngine.EntityClasses) + for (let (className, scriptClass) in ScriptEngine.EntityClasses) { if (!search.IsWhiteSpace && !className.Contains(search, true)) continue; @@ -472,9 +472,14 @@ namespace GlitchyEditor.EditWindows if (ImGui.Selectable(className.ToScopeCStr!(), className == scriptComponent.Instance?.ScriptClass.FullName)) { - scriptComponent.Instance = new ScriptInstance(script); - scriptComponent.Instance.ReleaseRef(); - ScriptEngine.InitializeInstance(entity, scriptComponent); + //scriptComponent.Instance = new ScriptInstance(scriptClass); + // decrement refCount to 1 (scriptComponent.Instance increments its) + //scriptComponent.Instance.ReleaseRef(); + //ScriptEngine.InitializeInstance(entity, scriptComponent); + + scriptComponent.ScriptClass = scriptClass; + + ScriptEngine.CreateScriptFieldMap(entity); } } ImGui.EndPopup(); @@ -505,9 +510,11 @@ namespace GlitchyEditor.EditWindows scriptInstance.SetFieldValue(scriptField, value); } - - void ShowClassFields(SharpClass sharpClass, ScriptInstance scriptInstance) + if (scriptComponent.Instance?.IsInitialized == true) { + SharpClass sharpClass = scriptComponent.Instance.ScriptClass; + ScriptInstance scriptInstance = scriptComponent.Instance; + for (let (fieldName, scriptField) in sharpClass.Fields) { var monoField = scriptField.[Friend]_monoField; @@ -579,67 +586,124 @@ namespace GlitchyEditor.EditWindows case .Entity: // TODO! - case .Class: - // TODO! case .Enum: // TODO! case .Struct: // TODO! - /*case .Struct: - ShowStructFields(); - { - - uint8[128] bla = ?; - GetFieldValue(scriptInstance, scriptField); - - Mono.MonoObject* dings = (Mono.MonoObject*)&bla; - - //ShowFields - }*/ default: Log.EngineLogger.Error($"Unhandled field type {scriptField.FieldType}"); } } } - /*void ShowStructFields(SharpClass sharpClass, ScriptInstance scriptInstance) + else if (scriptComponent.ScriptClass != null) { - for (let (fieldName, scriptField) in sharpClass.Fields) + let scriptFields = ScriptEngine.GetScriptFieldMap(entity); + + for (var (name, field) in ref scriptFields) { - var monoField = scriptField.[Friend]_monoField; - - switch (scriptField.FieldType) + switch (field.Field.FieldType) { + case .Bool: + var value = field.GetData(); + if (ImGui.Checkbox(name.ToScopeCStr!(), &value)) + field.SetData(value); + + case .SByte: + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .S8, &value)) + field.SetData(value); + case .Short: + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .S16, &value)) + field.SetData(value); case .Int: - int32 value = GetFieldValue(scriptInstance, scriptField); - if (ImGui.DragInt(fieldName.ToScopeCStr!(), &value)) - SetFieldValue(scriptInstance, scriptField, value); + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .S32, &value)) + field.SetData(value); + case .Int2: + var value = field.GetData(); + if (ImGui.DragScalarN(name.ToScopeCStr!(), .S32, &value, 2)) + field.SetData(value); + case .Int3: + var value = field.GetData(); + if (ImGui.DragScalarN(name.ToScopeCStr!(), .S32, &value, 3)) + field.SetData(value); + case .Int4: + var value = field.GetData(); + if (ImGui.DragScalarN(name.ToScopeCStr!(), .S32, &value, 4)) + field.SetData(value); + case .Long: + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .S64, &value)) + field.SetData(value); + + case .Byte: + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .U8, &value)) + field.SetData(value); + case .UShort: + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .U16, &value)) + field.SetData(value); + case .UInt: + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .U32, &value)) + field.SetData(value); + case .ULong: + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .U64, &value)) + field.SetData(value); + case .Float: - float value = GetFieldValue(scriptInstance, scriptField); - if (ImGui.DragFloat(fieldName.ToScopeCStr!(), &value)) - SetFieldValue(scriptInstance, scriptField, value); + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .Float, &value)) + field.SetData(value); + case .float2: + var value = field.GetData(); + if (ImGui.Editfloat2(name, ref value)) + field.SetData(value); + case .float3: + var value = field.GetData(); + if (ImGui.Editfloat3(name, ref value)) + field.SetData(value); + case .float4: + var value = field.GetData(); + if (ImGui.Editfloat4(name, ref value)) + field.SetData(value); + case .Double: - double value = GetFieldValue(scriptInstance, scriptField); - if (ImGui.DragScalar(fieldName.ToScopeCStr!(), .Double, &value)) - SetFieldValue(scriptInstance, scriptField, value); - /*case .Struct: - { - - uint8[128] bla = ?; - GetFieldValue(scriptInstance, scriptField); + var value = field.GetData(); + if (ImGui.DragScalar(name.ToScopeCStr!(), .Double, &value)) + field.SetData(value); + case .Double2: + var value = field.GetData(); + if (ImGui.DragScalarN(name.ToScopeCStr!(), .Double, &value, 2)) + field.SetData(value); + case .Double3: + var value = field.GetData(); + if (ImGui.DragScalarN(name.ToScopeCStr!(), .Double, &value, 3)) + field.SetData(value); + case .Double4: + var value = field.GetData(); + if (ImGui.DragScalarN(name.ToScopeCStr!(), .Double, &value, 4)) + field.SetData(value); - Mono.MonoObject* dings = (Mono.MonoObject*)&bla; + case .Enum: + // TODO! + + //case .String: + // TODO! + + case .Entity: + // TODO! + + case .Struct: + // TODO! - //ShowFields - }*/ default: - Log.EngineLogger.Error($"Unhandled field type {scriptField.FieldType}"); + Log.EngineLogger.Error($"Unhandled field type {field.Field.FieldType}"); } } - }*/ - - if (scriptComponent.Instance?.IsInitialized == true) - { - ShowClassFields(scriptComponent.Instance.ScriptClass, scriptComponent.Instance); } } diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 6908565..083f6be 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -620,16 +620,26 @@ namespace GlitchyEditor SceneFilePath = scope String(filename); - _editorScene.ReleaseRef(); - _editorScene = new Scene(); - _editor.CurrentScene = _editorScene; - var vpSize = _editor.SceneViewportWindow.ViewportSize; - _editorScene.OnViewportResize((.)vpSize.X, (.)vpSize.Y); + using (Scene newScene = new Scene()) + { + var vpSize = _editor.SceneViewportWindow.ViewportSize; + newScene.OnViewportResize((.)vpSize.X, (.)vpSize.Y); - SceneSerializer serializer = scope .(_editorScene); - serializer.Deserialize(SceneFilePath); + SceneSerializer serializer = scope .(newScene); + let result = serializer.Deserialize(SceneFilePath); - SetReference!(_activeScene, _editorScene); + // Make sure we actually loaded something! + if (result case .Ok) + { + SetReference!(_editorScene, newScene); + _editor.CurrentScene = _editorScene; + SetReference!(_activeScene, _editorScene); + } + else + { + Log.EngineLogger.Error("Failed to load scene file."); + } + } } private void DrawOpenRecentProjectMenu() diff --git a/GlitchyEngine/src/Scripting/ScriptClass.bf b/GlitchyEngine/src/Scripting/ScriptClass.bf index c236c24..81491e0 100644 --- a/GlitchyEngine/src/Scripting/ScriptClass.bf +++ b/GlitchyEngine/src/Scripting/ScriptClass.bf @@ -23,6 +23,32 @@ public struct ScriptField } } +public struct ScriptFieldInstance +{ + public ScriptField Field; + // TODO: I hate this, but we need to be able to store an entire matrix + internal uint8[sizeof(GlitchyEngine.Math.Matrix)] _data; + + public this(ScriptField field) + { + Field = field; + _data = default; + } + + public T GetData() + { +#unwarn + return *(T*)&_data; + } + + public void SetData(T value) mut + { + *(T*)&_data = value; + } +} + +public typealias ScriptFieldMap = Dictionary; + abstract class SharpType : RefCounter { protected String _namespace ~ delete _; diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index d69ccbf..1b3913f 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -23,7 +23,7 @@ enum ScriptFieldType SByte, Short, - Int,// int2, int3, int4, + Int, Int2, Int3, Int4, Long, Byte, UShort, @@ -31,11 +31,69 @@ enum ScriptFieldType ULong, // Half, Half2, Half3, Half4, Float, float2, float3, float4, - Double, // Double2, Double3, Double4, + Double, Double2, Double3, Double4, Entity } +extension ScriptFieldType +{ + public Type GetBeefType() + { + switch(this) + { + case .Bool: + return typeof(bool); + + case .SByte: + return typeof(int8); + case .Short: + return typeof(int16); + case .Int: + return typeof(int32); + case .Int2: + return typeof(int2); + case .Int3: + return typeof(int3); + case .Int4: + return typeof(int4); + case .Long: + return typeof(int64); + + + case .Byte: + return typeof(uint8); + case .UShort: + return typeof(uint16); + case .UInt: + return typeof(uint32); + case .ULong: + return typeof(uint64); + + case .Float: + return typeof(float); + case .float2: + return typeof(float2); + case .float3: + return typeof(float3); + case .float4: + return typeof(float4); + + case .Double: + return typeof(double); + case .Double2: + return typeof(double2); + case .Double3: + return typeof(double3); + case .Double4: + return typeof(double4); + + default: + return null; + } + } +} + static sealed class ScriptEngineHelper { private static Dictionary _scriptFieldTypes = new Dictionary() @@ -53,9 +111,10 @@ static sealed class ScriptEngineHelper ("System.UInt64", .ULong), ("System.Single", .Float), - ("GlitchyEngine.Math.float2", .float2), - ("GlitchyEngine.Math.float3", .float3), - ("GlitchyEngine.Math.float4", .float4), + // TODO: We probably want to switch the C#-Library to use the superior floatN-Names + ("GlitchyEngine.Math.Vector2", .float2), + ("GlitchyEngine.Math.Vector3", .float3), + ("GlitchyEngine.Math.Vector4", .float4), ("System.Double", .Double), @@ -102,6 +161,8 @@ static class ScriptEngine public static Scene Context => s_Context; + private static Dictionary _entityFields = new .() ~ DeleteDictionaryAndValues!(_); + internal static class Attributes { internal static MonoClass* s_ShowInEditorAttribute; @@ -148,6 +209,22 @@ static class ScriptEngine //_entityScriptInstances[entity.UUID] = script.Instance..AddRef(); script.Instance.Instantiate(entity.UUID); + + CopyEditorFieldsToInstance(entity, script); + } + + private static void CopyEditorFieldsToInstance(Entity entity, ScriptComponent* script) + { + // 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 fiels = GetScriptFieldMap(entity); + + for (var (fieldName, field) in fiels) + { + // TODO: a litte assertion maybe? + + script.Instance.SetFieldValue(field.Field, field._data); + } } private static MonoAssembly* LoadCSharpAssembly(StringView assemblyPath) @@ -290,7 +367,39 @@ static class ScriptEngine } } + public static void CreateScriptFieldMap(Entity entity) + { + Log.EngineLogger.AssertDebug(entity.IsValid); + if (_entityFields.TryGetValue(entity.UUID, var entityFields)) + { + entityFields.Clear(); + } + else + { + entityFields = new Dictionary(); + _entityFields.Add(entity.UUID, entityFields); + } + + let scriptComponent = entity.GetComponent(); + + for (let (fieldName, field) in scriptComponent.ScriptClass.Fields) + { + entityFields.Add(fieldName, ScriptFieldInstance(field)); + } + } + + 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]; + } diff --git a/GlitchyEngine/src/Scripting/ScriptInstance.bf b/GlitchyEngine/src/Scripting/ScriptInstance.bf index e15a408..d7d15c2 100644 --- a/GlitchyEngine/src/Scripting/ScriptInstance.bf +++ b/GlitchyEngine/src/Scripting/ScriptInstance.bf @@ -68,88 +68,4 @@ class ScriptInstance : RefCounter { _scriptClass.SetFieldValue(_instance, field.[Friend]_monoField, value); } - - public void CopyEditorFieldsTo(ScriptInstance target) - { - for (let (fieldName, field) in ScriptClass.Fields) - { - switch (field.FieldType) - { - case .Bool: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - - case .SByte: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - case .Short: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - case .Int: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - case .Long: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - - case .Byte: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - case .UShort: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - case .UInt: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - case .ULong: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - - case .Float: - var value = GetFieldValue(field); - target.SetFieldValue(field, value); - - /*case .float2: - GetFieldValue(field, var value); - if (ImGui.Editfloat2(fieldName, ref value)) - SetFieldValue(field, value); - case .float3: - GetFieldValue(field, var value); - if (ImGui.Editfloat3(fieldName, ref value)) - SetFieldValue(field, value); - case .float4: - GetFieldValue(field, var value); - if (ImGui.Editfloat4(fieldName, ref value)) - SetFieldValue(field, value); - - case .Double: - var value = GetFieldValue(field); - if (ImGui.DragScalar(fieldName.ToScopeCStr!(), .Double, &value)) - SetFieldValue(field, value); - - case .Entity: - // TODO! - - case .Class: - // TODO! - case .Enum: - // TODO! - case .Struct: - // TODO!*/ - /*case .Struct: - ShowStructFields(); - { - - uint8[128] bla = ?; - GetFieldValue(scriptField); - - Mono.MonoObject* dings = (Mono.MonoObject*)&bla; - - //ShowFields - }*/ - default: - Log.EngineLogger.Error($"Unhandled field type {field.FieldType}"); - } - } - } } \ No newline at end of file diff --git a/GlitchyEngine/src/World/Components/Components.bf b/GlitchyEngine/src/World/Components/Components.bf index ffea7dc..d27ee83 100644 --- a/GlitchyEngine/src/World/Components/Components.bf +++ b/GlitchyEngine/src/World/Components/Components.bf @@ -507,8 +507,16 @@ namespace GlitchyEngine.World struct ScriptComponent : IDisposableComponent { + private ScriptClass _scriptClass = null; + private ScriptInstance _instance = null; + public ScriptClass ScriptClass + { + get => _scriptClass; + set mut => SetReference!(_scriptClass, value); + } + public ScriptInstance Instance { [Inline] @@ -517,12 +525,16 @@ namespace GlitchyEngine.World set mut => SetReference!(_instance, value); } + /// Gets whether or not this script component has a script assigned (that means ScriptClass isn't null). + public bool HasScript => _scriptClass != null; + public bool IsInitialized => _instance?.IsInitialized ?? false; public bool IsCreated => _instance?.IsCreated ?? false; public void Dispose() mut { + ReleaseRefAndNullify!(_scriptClass); ReleaseRefAndNullify!(_instance); } } diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index db498ce..fd3613c 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -93,13 +93,11 @@ namespace GlitchyEngine.World Entity targetEntity = target.GetEntityByID(sourceEntity.UUID); ScriptComponent* targetComponent = targetEntity.AddComponent(); - targetComponent.Instance = new ScriptInstance(sourceComponent.Instance.ScriptClass); + targetComponent.Instance = new ScriptInstance(sourceComponent.ScriptClass); targetComponent.Instance..ReleaseRef(); - // We need an instance so we can copy the variables to it + // Initializes the created instance ScriptEngine.InitializeInstance(targetEntity, targetComponent); - - sourceComponent.Instance.CopyEditorFieldsTo(targetComponent.Instance); } // Copy transforms... needs special handling for the Parent<->Child relations diff --git a/GlitchyEngine/src/World/SceneSerializer.bf b/GlitchyEngine/src/World/SceneSerializer.bf index 6c40bed..18237de 100644 --- a/GlitchyEngine/src/World/SceneSerializer.bf +++ b/GlitchyEngine/src/World/SceneSerializer.bf @@ -8,6 +8,7 @@ using GlitchyEngine.Core; using System.Collections; using GlitchyEngine.Renderer; using GlitchyEngine.Content; +using GlitchyEngine.Scripting; namespace GlitchyEngine.World; @@ -185,6 +186,28 @@ class SceneSerializer { Serialize.Value(writer, "Material", component.Material); }); + + SerializeComponent(writer, entity, "ScriptComponent", scope (component) => + { + Serialize.Value(writer, "ScriptClass", component.ScriptClass?.FullName); + + if (component.HasScript) + { + let fields = ScriptEngine.GetScriptFieldMap(entity); + + for (var (fieldName, fieldInstance) in fields) + { + switch (fieldInstance.Field.FieldType) + { + case .Entity, .Enum, .Class, .Struct: + // TODO: implement + default: + writer.Identifier(fieldName); + Serialize.Value(writer, ValueView(fieldInstance.Field.FieldType.GetBeefType(), &fieldInstance.[Friend]_data), gBonEnv); + } + } + } + }); } writer.EntryEnd(); @@ -512,6 +535,73 @@ class SceneSerializer return .Ok; })); + case "ScriptComponent": + Try!(DeserializeComponent(reader, entity, scope (component) => + { + String scriptClassName = null; + + Try!(Deserialize.Value(reader, "ScriptClass", out scriptClassName)); + + if (scriptClassName != null) + { + if (ScriptEngine.EntityClasses.TryGetValue(scriptClassName, let scriptClass)) + { + component.ScriptClass = scriptClass; + } + + delete scriptClassName; + } + + if (component.HasScript) + { + // This whole operation is technically a bit junk, because we are not guaranteed to successfully deserialize the scene, + // however we are editing the ScriptEngine because it doesn't care about which scene is active right now. + // The UUID should be unique enough, however if they do overlap (e.g. loading the current scene or simply because we are unlucky) + // we will replace the fields of the active scene, even if deserialization fails... + // 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); + + var fields = ScriptEngine.GetScriptFieldMap(entity); + + Try!(reader.EntryEnd()); + + bool first = true; + + while (reader.ObjectHasMore()) + { + if (first) + first = false; + else + Try!(reader.EntryEnd()); + + // TODO: We could think about doing the Try! a little smarter... + // However we always might just fail to deserialize, so it doesn't really matter. + + StringView fieldName = Try!(reader.Identifier()); + + if (fields.ContainsKey(fieldName)) + { + var field = ref fields[fieldName]; + + void* data = &field.[Friend]_data; + + Try!(Deserialize.Value(reader, ValueView(field.Field.FieldType.GetBeefType(), data), gBonEnv)); + } + } + } + + return .Ok; + })); + /* + + + SerializeComponent(writer, entity, "ScriptComponent", scope (component) => + { + Serialize.Value(writer, "ScriptClass", component.ScriptClass?.FullName); + }); + */ default: Log.EngineLogger.AssertDebug(false, "Unknown component type"); //return .Err;