Skip to content

Commit

Permalink
Serialize: read relations as JSON array
Browse files Browse the repository at this point in the history
  • Loading branch information
friflo committed Nov 28, 2024
1 parent b515677 commit 0b2224e
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 40 deletions.
9 changes: 9 additions & 0 deletions src/ECS/Base/Types/ComponentType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using System;
using System.Runtime.CompilerServices;
using Friflo.Engine.ECS.Relations;
using Friflo.Engine.ECS.Serialize;
using Friflo.Json.Fliox;
using static Friflo.Engine.ECS.SchemaTypeKind;

// ReSharper disable ConvertToPrimaryConstructor
Expand Down Expand Up @@ -40,6 +42,7 @@ public abstract class ComponentType : SchemaType
internal abstract bool AddEntityComponentValue(Entity entity, object value);

internal virtual void WriteRelations(ComponentWriter writer, Entity entity) => throw new InvalidOperationException();
internal virtual void ReadRelation(ComponentReader reader, Entity entity, JsonValue json) => throw new InvalidOperationException();


internal abstract BatchComponent CreateBatchComponent();
Expand Down Expand Up @@ -160,6 +163,12 @@ internal override void WriteRelations(ComponentWriter writer, Entity entity)
writer.writer.SetPretty(pretty);
}

internal override void ReadRelation(ComponentReader reader, Entity entity, JsonValue json)
{
var relation = reader.componentReader.Read<T>(json);
EntityRelations.AddRelation(entity.store, entity.Id, relation);
}

