Save and Load script data from files

This commit is contained in:
Simon Lübeß
2024-01-07 18:35:33 +01:00
parent a302a26f80
commit bfc9d4a0a5
12 changed files with 994 additions and 224 deletions
+8 -2
View File
@@ -1023,7 +1023,7 @@ namespace GlitchyEditor
}
/// Sets the current editor scene
private void SetEditorScene(Scene scene)
private void SetEditorScene(Scene scene, Dictionary<UUID, SerializedObject> serializedObjects = null)
{
if (_editorScene != null)
{
@@ -1037,6 +1037,12 @@ namespace GlitchyEditor
if (_editorScene != null)
{
_editorScene.StartRuntime();
if (serializedObjects != null)
{
// Reconstruct state
ScriptEngine.DeserializeScriptInstances(serializedObjects);
}
}
}
@@ -1139,7 +1145,7 @@ namespace GlitchyEditor
// Make sure we actually loaded something!
if (result case .Ok)
{
SetEditorScene(newScene);
SetEditorScene(newScene, serializer.SerializedObjects);
String relativePath = scope .();
Path.GetRelativePath(filename, _currentProject.WorkspacePath, relativePath);
@@ -33,5 +33,67 @@ namespace Bon.Integrated
value = ?;
return Deserialize.Value(reader, ValueView(typeof(T), &value), env);
}
public static bool IsBool(BonReader reader, BonEnvironment env = gBonEnv)
{
return reader.inStr.StartsWith(bool.TrueString, StringComparison.OrdinalIgnoreCase) || reader.inStr.StartsWith(bool.FalseString, StringComparison.OrdinalIgnoreCase);
}
public enum NumberType
{
None,
Integer,
Float,
Double,
Decimal
}
/*public static bool IsNumber(BonReader reader, out NumberType numberType)
{
numberType = .None;
StringView tmpView = reader.inStr;
if (tmpView.StartsWith('-') || tmpView.StartsWith('+'))
tmpView.RemoveFromStart(1);
if (tmpView.StartsWith('NaN', StringComparison.OrdinalIgnoreCase) || tmpView.StartsWith('infinity', StringComparison.OrdinalIgnoreCase))
{
numberType = .Double;
return true;
}
bool isNumber = false;
while (true)
{
if (tmpView[0].IsNumber)
{
isNumber = true;
tmpView.RemoveFromStart(1);
}
else if (tmpView.StartsWith('.'))
{
// We also don't allow floats like .5f
if (!isNumber)
return false;
tmpView.RemoveFromStart(1);
// Err on the side of caution and assume double for precision
numberType = .Double;
}
}
if (tmpView.StartsWith('f'))
numberType = .Float;
else if (tmpView.StartsWith('m'))
numberType = .Decimal;
else if (numberType != .Double)
numberType = .Integer;
return isNumber;
}*/
}
}
+151
View File
@@ -0,0 +1,151 @@
using System;
using Bon.Integrated;
using Bon;
namespace GlitchyEngine.Scripting;
/// A struct providing access to the components of a mono decimal.
struct MonoDecimal
{
[Bitfield<uint16>(.Private, .BitsAt(16, 0), "_dontUse", .Read)]
[Bitfield<uint8>(.Public, .BitsAt(8, 16), "Exponent")]
[Bitfield<uint8>(.Private, .BitsAt(7, 24), "_dontUse2")]
[Bitfield<bool>(.Public, .BitsAt(1, 31), "Sign")]
private uint32 _flags;
private uint32 _high;
private uint32 _low;
private uint32 _mid;
public uint32[3] Mantissa => .(_low, _mid, _high);
static this
{
gBonEnv.typeHandlers.Add(typeof(MonoDecimal),
((.)new => AssetSerialize, new => AssetDeserialize));
}
static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state)
{
Log.EngineLogger.Assert(value.type == typeof(Self));
MonoDecimal decimal = value.Get<MonoDecimal>();
decimal.ToString(writer.outStr);
}
static Result<void> AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment, DeserializeValueState state)
{
Log.EngineLogger.Assert(value.type == typeof(Self));
return .Ok;
}
public override void ToString(String strBuffer)
{
uint64 d, r;
uint32[3] a = .(_high, _mid, _low);
char8[30] str = .();
char8* ptr = &str[29];
int digits = 0;
repeat
{
r = a [0];
d = r / 10;
r = ((r - d * 10) << 32) + a [1];
a [0] = (uint32)d;
d = r / 10;
r = ((r - d * 10) << 32) + a [2];
a [1] = (uint32)d;
d = r / 10;
r = r - d * 10;
a [2] = (uint32)d;
*ptr = '0' + (uint8)r;
ptr--;
digits++;
if (digits == Exponent)
{
*ptr = '.';
ptr--;
digits++;
}
}
while (a[0] > 0 || a[1] > 0 || a[2] > 0 || digits < (Exponent + 2));
if (Sign)
strBuffer.Append('-');
strBuffer.Append(StringView(&str[30 - digits], digits));
strBuffer.Append('m');
}
public static Result<MonoDecimal> Parse(StringView strBuffer)
{
var strBuffer;
MonoDecimal result = .();
if (strBuffer.StartsWith('-'))
{
strBuffer.RemoveFromStart(1);
result.Sign = true;
}
if (strBuffer.EndsWith('m'))
{
strBuffer.RemoveFromEnd(1);
}
int decimalPointIndex = strBuffer.IndexOf('.');
if (decimalPointIndex != -1)
{
int exponent = strBuffer.Length - decimalPointIndex - 1;
if (exponent < 0 || exponent > 28)
return .Err;
result.Exponent = (uint8)exponent;
if (strBuffer.IndexOf('.', decimalPointIndex + 1) != -1)
return .Err;
}
repeat
{
if (strBuffer[0] != '.')
{
int32 digit = strBuffer[0] - '0';
if (digit < 0 || digit > 10)
return .Err;
uint64 tmp, carry;
tmp = result._low * 10;
carry = (tmp + (uint64)digit) >> 32;
result._low = (uint32)tmp + (uint32)digit;
tmp = result._mid * 10 + carry;
carry = tmp >> 32;
result._mid = (uint32)tmp;
tmp = result._high * 10 + carry;
carry = tmp >> 32;
result._high = (uint32)tmp;
}
strBuffer.RemoveFromStart(1);
}
while (!strBuffer.IsEmpty);
return .Ok(result);
}
}
+25 -2
View File
@@ -528,6 +528,27 @@ static class ScriptGlue
id = UUID.Create();
}
[RegisterCall("ScriptGlue::Print_Decimal")]
static void Print_Decimal(MonoDecimal monoDecimal)
{
Log.ClientLogger.Info(scope $"{monoDecimal}");
}
[RegisterCall("ScriptGlue::Get_Decimal")]
static void Get_Decimal(MonoString* string, out MonoDecimal monoDecimal)
{
char8* str = Mono.mono_string_to_utf8(string);
Result<MonoDecimal> decimalo = MonoDecimal.Parse(StringView(str));
if (!(decimalo case .Ok(out monoDecimal)))
{
monoDecimal = default;
}
Mono.mono_free(str);
}
#region Application
[RegisterCall("ScriptGlue::Application_IsEditor")]
@@ -590,7 +611,7 @@ static class ScriptGlue
}
[RegisterCall("ScriptGlue::Serialization_DeserializeField")]
public static void Serialization_DeserializeField(void* internalContext, SerializationType expectedType, MonoString* fieldName, uint8* target)
public static void Serialization_DeserializeField(void* internalContext, SerializationType expectedType, MonoString* fieldName, uint8* target, out SerializationType actualType)
{
SerializedObject context = Internal.UnsafeCastToObject(internalContext) as SerializedObject;
@@ -598,7 +619,7 @@ static class ScriptGlue
char8* name = Mono.mono_string_to_utf8(fieldName);
context.GetField(StringView(name), expectedType, target);
context.GetField(StringView(name), expectedType, target, out actualType);
Mono.mono_free(name);
}
@@ -612,6 +633,8 @@ static class ScriptGlue
objectContext = null;
Log.EngineLogger.AssertDebug(context.AllObjects.ContainsKey(context.Id));
if (!context.AllObjects.TryGetValue(id, let foundObject))
return;
@@ -1,36 +1,45 @@
using GlitchyEngine.Core;
using System;
using Bon;
namespace GlitchyEngine.Serialization;
public enum SerializationType : int32
[BonTarget]
public enum SerializationType : uint32
{
case None;
case None = 0;
case Bool;
case Bool = 1 << 31;
case Char;
case String;
case TextTypes = 1 << 30;
case Int8;
case Int16;
case Int32;
case Int64;
case UInt8;
case UInt16;
case UInt32;
case UInt64;
case Char = TextTypes | 1;
case String = TextTypes | 2;
case Float;
case Double;
case Decimal;
case Number = 1 << 29;
case Integer = Number | 1 << 28;
case Enum;
case Int8 = Integer | 1;
case Int16 = Integer | 2;
case Int32 = Integer | 3;
case Int64 = Integer | 4;
case UInt8 = Integer | 5;
case UInt16 = Integer | 6;
case UInt32 = Integer | 7;
case UInt64 = Integer | 8;
case EntityReference;
case ComponentReference;
case FloatingPoint = Number | 1 << 27;
case ObjectReference;
case Float = FloatingPoint | 1;
case Double = FloatingPoint | 2;
case Decimal = FloatingPoint | 3;
case Enum = 1 << 26;
case EntityReference = 1 << 25;
case ComponentReference = 1 << 24;
case ObjectReference = 1 << 23;
public int GetSize()
{
@@ -39,7 +48,7 @@ public enum SerializationType : int32
case .Bool:
return 1;
case .Char:
return 1;
return 2;
case .Int8, .UInt8:
return 1;
case .Int16, .UInt16:
@@ -64,4 +73,16 @@ public enum SerializationType : int32
return 0;
}
}
public bool IsNumber => this.HasFlag(.Number);
public bool IsInteger => this.HasFlag(.Integer);
public bool IsFloatpoint => this.HasFlag(.FloatingPoint);
public bool CanConvertTo(SerializationType destinationType)
{
if (this.IsNumber && destinationType.IsNumber)
return true;
return false;
}
}
@@ -3,9 +3,13 @@ using GlitchyEngine.Scripting;
using Mono;
using System;
using System.Collections;
using Bon.Integrated;
using Bon;
using System.Reflection;
namespace GlitchyEngine.Serialization;
[BonTarget]
class SerializedObject
{
[Union]
@@ -61,6 +65,25 @@ class SerializedObject
TypeName = typeNameCopy;
}
private void SetDataSimple(SerializationType fieldType, void* rawValue, ref FieldData data)
{
Internal.MemCpy(&data.RawData, rawValue, fieldType.GetSize());
}
private void SetRawData<T>(SerializationType fieldType, T rawValue, ref FieldData data)
{
#unwarn
Internal.MemCpy(&data.RawData, &rawValue, fieldType.GetSize());
}
private void AddField(StringView fieldName, SerializationType fieldType, FieldData data)
{
String nameCopy = new String(fieldName);
_ownedString.Add(nameCopy);
Fields.Add(nameCopy, (fieldType, data));
}
public void AddField(StringView name, SerializationType primitiveType, MonoObject* value, MonoString* fullTypeName)
{
FieldData data = .();
@@ -103,25 +126,60 @@ class SerializedObject
default:
void* rawValue = Mono.mono_object_unbox(value);
Internal.MemCpy(&data.RawData, rawValue, primitiveType.GetSize());
String nameCopy = new String(name);
_ownedString.Add(nameCopy);
SetDataSimple(primitiveType, rawValue, ref data);
}
String nameCopy = new String(name);
_ownedString.Add(nameCopy);
Fields.Add(nameCopy, (primitiveType, data));
AddField(name, primitiveType, data);
}
public void GetField(StringView fieldName, SerializationType expectedType, uint8* target)
/*private void ConvertType(SerializationType fieldType, in FieldData fieldData, SerializationType expectedType, uint8* target)
{
#unwarn
void* rawData = &fieldData.RawData;
void SimpleConversion<TActual, TExpected>() 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<float, double>();
case .Double:
if (expectedType == .Float)
SimpleConversion<double, float>();
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)
{
actualType = .None;
if (!Fields.TryGetValue(fieldName, let field))
return;
if (expectedType != field.PrimitiveType)
actualType = field.PrimitiveType;
/*if (expectedType != field.PrimitiveType)
{
if (field.PrimitiveType.CanConvertTo(expectedType))
{
ConvertType(field.PrimitiveType, field.Data, expectedType, target);
}
return;
}*/
switch (field.PrimitiveType)
{
@@ -168,4 +226,396 @@ class SerializedObject
if (exception != null)
ScriptEngine.[Friend]HandleMonoException(exception, scriptInstance);
}
static this
{
gBonEnv.typeHandlers.Add(typeof(SerializedObject),
((.)new => AssetSerialize, null));
}
static T GetFieldDataAs<T>(FieldData fieldData)
{
return *(T*)&fieldData.RawData;
}
static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state)
{
Log.EngineLogger.Assert(value.type == typeof(Self));
SerializedObject object = value.Get<Self>();
writer.Type(object.TypeName);
using (writer.ObjectBlock())
{
Serialize.Value(writer, "ID", object.Id, environment);
for (let (fieldName, field) in object.Fields)
{
writer.Identifier(fieldName);
switch (field.PrimitiveType)
{
case .Bool:
Serialize.Value(writer, GetFieldDataAs<bool>(field.Data), environment);
case .Char:
Serialize.Value(writer, GetFieldDataAs<char16>(field.Data), environment);
case .String:
if (field.Data.StringView.IsNull)
{
writer.Type("string");
}
#unwarn
Serialize.Value(writer, ValueView(typeof(StringView), &field.Data.StringView), environment);
case .Int8:
writer.Type("int8");
Serialize.Value(writer, GetFieldDataAs<int8>(field.Data), environment);
case .Int16:
writer.Type("int16");
Serialize.Value(writer, GetFieldDataAs<int16>(field.Data), environment);
case .Int32:
writer.Type("int32");
Serialize.Value(writer, GetFieldDataAs<int32>(field.Data), environment);
case .Int64:
writer.Type("int64");
Serialize.Value(writer, GetFieldDataAs<int64>(field.Data), environment);
case .UInt8:
writer.Type("uint8");
Serialize.Value(writer, GetFieldDataAs<uint8>(field.Data), environment);
case .UInt16:
writer.Type("uint16");
Serialize.Value(writer, GetFieldDataAs<uint16>(field.Data), environment);
case .UInt32:
writer.Type("uint32");
Serialize.Value(writer, GetFieldDataAs<uint32>(field.Data), environment);
case .UInt64:
writer.Type("uint64");
Serialize.Value(writer, GetFieldDataAs<uint64>(field.Data), environment);
case .Float:
writer.Type("float");
Serialize.Value(writer, GetFieldDataAs<float>(field.Data), environment);
case .Double:
writer.Type("double");
Serialize.Value(writer, GetFieldDataAs<double>(field.Data), environment);
case .Decimal:
writer.Type("decimal");
Serialize.Value(writer, GetFieldDataAs<MonoDecimal>(field.Data), environment);
case .Enum:
writer.Type("Enum");
#unwarn
Serialize.Value(writer, ValueView(typeof(StringView), &field.Data.StringView), environment);
case .EntityReference:
writer.Type("Entity");
if (field.Data.EngineObject.FullTypeName != null)
writer.Type(field.Data.EngineObject.FullTypeName);
Serialize.Value(writer, field.Data.EngineObject.ID, environment);
case .ComponentReference:
writer.Type("Component");
if (field.Data.EngineObject.FullTypeName != null)
writer.Type(field.Data.EngineObject.FullTypeName);
Serialize.Value(writer, field.Data.EngineObject.ID, environment);
case .ObjectReference:
writer.outStr.Append('&');
GetFieldDataAs<UUID>(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<StringView> 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<SerializedObject> BonDeserialize(BonReader reader, Dictionary<UUID, SerializedObject> allObjects, BonEnvironment environment = gBonEnv)
{
StringView type = Try!(reader.Type());
Try!(reader.ObjectBlock());
Try!(Deserialize.Value<UUID>(reader, "ID", let objectId, environment));
SerializedObject object = new SerializedObject(allObjects, 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")
{
String enumValue = new .();
Deserialize.String!(reader, ref enumValue, environment);
object._ownedString.Add(enumValue);
fieldData.StringView = enumValue;
fieldType = .Enum;
break HandleField;
}
else if (fieldTypeName == "Entity" || fieldTypeName == "Component")
{
String entityTypeName = null;
if (reader.IsTyped())
{
StringView entityTypeNameView = Try!(reader.Type());
entityTypeName = new String(entityTypeNameView);
object._ownedString.Add(entityTypeName);
}
// Object Reference
uint64 id = 0;
Deserialize.[Friend]Integer!(typeof(uint64), reader, ValueView(typeof(uint64), &id));
UUID reference = UUID(id);
fieldType = (fieldTypeName == "Entity") ? .EntityReference : .ComponentReference;
fieldData.EngineObject = (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
{
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<double>());
// fieldType = .Double;
// object.SetDataSimple(fieldType, &reference, ref fieldData);
// }
// else if (numberType == .Float)
// {
// float double = Try!(Deserialize.ParseFloat<float>());
// 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<u>());
// 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);
}
}
+71 -100
View File
@@ -9,6 +9,7 @@ using System.Collections;
using GlitchyEngine.Renderer;
using GlitchyEngine.Content;
using GlitchyEngine.Scripting;
using GlitchyEngine.Serialization;
namespace GlitchyEngine.World;
@@ -26,6 +27,12 @@ class SceneSerializer
// Maps from ID in the prefab file to the actual ID in the scene.
private Dictionary<UUID, UUID> _fileToSceneId;
private Dictionary<UUID, SerializedObject> _serializedObjects ~ DeleteDictionaryAndValues!(_);
private HashSet<UUID> _objectsNotWritten;
public Dictionary<UUID, SerializedObject> SerializedObjects => _serializedObjects;
public this(Scene scene)
{
_scene = scene;
@@ -35,6 +42,17 @@ class SceneSerializer
{
Debug.Profiler.ProfileResourceFunction!();
_serializedObjects = new .();
ScriptEngine.SerializeScriptInstances(_serializedObjects);
_objectsNotWritten = new .(_serializedObjects.Count);
for (UUID key in _serializedObjects.Keys)
{
_objectsNotWritten.Add(key);
}
String buffer = scope String();
let writer = scope BonWriter(buffer, true);
var length = Serialize.Start(writer);
@@ -63,10 +81,24 @@ class SceneSerializer
}
writer.EntryEnd();
writer.Identifier("ReferencedObjects");
using (writer.ArrayBlock())
{
for (UUID id in _objectsNotWritten)
{
Serialize.Value(writer, _serializedObjects[id]);
}
}
writer.EntryEnd();
}
Serialize.End(writer, length);
delete _objectsNotWritten;
String targetDirectory = Path.GetDirectoryPath(filePath, .. scope String());
Directory.CreateDirectory(targetDirectory);
@@ -205,10 +237,19 @@ class SceneSerializer
{
Serialize.Value(writer, "ScriptClass", component.ScriptClassName);
if (_serializedObjects.TryGetValue(entity.UUID, let value))
{
Serialize.Value(writer, "Fields", value);
_objectsNotWritten.Remove(entity.UUID);
}
//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)
{
//if (ScriptEngine.GetScriptClass(component.ScriptClassName) != null)
//{
//Serialize.Value(writer, "Fields", );
// TODO: Serialize Script Instance!
/*let fields = ScriptEngine.GetScriptFieldMap(entity);
@@ -236,7 +277,7 @@ class SceneSerializer
}
}
}*/
}
//}
});
}
@@ -268,6 +309,8 @@ class SceneSerializer
{
Debug.Profiler.ProfileResourceFunction!();
_serializedObjects = new .();
_parentIdToChild = scope Dictionary<UUID, Entity>();
_entitiesMissingParent = scope List<(Entity Entity, UUID ParentId)>();
_fileToSceneId = scope Dictionary<UUID, UUID>();
@@ -308,6 +351,28 @@ class SceneSerializer
Try!(reader.ArrayBlockEnd());
Try!(reader.EntryEnd());
if (Try!(reader.Identifier()) != "ReferencedObjects")
return .Err;
Try!(reader.ArrayBlock());
first = true;
while (reader.ArrayHasMore())
{
if (!first)
{
Try!(reader.EntryEnd());
}
Try!(SerializedObject.BonDeserialize(reader, _serializedObjects, gBonEnv));
first = false;
}
Try!(reader.ArrayBlockEnd());
Try!(reader.ObjectBlockEnd());
Try!(Deserialize.End(reader));
@@ -605,112 +670,18 @@ class SceneSerializer
if (scriptClassName != null)
{
//if (ScriptEngine.EntityClasses.TryGetValue(scriptClassName, let scriptClass))
/*{
component.ScriptClass = scriptClass;
}*/
component.ScriptClassName = scriptClassName;
delete scriptClassName;
}
//if (component.HasScript)
if (ScriptEngine.GetScriptClass(component.ScriptClassName) != null)
if (reader.ObjectHasMore())
{
// 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 :)
// TODO: We need a new way to Serialize/Deserialize these fields!
/*ScriptEngine.CreateScriptFieldMap(entity);
var fields = ScriptEngine.GetScriptFieldMap(entity);
Try!(reader.EntryEnd());
if (Try!(reader.Identifier()) == "Fields")
{
Try!(reader.ArrayBlock());
bool dontRemoveComma = true;
// Remove whitespace before the check
while (reader..ConsumeEmpty().ArrayHasMore())
{
if (dontRemoveComma)
dontRemoveComma = false;
else
{
if (reader.[Friend]Check(',', false))
reader.EntryEnd();
else
reader..FileEntrySkip(1).ConsumeEmpty();
}
// 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.
// It does matter... in case of an error we should try to skip to the next entry.
StringView fieldName = Try!(reader.Identifier());
// Allocate a string on the stack, because the dictionary uses a string as key
String fieldNameString = scope .(fieldName);
Result<StringView> fieldTypeName = reader.Type();
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))
{
Log.EngineLogger.Error($"Error deserializing field type (Raw string: \"{fieldTypeName}\" of field: \"{fieldName}\" in script \"{component.ScriptClassName}\" of entity {entity.UUID} (\"{entity.Name}\")");
reader.FileEntrySkip(1);
dontRemoveComma = true;
continue;
}
uint8[sizeof(Matrix)] data = .();
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;
}
if (fields.ContainsKey(fieldNameString))
{
var field = ref fields[fieldNameString];
// Make sure the type we deserialized actually is correct.
if (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}\")");
continue;
}
field.SetData(data);
}
else
{
Log.EngineLogger.Error($"Script \"{component.ScriptClassName}\" doesn't have a field with name \"{fieldName}\". (Entity {entity.UUID} (\"{entity.Name}\"))");
}
}
Try!(reader.ArrayBlockEnd());
}*/
SerializedObject.BonDeserialize(reader, _serializedObjects, gBonEnv);
}
return .Ok;
+11
View File
@@ -51,6 +51,17 @@ public class Entity : EngineObject
Create(name, components);
}
protected void PrintDecimal(decimal value)
{
ScriptGlue.Print_Decimal(value);
}
protected decimal GetDecimal(string value)
{
ScriptGlue.Get_Decimal(value, out var dec);
return dec;
}
private void Create(string name, Type[] components)
{
ScriptGlue.Entity_Create(this, name, components, out _uuid);
+7 -1
View File
@@ -122,6 +122,12 @@ internal static class ScriptGlue
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern float4 modf_float4(float4 x, out float4 integerPart);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Print_Decimal(decimal value);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Get_Decimal(string str, out decimal value);
#endregion
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -152,7 +158,7 @@ internal static class ScriptGlue
internal static extern void Serialization_CreateObject(IntPtr currentContext, string fullTypeName, out IntPtr context, out UUID id);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern unsafe void Serialization_DeserializeField(IntPtr internalContext, SerializationType expectedType, string fieldName, byte* value);
public static extern unsafe void Serialization_DeserializeField(IntPtr internalContext, SerializationType expectedType, string fieldName, byte* value, out SerializationType actualType);
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void Serialization_GetObject(IntPtr internalContext, UUID id, out IntPtr objectContext);
@@ -38,7 +38,7 @@ public class DeserializationObject
private object _instance;
private Dictionary<string, Type> _fullNameToType = new();
private Dictionary<string, Type?> _fullNameToType = new();
private delegate object? DeserializeMethod(DeserializationObject container, string fieldName, Type fieldType);
@@ -47,7 +47,7 @@ public class DeserializationObject
/// <summary>
/// Gets the type that was originally stored in the container, or null, if the type doesn't exist.
/// </summary>
public Type StoredType
public Type? StoredType
{
get
{
@@ -88,7 +88,7 @@ public class DeserializationObject
}
}
public Type FindType(string fullName)
public Type? FindType(string fullName)
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse())
{
@@ -101,19 +101,19 @@ public class DeserializationObject
return null;
}
public Type GetTypeFromName(string fullName)
public Type? GetTypeFromName(string fullName)
{
if (_fullNameToType.TryGetValue(fullName, out Type storedType))
if (_fullNameToType.TryGetValue(fullName, out Type? storedType))
return storedType;
Type type = FindType(fullName);
Type? type = FindType(fullName);
_fullNameToType.Add(fullName, type);
return type;
}
public DeserializationObject GetDeserializedObject(UUID id)
public DeserializationObject? GetDeserializedObject(UUID id)
{
DeserializationObject context;
@@ -122,6 +122,11 @@ public class DeserializationObject
ScriptGlue.Serialization_GetObject(_internalContext, id, out IntPtr contextPtr);
if (contextPtr == IntPtr.Zero)
{
return null;
}
context = new DeserializationObject(contextPtr, id, DeserializedClasses);
DeserializedClasses.Add(id, context);
@@ -155,27 +160,42 @@ public class DeserializationObject
[FieldOffset(0)]
public EngineObjectReferenceHelper EngineObjectReference;
[FieldOffset(0)]
public ulong UInt;
[FieldOffset(0)]
public long Int;
[FieldOffset(0)]
public UUID UUID;
}
public T GetFieldValue<T>(string fieldName, SerializationType serializationType)
public T? GetFieldValue<T>(string fieldName, SerializationType expectedType)
{
return (T)GetFieldValue(fieldName, serializationType);
return (T?)GetFieldValue(fieldName, expectedType);
}
public unsafe object GetFieldValue(string fieldName, SerializationType serializationType)
public unsafe object? GetFieldValue(string fieldName, SerializationType expectedType)
{
string completeFieldName = $"{_structScopeName}{fieldName}";
// Decimal is the larges primitive we store so we use a decimal as stack allocated memory (because stackalloc doesn't seem to work :(
//decimal backingFieldOnStack = 0.0m;
byte* rawData = stackalloc byte[sizeof(DataHelper)];
//byte* rawData = (byte*)&backingFieldOnStack;
ref DataHelper dataHelper = ref Unsafe.AsRef<DataHelper>(rawData);
//byte* rawData = stackalloc byte[sizeof(DataHelper)];
//ref DataHelper dataHelper = ref Unsafe.AsRef<DataHelper>(rawData);
ScriptGlue.Serialization_DeserializeField(_internalContext, serializationType, completeFieldName, rawData);
DataHelper dataHelper = new();
byte* rawData = (byte*)Unsafe.AsPointer(ref dataHelper);
string GetString()
// //byte* rawData = stackalloc byte[sizeof(DataHelper)];
// //ref DataHelper dataHelper = ref Unsafe.AsRef<DataHelper>(rawData);
ScriptGlue.Serialization_DeserializeField(_internalContext, expectedType, completeFieldName, rawData, out SerializationType actualType);
string? GetString()
{
// rawData contains a Pointer and a string length!
byte* utf8Ptr = *(byte**)rawData;
@@ -191,48 +211,50 @@ public class DeserializationObject
return Encoding.UTF8.GetString(utf8Ptr, (int)length);
}
switch (serializationType)
object? value = actualType switch
{
case SerializationType.Bool:
return *(bool*)rawData;
case SerializationType.Char:
return *(char*)rawData;
case SerializationType.String:
return GetString();
case SerializationType.Int8:
return *(sbyte*)rawData;
case SerializationType.Int16:
return *(short*)rawData;
case SerializationType.Int32:
return *(int*)rawData;
case SerializationType.Int64:
return *(long*)rawData;
case SerializationType.UInt8:
return *(byte*)rawData;
case SerializationType.UInt16:
return *(ushort*)rawData;
case SerializationType.UInt32:
return *(uint*)rawData;
case SerializationType.UInt64:
return *(ulong*)rawData;
SerializationType.Bool => *(bool*)rawData,
SerializationType.Char => *(char*)rawData,
SerializationType.String => GetString(),
SerializationType.Int8 => *(sbyte*)rawData,
SerializationType.Int16 => *(short*)rawData,
SerializationType.Int32 => *(int*)rawData,
SerializationType.Int64 => *(long*)rawData,
SerializationType.UInt8 => *(byte*)rawData,
SerializationType.UInt16 => *(ushort*)rawData,
SerializationType.UInt32 => *(uint*)rawData,
SerializationType.UInt64 => *(ulong*)rawData,
SerializationType.Float => *(float*)rawData,
SerializationType.Double => *(double*)rawData,
SerializationType.Decimal => *(decimal*)rawData,
SerializationType.Enum => GetString(),
SerializationType.EntityReference => dataHelper.EngineObjectReference,
SerializationType.ComponentReference => dataHelper.EngineObjectReference,
SerializationType.ObjectReference => dataHelper.UUID,
_ => NoValueDeserialized
};
case SerializationType.Float:
return *(float*)rawData;
case SerializationType.Double:
return *(double*)rawData;
case SerializationType.Decimal:
return *(decimal*)rawData;
case SerializationType.Enum:
return GetString();
case SerializationType.EntityReference:
case SerializationType.ComponentReference:
return dataHelper.EngineObjectReference;
case SerializationType.ObjectReference:
return *(UUID*)rawData;
default:
if (actualType != expectedType)
{
if (!actualType.CanConvertTo(expectedType))
{
return NoValueDeserialized;
}
try
{
Type? targetType = expectedType.GetTypeInstance();
return targetType != null ? Convert.ChangeType(value, targetType) : NoValueDeserialized;
}
catch (Exception e)
{
Log.Error($"Failed to convert type {actualType} to {expectedType}: {e}");
return NoValueDeserialized;
}
}
return value;
}
public void Deserialize(Entity entity)
@@ -284,7 +306,8 @@ public class DeserializationObject
if (fieldType.IsGenericType && _customDeserializers.TryGetValue(fieldType.GetGenericTypeDefinition(), out deserializeMethod))
{
deserializedValue = deserializeMethod(this, fieldName, fieldType);
} return true;
return true;
}
}
catch (Exception e)
{
@@ -407,9 +430,12 @@ public class DeserializationObject
return null;
// Get serialization container for the instance
DeserializationObject deserializedObject = GetDeserializedObject(id);
DeserializationObject? deserializedObject = GetDeserializedObject(id);
Type type = deserializedObject.StoredType;
if (deserializedObject == null)
return NoValueDeserialized;
Type? type = deserializedObject.StoredType;
if (type == null || !type.IsAssignableTo(fieldType))
return null;
+65 -20
View File
@@ -7,34 +7,79 @@ using GlitchyEngine.Extensions;
namespace GlitchyEngine.Serialization;
public enum SerializationType : int
[Flags]
public enum SerializationType : uint
{
None,
None = 0,
Bool,
Bool = 1u << 31,
Char,
String,
TextTypes = 1 << 30,
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Char = TextTypes | 1,
String = TextTypes | 2,
Float,
Double,
Decimal,
Number = 1 << 29,
Integer = Number | 1 << 28,
Enum,
Int8 = Integer | 1,
Int16 = Integer | 2,
Int32 = Integer | 3,
Int64 = Integer | 4,
UInt8 = Integer | 5,
UInt16 = Integer | 6,
UInt32 = Integer | 7,
UInt64 = Integer | 8,
EntityReference,
ComponentReference,
FloatingPoint = Number | 1 << 27,
ObjectReference
Float = FloatingPoint | 1,
Double = FloatingPoint | 2,
Decimal = FloatingPoint | 3,
Enum = 1 << 26,
EntityReference = 1 << 25,
ComponentReference = 1 << 24,
ObjectReference = 1 << 23,
}
public static class SerializationTypeExtension
{
public static bool CanConvertTo(this SerializationType currentType, SerializationType targetType)
{
if (currentType.HasFlag(SerializationType.Number) && targetType.HasFlag(SerializationType.Number))
return true;
return false;
}
public static Type? GetTypeInstance(this SerializationType currentType)
{
return currentType switch
{
SerializationType.Bool => typeof(bool),
SerializationType.Char => typeof(char),
SerializationType.String => typeof(string),
SerializationType.Int8 => typeof(sbyte),
SerializationType.Int16 => typeof(short),
SerializationType.Int32 => typeof(int),
SerializationType.Int64 => typeof(long),
SerializationType.UInt8 => typeof(byte),
SerializationType.UInt16 => typeof(ushort),
SerializationType.UInt32 => typeof(uint),
SerializationType.UInt64 => typeof(ulong),
SerializationType.Float => typeof(float),
SerializationType.Double => typeof(double),
SerializationType.Decimal => typeof(decimal),
SerializationType.Enum => typeof(Enum),
SerializationType.EntityReference => typeof(UUID),
SerializationType.ComponentReference => typeof(UUID),
SerializationType.ObjectReference => typeof(UUID),
_ => null
};
}
}
internal static class EntitySerializer
+3 -5
View File
@@ -40,7 +40,7 @@ public class SerializedObject
if (_serializedClasses.TryGetValue(o, out context))
return (context, false);
ScriptGlue.Serialization_CreateObject(_internalContext, o.GetType().FullName, out IntPtr contextPtr, out UUID id);
ScriptGlue.Serialization_CreateObject(_internalContext, o.GetType().ToString(), out IntPtr contextPtr, out UUID id);
context = new SerializedObject(contextPtr, id, _serializedClasses);
@@ -102,7 +102,7 @@ public class SerializedObject
{
Type type = obj.GetType();
foreach (FieldInfo field in type.GetFields())
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{
if (!EntitySerializer.SerializeField(field))
continue;
@@ -144,7 +144,7 @@ public class SerializedObject
public void SerializeField(string fieldName, object fieldValue, Type fieldType)
{
if (fieldType.IsPrimitive)
if (fieldType.IsPrimitive || fieldType == typeof(decimal))
{
SerializePrimitive(fieldName, fieldValue, fieldType);
}
@@ -238,8 +238,6 @@ public class SerializedObject
public void SerializePrimitive(string fieldName, object fieldValue, Type fieldType)
{
Debug.Assert(fieldType.IsPrimitive, $"{fieldType} is not a primitive type.");
SerializationType type = SerializationType.None;
if (fieldType == typeof(bool))