using GlitchyEngine.Core; using GlitchyEngine.Scripting; using Mono; using System; using System.Collections; using Bon.Integrated; using Bon; using System.Reflection; using GlitchyEngine.Debug; using GlitchyEngine.Collections; namespace GlitchyEngine.Serialization; using internal GlitchyEngine.Scripting; [BonTarget] class SerializedObject { [Union] public struct FieldData { public uint8[16] RawData; public StringView StringView; public ListStringView ListStringView; public (StringView FullTypeName, UUID ID) EngineObject; public (ListStringView FullTypeName, UUID ID) ListEngineObject; static this() { // This is important, because we expect 24 Bytes on the C# side Compiler.Assert(sizeof(Self) == 24); } } /// ID used to identify the object represented by this SerializedObject. In case that the represented object is a /// Script Instance, the ID is the UUID of the Entity, random otherwise. /// Todo: This sucks because we don't know all UUIDs beforehand and might accidentally assign the ID of an Entity to some class public UUID Id; public bool IsStatic; public String TypeName ~ delete:append _; public ScriptInstanceSerializer Serializer; private append LessSimpleStringList _betterOwnedStrings = .(); //private List _ownedString = new List() ~ DeleteContainerAndItems!(_); 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; IsStatic = isStatic; if (id == null) { // Make sure we have no duplicate keys repeat { Id = UUID.Create(); } while (Serializer.ContainsId(Id)); } else { Id = id.Value; } Serializer.AddObject(this); TypeName = typeNameCopy; } private void SetDataSimple(SerializationType fieldType, void* rawValue, ref FieldData data) { Internal.MemCpy(&data.RawData, rawValue, fieldType.GetSize()); } private void SetRawData(SerializationType fieldType, T rawValue, ref FieldData data) { #unwarn Internal.MemCpy(&data.RawData, &rawValue, fieldType.GetSize()); } private void AddField(StringView fieldName, SerializationType fieldType, FieldData data) { ListStringView view = _betterOwnedStrings.Add(fieldName); //String nameCopy = new String(fieldName); //_ownedString.Add(nameCopy); 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 ListStringView valueView = .(); if (value != null) { StringView stringToCopy = *(StringView*)value; //String stringValue = new String(stringToCopy); //_ownedString.Add(stringValue); valueView = _betterOwnedStrings.Add(stringToCopy); } data.ListStringView = valueView; case .EngineObjectReference: Profiler.ProfileScope!("EngineObject"); ListStringView typeNameView = .(); if (!fullTypeName.IsEmpty) { typeNameView = _betterOwnedStrings.Add(fullTypeName); //_ownedString.Add(new String(fullTypeName)); } data.ListEngineObject = (FullTypeName: typeNameView, ID: *(UUID*)value); default: SetDataSimple(primitiveType, value, ref data); } AddField(name, primitiveType, data); } /*private void ConvertType(SerializationType fieldType, in FieldData fieldData, SerializationType expectedType, uint8* target) { #unwarn void* rawData = &fieldData.RawData; void SimpleConversion() where TExpected: operator explicit TActual { TActual actualValue = *(TActual*)rawData; *(TExpected*)target = (TExpected)actualValue; } if (fieldType.IsNumber && expectedType.IsNumber) { Convert.ConvertTo(Variant.Create(fieldType.)); } switch(fieldType) { case .Float: if (expectedType == .Double) SimpleConversion(); case .Double: if (expectedType == .Float) SimpleConversion(); default: Log.EngineLogger.Error($"No field data conversion from {fieldType} to {expectedType}."); return; } }*/ public void GetField(StringView fieldName, SerializationType expectedType, uint8* target, out SerializationType actualType) { // Profiler.ProfileFunction!(); actualType = .None; if (!Fields.TryGetValueAlt(fieldName, let field)) return; actualType = field.PrimitiveType; /*if (expectedType != field.PrimitiveType) { if (field.PrimitiveType.CanConvertTo(expectedType)) { ConvertType(field.PrimitiveType, field.Data, expectedType, target); } return; }*/ switch (field.PrimitiveType) { case .String, .Enum: *(StringView*)target = (StringView)field.Data.ListStringView; case .EngineObjectReference: 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;*/ /**(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 Internal.MemCpy(target, &field.Data.RawData, sizeof(FieldData)); } } 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); } static this { gBonEnv.typeHandlers.Add(typeof(SerializedObject), ((.)new => AssetSerialize, null)); } static T GetFieldDataAs(FieldData fieldData) { #unwarn return *(T*)&fieldData.RawData; } static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state) { // Profiler.ProfileFunction!(); Log.EngineLogger.Assert(value.type == typeof(Self)); SerializedObject object = value.Get(); writer.Type(object.TypeName); using (writer.ObjectBlock()) { Serialize.Value(writer, "ID", object.Id, environment); for (let (fieldName, field) in object.Fields) { writer.Identifier((StringView)fieldName); switch (field.PrimitiveType) { case .Bool: Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .Char: Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .String: if (field.Data.StringView.IsNull) { writer.Type("string"); } #unwarn StringView dataView = (StringView)field.Data.ListStringView; Serialize.Value(writer, ValueView(typeof(StringView), &dataView), environment); case .Int8: writer.Type("int8"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .Int16: writer.Type("int16"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .Int32: writer.Type("int32"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .Int64: writer.Type("int64"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .UInt8: writer.Type("uint8"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .UInt16: writer.Type("uint16"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .UInt32: writer.Type("uint32"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .UInt64: writer.Type("uint64"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .Float: writer.Type("float"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .Double: writer.Type("double"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .Decimal: writer.Type("decimal"); Serialize.Value(writer, GetFieldDataAs(field.Data), environment); case .Enum: writer.Type("Enum"); #unwarn StringView dataView = (StringView)field.Data.ListStringView; Serialize.Value(writer, ValueView(typeof(StringView), &dataView), environment); case .EngineObjectReference: writer.Type("EngineObject"); if (field.Data.EngineObject.FullTypeName.IsEmpty) writer.Type((StringView)field.Data.ListEngineObject.FullTypeName); Serialize.Value(writer, field.Data.EngineObject.ID, environment); case .ObjectReference: writer.outStr.Append('&'); GetFieldDataAs(field.Data).ToString(writer.outStr); writer.EntryEnd(); default: Log.EngineLogger.Warning("Unhandled primitive type"); } } } //AssetHandle.[Friend]AssetSerialize(writer, ValueView(typeof(AssetHandle), &handle), environment, state); //writer.String(identifier); } private static Result NestedIdentifier(BonReader reader) { let name = ParseNestedName(reader); if (name.Length == 0) reader.[Friend]Error!("Expected identifier name"); Try!(reader.ConsumeEmpty()); if (!reader.[Friend]Check('=')) reader.[Friend]Error!("Expected equals"); Try!(reader.ConsumeEmpty()); return name; } private static StringView ParseNestedName(BonReader reader) { var nameLen = 0; for (; nameLen < reader.inStr.Length; nameLen++) { let char = reader.inStr[nameLen]; if (!char.IsLetterOrDigit && char != '_' && char != '.') break; } let name = reader.inStr.Substring(0, nameLen); reader.inStr.RemoveFromStart(nameLen); return name; } public static Result BonDeserialize(BonReader reader, ScriptInstanceSerializer scriptSerializer, int sceneFileVersion, BonEnvironment environment = gBonEnv) { StringView type = Try!(reader.Type()); Try!(reader.ObjectBlock()); Try!(Deserialize.Value(reader, "ID", let objectId, environment)); SerializedObject object = new SerializedObject(scriptSerializer, false, type, objectId); while (reader.ObjectHasMore()) { // TODO: Consume until we hit a comma. In case the current field couldn't be deserialized Try!(reader.EntryEnd()); FieldData fieldData = .(); SerializationType fieldType = .None; StringView fieldName = Try!(NestedIdentifier(reader)); StringView fieldTypeName = String.Empty; if (reader.IsTyped()) { fieldTypeName = reader.Type(); } HandleField: do { if (!fieldTypeName.IsWhiteSpace) { if (fieldTypeName == "float") { float floatValue = 0.0f; Deserialize.[Friend]Float!(typeof(float), reader, ValueView(typeof(float), &floatValue)); fieldType = .Float; object.SetRawData(fieldType, floatValue, ref fieldData); break HandleField; } else if (fieldTypeName == "double") { double doubleValue = 0.0f; Deserialize.[Friend]Float!(typeof(double), reader, ValueView(typeof(double), &doubleValue)); fieldType = .Double; object.SetRawData(fieldType, doubleValue, ref fieldData); break HandleField; } else if (fieldTypeName.StartsWith("int")) { int64 intValue = 0; Deserialize.[Friend]Integer!(typeof(int64), reader, ValueView(typeof(int64), &intValue)); if (fieldTypeName.EndsWith("8")) { fieldType = .Int8; object.SetRawData(fieldType, (int8)intValue, ref fieldData); } else if (fieldTypeName.EndsWith("16")) { fieldType = .Int16; object.SetRawData(fieldType, (int16)intValue, ref fieldData); } else if (fieldTypeName.EndsWith("32")) { fieldType = .Int32; object.SetRawData(fieldType, (int32)intValue, ref fieldData); } else if (fieldTypeName.EndsWith("64")) { fieldType = .Int64; object.SetRawData(fieldType, (int64)intValue, ref fieldData); } else { Log.ClientLogger.Error($"Unknown integer type {fieldTypeName}"); } break HandleField; } else if (fieldTypeName.StartsWith("uint")) { uint64 intValue = 0; Deserialize.[Friend]Integer!(typeof(uint64), reader, ValueView(typeof(uint64), &intValue)); if (fieldTypeName.EndsWith("8")) { fieldType = .UInt8; object.SetRawData(fieldType, (uint8)intValue, ref fieldData); } else if (fieldTypeName.EndsWith("16")) { fieldType = .UInt16; object.SetRawData(fieldType, (uint16)intValue, ref fieldData); } else if (fieldTypeName.EndsWith("32")) { fieldType = .UInt32; object.SetRawData(fieldType, (uint32)intValue, ref fieldData); } else if (fieldTypeName.EndsWith("64")) { fieldType = .UInt64; object.SetRawData(fieldType, (uint64)intValue, ref fieldData); } else { Log.ClientLogger.Error($"Unknown integer type {fieldTypeName}"); } break HandleField; } else if (fieldTypeName == "decimal") { StringView numberView = Try!(reader.Floating()); if (reader.inStr.StartsWith('m')) reader.inStr.RemoveFromStart(1); MonoDecimal decimalValue = Try!(MonoDecimal.Parse(numberView)); fieldType = .Decimal; object.SetRawData(fieldType, decimalValue, ref fieldData); break HandleField; } else if (fieldTypeName == "Enum") { 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.ListStringView = object._betterOwnedStrings.Add(enumValue); //fieldData.StringView = enumValue; fieldType = .Enum; Try!(result); break HandleField; } else if (fieldTypeName == "EngineObject") { ListStringView entityTypeName = .(); if (reader.IsTyped()) { StringView entityTypeNameView = Try!(reader.Type()); //entityTypeName = new String(entityTypeNameView); //object._ownedString.Add(entityTypeName); entityTypeName = object._betterOwnedStrings.Add(entityTypeNameView); } // Object Reference uint64 id = 0; Deserialize.[Friend]Integer!(typeof(uint64), reader, ValueView(typeof(uint64), &id)); UUID reference = UUID(id); fieldType = .EngineObjectReference; fieldData.ListEngineObject = (FullTypeName: entityTypeName, ID: reference); break HandleField; } } if (Deserialize.IsBool(reader)) { bool boolValue = Try!(reader.Bool()); fieldType = .Bool; object.SetDataSimple(fieldType, &boolValue, ref fieldData); } else if (reader.[Friend]Check('\'', false)) { char32 c = Try!(reader.Char()); fieldType = .Char; object.SetDataSimple(fieldType, &c, ref fieldData); } else if (reader.[Friend]Check('"', false) || fieldTypeName == "string") { if (reader.inStr.StartsWith("null")) { fieldData.StringView = null; fieldType = .String; reader.inStr.RemoveFromStart(4); } else { 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;*/ } } // else if (Deserialize.IsNumber(reader, let numberType)) // { // if (numberType == .Double) // { // double double = Try!(Deserialize.ParseFloat()); // fieldType = .Double; // object.SetDataSimple(fieldType, &reference, ref fieldData); // } // else if (numberType == .Float) // { // float double = Try!(Deserialize.ParseFloat()); // fieldType = .Float; // object.SetDataSimple(fieldType, &reference, ref fieldData); // } // else if (numberType == .Integer) // { // bool isNegative = false; // if (reader.inStr.StartsWith('-')) // { // isNegative = true; // } // float double = Try!(Deserialize.ParseInt()); // fieldType = .Float; // object.SetDataSimple(fieldType, &reference, ref fieldData); // } // } else if (reader.IsReference()) { // Object Reference StringView referenceView = Try!(reader.Reference()); let referenceResult = uint64.Parse(referenceView); if (referenceResult case .Ok(let referenceInt)) { UUID reference = UUID(referenceInt); fieldType = .ObjectReference; object.SetRawData(fieldType, reference, ref fieldData); } } } if (fieldType != .None) { object.AddField(fieldName, fieldType, fieldData); } Try!(reader.ConsumeEmpty()); } Try!(reader.ObjectBlockEnd()); return .Ok(object); } }