internal override StructHeap CreateHeap() {
return new StructHeap<T>(StructIndex);
}
Expand Down
144 changes: 112 additions & 32 deletions src/ECS/Serialize/ComponentReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Friflo.Engine.ECS.Serialize;
/// </summary>
internal sealed class ComponentReader
{
private readonly ObjectReader componentReader;
internal readonly ObjectReader componentReader;
private readonly MapperContextEntityStore mapperContextStore;
private readonly Dictionary<string, SchemaType> schemaTypeByKey;
private readonly Dictionary<Type, ScriptType> scriptTypeByType;
Expand All @@ -32,14 +32,15 @@ internal sealed class ComponentReader
private readonly Dictionary<string, UnresolvedComponent> unresolvedComponentMap;
private readonly Dictionary<BytesHash, RawKey> rawKeyCache;
private Utf8JsonParser parser;
private Bytes buffer;
private RawComponent[] components;
private int componentCount;
private RawRelation[] relations;
private int relationCount;


internal ComponentReader(TypeStore typeStore) {
buffer = new Bytes(128);
components = new RawComponent[1];
relations = Array.Empty<RawRelation>();
componentReader = new ObjectReader(typeStore) { ErrorHandler = ObjectReader.NoThrow };
mapperContextStore = new MapperContextEntityStore();
componentReader.SetMapperContext(mapperContextStore);
Expand All @@ -61,6 +62,7 @@ internal string Read(DataEntity dataEntity, Entity entity, EntityStoreBase store
{
mapperContextStore.store = (EntityStore)store;
componentCount = 0;
relationCount = 0;
var error = ReadRaw(dataEntity, entity);
if (error != null) {
return error;
Expand All @@ -82,9 +84,10 @@ private string ReadRaw (DataEntity dataEntity, Entity entity)
break;
case JsonEvent.ObjectStart:
ev = ReadRawComponents();
if (ev != JsonEvent.ObjectEnd) {
// could support also scalar types in future: string, number or boolean
return $"'components' element must be an object. was {ev}. id: {entity.Id}, component: '{parser.key}'";
if (ev != JsonEvent.ObjectEnd &&
ev != JsonEvent.ArrayEnd) {
// could support also scalar types in the future: string, number or boolean
return $"'components' member must be object or array. was {ev}. id: {entity.Id}, component: '{parser.key}'";
}
break;
default:
Expand All @@ -103,30 +106,18 @@ private string ReadComponents(Entity entity)
}
for (int n = 0; n < componentCount; n++)
{
var component = components[n];
buffer.Clear();
var json = new JsonValue(parser.GetInputBytes(component.start - 1, component.end));
var schemaType = component.rawKey.schemaType;
if (schemaType == unresolvedType) {
unresolvedComponentList.Add(new UnresolvedComponent(component.rawKey.key, json));
continue;
}
switch (schemaType.Kind) {
case SchemaTypeKind.Script:
// --- read script
var scriptType = (ScriptType)schemaType;
scriptTypes.Remove(scriptType);
scriptType.ReadScript(componentReader, json, entity);
ref var component = ref components[n];
string error = null;
switch (component.type) {
case RawComponentType.Object:
error = ReadComponent(entity, component);
break;
case SchemaTypeKind.Component:
var componentType = (ComponentType)schemaType;
var heap = entity.archetype.heapMap[componentType.StructIndex]; // no range or null check required
// --- read & change component
heap.Read(componentReader, entity.compIndex, json);
case RawComponentType.Array:
error = ReadRelations(entity, component);
break;
}
if (componentReader.Error.ErrSet) {
return $"'components[{component.rawKey.key}]' - {componentReader.Error.GetMessage()}";
if (error != null) {
return error;
}
}
// --- remove missing scripts from entity
Expand All @@ -140,6 +131,45 @@ private string ReadComponents(Entity entity)
return null;
}

private string ReadComponent(Entity entity, in RawComponent component)
{
var json = new JsonValue(parser.GetInputBytes(component.start - 1, component.end));
var schemaType = component.rawKey.schemaType;
if (schemaType == unresolvedType) {
unresolvedComponentList.Add(new UnresolvedComponent(component.rawKey.key, json));
return null;
}
switch (schemaType.Kind) {
case SchemaTypeKind.Script:
// --- read script
var scriptType = (ScriptType)schemaType;
scriptTypes.Remove(scriptType);
scriptType.ReadScript(componentReader, json, entity);
break;
case SchemaTypeKind.Component:
var componentType = (ComponentType)schemaType;
var heap = entity.archetype.heapMap[componentType.StructIndex]; // no range or null check required
// --- read & change component
heap.Read(componentReader, entity.compIndex, json);
break;
}
if (componentReader.Error.ErrSet) {
return $"'components[{component.rawKey.key}]' - {componentReader.Error.GetMessage()}";
}
return null;
}

private string ReadRelations(Entity entity, in RawComponent component)
{
var relationType = (ComponentType)component.rawKey.schemaType;
for (int index = component.start; index < component.end; index++) {
var relation = relations[index];
var json = new JsonValue(parser.GetInputBytes(relation.start - 1, relation.end));
relationType.ReadRelation(this, entity, json);
}
return null;
}

private void AddUnresolvedComponents(Entity entity)
{
ref var unresolved = ref entity.GetComponent<Unresolved>();
Expand Down Expand Up @@ -280,14 +310,45 @@ private JsonEvent ReadRawComponents()
if (componentCount == components.Length) {
ArrayUtils.Resize(ref components, 2 * componentCount);
}
components[componentCount++] = new RawComponent(rawKey, start, parser.Position);
components[componentCount++] = new RawComponent(RawComponentType.Object, rawKey, start, parser.Position);
ev = parser.NextEvent();
if (ev == JsonEvent.ObjectEnd) {
return JsonEvent.ObjectEnd;
}
break;
case JsonEvent.ObjectEnd:
return JsonEvent.ObjectEnd;
case JsonEvent.ArrayStart:
return ReadRawRelations();
default:
return ev;
}
}
}

private JsonEvent ReadRawRelations()
{
var rawKey = ToRawKey(parser.key);
var startRelation = relationCount;
var ev = parser.NextEvent();
while (true) {
switch (ev) {
case JsonEvent.ObjectStart:
var start = parser.Position;
parser.SkipTree();
if (relationCount == relations.Length) {
ArrayUtils.Resize(ref relations, Math.Max(4, 2 * relationCount));
}
relations[relationCount++] = new RawRelation(start, parser.Position);
ev = parser.NextEvent();
if (ev == JsonEvent.ArrayEnd) {
if (componentCount == components.Length) {
ArrayUtils.Resize(ref components, 2 * componentCount);
}
components[componentCount++] = new RawComponent(RawComponentType.Array, rawKey, startRelation, relationCount);
return JsonEvent.ArrayEnd;
}
break;
default:
return ev;
}
Expand Down Expand Up @@ -337,17 +398,36 @@ internal RawKey(string key, SchemaType schemaType) {
}
}

internal enum RawComponentType
{
Object,
Array
}

internal readonly struct RawComponent
{
internal readonly RawKey rawKey;
internal readonly int start;
internal readonly int end;
internal readonly RawComponentType type;
internal readonly RawKey rawKey;
internal readonly int start;
internal readonly int end;

public override string ToString() => rawKey.ToString();

internal RawComponent(in RawKey rawKey, int start, int end) {
internal RawComponent(RawComponentType type, in RawKey rawKey, int start, int end) {
this.type = type;
this.rawKey = rawKey;
this.start = start;
this.end = end;
}
}

internal readonly struct RawRelation
{
internal readonly int start;
internal readonly int end;

internal RawRelation(int start, int end) {
this.start = start;
this.end = end;
}
}
2 changes: 1 addition & 1 deletion src/Tests-internal/ECS/Test_ComponentReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class Test_ComponentReader
public static void Test_ComponentReader_RawComponent() {
var unresolved = EntityStore.GetEntitySchema().unresolvedType;
var rawKey = new RawKey("test", unresolved);
var rawComponent = new RawComponent(rawKey, 0, 0);
var rawComponent = new RawComponent(RawComponentType.Object, rawKey, 0, 0);
Assert.AreEqual("test - Unresolved", rawComponent.ToString());
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Tests/ECS/Serialize/Test_ComponentReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,11 @@ public static void Test_ComponentReader_read_invalid_component()
var store = new EntityStore(PidType.UsePidAsId);
var converter = EntityConverter.Default;

var json = new JsonValue("{ \"pos\": [] }");
var json = new JsonValue("{ \"pos\": 123 }");
var node = new DataEntity { pid = 10, components = json };
var entity = converter.DataEntityToEntity(node, store, out var error);
NotNull(entity);
AreEqual("'components' element must be an object. was ArrayStart. id: 10, component: 'pos'", error);
AreEqual("'components' member must be object or array. was ValueNumber. id: 10, component: 'pos'", error);
}

[Test]
Expand Down
23 changes: 18 additions & 5 deletions src/Tests/ECS/Serialize/Test_SerializeRelations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@
using static NUnit.Framework.Assert;


// ReSharper disable MethodHasAsyncOverload
// ReSharper disable HeuristicUnreachableCode
// ReSharper disable InconsistentNaming
namespace Tests.ECS.Serialize {

public static class Test_SerializeRelations
{
/// referenced entity is loaded before entity reference
private const string Json =
@"[{
""id"": 1,
Expand All @@ -22,9 +19,26 @@ public static class Test_SerializeRelations
}
}]";


#region read relations
[Test]
public static void Test_SerializeRelations_read()
{
var store = new EntityStore();
var serializer = new EntitySerializer();
var stream = Test_Serializer.StringAsStream(Json);

var result = serializer.ReadIntoStore(store, stream);
IsNull(result.error);

var entity1 = store.GetEntityById(1);
var relations = entity1.GetRelations<IntRelation>();
AreEqual(2, relations.Length);
}
#endregion


#region write Entity
#region write relations
[Test]
public static void Test_SerializeRelations_write()
{
Expand All @@ -40,7 +54,6 @@ public static void Test_SerializeRelations_write()
var str = Test_Serializer.MemoryStreamAsString(writeStream);
AreEqual(Json, str);
}

#endregion
}

Expand Down

0 comments on commit 0b2224e

Please sign in to comment.