Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 49 additions & 22 deletions sources/engine/Stride.Engine.Tests/TestEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using Xunit;
using Stride.Core;
using Stride.Core.Annotations;
using Stride.Engine.Design;
using Stride.Rendering;

Expand Down Expand Up @@ -129,8 +130,6 @@ public void TestMultipleComponents()
[Fact]
public void TestEntityAndPrefabClone()
{
Prefab prefab = null;

var entity = new Entity("Parent");
var childEntity = new Entity("Child");
entity.AddChild(childEntity);
Expand All @@ -141,17 +140,25 @@ public void TestEntityAndPrefabClone()

var newEntity = entity.Clone();

// NOTE: THE CODE AFTER THIS IS EXECUTED TWO TIMES
// 1st time: newEntity = entity.Clone();
// 2nd time: newEntity = prefab.Instantiate()[0];
check_new_Entity:
CheckEntity(newEntity);

// Check prefab cloning
var prefab = new Prefab();
prefab.Entities.Add(entity);
var newEntities = prefab.Instantiate();
Assert.Single(newEntities);

CheckEntity(newEntities[0]);
return;

void CheckEntity([NotNull] Entity e)
{
Assert.Single(newEntity.Transform.Children);
var newChildEntity = newEntity.Transform.Children[0].Entity;
Assert.Single(e.Transform.Children);
var newChildEntity = e.Transform.Children[0].Entity;
Assert.Equal("Child", newChildEntity.Name);

Assert.NotNull(newEntity.Get<CustomEntityComponent>());
var newCustom = newEntity.Get<CustomEntityComponent>();
Assert.NotNull(e.Get<CustomEntityComponent>());
var newCustom = e.Get<CustomEntityComponent>();

// Make sure that the old component and the new component are different
Assert.NotEqual(custom, newCustom);
Expand All @@ -162,19 +169,31 @@ public void TestEntityAndPrefabClone()
// Verify that objects references outside the Entity/Component hierarchy are not cloned (shared)
Assert.Equal(custom.CustomObject, newCustom.CustomObject);
}
}

// Woot, ugly test using a goto, avoid factorizing code in a delegate method, ugly but effective, goto FTW
if (prefab == null)
{
// Check prefab cloning
prefab = new Prefab();
prefab.Entities.Add(entity);
var newEntities = prefab.Instantiate();
Assert.Single(newEntities);

newEntity = newEntities[0];
goto check_new_Entity;
}
[Fact]
public void TestCloningBehavior()
{
var externalEntity = new Entity();
var sourceEntity = new Entity();
var sourceComponent = new EntityComponentWithPrefab { Prefab = new Prefab(), ExternalEntityRef = externalEntity/*, ExternalComponentRef = externalEntity.Transform*/ };
sourceComponent.Prefab.Entities.Add(sourceEntity);
sourceEntity.Add(sourceComponent);

var clonedComponent = sourceComponent.Prefab.Instantiate()[0].Get<EntityComponentWithPrefab>();

// Validate that cloning did clone the entity
Assert.NotEqual(clonedComponent.Entity, sourceComponent.Entity);

// References to prefabs should not be deep cloned as they are content types
Assert.Equal(clonedComponent.Prefab, sourceComponent.Prefab);

// References to entities outside this one's hierarchy should not clone the entity referenced, it should point to the same reference
Assert.Equal(clonedComponent.ExternalEntityRef, sourceComponent.ExternalEntityRef);

// References to entity component outside this one's hierarchy should not clone the component referenced, it should point to the same reference
// Not yet supported
/*Assert.Equal(clonedComponent.ExternalComponentRef, sourceComponent.ExternalComponentRef);*/
}

private class DelegateEntityComponentNotify : EntityManager
Expand Down Expand Up @@ -270,4 +289,12 @@ public abstract class CustomEntityComponentBase : EntityComponent
{
public Action<CustomEntityComponentEventArgs> Changed;
}

