From 32050c1edc4b17ff5772d78d76f320ea190b6446 Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Tue, 1 Sep 2020 17:55:06 +0100 Subject: [PATCH 1/4] first pass --- .../SceneAuthoring/SceneConverter.cs | 107 ++++++++++++++++++ .../SceneAuthoring/SceneConverter.cs.meta | 11 ++ 2 files changed, 118 insertions(+) create mode 100644 workers/unity/Packages/io.improbable.gdk.core/SceneAuthoring/SceneConverter.cs create mode 100644 workers/unity/Packages/io.improbable.gdk.core/SceneAuthoring/SceneConverter.cs.meta diff --git a/workers/unity/Packages/io.improbable.gdk.core/SceneAuthoring/SceneConverter.cs b/workers/unity/Packages/io.improbable.gdk.core/SceneAuthoring/SceneConverter.cs new file mode 100644 index 0000000000..cf1b56fee1 --- /dev/null +++ b/workers/unity/Packages/io.improbable.gdk.core/SceneAuthoring/SceneConverter.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace Improbable.Gdk.Core.SceneAuthoring.Editor +{ + public static class SceneConverter + { + public static Snapshot Convert(Scene scene, bool includeChildren = false) + { + return Convert(scene.GetRootGameObjects(), includeChildren); + } + + public static Snapshot Convert(IEnumerable gameObjects, bool includeChildren = false) + { + var (entities, entitiesWithIds) = gameObjects + .SelectMany(gameObject => CollectGameObjects(gameObject, includeChildren)) + .SelectMany(GetEntities) + .Partition(); + + var snapshot = new Snapshot(); + + foreach (var kvp in entitiesWithIds) + { + snapshot.AddEntity(kvp.Key, kvp.Value); + } + + var nextId = 1; + + foreach (var entity in entities) + { + while (entitiesWithIds.ContainsKey(new EntityId(nextId))) + { + nextId++; + } + + snapshot.AddEntity(new EntityId(nextId), entity); + nextId++; + } + + return snapshot; + } + + private static IEnumerable CollectGameObjects(GameObject root, bool includeChildren) + { + yield return root; + + if (!includeChildren) + { + yield break; + } + + foreach (Transform childTransform in root.transform) + { + foreach (var child in CollectGameObjects(childTransform.gameObject, true)) + { + yield return child; + } + } + } + + private static IEnumerable GetEntities(GameObject gameObject) + { + var converters = gameObject.GetComponents(); + + switch (converters.Length) + { + case 0: + return Enumerable.Empty(); + case 1: + return converters[0].Convert(); + default: + var componentNames = string.Join(", ", converters.Select(c => c.GetType().Name)); + throw new InvalidOperationException($"GameObject {gameObject} has more than one component that implements {nameof(IConvertGameObjectToSpatialOsEntity)}: '{componentNames}'"); + } + } + + private static (List, Dictionary) Partition( + this IEnumerable convertedEntities) + { + var entities = new List(); + var entitiesWithIds = new Dictionary(); + + foreach (var convertedEntity in convertedEntities) + { + if (!convertedEntity.EntityId.HasValue) + { + entities.Add(convertedEntity.Template); + continue; + } + + var entityId = convertedEntity.EntityId.Value; + + if (entitiesWithIds.ContainsKey(entityId)) + { + throw new InvalidOperationException($"More than one entity is specified with EntityId {entityId}"); + } + + entitiesWithIds[entityId] = convertedEntity.Template; + } + + return (entities, entitiesWithIds); + } + } +} diff --git a/workers/unity/Packages/io.improbable.gdk.core/SceneAuthoring/SceneConverter.cs.meta b/workers/unity/Packages/io.improbable.gdk.core/SceneAuthoring/SceneConverter.cs.meta new file mode 100644 index 0000000000..717f4d6520 --- /dev/null +++ b/workers/unity/Packages/io.improbable.gdk.core/SceneAuthoring/SceneConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23400c5caa183b14d9394b88145c5e99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From ef9e78e5b2d39bf028e2873b9d7867fb98368925 Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Wed, 2 Sep 2020 12:48:07 +0100 Subject: [PATCH 2/4] snapshot auto adds persistence --- .../Packages/io.improbable.gdk.core/Utility/Snapshot.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs b/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs index 74a07e09d1..0ac4a4d2e2 100644 --- a/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs +++ b/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs @@ -9,6 +9,8 @@ namespace Improbable.Gdk.Core /// public class Snapshot { + private const int PersistenceComponentId = 55; + private readonly Dictionary entities = new Dictionary(); public int Count => entities.Count; @@ -50,7 +52,10 @@ public EntityId AddEntity(EntityTemplate entityTemplate) /// public void AddEntity(EntityId entityId, EntityTemplate entityTemplate) { - entities[entityId] = entityTemplate.GetEntity(); + var entity = entityTemplate.GetEntity(); + // This is a no-op if the entity already has persistence. + entity.Add(new ComponentData(PersistenceComponentId, SchemaComponentData.Create())); + entities[entityId] = entity; } /// From 59970f4f06c049cd72bc2a236d772ed1c7aea4d9 Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Thu, 3 Sep 2020 11:30:20 +0100 Subject: [PATCH 3/4] add some tests --- .../SceneAuthoring/SceneConverterTests.cs | 122 ++++++++++++++++++ .../SceneConverterTests.cs.meta | 3 + .../Utility/Snapshot.cs | 2 + 3 files changed, 127 insertions(+) create mode 100644 workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs create mode 100644 workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs.meta diff --git a/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs b/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs new file mode 100644 index 0000000000..ec7d38ce45 --- /dev/null +++ b/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using Improbable.Gdk.Core; +using Improbable.Gdk.Core.SceneAuthoring; +using Improbable.Gdk.Core.SceneAuthoring.AuthoringComponents; +using Improbable.Gdk.Core.SceneAuthoring.Editor; +using Improbable.Worker.CInterop; +using NUnit.Framework; +using UnityEngine; + +namespace Improbable.Gdk.EditmodeTests.SceneAuthoring +{ + [TestFixture] + public class SceneConverterTests + { + [Test] + public void GameObjects_with_duplicate_entity_ids_throws_exception() + { + var entityId = new EntityId(1); + var gameObjects = new[] + { + CreateGameObject(desiredEntityId: entityId), CreateGameObject(desiredEntityId: entityId) + }; + Assert.Throws(() => SceneConverter.Convert(gameObjects)); + } + + [Test] + public void GameObjects_with_multiple_converters_are_rejected() + { + var gameObject = CreateGameObject(); + gameObject.AddComponent(); + + Assert.Throws(() => SceneConverter.Convert(new[] { gameObject })); + } + + [Test] + public void Child_gameobjects_are_not_considered_if_includeChildren_is_false() + { + var gameobject = CreateGameObject(); + var child = CreateGameObject(); + child.transform.SetParent(gameobject.transform); + + var snapshot = SceneConverter.Convert(new[] { gameobject }); + Assert.AreEqual(1, snapshot.Count); + } + + [Test] + public void Child_gameobjects_are_considered_if_includeChildren_is_true() + { + var gameobject = CreateGameObject(); + var child = CreateGameObject(); + var grandChild = CreateGameObject(); + + child.transform.SetParent(gameobject.transform); + grandChild.transform.SetParent(child.transform); + + var snapshot = SceneConverter.Convert(new[] { gameobject }, includeChildren: true); + Assert.AreEqual(3, snapshot.Count); + } + + [Test] + public void GameObjects_with_specific_entity_id_are_always_put_there() + { + var firstPosition = new Vector3(1, 0, 0); + var secondPosition = new Vector3(0, 1, 0); + var restPosition = new Vector3(0, 0, 1); + + var firstEntityId = new EntityId(1); + var secondEntityId = new EntityId(2); + + var gameObjects = new[] + { + CreateGameObject(position: restPosition), + CreateGameObject(desiredEntityId: firstEntityId, position: firstPosition), + CreateGameObject(position: restPosition), + CreateGameObject(desiredEntityId: secondEntityId, position: secondPosition), + CreateGameObject(position: restPosition) + }; + + var snapshot = SceneConverter.Convert(gameObjects); + + var firstEntity = snapshot[firstEntityId]; + var firstEntityPosition = GetPosition(firstEntity).Coords.ToUnityVector(); + Assert.AreEqual(firstPosition, firstEntityPosition); + + var secondEntity = snapshot[secondEntityId]; + var secondEntityPosition = GetPosition(secondEntity).Coords.ToUnityVector(); + Assert.AreEqual(secondPosition, secondEntityPosition); + } + + private static GameObject CreateGameObject(EntityId desiredEntityId = default, Vector3 position = default) + { + var go = new GameObject(); + go.transform.position = position; + + go.AddComponent(); + var conversion = go.AddComponent(); + + if (desiredEntityId.IsValid()) + { + conversion.UseSpecificEntityId = true; + conversion.DesiredEntityId = desiredEntityId.Id; + } + + return go; + } + + private static Position.Snapshot GetPosition(Entity entity) + { + var schemaObject = entity.Get(Position.ComponentId).Value.SchemaData.Value.GetFields(); + return Position.Serialization.DeserializeSnapshot(schemaObject); + } + + private class CustomConverter : MonoBehaviour, IConvertGameObjectToSpatialOsEntity + { + public List Convert() + { + throw new NotImplementedException(); + } + } + } +} diff --git a/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs.meta b/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs.meta new file mode 100644 index 0000000000..753d638333 --- /dev/null +++ b/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e084d31c789d41669d999d5d3a99ae30 +timeCreated: 1599126128 \ No newline at end of file diff --git a/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs b/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs index 0ac4a4d2e2..fada487dc0 100644 --- a/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs +++ b/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs @@ -84,5 +84,7 @@ public void WriteToFile(string path) } } } + + internal Entity this[EntityId entityId] => entities[entityId]; } } From f2497f5c1f8022745d5798194043306512e1cc98 Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Thu, 3 Sep 2020 12:23:57 +0100 Subject: [PATCH 4/4] snapshot implements Dispose --- .../SnapshotGenerator/SnapshotGenerator.cs | 9 +++--- .../SceneAuthoring/SceneConverterTests.cs | 29 +++++++++++-------- .../Utility/Snapshot.cs | 17 ++++++++++- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/workers/unity/Assets/Playground/Editor/SnapshotGenerator/SnapshotGenerator.cs b/workers/unity/Assets/Playground/Editor/SnapshotGenerator/SnapshotGenerator.cs index ef4045364d..594b86065e 100644 --- a/workers/unity/Assets/Playground/Editor/SnapshotGenerator/SnapshotGenerator.cs +++ b/workers/unity/Assets/Playground/Editor/SnapshotGenerator/SnapshotGenerator.cs @@ -19,10 +19,11 @@ public struct Arguments public static void Generate(Arguments arguments) { Debug.Log("Generating snapshot."); - var snapshot = CreateSnapshot(arguments.NumberEntities); - - Debug.Log($"Writing snapshot to: {arguments.OutputPath}"); - snapshot.WriteToFile(arguments.OutputPath); + using (var snapshot = CreateSnapshot(arguments.NumberEntities)) + { + Debug.Log($"Writing snapshot to: {arguments.OutputPath}"); + snapshot.WriteToFile(arguments.OutputPath); + } } private static Snapshot CreateSnapshot(int cubeCount) diff --git a/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs b/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs index ec7d38ce45..1838112510 100644 --- a/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs +++ b/workers/unity/Assets/Tests/EditmodeTests/Correctness/SceneAuthoring/SceneConverterTests.cs @@ -40,8 +40,10 @@ public void Child_gameobjects_are_not_considered_if_includeChildren_is_false() var child = CreateGameObject(); child.transform.SetParent(gameobject.transform); - var snapshot = SceneConverter.Convert(new[] { gameobject }); - Assert.AreEqual(1, snapshot.Count); + using (var snapshot = SceneConverter.Convert(new[] { gameobject })) + { + Assert.AreEqual(1, snapshot.Count); + } } [Test] @@ -54,8 +56,10 @@ public void Child_gameobjects_are_considered_if_includeChildren_is_true() child.transform.SetParent(gameobject.transform); grandChild.transform.SetParent(child.transform); - var snapshot = SceneConverter.Convert(new[] { gameobject }, includeChildren: true); - Assert.AreEqual(3, snapshot.Count); + using (var snapshot = SceneConverter.Convert(new[] { gameobject }, includeChildren: true)) + { + Assert.AreEqual(3, snapshot.Count); + } } [Test] @@ -77,15 +81,16 @@ public void GameObjects_with_specific_entity_id_are_always_put_there() CreateGameObject(position: restPosition) }; - var snapshot = SceneConverter.Convert(gameObjects); - - var firstEntity = snapshot[firstEntityId]; - var firstEntityPosition = GetPosition(firstEntity).Coords.ToUnityVector(); - Assert.AreEqual(firstPosition, firstEntityPosition); + using (var snapshot = SceneConverter.Convert(gameObjects)) + { + var firstEntity = snapshot[firstEntityId]; + var firstEntityPosition = GetPosition(firstEntity).Coords.ToUnityVector(); + Assert.AreEqual(firstPosition, firstEntityPosition); - var secondEntity = snapshot[secondEntityId]; - var secondEntityPosition = GetPosition(secondEntity).Coords.ToUnityVector(); - Assert.AreEqual(secondPosition, secondEntityPosition); + var secondEntity = snapshot[secondEntityId]; + var secondEntityPosition = GetPosition(secondEntity).Coords.ToUnityVector(); + Assert.AreEqual(secondPosition, secondEntityPosition); + } } private static GameObject CreateGameObject(EntityId desiredEntityId = default, Vector3 position = default) diff --git a/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs b/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs index fada487dc0..6717e5535d 100644 --- a/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs +++ b/workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Improbable.Worker.CInterop; using UnityEngine; @@ -7,7 +8,7 @@ namespace Improbable.Gdk.Core /// /// Convenience wrapper around the WorkerSDK Snapshot API. /// - public class Snapshot + public class Snapshot : IDisposable { private const int PersistenceComponentId = 55; @@ -85,6 +86,20 @@ public void WriteToFile(string path) } } + public void Dispose() + { + foreach (var kvp in entities) + { + var entity = kvp.Value; + + foreach (var id in entity.GetComponentIds()) + { + var componentData = entity.Get(id).Value; + componentData.SchemaData?.Destroy(); + } + } + } + internal Entity this[EntityId entityId] => entities[entityId]; } }