Files
Nerfed/Nerfed.Runtime/Scene/JsonSceneSerializer.cs
T
max fec2cd8d24 testing building some core systems
- serialization
- chunks
- parralelfor test
2026-04-24 19:21:03 +02:00

189 lines
7.5 KiB
C#

using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Nerfed.Runtime.Scene;
/// <summary>
/// Human-readable JSON scene serializer.
///
/// Example output:
/// <code>
/// {
/// "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": {}
/// }
/// ]
/// }
/// </code>
/// </summary>
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<SceneData>(stream, Options)
?? throw new InvalidOperationException("Failed to deserialize scene: root element was null.");
}
// -------------------------------------------------------------------------
// Converters
// -------------------------------------------------------------------------
private sealed class Vector3JsonConverter : JsonConverter<Vector3>
{
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<Quaternion>
{
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();
}
}
/// <summary>
/// Buffers the full JSON object, resolves the CLR component type from the "type" field,
/// then deserializes "data" using that concrete type.
/// </summary>
private sealed class SceneComponentDataJsonConverter : JsonConverter<SceneComponentData>
{
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();
}
}
/// <summary>
/// Same pattern as <see cref="SceneComponentDataJsonConverter"/> but for relation data.
/// Resolves the type via <see cref="SceneManager.GetRelationType"/>.
/// </summary>
private sealed class SceneRelationDataJsonConverter : JsonConverter<SceneRelationData>
{
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();
}
}
}