[DataContract]
public class EntityComponentWithPrefab : EntityComponent
{
public required Prefab Prefab { get; set; }
public required Entity ExternalEntityRef { get; set; }
/*public required TransformComponent ExternalComponentRef { get; set; }*/
}
}
10 changes: 8 additions & 2 deletions sources/engine/Stride.Engine/Engine/Design/CloneSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public override void PreSerialize(ref T obj, ArchiveMode mode, SerializationStre
}
else
{
var dataSerializer = cloneContext.EntitySerializerSelector.GetSerializer<T>();
if (dataSerializer != this)
dataSerializer.PreSerialize(ref obj, mode, stream);
cloneContext.SerializedObjects.Add(obj);
}
}
Expand All @@ -58,7 +61,9 @@ public override void PreSerialize(ref T obj, ArchiveMode mode, SerializationStre
}
else
{
base.PreSerialize(ref obj, mode, stream);
var dataSerializer = cloneContext.EntitySerializerSelector.GetSerializer<T>();
if (dataSerializer != this)
dataSerializer.PreSerialize(ref obj, mode, stream);
cloneContext.SerializedObjects.Add(obj);
}
}
Expand All @@ -75,7 +80,8 @@ public override void Serialize(ref T obj, ArchiveMode mode, SerializationStream

// Serialize object
//stream.Context.Set(EntitySerializer.InsideEntityComponentProperty, false);
dataSerializer.Serialize(ref obj, mode, stream);
if (dataSerializer != this)
dataSerializer.Serialize(ref obj, mode, stream);

if (obj is EntityComponent)
{
Expand Down
15 changes: 12 additions & 3 deletions sources/engine/Stride.Engine/Engine/Design/EntityCloner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ namespace Stride.Engine.Design
[DataSerializerGlobal(typeof(CloneSerializer<OfflineRasterizedSpriteFont>), Profile = "Clone")]
[DataSerializerGlobal(typeof(CloneSerializer<RuntimeRasterizedSpriteFont>), Profile = "Clone")]
[DataSerializerGlobal(typeof(CloneSerializer<SignedDistanceFieldSpriteFont>), Profile = "Clone")]
[DataSerializerGlobal(typeof(CloneSerializer<Prefab>), Profile = "Clone")]
[DataSerializerGlobal(typeof(CloneSerializer<Entity>), Profile = "Clone")]
public class EntityCloner
{
private static readonly CloneContext cloneContext = new CloneContext();
private static SerializerSelector cloneSerializerSelector = null;
private static SerializerSelector entitySerializerSelector = null;
public static readonly PropertyKey<CloneContext> CloneContextProperty = new PropertyKey<CloneContext>("CloneContext", typeof(EntityCloner));

// CloneObject TLS used to clone entities, so that we don't create one everytime we clone
Expand All @@ -51,7 +54,7 @@ private static HashSet<object> ClonedObjects()
/// </summary>
/// <param name="prefab">The prefab to clone.</param>
/// <returns>A cloned prefab</returns>
public static Prefab Clone(Prefab prefab)
public static List<Entity> Instantiate(Prefab prefab)
{
if (prefab == null) throw new ArgumentNullException(nameof(prefab));
var clonedObjects = ClonedObjects();
Expand All @@ -61,7 +64,8 @@ public static Prefab Clone(Prefab prefab)
{
CollectEntityTreeHelper(entity, clonedObjects);
}
return Clone(clonedObjects, null, prefab);

return Clone(clonedObjects, null, prefab.Entities);
}
finally
{
Expand Down Expand Up @@ -132,12 +136,17 @@ private static T Clone<T>(HashSet<object> clonedObjects, TryGetValueFunction<obj
cloneSerializerSelector = new SerializerSelector(true, false, "Default", "Clone");
}

if (entitySerializerSelector == null)
{
entitySerializerSelector = new SerializerSelector(true, false, "Default");
}

// Initialize CloneContext
lock (cloneContext)
{
try
{
cloneContext.EntitySerializerSelector = cloneSerializerSelector;
cloneContext.EntitySerializerSelector = entitySerializerSelector;

cloneContext.ClonedObjects = clonedObjects;
cloneContext.MappedObjects = mappedObjects;
Expand Down
3 changes: 1 addition & 2 deletions sources/engine/Stride.Engine/Engine/Prefab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ public sealed class Prefab
/// <returns>A collection of entities extracted from the prefab</returns>
public List<Entity> Instantiate()
{
var newPrefab = EntityCloner.Clone(this);
return newPrefab.Entities;
return EntityCloner.Instantiate(this);
}
}
}
Loading