From c94ffbef64cf1fd153c353f242ae5d495a0aecfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sun, 24 Aug 2025 16:54:26 +0200 Subject: [PATCH] Made release buildable --- .../src/EditWindows/ContentBrowserWindow.bf | 2 +- GlitchyEngine.Benchmark/src/BasicBenchmark.bf | 29 +++- .../src/SimpleStringList.bf | 54 ------- GlitchyEngine/BeefProj.toml | 2 +- .../src/Collections/SimpleStringList.bf | 135 +++++++++++++++++ GlitchyEngine/src/Scripting/ScriptEngine.bf | 3 +- GlitchyEngine/src/Scripting/ScriptGlue.bf | 4 + .../src/Serialization/SerializedObject.bf | 143 +++++++++++++----- GlitchyEngine/vendor/freetype | 2 +- ScriptCore/BeefProj.toml | 3 + ScriptCore/ScriptCore.csproj | 2 - ScriptCore/ScriptGlue.cs | 105 ++++++++++--- .../Serialization/DictionarySerializer.cs | 6 +- ScriptCore/Serialization/SerializedObject.cs | 51 ++++--- vendor/NetHostBeef | 2 +- 15 files changed, 396 insertions(+), 147 deletions(-) delete mode 100644 GlitchyEngine.Benchmark/src/SimpleStringList.bf create mode 100644 GlitchyEngine/src/Collections/SimpleStringList.bf diff --git a/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf b/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf index 15c3b28..986e78a 100644 --- a/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf +++ b/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf @@ -1376,7 +1376,7 @@ namespace GlitchyEditor.EditWindows private void CopySelectedFiles(bool cutFiles) { _filesToCopy.ClearAndDeleteItems(); - _selectedFiles.Select((file) => new String(file)).ToList(_filesToCopy); + _selectedFiles.Select(scope (file) => new String(file)).ToList(_filesToCopy); _cutFiles = cutFiles; } diff --git a/GlitchyEngine.Benchmark/src/BasicBenchmark.bf b/GlitchyEngine.Benchmark/src/BasicBenchmark.bf index 7fe7015..9d29667 100644 --- a/GlitchyEngine.Benchmark/src/BasicBenchmark.bf +++ b/GlitchyEngine.Benchmark/src/BasicBenchmark.bf @@ -4,6 +4,9 @@ using System; using System.Collections; using System.IO; using System.Diagnostics; +using Bon.Integrated; +using Bon; + namespace GlitchyEngine.Benchmark; class BasicBenchmark @@ -47,6 +50,8 @@ class BasicBenchmark Shutdown(); }*/ +#define DEBUGGING + public static void BenchmarkSerializer() { // Before benchmark @@ -66,8 +71,15 @@ class BasicBenchmark benchmark.BeforeRun = scope [&]() => { serializer = new .(); }; + benchmark.Run = scope [&]() => { serializer.SerializeScriptInstances(); + +#if DEBUGGING + gBonEnv.serializeFlags |= .Verbose; // Output is formatted for editing & readability + let serialized = Bon.Bon.Serialize(serializer.[Friend]SerializedObjects, .. scope String()); + File.WriteAllText("ser.bon", serialized); +#endif }; benchmark.AfterRun = scope [&]() => { delete serializer; @@ -76,11 +88,13 @@ class BasicBenchmark scene.Stop(); }; +#if !DEBUGGING Console.WriteLine("Benchmark empty scene:"); { benchmark.Name = "Empty Scene"; benchmark.Run(resultCollector); } +#endif List references = new List(1000); defer delete references; @@ -109,12 +123,12 @@ class BasicBenchmark references.Clear(); } - +#if !DEBUGGING BenchmarkTrivialEntity(1); BenchmarkTrivialEntity(10); BenchmarkTrivialEntity(100); BenchmarkTrivialEntity(1000); - +#endif void BenchmarkLargeEntity(int entityCount) { Console.WriteLine($"Benchmark {entityCount} Large Entity:"); @@ -130,7 +144,12 @@ class BasicBenchmark benchmark.Name = scope $"Large Entity {entityCount}"; benchmark.RunInfo["EntityCount"] = Variant.Create(entityCount); + +#if !DEBUGGING benchmark.Run(resultCollector); +#else + benchmark.Run(resultCollector, 0, 1); +#endif for (Entity e in references) { @@ -139,11 +158,15 @@ class BasicBenchmark references.Clear(); } - + +#if !DEBUGGING BenchmarkLargeEntity(1); BenchmarkLargeEntity(10); BenchmarkLargeEntity(100); BenchmarkLargeEntity(1000); +#else + BenchmarkLargeEntity(1000); +#endif // After Bench Shutdown(); diff --git a/GlitchyEngine.Benchmark/src/SimpleStringList.bf b/GlitchyEngine.Benchmark/src/SimpleStringList.bf deleted file mode 100644 index d84b21e..0000000 --- a/GlitchyEngine.Benchmark/src/SimpleStringList.bf +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections; - -namespace GlitchyEngine.Benchmark; - -class SimpleStringList -{ - private append String _buffer = .(); - private append List<(int start, int length)> _views = .(); - - public StringView this[int index] - { - get - { - let (start, length) = _views[index]; - return StringView(_buffer, start, length); - } - } - - public StringView Add(StringView text) - { - int lengthBefore = _buffer.Length; - - _buffer.Append(text); - - StringView newStringView = StringView(_buffer, lengthBefore); - _views.Add((lengthBefore, newStringView.Length)); - - // Append a null terminator to ensure that every string can easily be a C string. - // Append after creating string view so it isn't part of the view. - _buffer.Append('\0'); - - return newStringView; - } - - public delegate void CustomToString(String buffer); - - // Allows adding a string directly into the buffer, using the internal buffer as target for e.g. ToString-opeations. - public StringView Add(CustomToString toString) - { - int lengthBefore = _buffer.Length; - - toString(_buffer); - - StringView newStringView = StringView(_buffer, lengthBefore); - _views.Add((lengthBefore, newStringView.Length)); - - // Append a null terminator to ensure that every string can easily be a C string. - // Append after creating string view so it isn't part of the view. - _buffer.Append('\0'); - - return newStringView; - } -} diff --git a/GlitchyEngine/BeefProj.toml b/GlitchyEngine/BeefProj.toml index 406d8eb..f17c9a0 100644 --- a/GlitchyEngine/BeefProj.toml +++ b/GlitchyEngine/BeefProj.toml @@ -21,7 +21,7 @@ PreprocessorMacros = ["IMGUI", "RELEASE", "GE_PROFILE"] PreprocessorMacros = ["TEST", "GE_WINDOWS"] [Configs.Test.Win64] -PreprocessorMacros = ["TEST", "GE_WINDOWS"] +PreprocessorMacros = ["TEST", "GE_PROFILE", "GE_PROFILE_RENDERER", "GE_PROFILE_RESOURCES", "IMGUI", "GE_RESOURCE_DEBUG_NAMES"] [Configs.Debug.Win64] PreprocessorMacros = ["DEBUG", "GE_PROFILE", "GE_PROFILE_RENDERER", "GE_PROFILE_RESOURCES", "IMGUI", "GE_RESOURCE_DEBUG_NAMES"] diff --git a/GlitchyEngine/src/Collections/SimpleStringList.bf b/GlitchyEngine/src/Collections/SimpleStringList.bf new file mode 100644 index 0000000..a51d023 --- /dev/null +++ b/GlitchyEngine/src/Collections/SimpleStringList.bf @@ -0,0 +1,135 @@ +using System; +using System.Collections; + +namespace GlitchyEngine.Collections; + +struct ListStringView : IHashable +{ + public LessSimpleStringList List; + public int Index; + + public static explicit operator StringView(ListStringView view) + { + return view.List?[view.Index] ?? .(); + } + + [Commutable] + public static bool operator==(ListStringView s1, StringView s2) + { + return s2.Equals((StringView)s1); + } + + public int GetHashCode() + { + return ((StringView)this).GetHashCode(); + } +} + +class LessSimpleStringList +{ + private append String _buffer = .(); + private append List<(int start, int length)> _views = .(); + + public StringView this[int index] + { + get + { + let (start, length) = _views[index]; + return StringView(_buffer, start, length); + } + } + + public ListStringView Add(StringView text) + { + int lengthBefore = _buffer.Length; + + _buffer.Append(text); + + StringView newStringView = StringView(_buffer, lengthBefore); + _views.Add((lengthBefore, newStringView.Length)); + + // Append a null terminator to ensure that every string can easily be a C string. + // Append after creating string view so it isn't part of the view. + _buffer.Append('\0'); + + return .() + { + List = this, + Index = _views.Count - 1 + }; + } + + public delegate void CustomToString(String buffer); + + // Allows adding a string directly into the buffer, using the internal buffer as target for e.g. ToString-opeations. + public ListStringView Add(CustomToString toString) + { + int lengthBefore = _buffer.Length; + + toString(_buffer); + + StringView newStringView = StringView(_buffer, lengthBefore); + _views.Add((lengthBefore, newStringView.Length)); + + // Append a null terminator to ensure that every string can easily be a C string. + // Append after creating string view so it isn't part of the view. + _buffer.Append('\0'); + + return .() + { + List = this, + Index = _views.Count - 1 + }; + } +} + + +class SimpleStringList +{ + private append String _buffer = .(); + private append List<(int start, int length)> _views = .(); + + public StringView this[int index] + { + get + { + let (start, length) = _views[index]; + return StringView(_buffer, start, length); + } + } + + public StringView Add(StringView text) + { + int lengthBefore = _buffer.Length; + + _buffer.Append(text); + + StringView newStringView = StringView(_buffer, lengthBefore); + _views.Add((lengthBefore, newStringView.Length)); + + // Append a null terminator to ensure that every string can easily be a C string. + // Append after creating string view so it isn't part of the view. + _buffer.Append('\0'); + + return newStringView; + } + + public delegate void CustomToString(String buffer); + + // Allows adding a string directly into the buffer, using the internal buffer as target for e.g. ToString-opeations. + public StringView Add(CustomToString toString) + { + int lengthBefore = _buffer.Length; + + toString(_buffer); + + StringView newStringView = StringView(_buffer, lengthBefore); + _views.Add((lengthBefore, newStringView.Length)); + + // Append a null terminator to ensure that every string can easily be a C string. + // Append after creating string view so it isn't part of the view. + _buffer.Append('\0'); + + return newStringView; + } +} diff --git a/GlitchyEngine/src/Scripting/ScriptEngine.bf b/GlitchyEngine/src/Scripting/ScriptEngine.bf index 501ac74..38a4c36 100644 --- a/GlitchyEngine/src/Scripting/ScriptEngine.bf +++ b/GlitchyEngine/src/Scripting/ScriptEngine.bf @@ -484,7 +484,8 @@ static class ScriptEngine s_RootDomain = null;*/ } - public static NewScriptClass GetScriptClass(StringView name) + /// Returns the script class with the given name, or null, if no such class exists. + private static NewScriptClass GetScriptClass(StringView name) { EntityClasses.TryGetValue(name, let scriptClass); diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 01ce165..c01d096 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -1442,6 +1442,8 @@ static class ScriptGlue [RegisterCall, CallingConvention(.Cdecl)] static void Serialization_SerializeField(void* serializationContext, SerializationType type, StringView fieldName, void* valueObject, StringView fullTypeName) { + Debug.Profiler.ProfileFunction!(); + SerializedObject context = Internal.UnsafeCastToObject(serializationContext) as SerializedObject; Log.EngineLogger.AssertDebug(context != null); @@ -1452,6 +1454,8 @@ static class ScriptGlue [RegisterCall, CallingConvention(.Cdecl)] static void Serialization_CreateObject(void* currentContext, bool isStatic, StringView typeName, out void* newContext, out UUID newId) { + Debug.Profiler.ProfileFunction!(); + SerializedObject context = Internal.UnsafeCastToObject(currentContext) as SerializedObject; Log.EngineLogger.AssertDebug(context != null); diff --git a/GlitchyEngine/src/Serialization/SerializedObject.bf b/GlitchyEngine/src/Serialization/SerializedObject.bf index 401e8ab..83f9081 100644 --- a/GlitchyEngine/src/Serialization/SerializedObject.bf +++ b/GlitchyEngine/src/Serialization/SerializedObject.bf @@ -6,6 +6,8 @@ using System.Collections; using Bon.Integrated; using Bon; using System.Reflection; +using GlitchyEngine.Debug; +using GlitchyEngine.Collections; namespace GlitchyEngine.Serialization; @@ -19,12 +21,14 @@ class SerializedObject { public uint8[16] RawData; public StringView StringView; - public (String FullTypeName, UUID ID) EngineObject; + public ListStringView ListStringView; + public (StringView FullTypeName, UUID ID) EngineObject; + public (ListStringView FullTypeName, UUID ID) ListEngineObject; static this() { - // This is important, because we expect 16 Bytes on the C# side - Compiler.Assert(sizeof(Self) == 16); + // This is important, because we expect 24 Bytes on the C# side + Compiler.Assert(sizeof(Self) == 24); } } @@ -39,13 +43,17 @@ class SerializedObject public ScriptInstanceSerializer Serializer; - private List _ownedString = new List() ~ DeleteContainerAndItems!(_); + private append LessSimpleStringList _betterOwnedStrings = .(); + + //private List _ownedString = new List() ~ DeleteContainerAndItems!(_); - public append Dictionary Fields = .(); + public append Dictionary Fields = .(); [AllowAppend] public this(ScriptInstanceSerializer serializer, bool isStatic, StringView? typeName, UUID? id = null) { + //Profiler.ProfileFunction!(); + String typeNameCopy = append String(typeName.Value); Serializer = serializer; @@ -84,41 +92,47 @@ class SerializedObject private void AddField(StringView fieldName, SerializationType fieldType, FieldData data) { - String nameCopy = new String(fieldName); - _ownedString.Add(nameCopy); + ListStringView view = _betterOwnedStrings.Add(fieldName); + //String nameCopy = new String(fieldName); + //_ownedString.Add(nameCopy); - Fields.Add(nameCopy, (fieldType, data)); + Fields.Add(view, (fieldType, data)); } public void AddField(StringView name, SerializationType primitiveType, void* value, StringView fullTypeName) { + Profiler.ProfileFunction!(); + FieldData data = .(); switch (primitiveType) { case .String, .Enum: + Profiler.ProfileScope!("String/Enum"); // If the string is null, we store a nullptr and 0-length - StringView valueView = StringView(null, 0); + ListStringView valueView = .(); if (value != null) { - String stringValue = new String((char8*)value); + StringView stringToCopy = *(StringView*)value; + //String stringValue = new String(stringToCopy); - _ownedString.Add(stringValue); - - valueView = stringValue; + //_ownedString.Add(stringValue); + valueView = _betterOwnedStrings.Add(stringToCopy); } - data.StringView = valueView; + data.ListStringView = valueView; case .EngineObjectReference: - String typeName = null; + Profiler.ProfileScope!("EngineObject"); + ListStringView typeNameView = .(); if (!fullTypeName.IsEmpty) { - _ownedString.Add(new String(fullTypeName)); + typeNameView = _betterOwnedStrings.Add(fullTypeName); + //_ownedString.Add(new String(fullTypeName)); } - data.EngineObject = (FullTypeName: typeName, ID: *(UUID*)value); + data.ListEngineObject = (FullTypeName: typeNameView, ID: *(UUID*)value); default: SetDataSimple(primitiveType, value, ref data); } @@ -158,9 +172,11 @@ class SerializedObject public void GetField(StringView fieldName, SerializationType expectedType, uint8* target, out SerializationType actualType) { + // Profiler.ProfileFunction!(); + actualType = .None; - if (!Fields.TryGetValue(fieldName, let field)) + if (!Fields.TryGetValueAlt(fieldName, let field)) return; actualType = field.PrimitiveType; @@ -178,13 +194,20 @@ class SerializedObject switch (field.PrimitiveType) { case .String, .Enum: - *(StringView*)target = field.Data.StringView; + *(StringView*)target = (StringView)field.Data.ListStringView; case .EngineObjectReference: - let engineObjectData = field.Data.EngineObject; - - *(char8**)target = engineObjectData.FullTypeName?.CStr(); + let engineObjectData = field.Data.ListEngineObject; + + (*(FieldData*)(void*)target).EngineObject.FullTypeName = (StringView)engineObjectData.FullTypeName; + (*(FieldData*)(void*)target).EngineObject.ID = engineObjectData.ID; + + /**(char8**)target = engineObjectData.FullTypeName?.CStr(); *(int*)(target + sizeof(char8*)) = engineObjectData.FullTypeName?.Length ?? 0; - *(UUID*)(target + sizeof(char8*) + sizeof(int)) = engineObjectData.ID; + *(UUID*)(target + sizeof(char8*) + sizeof(int)) = engineObjectData.ID;*/ + + /**(char8**)target = engineObjectData.FullTypeName?.CStr(); + *(int*)(target + sizeof(char8*)) = engineObjectData.FullTypeName?.Length ?? 0; + *(UUID*)(target + sizeof(char8*) + sizeof(int)) = engineObjectData.ID;*/ default: // Most values can simply be copied, the conversion will be done in C# #unwarn @@ -194,21 +217,25 @@ class SerializedObject public void Serialize(NewScriptInstance scriptInstance) { + Profiler.ProfileFunction!(); ScriptEngine.Classes.EntitySerializer.Serialize(scriptInstance, this); } public void Deserialize(NewScriptInstance scriptInstance) { + // Profiler.ProfileFunction!(); ScriptEngine.Classes.EntitySerializer.Deserialize(scriptInstance, this); } public void SerializeStaticFields(NewScriptClass scriptClass) { + Profiler.ProfileFunction!(); ScriptEngine.Classes.EntitySerializer.SerializeStatic(scriptClass, this); } public void DeserializeStaticFields(NewScriptClass scriptClass) { + // Profiler.ProfileFunction!(); ScriptEngine.Classes.EntitySerializer.DeserializeStatic(scriptClass, this); } @@ -225,6 +252,7 @@ class SerializedObject static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state) { + // Profiler.ProfileFunction!(); Log.EngineLogger.Assert(value.type == typeof(Self)); SerializedObject object = value.Get(); @@ -237,7 +265,7 @@ class SerializedObject for (let (fieldName, field) in object.Fields) { - writer.Identifier(fieldName); + writer.Identifier((StringView)fieldName); switch (field.PrimitiveType) { case .Bool: @@ -250,7 +278,8 @@ class SerializedObject writer.Type("string"); } #unwarn - Serialize.Value(writer, ValueView(typeof(StringView), &field.Data.StringView), environment); + StringView dataView = (StringView)field.Data.ListStringView; + Serialize.Value(writer, ValueView(typeof(StringView), &dataView), environment); case .Int8: writer.Type("int8"); @@ -290,11 +319,12 @@ class SerializedObject case .Enum: writer.Type("Enum"); #unwarn - Serialize.Value(writer, ValueView(typeof(StringView), &field.Data.StringView), environment); + StringView dataView = (StringView)field.Data.ListStringView; + Serialize.Value(writer, ValueView(typeof(StringView), &dataView), environment); case .EngineObjectReference: writer.Type("EngineObject"); if (field.Data.EngineObject.FullTypeName != null) - writer.Type(field.Data.EngineObject.FullTypeName); + writer.Type((StringView)field.Data.ListEngineObject.FullTypeName); Serialize.Value(writer, field.Data.EngineObject.ID, environment); case .ObjectReference: writer.outStr.Append('&'); @@ -476,26 +506,45 @@ class SerializedObject } else if (fieldTypeName == "Enum") { - String enumValue = new .(); - Deserialize.String!(reader, ref enumValue, environment); + Result result = .Ok; + + Result GetEnum(String outBuffer) + { + var outBuffer; + Deserialize.String!(reader, ref outBuffer, environment); + return .Ok; + } + + fieldData.ListStringView = object._betterOwnedStrings.Add(scope [&](s) => + { + result = GetEnum(s); + }); + + /*String enumValue = new .(); + Deserialize.String!(reader, ref enumValue, environment);*/ - object._ownedString.Add(enumValue); - fieldData.StringView = enumValue; + //object._ownedString.Add(enumValue); + //fieldData.ListStringView = object._betterOwnedStrings.Add(enumValue); + //fieldData.StringView = enumValue; fieldType = .Enum; - + + Try!(result); + break HandleField; } else if (fieldTypeName == "EngineObject") { - String entityTypeName = null; + ListStringView entityTypeName = .(); if (reader.IsTyped()) { StringView entityTypeNameView = Try!(reader.Type()); - entityTypeName = new String(entityTypeNameView); + //entityTypeName = new String(entityTypeNameView); - object._ownedString.Add(entityTypeName); + //object._ownedString.Add(entityTypeName); + + entityTypeName = object._betterOwnedStrings.Add(entityTypeNameView); } // Object Reference @@ -505,7 +554,7 @@ class SerializedObject UUID reference = UUID(id); fieldType = .EngineObjectReference; - fieldData.EngineObject = (FullTypeName: entityTypeName, ID: reference); + fieldData.ListEngineObject = (FullTypeName: entityTypeName, ID: reference); break HandleField; } @@ -534,12 +583,30 @@ class SerializedObject } else { - String target = new .(); + Result result = .Ok; + + Result GetStringValue(String outBuffer) + { + var outBuffer; + Deserialize.String!(reader, ref outBuffer, environment); + return .Ok; + } + + fieldData.ListStringView = object._betterOwnedStrings.Add(scope [&](s) => + { + result = GetStringValue(s); + }); + + fieldType = .String; + + Try!(result); + + /*String target = new .(); Deserialize.String!(reader, ref target, environment); object._ownedString.Add(target); fieldData.StringView = target; - fieldType = .String; + fieldType = .String;*/ } } // else if (Deserialize.IsNumber(reader, let numberType)) diff --git a/GlitchyEngine/vendor/freetype b/GlitchyEngine/vendor/freetype index 6567746..9168ee0 160000 --- a/GlitchyEngine/vendor/freetype +++ b/GlitchyEngine/vendor/freetype @@ -1 +1 @@ -Subproject commit 6567746ffd45d469c8e2b38690ca3dea4e70f69e +Subproject commit 9168ee0f2cb0e7708a10b9dd2171a2a917dbdce0 diff --git a/ScriptCore/BeefProj.toml b/ScriptCore/BeefProj.toml index a266402..0a42bd2 100644 --- a/ScriptCore/BeefProj.toml +++ b/ScriptCore/BeefProj.toml @@ -6,3 +6,6 @@ TargetType = "CustomBuild" [Configs.Debug.Win64] PostBuildCmds = ["dotnet build \"$(WorkspaceDir)/ScriptCore/ScriptCore.csproj\" -c Debug"] + +[Configs.Release.Win64] +PostBuildCmds = ["dotnet build \"$(WorkspaceDir)/ScriptCore/ScriptCore.csproj\" -c Release"] diff --git a/ScriptCore/ScriptCore.csproj b/ScriptCore/ScriptCore.csproj index 12ce6d2..4df795d 100644 --- a/ScriptCore/ScriptCore.csproj +++ b/ScriptCore/ScriptCore.csproj @@ -3,7 +3,6 @@ net9.0 latest - true GlitchyEngine true true @@ -25,7 +24,6 @@ - diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index 1014b85..7dcd7d0 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -1,6 +1,8 @@ using GlitchyEngine.Core; using GlitchyEngine.Editor; using GlitchyEngine.Extensions; +using GlitchyEngine.Native; +using GlitchyEngine.Physics; using GlitchyEngine.Serialization; using ImGuiNET; using System; @@ -11,8 +13,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; -using GlitchyEngine.Native; -using GlitchyEngine.Physics; +using System.Text; namespace GlitchyEngine; @@ -552,43 +553,111 @@ internal static unsafe partial class ScriptGlue instance = null; } - public static void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string fieldName, object? valueObject, string fullTypeName) + public static void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string fieldName, object? valueObject, string? fullTypeName) { - StringView fieldNameConverted = StringView.FromManagedString(fieldName); - StringView fullTypeNameConverted = StringView.FromManagedString(fullTypeName); + StringView fieldNameConverted; + { + int maxByteCount = Encoding.UTF8.GetMaxByteCount(fieldName.Length); + byte* pointer = stackalloc byte[checked(maxByteCount + 1)]; + int bytes = Encoding.UTF8.GetBytes((ReadOnlySpan)fieldName, new Span(pointer, maxByteCount)); + pointer[bytes] = 0; + fieldNameConverted = new StringView(pointer, bytes); + } - void* valueObjectConverted = null; - bool deleteValueObject = false; + StringView fullTypeNameConverted = new StringView(); + if (fullTypeName != null) + { + int maxByteCount = Encoding.UTF8.GetMaxByteCount(fullTypeName.Length); + byte* pointer = stackalloc byte[checked(maxByteCount + 1)]; + int bytes = Encoding.UTF8.GetBytes((ReadOnlySpan)fullTypeName, new Span(pointer, maxByteCount)); + pointer[bytes] = 0; + fullTypeNameConverted = new StringView(pointer, bytes); + } switch (type) { case SerializationType.String: case SerializationType.Enum: + bool onHeap = false; + StringView nativeString = new StringView(); + if (valueObject is string stringValue) { - StringView nativeString = StringView.FromManagedString(stringValue); - valueObjectConverted = nativeString.Utf8Ptr; - deleteValueObject = true; + int maxByteCount = Encoding.UTF8.GetMaxByteCount(stringValue.Length); + int actualByteCount = checked(maxByteCount + 1); + + onHeap = actualByteCount > 256; + + byte* pointer; + if (!onHeap) + { + byte* pointer2 = stackalloc byte[actualByteCount]; + pointer = pointer2; + } + else + { + pointer = (byte*)NativeMemory.Alloc((nuint)actualByteCount); + } + + int bytes = Encoding.UTF8.GetBytes((ReadOnlySpan)stringValue, new Span(pointer, maxByteCount)); + pointer[bytes] = 0; + nativeString = new StringView(pointer, bytes); } + + _engineFunctions.Serialization_SerializeField((void*)serializationContext, type, fieldNameConverted, &nativeString, fullTypeNameConverted); + + if (onHeap) + StringView.FreeNativeMemory(nativeString); break; default: if (valueObject is not null) { + // This is probably the dirtiest piece of C# Code, that I have ever seen. + // We first take a pointer the Object (which itself is a reference type -> object* is a double pointer!) + // We then dereference this double pointer, which gives us a pointer to the objects internals. + // These are Header (64bit), Method Table (64bit), and the objects content (at least another 64bit) + // I believe, that the actual reference points to the method table however and not the header (compare gcenv.object.h) + // Below we calculate a pointer to the objects contents by adding the size of a pointer. + #pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type object?* objectRef = &valueObject; // Skip Object Header (IntPtr) + Method Table (IntPtr) - valueObjectConverted = (byte*)*(IntPtr*)objectRef + sizeof(IntPtr); + byte* valueRef = (byte*)*(IntPtr*)objectRef + sizeof(IntPtr); + float* fRef = (float*)(void*)valueRef; + _engineFunctions.Serialization_SerializeField((void*)serializationContext, type, fieldNameConverted, valueRef, fullTypeNameConverted); } break; } - - _engineFunctions.Serialization_SerializeField((void*)serializationContext, type, fieldNameConverted, valueObjectConverted, fullTypeNameConverted); - - NativeMemory.Free(fieldNameConverted.Utf8Ptr); - NativeMemory.Free(fullTypeNameConverted.Utf8Ptr); - if (deleteValueObject) - NativeMemory.Free(valueObjectConverted); + //StringView.FreeNativeMemory(fieldNameConverted); + //StringView.FreeNativeMemory(fullTypeNameConverted); + } + + public static void Serialization_SerializeValueType(IntPtr serializationContext, SerializationType type, string fieldName, in T value, string? fullTypeName) where T : unmanaged + { + StringView fieldNameConverted; + { + int maxByteCount = Encoding.UTF8.GetMaxByteCount(fieldName.Length); + byte* pointer = stackalloc byte[checked(maxByteCount + 1)]; + int bytes = Encoding.UTF8.GetBytes((ReadOnlySpan)fieldName, new Span(pointer, maxByteCount)); + pointer[bytes] = 0; + fieldNameConverted = new StringView(pointer, bytes); + } + + StringView fullTypeNameConverted = new StringView(); + if (fullTypeName != null) + { + int maxByteCount = Encoding.UTF8.GetMaxByteCount(fullTypeName.Length); + byte* pointer = stackalloc byte[checked(maxByteCount + 1)]; + int bytes = Encoding.UTF8.GetBytes((ReadOnlySpan)fullTypeName, new Span(pointer, maxByteCount)); + pointer[bytes] = 0; + fullTypeNameConverted = new StringView(pointer, bytes); + } + + fixed (T* valuePtr = &value) + { + _engineFunctions.Serialization_SerializeField((void*)serializationContext, type, fieldNameConverted, valuePtr, fullTypeNameConverted); + } } #endregion Custom engine call implementations diff --git a/ScriptCore/Serialization/DictionarySerializer.cs b/ScriptCore/Serialization/DictionarySerializer.cs index 4793fbb..e11669a 100644 --- a/ScriptCore/Serialization/DictionarySerializer.cs +++ b/ScriptCore/Serialization/DictionarySerializer.cs @@ -27,7 +27,7 @@ public static class DictionarySerializer if (fieldValue == null) { // Write a null pointer early out - container.AddField(fieldName, SerializationType.ObjectReference, UUID.Zero); + container.AddValueTypeField(fieldName, SerializationType.ObjectReference, UUID.Zero); return; } @@ -43,7 +43,7 @@ public static class DictionarySerializer IDictionary dictionary = (IDictionary)fieldValue; ICollection collection = (ICollection)fieldValue; - context.AddField("Count", SerializationType.Int32, collection.Count); + context.AddValueTypeField("Count", SerializationType.Int32, collection.Count); int index = 0; @@ -61,7 +61,7 @@ public static class DictionarySerializer } // Write the reference to our dictionary into the field of the parent. - container.AddField(fieldName, SerializationType.ObjectReference, context.Id); + container.AddValueTypeField(fieldName, SerializationType.ObjectReference, context.Id); } /// diff --git a/ScriptCore/Serialization/SerializedObject.cs b/ScriptCore/Serialization/SerializedObject.cs index 2fc456a..2ed9abb 100644 --- a/ScriptCore/Serialization/SerializedObject.cs +++ b/ScriptCore/Serialization/SerializedObject.cs @@ -97,6 +97,13 @@ public class SerializedObject ScriptGlue.Serialization_SerializeField(_internalContext, serializationType, completeFieldName, value, fullTypeName); } + + public void AddValueTypeField(string fieldName, SerializationType serializationType, in T value, string? fullTypeName = null) where T : unmanaged + { + string completeFieldName = $"{_structScopeName}{fieldName}"; + + ScriptGlue.Serialization_SerializeValueType(_internalContext, serializationType, completeFieldName, value, fullTypeName); + } public void Serialize(Entity entity) { @@ -236,7 +243,7 @@ public class SerializedObject { if (listObject == null) { - AddField(fieldName, SerializationType.ObjectReference, UUID.Zero); + AddValueTypeField(fieldName, SerializationType.ObjectReference, UUID.Zero); } else { @@ -248,7 +255,7 @@ public class SerializedObject { IList list = (IList)listObject; - context.AddField("Count", SerializationType.Int32, list.Count); + context.AddValueTypeField("Count", SerializationType.Int32, list.Count); for (int i = 0; i < list.Count; i++) { @@ -257,47 +264,43 @@ public class SerializedObject context.SerializeField($"{i}", element, element?.GetType() ?? elementType); } } - - AddField(fieldName, SerializationType.ObjectReference, context._id); + + AddValueTypeField(fieldName, SerializationType.ObjectReference, context._id); } } public void SerializePrimitive(string fieldName, object fieldValue, Type fieldType) { - SerializationType type = SerializationType.None; - if (fieldType == typeof(bool)) - type = SerializationType.Bool; + AddValueTypeField(fieldName, SerializationType.Bool, (bool)fieldValue); else if (fieldType == typeof(char)) - type = SerializationType.Char; + AddValueTypeField(fieldName, SerializationType.Char, (char)fieldValue); else if (fieldType == typeof(byte)) - type = SerializationType.UInt8; + AddValueTypeField(fieldName, SerializationType.UInt8, (byte)fieldValue); else if (fieldType == typeof(sbyte)) - type = SerializationType.Int8; + AddValueTypeField(fieldName, SerializationType.Int8, (sbyte)fieldValue); else if (fieldType == typeof(ushort)) - type = SerializationType.UInt16; + AddValueTypeField(fieldName, SerializationType.UInt16, (ushort)fieldValue); else if (fieldType == typeof(short)) - type = SerializationType.Int16; + AddValueTypeField(fieldName, SerializationType.Int16, (short)fieldValue); else if (fieldType == typeof(uint)) - type = SerializationType.UInt32; + AddValueTypeField(fieldName, SerializationType.UInt32, (uint)fieldValue); else if (fieldType == typeof(int)) - type = SerializationType.Int32; + AddValueTypeField(fieldName, SerializationType.Int32, (int)fieldValue); else if (fieldType == typeof(ulong)) - type = SerializationType.UInt64; + AddValueTypeField(fieldName, SerializationType.UInt64, (ulong)fieldValue); else if (fieldType == typeof(long)) - type = SerializationType.Int64; + AddValueTypeField(fieldName, SerializationType.Int64, (long)fieldValue); else if (fieldType == typeof(float)) - type = SerializationType.Float; + AddValueTypeField(fieldName, SerializationType.Float, (float)fieldValue); else if (fieldType == typeof(double)) - type = SerializationType.Double; + AddValueTypeField(fieldName, SerializationType.Double, (double)fieldValue); else if (fieldType == typeof(decimal)) - type = SerializationType.Decimal; + AddValueTypeField(fieldName, SerializationType.Decimal, (decimal)fieldValue); else { Log.Error($"The primitive {fieldType} is not implemented."); } - - AddField(fieldName, type, fieldValue); } public void SerializeEnum(string fieldName, object? fieldValue, Type fieldType) @@ -318,13 +321,13 @@ public class SerializedObject { if (fieldType.IsSubclassOf(typeof(EngineObject))) { - AddField(fieldName, SerializationType.EngineObjectReference, ((EngineObject?)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName); + AddValueTypeField(fieldName, SerializationType.ObjectReference, ((EngineObject?)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName); } else { if (fieldValue == null) { - AddField(fieldName, SerializationType.ObjectReference, UUID.Zero); + AddValueTypeField(fieldName, SerializationType.ObjectReference, UUID.Zero); } else { @@ -335,7 +338,7 @@ public class SerializedObject context.SerializeInstanceFields(fieldValue); } - AddField(fieldName, SerializationType.ObjectReference, context._id); + AddValueTypeField(fieldName, SerializationType.ObjectReference, context._id); } } } diff --git a/vendor/NetHostBeef b/vendor/NetHostBeef index aeb0995..8a3c8fe 160000 --- a/vendor/NetHostBeef +++ b/vendor/NetHostBeef @@ -1 +1 @@ -Subproject commit aeb099576d0b75dbcfbde0d864986fc8bcca13bf +Subproject commit 8a3c8fe2571e022f152735fcd57abe53b4132c0d