Skip to content

Commit

Permalink
change implementation of CloneEntity() - replaced JSON serialization …
Browse files Browse the repository at this point in the history
…by static CopyValue(source, target) methods
  • Loading branch information
friflo committed Dec 9, 2024
1 parent b6220fd commit c68495f
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 50 deletions.
6 changes: 4 additions & 2 deletions src/ECS/Archetype/Archetype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,12 @@ internal static void MoveLastComponentsTo(Archetype arch, int newIndex, bool upd
}

/// <remarks>Must be used only on case all <see cref="ComponentTypes"/> are <see cref="ComponentType.IsBlittable"/></remarks>
internal static void CopyComponents(Archetype arch, int sourceIndex, int targetIndex)
internal static void CopyComponents(Archetype arch, CopyContext context)
{
var sourceIndex = context.source.compIndex;
var targetIndex = context.target.compIndex;
foreach (var sourceHeap in arch.structHeaps) {
sourceHeap.CopyComponent(sourceIndex, targetIndex);
sourceHeap.CopyComponent(sourceIndex, targetIndex, context);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/ECS/Archetype/StructHeap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal abstract class StructHeap : IComponentStash
internal abstract void ResizeComponents (int capacity, int count);
internal abstract void MoveComponent (int from, int to);
internal abstract void CopyComponentTo (int sourcePos, StructHeap target, int targetPos);
internal abstract void CopyComponent (int sourcePos, int targetPos);
internal abstract void CopyComponent (int sourcePos, int targetPos, in CopyContext context);
internal abstract void SetComponentDefault (int compIndex);
internal abstract void SetComponentsDefault (int compIndexStart, int count);
internal abstract object GetComponentDebug (int compIndex);
Expand Down
9 changes: 7 additions & 2 deletions src/ECS/Archetype/StructHeap.generic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,14 @@ internal override void CopyComponentTo(int sourcePos, StructHeap target, int tar
/// <see cref="ComponentType"/>'s.<br/>
/// If not <see cref="ComponentType.IsBlittable"/> serialization must be used.
/// </remarks>
internal override void CopyComponent(int sourcePos, int targetPos)
internal override void CopyComponent(int sourcePos, int targetPos, in CopyContext context)
{
components[targetPos] = components[sourcePos];
var copyValue = CopyValueUtils<T>.CopyValue;
if (copyValue == null) {
components[targetPos] = components[sourcePos];
return;
}
copyValue(components[sourcePos], ref components[targetPos], context);
}

internal override void SetComponentDefault (int compIndex) {
Expand Down
29 changes: 29 additions & 0 deletions src/ECS/Base/CopyScriptUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

// ReSharper disable once CheckNamespace
namespace Friflo.Engine.ECS;

internal delegate void CopyScript<in TScript>(TScript value, TScript target);


internal static class CopyScriptUtils
{
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2090", Justification = "TODO")] // TODO
internal static CopyScript<TScript> CreateCopyScript<TScript>()
{
const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.InvokeMethod;
var method = typeof(TScript).GetMethod("CopyScript", flags);
if (method is null) {
return null;
}
var genericDelegate = Delegate.CreateDelegate(typeof(CopyScript<TScript>), method);
return (CopyScript<TScript>)genericDelegate;
}
}

internal static class CopyScriptUtils<TValue>
{
internal static readonly CopyScript<TValue> CopyScript = CopyScriptUtils.CreateCopyScript<TValue>();
}
51 changes: 51 additions & 0 deletions src/ECS/Base/CopyValueUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

// ReSharper disable once CheckNamespace
namespace Friflo.Engine.ECS;

public readonly struct CopyContext
{
public readonly Entity source;
public readonly Entity target;

internal CopyContext(Entity source, Entity target) {
this.source = source;
this.target = target;
}
}

internal delegate void CopyValue<TValue>(in TValue value, ref TValue target, in CopyContext context);


internal static class CopyValueUtils
{
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2090", Justification = "TODO")] // TODO
internal static CopyValue<TComponent> CreateCopyValue<TComponent>()
{
const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.InvokeMethod;
var method = typeof(TComponent).GetMethod("CopyValue", flags);
if (method is null) {
var schema = EntityStore.Static.EntitySchema;
var componentType = schema.ComponentTypeByType[typeof(TComponent)];
if (componentType.IsBlittable) {
return null;
}
return MissingCopyValue;
}
var genericDelegate = Delegate.CreateDelegate(typeof(CopyValue<TComponent>), method);
return (CopyValue<TComponent>)genericDelegate;
}

private static void MissingCopyValue<TValue>(in TValue value, ref TValue target, in CopyContext context) {
var name = typeof(TValue).Name;
var msg = $"at {typeof(TValue).Namespace}.{name} - expect: static void CopyValue(in {name} source, ref {name} target, in CopyContext context)";
throw new MissingMethodException(msg);
}
}

internal static class CopyValueUtils<TValue>
{
internal static readonly CopyValue<TValue> CopyValue = CopyValueUtils.CreateCopyValue<TValue>();
}
11 changes: 6 additions & 5 deletions src/ECS/Base/SchemaUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,14 @@ internal static ScriptType CreateScriptType<T>(int scriptIndex)
{
var scriptKey = GetComponentKey(typeof(T));
var isBlittable = SchemaType.GetBlittableType(typeof(T)) == BlittableType.Blittable;
CloneScript cloneScript = null;
if (isBlittable) {
cloneScript = CreateCloneScriptDelegate();
}
return new ScriptType<T>(scriptKey, scriptIndex, isBlittable, cloneScript);
// CloneScript cloneScript = null;
// if (isBlittable) {
// cloneScript = CreateCloneScriptDelegate();
// }
return new ScriptType<T>(scriptKey, scriptIndex, isBlittable);
}

[ExcludeFromCodeCoverage] // unused - kept as reference. Was used to clone scripts.
private static CloneScript CreateCloneScriptDelegate()
{
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
Expand Down
30 changes: 20 additions & 10 deletions src/ECS/Base/Types/ScriptType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,25 @@ public abstract class ScriptType : SchemaType
public readonly int ScriptIndex; // 4
/// <summary> Return true if <see cref="Script"/>'s of this type can be copied. </summary>
public readonly bool IsBlittable; // 4
private readonly CloneScript cloneScript; // 8
#endregion

#region methods
internal abstract Script CreateScript();
internal abstract void ReadScript (ObjectReader reader, JsonValue json, Entity entity);

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070", Justification = "MemberwiseClone is part of BCL")]
internal ScriptType(string scriptKey, int scriptIndex, Type type, bool isBlittable, CloneScript cloneScript)
internal ScriptType(string scriptKey, int scriptIndex, Type type, bool isBlittable)
: base (scriptKey, type, SchemaTypeKind.Script)
{
ScriptIndex = scriptIndex;
IsBlittable = isBlittable;
this.cloneScript = cloneScript;
ScriptIndex = scriptIndex;
IsBlittable = isBlittable;
}

internal Script CloneScript(Script original)
{
internal abstract Script CloneScript(Script source);
/* {
var clone = cloneScript(original);
return (Script)clone;
}
}*/
#endregion
}

Expand All @@ -69,15 +67,27 @@ internal sealed class ScriptType<T> : ScriptType


#region methods
internal ScriptType(string scriptComponentKey, int scriptIndex, bool isBlittable, CloneScript cloneScript)
: base(scriptComponentKey, scriptIndex, typeof(T), isBlittable, cloneScript)
internal ScriptType(string scriptComponentKey, int scriptIndex, bool isBlittable)
: base(scriptComponentKey, scriptIndex, typeof(T), isBlittable)
{
}

internal override Script CreateScript() {
return new T();
}

internal override Script CloneScript(Script source) {
var clone = new T();
var copyScript = CopyScriptUtils<T>.CopyScript;
if (copyScript != null) {
copyScript((T)source, clone);
return clone;
}
var name = typeof(T).Name;
var msg = $"at {typeof(T).Namespace}.{name} - expect: static void CopyScript({name} source, {name} target)";
throw new MissingMethodException(msg);
}

internal override void ReadScript(ObjectReader reader, JsonValue json, Entity entity) {
var mapper = (TypeMapper<T>)reader.TypeCache.GetTypeMapper(typeof(T));
var script = entity.GetScript<T>();
Expand Down
36 changes: 19 additions & 17 deletions src/ECS/Entity/Store/Entities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,25 @@ public Entity CloneEntity(Entity entity)
CreateEntityInternal(archetype, id, out var revision);
var clone = new Entity(this, id, revision);

var isBlittable = IsBlittable(entity);
// var isBlittable = IsBlittable(entity);

// todo optimize - serialize / deserialize only non blittable components and scripts
if (isBlittable) {
var scriptTypeByType = Static.EntitySchema.ScriptTypeByType;
// CopyComponents() must be used only in case all component types are blittable
Archetype.CopyComponents(archetype, entity.compIndex, clone.compIndex);
if (clone.HasComponent<TreeNode>()) {
clone.GetComponent<TreeNode>() = default; // clear child ids. See child entities note in remarks.
}
// --- clone scripts
foreach (var script in entity.Scripts) {
var scriptType = scriptTypeByType[script.GetType()];
var scriptClone = scriptType.CloneScript(script);
scriptClone.entity = clone;
extension.AddScript(clone, scriptClone, scriptType);
}
} else {
// if (true) {
var scriptTypeByType = Static.EntitySchema.ScriptTypeByType;
// CopyComponents() must be used only in case all component types are blittable
var context = new CopyContext(entity, clone);
Archetype.CopyComponents(archetype, context);
if (clone.HasComponent<TreeNode>()) {
clone.GetComponent<TreeNode>() = default; // clear child ids. See child entities note in remarks.
}
// --- clone scripts
foreach (var script in entity.Scripts) {
var scriptType = scriptTypeByType[script.GetType()];
var scriptClone = scriptType.CloneScript(script);
scriptClone.entity = clone;
extension.AddScript(clone, scriptClone, scriptType);
}
/* } else {
// --- serialize entity
var converter = EntityConverter.Default;
converter.EntityToDataEntity(entity, dataBuffer, false);
Expand All @@ -119,7 +120,7 @@ public Entity CloneEntity(Entity entity)
// convert will use entity created above
converter.DataEntityToEntity(dataBuffer, this, out string error); // error == null. No possibility for mapping errors
AssertNoError(error);
}
} */
// Send event. See: SEND_EVENT notes
CreateEntityEvent(clone);
return clone;
Expand All @@ -133,6 +134,7 @@ private static void AssertNoError(string error) {
throw new InvalidOperationException($"unexpected error: {error}");
}

[ExcludeFromCodeCoverage] // unused - method obsolete
private static bool IsBlittable(Entity original)
{
foreach (var componentType in original.Archetype.componentTypes)
Expand Down
9 changes: 5 additions & 4 deletions src/Tests/ECS/Base/Test_ComponentSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ public static void Test_ComponentTypes()
var components = schema.Components;
var scripts = schema.Scripts;

AreEqual("components: 69 scripts: 10 entity tags: 16", schema.ToString());
AreEqual(70, components.Length);
AreEqual("components: 72 scripts: 10 entity tags: 16", schema.ToString());
AreEqual(73, components.Length);
AreEqual(11, scripts.Length);

AreEqual(75, schema.SchemaTypeByKey.Count);
AreEqual(69, schema.ComponentTypeByType.Count);
AreEqual(78, schema.SchemaTypeByKey.Count);
AreEqual(72, schema.ComponentTypeByType.Count);
AreEqual(10, schema.ScriptTypeByType.Count);

IsNull(components[0]);
Expand Down Expand Up @@ -94,6 +94,7 @@ public static void Test_ComponentTypes()
AssertBlittableComponent<Unresolved> (schema, false);

// --- BCL types
AssertBlittableComponent<BlittableEnum> (schema, true);
AssertBlittableComponent<BlittableDatetime> (schema, true);
AssertBlittableComponent<BlittableGuid> (schema, true);
AssertBlittableComponent<BlittableBigInteger> (schema, true);
Expand Down
53 changes: 47 additions & 6 deletions src/Tests/ECS/Components.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Friflo.Engine.ECS;
using static NUnit.Framework.Assert;
Expand Down Expand Up @@ -46,6 +47,24 @@ public struct MyComponent6 : IComponent { public int b; }
[ComponentKey("my7")]
public struct MyComponent7 : IComponent { public int b; }

/// <summary>
/// Component contains a non blittable field typ. This requires a <see cref="CopyValue"/> method.
/// </summary>
public struct CopyComponent : IComponent
{
public List<int> list;

public static void CopyValue(in CopyComponent source, ref CopyComponent target, in CopyContext context) {
if (source.list == null) {
target.list = null;
return;
}
target.list ??= new List<int>();
target.list.Clear();
target.list.AddRange(source.list);
}
}

public struct EntityReference : IComponent {
public Entity entity;

Expand All @@ -57,11 +76,19 @@ public struct NonBlittableList : IComponent { internal List<int>
public struct NonBlittableDictionary : IComponent { internal Dictionary<int, int> map; }
public struct NonBlittableCycle : IComponent { internal CycleClass cycle; }
public struct NonBlittableCycle2 : IComponent { internal CycleClass1 cycle1; }
public struct NonBlittableComponent : IComponent { internal int[] array; }

public struct BlittableEnum : IComponent { public BlittableEnumType value; }
public struct BlittableDatetime : IComponent { public DateTime dateTime; }
public struct BlittableGuid : IComponent { public Guid guid; }
public struct BlittableBigInteger : IComponent { public BigInteger bigInteger; }
public struct BlittableUri : IComponent { public Uri uri; } // throws exception when serialized

public struct BlittableDatetime : IComponent { public DateTime dateTime; }
public struct BlittableGuid : IComponent { public Guid guid; }
public struct BlittableBigInteger : IComponent { public BigInteger bigInteger; }
public struct BlittableUri : IComponent { public Uri uri; } // throws exception when serialized
public enum BlittableEnumType
{
One = 1,
Two = 2,
}

[ComponentKey(null)]
public struct NonSerializedComponent : IComponent { public int value; }
Expand Down Expand Up @@ -151,7 +178,14 @@ public struct TestTag5 : ITag { }
// ------------------------------------------------ scripts
[CodeCoverageTest]
[ComponentKey("script1")]
public class TestScript1 : Script { public int val1; }
public class TestScript1 : Script
{
public int val1;

private static void CopyScript(TestScript1 source, TestScript1 target) {
target.val1 = source.val1;
}
}

[ComponentKey("script2")]
public class TestScript2 : Script { public int val2; }
Expand All @@ -162,7 +196,14 @@ class TestScript3 : Script { public int val3; }
[ComponentKey("script4")]
class TestScript4 : Script { public int val4; }

class NonBlittableScript : Script { internal int[] array; }
class NonBlittableScript : Script
{
internal int[] array;

private static void CopyScript(NonBlittableScript source, NonBlittableScript target) {
target.array = source.array?.ToArray();
}
}

[ComponentKey("test")]
class TestComponent : Script
Expand Down
Loading

0 comments on commit c68495f

Please sign in to comment.