Skip to content
This repository has been archived by the owner on Jan 18, 2022. It is now read-only.

Default SceneConverter implementation #1477

Closed
wants to merge 4 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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<InvalidOperationException>(() => SceneConverter.Convert(gameObjects));
}

[Test]
public void GameObjects_with_multiple_converters_are_rejected()
{
var gameObject = CreateGameObject();
gameObject.AddComponent<CustomConverter>();

Assert.Throws<InvalidOperationException>(() => 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);

using (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);

using (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)
};

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);
}
}

private static GameObject CreateGameObject(EntityId desiredEntityId = default, Vector3 position = default)
{
var go = new GameObject();
go.transform.position = position;

go.AddComponent<PositionFromGameObjectAuthoringComponent>();
var conversion = go.AddComponent<ConvertToSingleEntity>();

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<ConvertedEntity> Convert()
{
throw new NotImplementedException();
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<GameObject> 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<GameObject> 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<ConvertedEntity> GetEntities(GameObject gameObject)
{
var converters = gameObject.GetComponents<IConvertGameObjectToSpatialOsEntity>();

switch (converters.Length)
{
case 0:
return Enumerable.Empty<ConvertedEntity>();
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<EntityTemplate>, Dictionary<EntityId, EntityTemplate>) Partition(
this IEnumerable<ConvertedEntity> convertedEntities)
{
var entities = new List<EntityTemplate>();
var entitiesWithIds = new Dictionary<EntityId, EntityTemplate>();

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);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions workers/unity/Packages/io.improbable.gdk.core/Utility/Snapshot.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using Improbable.Worker.CInterop;
using UnityEngine;
Expand All @@ -7,8 +8,10 @@ namespace Improbable.Gdk.Core
/// <summary>
/// Convenience wrapper around the WorkerSDK Snapshot API.
/// </summary>
public class Snapshot
public class Snapshot : IDisposable
{
private const int PersistenceComponentId = 55;

private readonly Dictionary<EntityId, Entity> entities = new Dictionary<EntityId, Entity>();

public int Count => entities.Count;
Expand Down Expand Up @@ -50,7 +53,10 @@ public EntityId AddEntity(EntityTemplate entityTemplate)
/// </remarks>
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;
}

/// <summary>
Expand Down Expand Up @@ -79,5 +85,21 @@ 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];
}
}