using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; namespace Nerfed.Runtime.Scene; /// /// Human-readable JSON scene serializer. /// /// Example output: /// /// { /// "version": 1, /// "name": "MyScene", /// "entities": [ /// { /// "id": "a1b2c3d4-...", /// "tag": "Player", /// "parentId": null, /// "components": [ /// { /// "type": "Nerfed.Runtime.Components.LocalTransform", /// "data": { /// "position": { "x": 0.0, "y": 0.0, "z": 0.0 }, /// "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }, /// "scale": { "x": 1.0, "y": 1.0, "z": 1.0 } /// } /// } /// ] /// } /// ], /// "relations": [ /// { /// "type": "Nerfed.Runtime.Components.OwnerRelation", /// "entityA": "a1b2c3d4-...", /// "entityB": "e5f6a7b8-...", /// "data": {} /// } /// ] /// } /// /// public sealed class JsonSceneSerializer : ISceneSerializer { private static readonly JsonSerializerOptions Options = new() { WriteIndented = true, Converters = { new Vector3JsonConverter(), new QuaternionJsonConverter(), new SceneComponentDataJsonConverter(), new SceneRelationDataJsonConverter(), }, }; public void Serialize(SceneData scene, Stream stream) { JsonSerializer.Serialize(stream, scene, Options); } public SceneData Deserialize(Stream stream) { return JsonSerializer.Deserialize(stream, Options) ?? throw new InvalidOperationException("Failed to deserialize scene: root element was null."); } // ------------------------------------------------------------------------- // Converters // ------------------------------------------------------------------------- private sealed class Vector3JsonConverter : JsonConverter { public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { float x = 0f, y = 0f, z = 0f; reader.Read(); // StartObject while(reader.Read() && reader.TokenType != JsonTokenType.EndObject) { string name = reader.GetString()!; reader.Read(); switch(name) { case "x": x = reader.GetSingle(); break; case "y": y = reader.GetSingle(); break; case "z": z = reader.GetSingle(); break; } } return new Vector3(x, y, z); } public override void Write(Utf8JsonWriter writer, Vector3 value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteNumber("x", value.X); writer.WriteNumber("y", value.Y); writer.WriteNumber("z", value.Z); writer.WriteEndObject(); } } private sealed class QuaternionJsonConverter : JsonConverter { public override Quaternion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { float x = 0f, y = 0f, z = 0f, w = 1f; reader.Read(); // StartObject while(reader.Read() && reader.TokenType != JsonTokenType.EndObject) { string name = reader.GetString()!; reader.Read(); switch(name) { case "x": x = reader.GetSingle(); break; case "y": y = reader.GetSingle(); break; case "z": z = reader.GetSingle(); break; case "w": w = reader.GetSingle(); break; } } return new Quaternion(x, y, z, w); } public override void Write(Utf8JsonWriter writer, Quaternion value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteNumber("x", value.X); writer.WriteNumber("y", value.Y); writer.WriteNumber("z", value.Z); writer.WriteNumber("w", value.W); writer.WriteEndObject(); } } /// /// Buffers the full JSON object, resolves the CLR component type from the "type" field, /// then deserializes "data" using that concrete type. /// private sealed class SceneComponentDataJsonConverter : JsonConverter { public override SceneComponentData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using JsonDocument doc = JsonDocument.ParseValue(ref reader); JsonElement root = doc.RootElement; string typeName = root.GetProperty("type").GetString() ?? throw new JsonException("Missing or null 'type' field in component data."); Type componentType = SceneManager.GetComponentType(typeName) ?? throw new JsonException($"Unknown component type '{typeName}'. Ensure the struct is marked with [SceneComponent]."); string rawData = root.GetProperty("data").GetRawText(); ValueType value = (ValueType)JsonSerializer.Deserialize(rawData, componentType, options)!; return new SceneComponentData { Type = typeName, Value = value }; } public override void Write(Utf8JsonWriter writer, SceneComponentData value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString("type", value.Type); writer.WritePropertyName("data"); JsonSerializer.Serialize(writer, value.Value, value.Value.GetType(), options); writer.WriteEndObject(); } } /// /// Same pattern as but for relation data. /// Resolves the type via . /// private sealed class SceneRelationDataJsonConverter : JsonConverter { public override SceneRelationData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using JsonDocument doc = JsonDocument.ParseValue(ref reader); JsonElement root = doc.RootElement; string typeName = root.GetProperty("type").GetString() ?? throw new JsonException("Missing or null 'type' field in relation data."); Type relationType = SceneManager.GetRelationType(typeName) ?? throw new JsonException($"Unknown relation type '{typeName}'. Ensure the struct is marked with [SceneRelation]."); Guid entityA = root.GetProperty("entityA").GetGuid(); Guid entityB = root.GetProperty("entityB").GetGuid(); string rawData = root.GetProperty("data").GetRawText(); ValueType value = (ValueType)JsonSerializer.Deserialize(rawData, relationType, options)!; return new SceneRelationData { Type = typeName, EntityA = entityA, EntityB = entityB, Value = value }; } public override void Write(Utf8JsonWriter writer, SceneRelationData value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString("type", value.Type); writer.WriteString("entityA", value.EntityA); writer.WriteString("entityB", value.EntityB); writer.WritePropertyName("data"); JsonSerializer.Serialize(writer, value.Value, value.Value.GetType(), options); writer.WriteEndObject(); } } }