From 755edc2715dff63936ec69e07266f14c5635cf83 Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Mon, 24 Apr 2023 09:13:25 -0400 Subject: [PATCH 01/16] WIP --- .../Runtime/Entities/Lifecycle/Migration.meta | 3 + .../Migration/MigrationReflectionHelper.cs | 116 ++++++++++++++++++ .../MigrationReflectionHelper.cs.meta | 3 + .../Migration/WorldEntityMigrationSystem.cs | 34 +++++ .../WorldEntityMigrationSystem.cs.meta | 3 + .../TaskDriver/TaskDriverManagementSystem.cs | 2 +- .../DataSource/EntityProxyDataSource.cs | 45 ++++++- .../DataStream/EntityProxyDataStream.cs | 2 + 8 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration.meta create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs.meta create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration.meta b/Scripts/Runtime/Entities/Lifecycle/Migration.meta new file mode 100644 index 00000000..2efc29e8 --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 89e47482399f494b92110e659d053925 +timeCreated: 1681919083 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs new file mode 100644 index 00000000..284266b7 --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs @@ -0,0 +1,116 @@ +using Anvil.Unity.DOTS.Entities.TaskDriver; +using System; +using System.Reflection; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using UnityEngine; + +namespace Anvil.Unity.DOTS.Entities +{ + internal static class MigrationReflectionHelper + { + private static NativeParallelHashMap s_TypeOffsetsLookup; + private static NativeList s_EntityOffsetList; + private static NativeList s_BlobAssetRefOffsetList; + private static NativeList s_WeakAssetRefOffsetList; + + private static readonly Type I_ENTITY_PROXY_INSTANCE = typeof(IEntityProxyInstance); + + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void Init() + { + if (s_TypeOffsetsLookup.IsCreated) + { + s_TypeOffsetsLookup.Dispose(); + } + if (s_EntityOffsetList.IsCreated) + { + s_EntityOffsetList.Dispose(); + } + if (s_BlobAssetRefOffsetList.IsCreated) + { + s_BlobAssetRefOffsetList.Dispose(); + } + if (s_WeakAssetRefOffsetList.IsCreated) + { + s_WeakAssetRefOffsetList.Dispose(); + } + + s_TypeOffsetsLookup = new NativeParallelHashMap(256, Allocator.Persistent); + s_EntityOffsetList = new NativeList(Allocator.Persistent); + s_BlobAssetRefOffsetList = new NativeList(Allocator.Persistent); + s_WeakAssetRefOffsetList = new NativeList(Allocator.Persistent); + + + Type genericWrapperType = typeof(EntityProxyInstanceWrapper<>); + + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.IsDynamic) + { + return; + } + foreach (Type type in assembly.GetTypes()) + { + if (!type.IsValueType || !I_ENTITY_PROXY_INSTANCE.IsAssignableFrom(type)) + { + continue; + } + + Type concreteType = genericWrapperType.MakeGenericType(type); + long typeHash = BurstRuntime.GetHashCode64(concreteType); + + int entityOffsetStartIndex = s_EntityOffsetList.Length; + int blobOffsetStartIndex = s_BlobAssetRefOffsetList.Length; + int weakAssetStartIndex = s_WeakAssetRefOffsetList.Length; + + EntityRemapUtility.CalculateFieldOffsetsUnmanaged( + concreteType, + out bool hasEntityRefs, + out bool hasBlobRefs, + out bool hasWeakAssetRefs, + ref s_EntityOffsetList, + ref s_BlobAssetRefOffsetList, + ref s_WeakAssetRefOffsetList); + + if (!hasEntityRefs && !hasBlobRefs && !hasWeakAssetRefs) + { + continue; + } + + s_TypeOffsetsLookup.Add(typeHash, new TypeOffsetInfo( + entityOffsetStartIndex, + s_EntityOffsetList.Length - entityOffsetStartIndex, + blobOffsetStartIndex, + s_BlobAssetRefOffsetList.Length - blobOffsetStartIndex, + weakAssetStartIndex, + s_WeakAssetRefOffsetList.Length - weakAssetStartIndex)); + } + } + } + + + + public readonly struct TypeOffsetInfo + { + public readonly int EntityOffsetStartIndex; + public readonly int EntityOffsetCount; + public readonly int BlobAssetStartIndex; + public readonly int BlobAssetCount; + public readonly int WeakAssetStartIndex; + public readonly int WeakAssetCount; + + public TypeOffsetInfo(int entityOffsetStartIndex, int entityOffsetCount, int blobAssetStartIndex, int blobAssetCount, int weakAssetStartIndex, int weakAssetCount) + { + EntityOffsetStartIndex = entityOffsetStartIndex; + EntityOffsetCount = entityOffsetCount; + BlobAssetStartIndex = blobAssetStartIndex; + BlobAssetCount = blobAssetCount; + WeakAssetStartIndex = weakAssetStartIndex; + WeakAssetCount = weakAssetCount; + } + } + } +} diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs.meta new file mode 100644 index 00000000..ccefca9b --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 534879a926794cac8340e9b357446d08 +timeCreated: 1682019695 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs new file mode 100644 index 00000000..84ddd009 --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Unity.Collections; +using Unity.Entities; + +namespace Anvil.Unity.DOTS.Entities +{ + public interface IMigratable + { + public void Migrate(NativeArray remapArray); + } + + public class WorldEntityMigrationSystem : AbstractDataSystem + { + private readonly List m_Migratables; + + public WorldEntityMigrationSystem() + { + m_Migratables = new List(); + } + + public void AddMigratable(IMigratable migratable) + { + m_Migratables.Add(migratable); + } + + public void Remap(NativeArray remapArray) + { + foreach (IMigratable migratable in m_Migratables) + { + migratable.Migrate(remapArray); + } + } + } +} diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs.meta new file mode 100644 index 00000000..dfbe8927 --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 102d640f6a5d4b78a55c7c7fb61b73ed +timeCreated: 1681919099 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index fac94909..a8f2c086 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Unity.Collections; using Unity.Entities; using Unity.Jobs; @@ -209,7 +210,6 @@ protected sealed override void OnUpdate() Dependency = dependsOn; } - //************************************************************************************************************* // SAFETY //************************************************************************************************************* diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index 8e360d20..66781bbb 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -1,12 +1,20 @@ using Anvil.Unity.DOTS.Jobs; using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities.TaskDriver { - internal class EntityProxyDataSource : AbstractDataSource> + internal class EntityProxyDataSource : AbstractDataSource>, + IMigratable where TInstance : unmanaged, IEntityProxyInstance { + private static NativeList s_EntityOffsetList = new NativeList(Allocator.Persistent); + private static NativeList s_BlobAssetRefOffsetList = new NativeList(Allocator.Persistent); + private static NativeList s_WeakAssetRefOffsetList = new NativeList(Allocator.Persistent); + private EntityProxyDataSourceConsolidator m_Consolidator; public EntityProxyDataSource(TaskDriverManagementSystem taskDriverManagementSystem) : base(taskDriverManagementSystem) { } @@ -17,6 +25,41 @@ protected override void DisposeSelf() base.DisposeSelf(); } + public unsafe void Migrate(NativeArray remapArray) + { + + PendingData.Acquire(AccessType.ExclusiveWrite); + //TODO: Need to ensure this is actually the ref - look at NativeParallelHashMap + foreach (EntityProxyInstanceWrapper entry in PendingData.Pending) + { + Entity entity = entry.InstanceID.Entity; + Entity remapEntity = EntityRemapUtility.RemapEntity(ref remapArray, entity); + + EntityRemapUtility.HasEntityReferencesManaged(typeof(EntityProxyInstanceWrapper), out bool hasEntityReferences, out bool hasBlobReferences); + EntityRemapUtility.CalculateFieldOffsetsUnmanaged(typeof(EntityProxyInstanceWrapper), out bool hasEntityRefs, out bool hasBlobRefs, + out bool hasWeakAssetRefs, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, ref s_WeakAssetRefOffsetList); + + EntityProxyInstanceWrapper copy = entry; + byte* copyPtr = (byte*)UnsafeUtility.AddressOf(ref copy); + void* startCopyPtr = copyPtr; + for (int i = 0; i < s_EntityOffsetList.Length; ++i) + { + TypeManager.EntityOffsetInfo offsetInfo = s_EntityOffsetList[i]; + copyPtr += offsetInfo.Offset; + Entity* offsetEntity = (Entity*)copyPtr; + *offsetEntity = EntityRemapUtility.RemapEntity(ref remapArray, *offsetEntity); + } + + + + copy = *(EntityProxyInstanceWrapper*)startCopyPtr; + + float a = 5.0f; + + } + PendingData.Release(); + } + protected override void HardenSelf() { base.HardenSelf(); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs index 88676971..02751027 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs @@ -33,6 +33,8 @@ public EntityProxyDataStream(ITaskSetOwner taskSetOwner, CancelRequestBehaviour { TaskDriverManagementSystem taskDriverManagementSystem = taskSetOwner.World.GetOrCreateSystem(); m_DataSource = taskDriverManagementSystem.GetOrCreateEntityProxyDataSource(); + //TODO: Move to abstract and make virtual + taskSetOwner.World.GetOrCreateSystem().AddMigratable(m_DataSource); m_ActiveArrayData = m_DataSource.CreateActiveArrayData(taskSetOwner, cancelRequestBehaviour); From 97dc1508e1717c258d53f385ce645ae301e8daee Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Wed, 26 Apr 2023 10:07:36 -0400 Subject: [PATCH 02/16] WIP --- .../Lifecycle/Migration/IMigrationObserver.cs | 10 ++ .../Migration/IMigrationObserver.cs.meta | 3 + .../Migration/MigrationReflectionHelper.cs | 7 ++ .../Migration/WorldEntityMigrationSystem.cs | 34 ++++-- .../Entities/TaskDriver/AbstractTaskDriver.cs | 47 +++++++- .../TaskDriver/TaskDriverManagementSystem.cs | 62 +++++++++- .../Job/JobConfig/AbstractJobConfig.cs | 1 + .../DataSource/AbstractDataSource.cs | 7 ++ .../DataSource/EntityProxyDataSource.cs | 114 +++++++++++++----- .../DataStream/DataSource/IDataSource.cs | 4 + .../EntityProxyInstanceID.cs | 21 +++- .../EntityProxyInstanceWrapper.cs | 6 +- .../EntityProxyInstanceWrapperExtension.cs | 20 +++ ...ntityProxyInstanceWrapperExtension.cs.meta | 3 + .../Entities/TaskDriver/TaskSet/TaskSet.cs | 27 ++++- 15 files changed, 310 insertions(+), 56 deletions(-) create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs.meta create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs new file mode 100644 index 00000000..9d223e9c --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs @@ -0,0 +1,10 @@ +using Unity.Collections; +using Unity.Entities; + +namespace Anvil.Unity.DOTS.Entities +{ + public interface IMigrationObserver + { + public void Migrate(World destinationWorld, ref NativeArray remapArray); + } +} diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs.meta new file mode 100644 index 00000000..6ca595fc --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4f520dd61044412aaca8cd01df7e80d9 +timeCreated: 1682434141 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs index 284266b7..378d96d8 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs @@ -91,6 +91,13 @@ private static void Init() } } + public static void PatchEntityIfMoved() + { + long typeHash = BurstRuntime.GetHashCode64(); + TypeOffsetInfo typeOffsetInfo = s_TypeOffsetsLookup[typeHash]; + + } + public readonly struct TypeOffsetInfo diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs index 84ddd009..98c52c7d 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs @@ -4,31 +4,41 @@ namespace Anvil.Unity.DOTS.Entities { - public interface IMigratable - { - public void Migrate(NativeArray remapArray); - } - public class WorldEntityMigrationSystem : AbstractDataSystem { - private readonly List m_Migratables; + private readonly HashSet m_MigrationObservers; public WorldEntityMigrationSystem() { - m_Migratables = new List(); + m_MigrationObservers = new HashSet(); + } + + public void AddMigrationObserver(IMigrationObserver migrationObserver) + { + m_MigrationObservers.Add(migrationObserver); } - public void AddMigratable(IMigratable migratable) + public void RemoveMigrationObserver(IMigrationObserver migrationObserver) { - m_Migratables.Add(migratable); + m_MigrationObservers.Remove(migrationObserver); } - public void Remap(NativeArray remapArray) + private void NotifyObservers(World destinationWorld, ref NativeArray remapArray) { - foreach (IMigratable migratable in m_Migratables) + foreach (IMigrationObserver migrationObserver in m_MigrationObservers) { - migratable.Migrate(remapArray); + migrationObserver.Migrate(destinationWorld, ref remapArray); } } + + public void MigrateTo(World destinationWorld, EntityQuery entitiesToMigrateQuery) + { + // Do move + NativeArray remapArray = EntityManager.CreateEntityRemapArray(Allocator.TempJob); + destinationWorld.EntityManager.MoveEntitiesFrom(EntityManager, entitiesToMigrateQuery, remapArray); + + NotifyObservers(destinationWorld, ref remapArray); + remapArray.Dispose(); + } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs index 63b5fc34..b2cfea4d 100644 --- a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs +++ b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs @@ -27,9 +27,12 @@ public abstract class AbstractTaskDriver : AbstractAnvilBase, ITaskSetOwner private static readonly Type TASK_DRIVER_SYSTEM_TYPE = typeof(TaskDriverSystem<>); private static readonly Type COMPONENT_SYSTEM_GROUP_TYPE = typeof(ComponentSystemGroup); + + private readonly PersistentDataSystem m_PersistentDataSystem; private readonly List m_SubTaskDrivers; private readonly uint m_ID; + private readonly string m_UniqueMigrationSuffix; private bool m_IsHardened; private bool m_HasCancellableData; @@ -77,8 +80,9 @@ bool ITaskSetOwner.HasCancellableData } } - protected AbstractTaskDriver(World world) + protected AbstractTaskDriver(World world, string uniqueMigrationSuffix = null) { + m_UniqueMigrationSuffix = uniqueMigrationSuffix ?? string.Empty; World = world; TaskDriverManagementSystem taskDriverManagementSystem = World.GetOrCreateSystem(); m_PersistentDataSystem = World.GetOrCreateSystem(); @@ -416,11 +420,52 @@ void ITaskSetOwner.AddResolvableDataStreamsTo(Type type, List migrationTaskSetOwnerIDLookup, + Dictionary migrationActiveIDLookup) + { + //Construct the unique path for this TaskDriver and ensure we don't need a user provided suffix + string typeName = GetType().GetReadableName(); + string path = $"{parentPath}{typeName}{m_UniqueMigrationSuffix}-"; + Debug_EnsureNoDuplicateMigrationData(path, migrationTaskSetOwnerIDLookup); + migrationTaskSetOwnerIDLookup.Add(path, m_ID); + + //Get our TaskSet to populate all the possible ActiveIDs + TaskSet.AddToMigrationLookup(path, migrationActiveIDLookup); + + //Try and do the same for our system (there can only be one), will gracefully fail if we have. + string systemPath = $"{typeName}-System"; + if (migrationTaskSetOwnerIDLookup.TryAdd(systemPath, TaskDriverSystem.ID)) + { + TaskDriverSystem.TaskSet.AddToMigrationLookup(systemPath, migrationActiveIDLookup); + } + + //Then recurse downward to catch all the sub task drivers + foreach (AbstractTaskDriver subTaskDriver in m_SubTaskDrivers) + { + subTaskDriver.AddToMigrationLookup(path, migrationTaskSetOwnerIDLookup, migrationActiveIDLookup); + } + } + //************************************************************************************************************* // SAFETY //************************************************************************************************************* + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void Debug_EnsureNoDuplicateMigrationData(string path, Dictionary migrationTaskSetOwnerIDLookup) + { + if (migrationTaskSetOwnerIDLookup.ContainsKey(path)) + { + throw new InvalidOperationException($"TaskDriver {GetType().GetReadableName()} at path {path} already exists. There are two or more of the same task driver at the same level. They will require a unique migration suffix to be set in their constructor."); + } + } + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] private void Debug_EnsureNotHardened() { diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index a8f2c086..1cfb9106 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -1,11 +1,14 @@ using Anvil.CSharp.Collections; using Anvil.CSharp.Data; +using Anvil.CSharp.Logging; using Anvil.Unity.DOTS.Jobs; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Unity.Burst; using Unity.Collections; +using Unity.Core; using Unity.Entities; using Unity.Jobs; @@ -14,7 +17,8 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver //TODO: #108 - Custom Profiling - https://github.com/decline-cookies/anvil-unity-dots/pull/111 //TODO: #86 - Revisit with Entities 1.0 for "Create Before/After" [UpdateInGroup(typeof(InitializationSystemGroup), OrderFirst = true)] - internal partial class TaskDriverManagementSystem : AbstractAnvilSystemBase + internal partial class TaskDriverManagementSystem : AbstractAnvilSystemBase, + IMigrationObserver { private readonly Dictionary m_EntityProxyDataSourcesByType; private readonly HashSet m_AllTaskDrivers; @@ -25,6 +29,8 @@ internal partial class TaskDriverManagementSystem : AbstractAnvilSystemBase private readonly CancelCompleteDataSource m_CancelCompleteDataSource; private readonly List m_CancelProgressFlows; private readonly Dictionary m_UnityEntityDataAccessControllers; + private readonly Dictionary m_MigrationTaskSetOwnerIDLookup; + private readonly Dictionary m_MigrationActiveIDLookup; private bool m_IsInitialized; private bool m_IsHardened; @@ -45,6 +51,15 @@ public TaskDriverManagementSystem() m_CancelCompleteDataSource = new CancelCompleteDataSource(this); m_CancelProgressFlows = new List(); m_UnityEntityDataAccessControllers = new Dictionary(); + m_MigrationTaskSetOwnerIDLookup = new Dictionary(); + m_MigrationActiveIDLookup = new Dictionary(); + } + + protected override void OnCreate() + { + base.OnCreate(); + WorldEntityMigrationSystem worldEntityMigrationSystem = World.GetOrCreateSystem(); + worldEntityMigrationSystem.AddMigrationObserver(this); } protected override void OnStartRunning() @@ -105,6 +120,8 @@ private void Harden() topLevelTaskDriver.Harden(); } + PopulateMigrationLookup(); + //All the data has been hardened, we can Harden the Update Phase for the Systems foreach (AbstractTaskDriverSystem taskDriverSystem in m_AllTaskDriverSystems) { @@ -124,6 +141,18 @@ private void Harden() m_CancelProgressFlowBulkJobScheduler = new BulkJobScheduler(m_CancelProgressFlows.ToArray()); } + private void PopulateMigrationLookup() + { + //Generate a World ID + foreach (AbstractTaskDriver topLevelTaskDriver in m_TopLevelTaskDrivers) + { + topLevelTaskDriver.AddToMigrationLookup( + string.Empty, + m_MigrationTaskSetOwnerIDLookup, + m_MigrationTaskSetOwnerIDLookup); + } + } + public EntityProxyDataSource GetOrCreateEntityProxyDataSource() where TInstance : unmanaged, IEntityProxyInstance { @@ -209,6 +238,28 @@ protected sealed override void OnUpdate() Dependency = dependsOn; } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public void Migrate(World destinationWorld, ref NativeArray remapArray) + { + TaskDriverManagementSystem destinationTaskDriverManagementSystem = destinationWorld.GetOrCreateSystem(); + Debug_EnsureOtherWorldTaskDriverManagementSystemExists(destinationWorld, destinationTaskDriverManagementSystem); + + //TODO: Lazy create a World to World mapping lookup for ActiveIDs and TaskSetOwnerIDs + + Dictionary destinationEntityProxyDataSourcesByType = destinationTaskDriverManagementSystem.m_EntityProxyDataSourcesByType; + + foreach (KeyValuePair entry in m_EntityProxyDataSourcesByType) + { + //We may not have a corresponding destination Data Source in the destination world but we still want to process the migration so that + //we remove any references in this world. If we do have the corresponding data source, we'll transfer over to the other world. + destinationEntityProxyDataSourcesByType.TryGetValue(entry.Key, out IDataSource destinationDataSource); + entry.Value.MigrateTo(destinationDataSource, ref remapArray); + } + } //************************************************************************************************************* // SAFETY @@ -222,5 +273,14 @@ private void Debug_EnsureNotHardened() throw new InvalidOperationException($"Expected {this} to not yet be Hardened but {nameof(Harden)} has already been called!"); } } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void Debug_EnsureOtherWorldTaskDriverManagementSystemExists(World destinationWorld, TaskDriverManagementSystem taskDriverManagementSystem) + { + if (taskDriverManagementSystem == null) + { + throw new InvalidOperationException($"Expected World {destinationWorld} to have a {nameof(TaskDriverManagementSystem)} but it does not!"); + } + } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/AbstractJobConfig.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/AbstractJobConfig.cs index 24f362d6..439c6f28 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/AbstractJobConfig.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/AbstractJobConfig.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractDataSource.cs index 32e13c2c..1b23edb7 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractDataSource.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Unity.Collections; +using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities.TaskDriver @@ -132,6 +133,12 @@ protected void AddConsolidationData(AbstractData data, AccessType accessType) { m_ConsolidationData.Add(new DataAccessWrapper(data, accessType)); } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public abstract void MigrateTo(IDataSource destinationDataSource, ref NativeArray remapArray); //************************************************************************************************************* // EXECUTION diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index 66781bbb..a5f5b677 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -1,3 +1,4 @@ +using Anvil.Unity.DOTS.Data; using Anvil.Unity.DOTS.Jobs; using Unity.Burst; using Unity.Collections; @@ -7,14 +8,9 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver { - internal class EntityProxyDataSource : AbstractDataSource>, - IMigratable + internal class EntityProxyDataSource : AbstractDataSource> where TInstance : unmanaged, IEntityProxyInstance { - private static NativeList s_EntityOffsetList = new NativeList(Allocator.Persistent); - private static NativeList s_BlobAssetRefOffsetList = new NativeList(Allocator.Persistent); - private static NativeList s_WeakAssetRefOffsetList = new NativeList(Allocator.Persistent); - private EntityProxyDataSourceConsolidator m_Consolidator; public EntityProxyDataSource(TaskDriverManagementSystem taskDriverManagementSystem) : base(taskDriverManagementSystem) { } @@ -25,19 +21,92 @@ protected override void DisposeSelf() base.DisposeSelf(); } - public unsafe void Migrate(NativeArray remapArray) + protected override void HardenSelf() { + base.HardenSelf(); + + //We need to ensure we get the right access to any of the cancel data structures + foreach (AbstractData data in ActiveDataLookupByID.Values) + { + //If this piece of data can be cancelled, we need to be able to read the associated Cancel Request lookup + if (data.CancelRequestBehaviour is CancelRequestBehaviour.Delete or CancelRequestBehaviour.Unwind) + { + AddConsolidationData(data.TaskSetOwner.TaskSet.CancelRequestsDataStream.ActiveLookupData, AccessType.SharedRead); + } + } + + m_Consolidator = new EntityProxyDataSourceConsolidator(PendingData, ActiveDataLookupByID); + } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public override void MigrateTo( + IDataSource destinationDataSource, + ref NativeArray remapArray, + ref NativeParallelHashMap taskSetOwnerIDMapping, + ref NativeParallelHashMap activeIDMapping) + { + EntityProxyDataSource destination = destinationDataSource as EntityProxyDataSource; + + PendingData.Acquire(AccessType.ExclusiveWrite); + + UnsafeTypedStream> stream = PendingData.Pending; + NativeArray> instanceArray = stream.ToNativeArray(Allocator.Temp); + stream.Clear(); + + for (int i = 0; i < instanceArray.Length; ++i) + { + //If we didn't move, we need to rewrite to the stream since we didn't move + EntityProxyInstanceWrapper instance = instanceArray[i]; + EntityProxyInstanceID instanceID = instance.InstanceID; + + //See what our entity was remapped to + Entity remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, instanceID.Entity); + //If we were remapped to null, then we don't exist in the new world, we should just stay here + if (remappedEntity == Entity.Null) + { + //TODO: Write back to the original stream, we stayed in this world + continue; + } + + //If we don't have a destination in the new world, then we can just let these cease to exist + if (destination == null) + { + continue; + } + + //If we do have a destination, then we will want to patch the entity references + instance.PatchEntityReferences(remappedEntity); + + //Rewrite the memory for the TaskSetOwnerID and ActiveID + instance.PatchIDs( + taskSetOwnerIDMapping[instanceID.TaskSetOwnerID], + activeIDMapping[instanceID.ActiveID]); + + //TODO: Write to the new stream + + } + + PendingData.Release(); + } + + public unsafe void Migrate(NativeArray remapArray) + { PendingData.Acquire(AccessType.ExclusiveWrite); //TODO: Need to ensure this is actually the ref - look at NativeParallelHashMap foreach (EntityProxyInstanceWrapper entry in PendingData.Pending) { - Entity entity = entry.InstanceID.Entity; - Entity remapEntity = EntityRemapUtility.RemapEntity(ref remapArray, entity); - - EntityRemapUtility.HasEntityReferencesManaged(typeof(EntityProxyInstanceWrapper), out bool hasEntityReferences, out bool hasBlobReferences); - EntityRemapUtility.CalculateFieldOffsetsUnmanaged(typeof(EntityProxyInstanceWrapper), out bool hasEntityRefs, out bool hasBlobRefs, - out bool hasWeakAssetRefs, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, ref s_WeakAssetRefOffsetList); + EntityRemapUtility.CalculateFieldOffsetsUnmanaged( + typeof(EntityProxyInstanceWrapper), + out bool hasEntityRefs, + out bool hasBlobRefs, + out bool hasWeakAssetRefs, + ref s_EntityOffsetList, + ref s_BlobAssetRefOffsetList, + ref s_WeakAssetRefOffsetList); EntityProxyInstanceWrapper copy = entry; byte* copyPtr = (byte*)UnsafeUtility.AddressOf(ref copy); @@ -50,33 +119,14 @@ public unsafe void Migrate(NativeArray remap *offsetEntity = EntityRemapUtility.RemapEntity(ref remapArray, *offsetEntity); } - copy = *(EntityProxyInstanceWrapper*)startCopyPtr; float a = 5.0f; - } PendingData.Release(); } - protected override void HardenSelf() - { - base.HardenSelf(); - - //We need to ensure we get the right access to any of the cancel data structures - foreach (AbstractData data in ActiveDataLookupByID.Values) - { - //If this piece of data can be cancelled, we need to be able to read the associated Cancel Request lookup - if (data.CancelRequestBehaviour is CancelRequestBehaviour.Delete or CancelRequestBehaviour.Unwind) - { - AddConsolidationData(data.TaskSetOwner.TaskSet.CancelRequestsDataStream.ActiveLookupData, AccessType.SharedRead); - } - } - - m_Consolidator = new EntityProxyDataSourceConsolidator(PendingData, ActiveDataLookupByID); - } - //************************************************************************************************************* // EXECUTION //************************************************************************************************************* diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs index f4c17e0d..496b0ad5 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs @@ -1,6 +1,8 @@ using Anvil.CSharp.Core; using Anvil.Unity.DOTS.Jobs; using System.Reflection; +using Unity.Collections; +using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities.TaskDriver @@ -11,5 +13,7 @@ internal interface IDataSource : IAnvilDisposable public void Harden(); public JobHandle Consolidate(JobHandle dependsOn); + + public void MigrateTo(IDataSource destinationDataSource, ref NativeArray remapArray); } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs index 99b28844..bb598fc5 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs @@ -1,16 +1,29 @@ using Anvil.Unity.DOTS.Util; using System; +using System.Reflection; using System.Runtime.InteropServices; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; +using UnityEngine; namespace Anvil.Unity.DOTS.Entities.TaskDriver { //TODO: #136 - Maybe have this implement IEntityProxyInstance. https://github.com/decline-cookies/anvil-unity-dots/pull/157#discussion_r1093730973 [BurstCompatible] - [StructLayout(LayoutKind.Sequential, Size = 16)] + [StructLayout(LayoutKind.Explicit, Size = 16)] internal readonly struct EntityProxyInstanceID : IEquatable { + public static readonly int TASK_SET_OWNER_ID_OFFSET = typeof(EntityProxyInstanceID) + .GetField(nameof(TaskSetOwnerID)) + .GetCustomAttribute() + .Value; + + public static readonly int ACTIVE_ID_OFFSET = typeof(EntityProxyInstanceID) + .GetField(nameof(ActiveID)) + .GetCustomAttribute() + .Value; + public static bool operator ==(EntityProxyInstanceID lhs, EntityProxyInstanceID rhs) { return lhs.Entity == rhs.Entity && lhs.TaskSetOwnerID == rhs.TaskSetOwnerID; @@ -21,9 +34,9 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver return !(lhs == rhs); } - public readonly Entity Entity; - public readonly uint TaskSetOwnerID; - public readonly uint ActiveID; + [FieldOffset(0)] public readonly Entity Entity; + [FieldOffset(8)] public readonly uint TaskSetOwnerID; + [FieldOffset(12)] public readonly uint ActiveID; public EntityProxyInstanceID(Entity entity, uint taskSetOwnerID, uint activeID) { diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs index 886f239e..734f7888 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs @@ -7,7 +7,7 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver { [BurstCompatible] - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Explicit)] internal readonly struct EntityProxyInstanceWrapper : IEquatable> where TInstance : unmanaged, IEntityProxyInstance { @@ -24,8 +24,8 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver return !(lhs == rhs); } - public readonly TInstance Payload; - public readonly EntityProxyInstanceID InstanceID; + [FieldOffset(0)] public readonly EntityProxyInstanceID InstanceID; + [FieldOffset(16)] public readonly TInstance Payload; public EntityProxyInstanceWrapper(Entity entity, uint taskSetOwnerID, uint activeID, ref TInstance payload) { diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs new file mode 100644 index 00000000..4f8b981c --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs @@ -0,0 +1,20 @@ +using Unity.Collections.LowLevel.Unsafe; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + internal static class EntityProxyInstanceWrapperExtension + { + public static unsafe void PatchIDs( + this ref EntityProxyInstanceWrapper instanceWrapper, + uint taskSetOwnerID, + uint activeID) + where TInstance : unmanaged, IEntityProxyInstance + { + byte* ptr = (byte*)UnsafeUtility.AddressOf(ref instanceWrapper); + uint* taskSetOwnerIDPtr = (uint*)(ptr + EntityProxyInstanceID.TASK_SET_OWNER_ID_OFFSET); + *taskSetOwnerIDPtr = taskSetOwnerID; + uint* activeIDPtr = (uint*)(ptr + EntityProxyInstanceID.ACTIVE_ID_OFFSET); + *activeIDPtr = activeID; + } + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs.meta new file mode 100644 index 00000000..a9b3a405 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6445b87fdf1841eabb6fe8535b97ff5f +timeCreated: 1682449657 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index db421fc1..73435d3b 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -1,5 +1,6 @@ using Anvil.CSharp.Collections; using Anvil.CSharp.Core; +using Anvil.CSharp.Logging; using Anvil.Unity.DOTS.Jobs; using System; using System.Collections.Generic; @@ -331,20 +332,40 @@ public void ReleaseCancelRequestsWriter() { CancelRequestsDataStream.ReleasePending(); } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public void AddToMigrationLookup(string parentPath, Dictionary migrationActiveIDLookup) + { + foreach (KeyValuePair entry in m_PublicDataStreamsByType) + { + string path = $"{parentPath}-{entry.Key.GetReadableName()}"; + Debug_EnsureNoDuplicateMigrationData(path, migrationActiveIDLookup); + migrationActiveIDLookup.Add(path, entry.Value.ActiveID); + } + } //************************************************************************************************************* // SAFETY //************************************************************************************************************* [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] - private void Debug_EnsureNotHardened() + private void Debug_EnsureNoDuplicateMigrationData(string path, Dictionary migrationActiveIDLookup) { - if (m_IsHardened) + if (migrationActiveIDLookup.ContainsKey(path)) { - throw new InvalidOperationException($"Trying to Harden {this} but {nameof(Harden)} has already been called!"); + throw new InvalidOperationException($"Trying to add migration data for {this} but {path} is already in the lookup!"); } } + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void Debug_EnsureNotHardened() + { + + } + [Conditional("ANVIL_DEBUG_SAFETY_EXPENSIVE")] private void Debug_EnsureNoDuplicateJobSchedulingDelegates(Delegate jobSchedulingDelegate) { From bae9b8e8171d0709a21443ade4c526ae832c073d Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Thu, 27 Apr 2023 09:31:39 -0400 Subject: [PATCH 03/16] Working Patching but order of Worlds messes up if we're not careful --- .../Migration/MigrationReflectionHelper.cs | 107 ++++++++++-------- .../Entities/TaskDriver/Migration.meta | 3 + .../Migration/DestinationWorldDataMap.cs | 26 +++++ .../Migration/DestinationWorldDataMap.cs.meta | 3 + .../Migration/TaskDriverMigrationData.cs | 83 ++++++++++++++ .../Migration/TaskDriverMigrationData.cs.meta | 3 + .../TaskDriver/TaskDriverManagementSystem.cs | 28 ++--- .../DataSource/AbstractDataSource.cs | 5 +- .../DataSource/CancelProgressDataSource.cs | 8 ++ .../DataSource/CancelRequestsDataSource.cs | 7 ++ .../DataSource/EntityProxyDataSource.cs | 93 ++++++++------- .../DataStream/DataSource/IDataSource.cs | 5 +- .../DataStream/EntityProxyDataStream.cs | 2 - .../EntityProxyInstanceID.cs | 19 +--- .../EntityProxyInstanceWrapper.cs | 13 ++- .../EntityProxyInstanceWrapperExtension.cs | 2 +- 16 files changed, 280 insertions(+), 127 deletions(-) create mode 100644 Scripts/Runtime/Entities/TaskDriver/Migration.meta create mode 100644 Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs.meta create mode 100644 Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs index 378d96d8..3f73f0db 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs @@ -3,6 +3,7 @@ using System.Reflection; using Unity.Burst; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using UnityEngine; @@ -10,42 +11,28 @@ namespace Anvil.Unity.DOTS.Entities { internal static class MigrationReflectionHelper { - private static NativeParallelHashMap s_TypeOffsetsLookup; - private static NativeList s_EntityOffsetList; - private static NativeList s_BlobAssetRefOffsetList; - private static NativeList s_WeakAssetRefOffsetList; + private static NativeParallelHashMap s_TypeOffsetsLookup = new NativeParallelHashMap(256, Allocator.Persistent); + private static NativeList s_EntityOffsetList = new NativeList(Allocator.Persistent); + private static NativeList s_BlobAssetRefOffsetList = new NativeList(Allocator.Persistent); + private static NativeList s_WeakAssetRefOffsetList = new NativeList(Allocator.Persistent); private static readonly Type I_ENTITY_PROXY_INSTANCE = typeof(IEntityProxyInstance); - + private static bool s_AppDomainUnloadRegistered; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void Init() { - if (s_TypeOffsetsLookup.IsCreated) + if (!s_AppDomainUnloadRegistered) { - s_TypeOffsetsLookup.Dispose(); + AppDomain.CurrentDomain.DomainUnload += CurrentDomain_OnDomainUnload; + s_AppDomainUnloadRegistered = true; } - if (s_EntityOffsetList.IsCreated) - { - s_EntityOffsetList.Dispose(); - } - if (s_BlobAssetRefOffsetList.IsCreated) - { - s_BlobAssetRefOffsetList.Dispose(); - } - if (s_WeakAssetRefOffsetList.IsCreated) - { - s_WeakAssetRefOffsetList.Dispose(); - } - - s_TypeOffsetsLookup = new NativeParallelHashMap(256, Allocator.Persistent); - s_EntityOffsetList = new NativeList(Allocator.Persistent); - s_BlobAssetRefOffsetList = new NativeList(Allocator.Persistent); - s_WeakAssetRefOffsetList = new NativeList(Allocator.Persistent); Type genericWrapperType = typeof(EntityProxyInstanceWrapper<>); - + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { if (assembly.IsDynamic) @@ -65,58 +52,88 @@ private static void Init() int entityOffsetStartIndex = s_EntityOffsetList.Length; int blobOffsetStartIndex = s_BlobAssetRefOffsetList.Length; int weakAssetStartIndex = s_WeakAssetRefOffsetList.Length; - + EntityRemapUtility.CalculateFieldOffsetsUnmanaged( - concreteType, + concreteType, out bool hasEntityRefs, out bool hasBlobRefs, out bool hasWeakAssetRefs, ref s_EntityOffsetList, ref s_BlobAssetRefOffsetList, - ref s_WeakAssetRefOffsetList); + ref s_WeakAssetRefOffsetList); if (!hasEntityRefs && !hasBlobRefs && !hasWeakAssetRefs) { continue; } - s_TypeOffsetsLookup.Add(typeHash, new TypeOffsetInfo( - entityOffsetStartIndex, - s_EntityOffsetList.Length - entityOffsetStartIndex, - blobOffsetStartIndex, - s_BlobAssetRefOffsetList.Length - blobOffsetStartIndex, - weakAssetStartIndex, - s_WeakAssetRefOffsetList.Length - weakAssetStartIndex)); + s_TypeOffsetsLookup.Add( + typeHash, + new TypeOffsetInfo( + entityOffsetStartIndex, + s_EntityOffsetList.Length, + blobOffsetStartIndex, + s_BlobAssetRefOffsetList.Length, + weakAssetStartIndex, + s_WeakAssetRefOffsetList.Length)); } } } - public static void PatchEntityIfMoved() + private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) + { + if (s_TypeOffsetsLookup.IsCreated) + { + s_TypeOffsetsLookup.Dispose(); + } + if (s_EntityOffsetList.IsCreated) + { + s_EntityOffsetList.Dispose(); + } + if (s_BlobAssetRefOffsetList.IsCreated) + { + s_BlobAssetRefOffsetList.Dispose(); + } + if (s_WeakAssetRefOffsetList.IsCreated) + { + s_WeakAssetRefOffsetList.Dispose(); + } + } + + + public static unsafe void PatchEntityReferences(this ref T instance, ref Entity remappedEntity) + where T : unmanaged { long typeHash = BurstRuntime.GetHashCode64(); TypeOffsetInfo typeOffsetInfo = s_TypeOffsetsLookup[typeHash]; - - } + byte* instancePtr = (byte*)UnsafeUtility.AddressOf(ref instance); + for (int i = typeOffsetInfo.EntityOffsetStartIndex; i < typeOffsetInfo.EntityOffsetEndIndex; ++i) + { + TypeManager.EntityOffsetInfo entityOffsetInfo = s_EntityOffsetList[i]; + Entity* entityPtr = (Entity*)(instancePtr + entityOffsetInfo.Offset); + *entityPtr = remappedEntity; + } + } public readonly struct TypeOffsetInfo { public readonly int EntityOffsetStartIndex; - public readonly int EntityOffsetCount; + public readonly int EntityOffsetEndIndex; public readonly int BlobAssetStartIndex; - public readonly int BlobAssetCount; + public readonly int BlobAssetEndIndex; public readonly int WeakAssetStartIndex; - public readonly int WeakAssetCount; + public readonly int WeakAssetEndIndex; - public TypeOffsetInfo(int entityOffsetStartIndex, int entityOffsetCount, int blobAssetStartIndex, int blobAssetCount, int weakAssetStartIndex, int weakAssetCount) + public TypeOffsetInfo(int entityOffsetStartIndex, int entityOffsetEndIndex, int blobAssetStartIndex, int blobAssetEndIndex, int weakAssetStartIndex, int weakAssetEndIndex) { EntityOffsetStartIndex = entityOffsetStartIndex; - EntityOffsetCount = entityOffsetCount; + EntityOffsetEndIndex = entityOffsetEndIndex; BlobAssetStartIndex = blobAssetStartIndex; - BlobAssetCount = blobAssetCount; + BlobAssetEndIndex = blobAssetEndIndex; WeakAssetStartIndex = weakAssetStartIndex; - WeakAssetCount = weakAssetCount; + WeakAssetEndIndex = weakAssetEndIndex; } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration.meta b/Scripts/Runtime/Entities/TaskDriver/Migration.meta new file mode 100644 index 00000000..9e7c6692 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/Migration.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cf2e30c6b6344c54b8386cf1e1394b10 +timeCreated: 1682534640 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs b/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs new file mode 100644 index 00000000..d5392ff1 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs @@ -0,0 +1,26 @@ +using Anvil.CSharp.Core; +using Unity.Collections; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + internal class DestinationWorldDataMap : AbstractAnvilBase + { + public NativeParallelHashMap TaskSetOwnerIDMapping; + public NativeParallelHashMap ActiveIDMapping; + + public DestinationWorldDataMap( + NativeParallelHashMap taskSetOwnerIDMapping, + NativeParallelHashMap activeIDMapping) + { + TaskSetOwnerIDMapping = taskSetOwnerIDMapping; + ActiveIDMapping = activeIDMapping; + } + + protected override void DisposeSelf() + { + TaskSetOwnerIDMapping.Dispose(); + ActiveIDMapping.Dispose(); + base.DisposeSelf(); + } + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs.meta b/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs.meta new file mode 100644 index 00000000..90f88469 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e7bb882acc0f4d849d0183dbc35f7896 +timeCreated: 1682534677 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs new file mode 100644 index 00000000..ca5963cc --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs @@ -0,0 +1,83 @@ +using Anvil.CSharp.Collections; +using Anvil.CSharp.Core; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Entities; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + internal class TaskDriverMigrationData : AbstractAnvilBase + { + private readonly Dictionary m_MigrationTaskSetOwnerIDLookup; + private readonly Dictionary m_MigrationActiveIDLookup; + private readonly Dictionary m_DestinationWorldDataMaps; + + public int TaskSetOwnerCount + { + get => m_MigrationTaskSetOwnerIDLookup.Count; + } + + public int ActiveIDCount + { + get => m_MigrationActiveIDLookup.Count; + } + + public TaskDriverMigrationData() + { + m_MigrationTaskSetOwnerIDLookup = new Dictionary(); + m_MigrationActiveIDLookup = new Dictionary(); + m_DestinationWorldDataMaps = new Dictionary(); + } + + protected override void DisposeSelf() + { + m_DestinationWorldDataMaps.DisposeAllValuesAndClear(); + base.DisposeSelf(); + } + + public void PopulateMigrationLookup(List topLevelTaskDrivers) + { + //Generate a World ID + foreach (AbstractTaskDriver topLevelTaskDriver in topLevelTaskDrivers) + { + topLevelTaskDriver.AddToMigrationLookup( + string.Empty, + m_MigrationTaskSetOwnerIDLookup, + m_MigrationActiveIDLookup); + } + } + + public DestinationWorldDataMap GetOrCreateDestinationWorldDataMapFor(World destinationWorld, TaskDriverMigrationData destinationMigrationData) + { + if (!m_DestinationWorldDataMaps.TryGetValue(destinationWorld, out DestinationWorldDataMap destinationWorldDataMap)) + { + //We're going to the Destination World so we can't have more than they have + NativeParallelHashMap taskSetOwnerIDMapping = new NativeParallelHashMap(destinationMigrationData.TaskSetOwnerCount, Allocator.Persistent); + NativeParallelHashMap activeIDMapping = new NativeParallelHashMap(destinationMigrationData.ActiveIDCount, Allocator.Persistent); + + foreach (KeyValuePair entry in m_MigrationTaskSetOwnerIDLookup) + { + if (!destinationMigrationData.m_MigrationTaskSetOwnerIDLookup.TryGetValue(entry.Key, out uint dstTaskSetOwnerID)) + { + continue; + } + taskSetOwnerIDMapping.Add(entry.Value, dstTaskSetOwnerID); + } + + foreach (KeyValuePair entry in m_MigrationActiveIDLookup) + { + if (!destinationMigrationData.m_MigrationActiveIDLookup.TryGetValue(entry.Key, out uint dstActiveID)) + { + continue; + } + activeIDMapping.Add(entry.Value, dstActiveID); + } + + + destinationWorldDataMap = new DestinationWorldDataMap(taskSetOwnerIDMapping, activeIDMapping); + m_DestinationWorldDataMaps.Add(destinationWorld, destinationWorldDataMap); + } + return destinationWorldDataMap; + } + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs.meta b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs.meta new file mode 100644 index 00000000..db59de09 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e58230bcc0444fd8811d61fa44165671 +timeCreated: 1682534957 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 1cfb9106..c4b987a5 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -29,8 +29,8 @@ internal partial class TaskDriverManagementSystem : AbstractAnvilSystemBase, private readonly CancelCompleteDataSource m_CancelCompleteDataSource; private readonly List m_CancelProgressFlows; private readonly Dictionary m_UnityEntityDataAccessControllers; - private readonly Dictionary m_MigrationTaskSetOwnerIDLookup; - private readonly Dictionary m_MigrationActiveIDLookup; + private readonly TaskDriverMigrationData m_TaskDriverMigrationData; + private bool m_IsInitialized; private bool m_IsHardened; @@ -51,8 +51,7 @@ public TaskDriverManagementSystem() m_CancelCompleteDataSource = new CancelCompleteDataSource(this); m_CancelProgressFlows = new List(); m_UnityEntityDataAccessControllers = new Dictionary(); - m_MigrationTaskSetOwnerIDLookup = new Dictionary(); - m_MigrationActiveIDLookup = new Dictionary(); + m_TaskDriverMigrationData = new TaskDriverMigrationData(); } protected override void OnCreate() @@ -83,6 +82,7 @@ protected sealed override void OnDestroy() m_CancelProgressFlowBulkJobScheduler?.Dispose(); m_CancelProgressFlows.DisposeAllAndTryClear(); m_UnityEntityDataAccessControllers.DisposeAllValuesAndClear(); + m_TaskDriverMigrationData.Dispose(); m_CancelRequestsDataSource.Dispose(); m_CancelCompleteDataSource.Dispose(); @@ -120,8 +120,6 @@ private void Harden() topLevelTaskDriver.Harden(); } - PopulateMigrationLookup(); - //All the data has been hardened, we can Harden the Update Phase for the Systems foreach (AbstractTaskDriverSystem taskDriverSystem in m_AllTaskDriverSystems) { @@ -139,18 +137,9 @@ private void Harden() .Select((topLevelTaskDriver) => new CancelProgressFlow(topLevelTaskDriver))); m_CancelProgressFlowBulkJobScheduler = new BulkJobScheduler(m_CancelProgressFlows.ToArray()); - } - - private void PopulateMigrationLookup() - { - //Generate a World ID - foreach (AbstractTaskDriver topLevelTaskDriver in m_TopLevelTaskDrivers) - { - topLevelTaskDriver.AddToMigrationLookup( - string.Empty, - m_MigrationTaskSetOwnerIDLookup, - m_MigrationTaskSetOwnerIDLookup); - } + + //Build the Migration Data for this world + m_TaskDriverMigrationData.PopulateMigrationLookup(m_TopLevelTaskDrivers); } public EntityProxyDataSource GetOrCreateEntityProxyDataSource() @@ -249,6 +238,7 @@ public void Migrate(World destinationWorld, ref NativeArray destinationEntityProxyDataSourcesByType = destinationTaskDriverManagementSystem.m_EntityProxyDataSourcesByType; @@ -257,7 +247,7 @@ public void Migrate(World destinationWorld, ref NativeArray remapArray); + public abstract void MigrateTo( + IDataSource destinationDataSource, + ref NativeArray remapArray, + DestinationWorldDataMap destinationWorldDataMap); //************************************************************************************************************* // EXECUTION diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs index 91f5fe2d..3b81501c 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs @@ -1,4 +1,6 @@ using System; +using Unity.Collections; +using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities.TaskDriver @@ -11,5 +13,11 @@ protected override JobHandle ConsolidateSelf(JobHandle dependsOn) { throw new InvalidOperationException($"CancelProgress Data Never needs to be consolidated"); } + + + public override void MigrateTo(IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) + { + //DOES NOTHING RIGHT NOW + } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs index fd322143..bf3e52b2 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs @@ -1,5 +1,7 @@ using Anvil.Unity.DOTS.Jobs; using Unity.Burst; +using Unity.Collections; +using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities.TaskDriver @@ -40,6 +42,11 @@ protected override JobHandle ConsolidateSelf(JobHandle dependsOn) return dependsOn; } + public override void MigrateTo(IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) + { + //DOES NOTHING RIGHT NOW + } + //************************************************************************************************************* // JOBS //************************************************************************************************************* diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index a5f5b677..32c84a10 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -45,16 +45,21 @@ protected override void HardenSelf() public override void MigrateTo( IDataSource destinationDataSource, ref NativeArray remapArray, - ref NativeParallelHashMap taskSetOwnerIDMapping, - ref NativeParallelHashMap activeIDMapping) + DestinationWorldDataMap destinationWorldDataMap) { EntityProxyDataSource destination = destinationDataSource as EntityProxyDataSource; PendingData.Acquire(AccessType.ExclusiveWrite); + destination.PendingData.Acquire(AccessType.ExclusiveWrite); UnsafeTypedStream> stream = PendingData.Pending; NativeArray> instanceArray = stream.ToNativeArray(Allocator.Temp); stream.Clear(); + + //TEMP Main Thread Writer + UnsafeTypedStream>.LaneWriter laneWriter = stream.AsLaneWriter(ParallelAccessUtil.CollectionIndexForMainThread()); + UnsafeTypedStream>.LaneWriter destinationLaneWriter = destination.PendingData.Pending.AsLaneWriter(ParallelAccessUtil.CollectionIndexForMainThread()); + for (int i = 0; i < instanceArray.Length; ++i) { @@ -67,65 +72,69 @@ public override void MigrateTo( //If we were remapped to null, then we don't exist in the new world, we should just stay here if (remappedEntity == Entity.Null) { - //TODO: Write back to the original stream, we stayed in this world + laneWriter.Write(instance); continue; } //If we don't have a destination in the new world, then we can just let these cease to exist - if (destination == null) + //Check the TaskSetOwnerIDMapping/ActiveIDMapping + if (destination == null + || !destinationWorldDataMap.TaskSetOwnerIDMapping.TryGetValue(instanceID.TaskSetOwnerID, out uint destinationTaskSetOwnerID) + || !destinationWorldDataMap.ActiveIDMapping.TryGetValue(instanceID.ActiveID, out uint destinationActiveID)) { continue; } //If we do have a destination, then we will want to patch the entity references - instance.PatchEntityReferences(remappedEntity); + instance.PatchEntityReferences(ref remappedEntity); //Rewrite the memory for the TaskSetOwnerID and ActiveID instance.PatchIDs( - taskSetOwnerIDMapping[instanceID.TaskSetOwnerID], - activeIDMapping[instanceID.ActiveID]); - - //TODO: Write to the new stream + destinationTaskSetOwnerID, + destinationActiveID); + //Write to the destination stream + destinationLaneWriter.Write(instance); } PendingData.Release(); + destination.PendingData.Release(); } - public unsafe void Migrate(NativeArray remapArray) - { - PendingData.Acquire(AccessType.ExclusiveWrite); - //TODO: Need to ensure this is actually the ref - look at NativeParallelHashMap - foreach (EntityProxyInstanceWrapper entry in PendingData.Pending) - { - EntityRemapUtility.CalculateFieldOffsetsUnmanaged( - typeof(EntityProxyInstanceWrapper), - out bool hasEntityRefs, - out bool hasBlobRefs, - out bool hasWeakAssetRefs, - ref s_EntityOffsetList, - ref s_BlobAssetRefOffsetList, - ref s_WeakAssetRefOffsetList); - - EntityProxyInstanceWrapper copy = entry; - byte* copyPtr = (byte*)UnsafeUtility.AddressOf(ref copy); - void* startCopyPtr = copyPtr; - for (int i = 0; i < s_EntityOffsetList.Length; ++i) - { - TypeManager.EntityOffsetInfo offsetInfo = s_EntityOffsetList[i]; - copyPtr += offsetInfo.Offset; - Entity* offsetEntity = (Entity*)copyPtr; - *offsetEntity = EntityRemapUtility.RemapEntity(ref remapArray, *offsetEntity); - } - - - copy = *(EntityProxyInstanceWrapper*)startCopyPtr; - - float a = 5.0f; - } - PendingData.Release(); - } + // public unsafe void Migrate(NativeArray remapArray) + // { + // PendingData.Acquire(AccessType.ExclusiveWrite); + // //TODO: Need to ensure this is actually the ref - look at NativeParallelHashMap + // foreach (EntityProxyInstanceWrapper entry in PendingData.Pending) + // { + // EntityRemapUtility.CalculateFieldOffsetsUnmanaged( + // typeof(EntityProxyInstanceWrapper), + // out bool hasEntityRefs, + // out bool hasBlobRefs, + // out bool hasWeakAssetRefs, + // ref s_EntityOffsetList, + // ref s_BlobAssetRefOffsetList, + // ref s_WeakAssetRefOffsetList); + // + // EntityProxyInstanceWrapper copy = entry; + // byte* copyPtr = (byte*)UnsafeUtility.AddressOf(ref copy); + // void* startCopyPtr = copyPtr; + // for (int i = 0; i < s_EntityOffsetList.Length; ++i) + // { + // TypeManager.EntityOffsetInfo offsetInfo = s_EntityOffsetList[i]; + // copyPtr += offsetInfo.Offset; + // Entity* offsetEntity = (Entity*)copyPtr; + // *offsetEntity = EntityRemapUtility.RemapEntity(ref remapArray, *offsetEntity); + // } + // + // + // copy = *(EntityProxyInstanceWrapper*)startCopyPtr; + // + // float a = 5.0f; + // } + // PendingData.Release(); + // } //************************************************************************************************************* // EXECUTION diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs index 496b0ad5..4fa8bc75 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs @@ -14,6 +14,9 @@ internal interface IDataSource : IAnvilDisposable public JobHandle Consolidate(JobHandle dependsOn); - public void MigrateTo(IDataSource destinationDataSource, ref NativeArray remapArray); + public void MigrateTo( + IDataSource destinationDataSource, + ref NativeArray remapArray, + DestinationWorldDataMap destinationWorldDataMap); } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs index 02751027..88676971 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs @@ -33,8 +33,6 @@ public EntityProxyDataStream(ITaskSetOwner taskSetOwner, CancelRequestBehaviour { TaskDriverManagementSystem taskDriverManagementSystem = taskSetOwner.World.GetOrCreateSystem(); m_DataSource = taskDriverManagementSystem.GetOrCreateEntityProxyDataSource(); - //TODO: Move to abstract and make virtual - taskSetOwner.World.GetOrCreateSystem().AddMigratable(m_DataSource); m_ActiveArrayData = m_DataSource.CreateActiveArrayData(taskSetOwner, cancelRequestBehaviour); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs index bb598fc5..0ffae2f0 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs @@ -11,18 +11,11 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver { //TODO: #136 - Maybe have this implement IEntityProxyInstance. https://github.com/decline-cookies/anvil-unity-dots/pull/157#discussion_r1093730973 [BurstCompatible] - [StructLayout(LayoutKind.Explicit, Size = 16)] + [StructLayout(LayoutKind.Sequential, Size = 16)] internal readonly struct EntityProxyInstanceID : IEquatable { - public static readonly int TASK_SET_OWNER_ID_OFFSET = typeof(EntityProxyInstanceID) - .GetField(nameof(TaskSetOwnerID)) - .GetCustomAttribute() - .Value; - - public static readonly int ACTIVE_ID_OFFSET = typeof(EntityProxyInstanceID) - .GetField(nameof(ActiveID)) - .GetCustomAttribute() - .Value; + public static readonly int TASK_SET_OWNER_ID_OFFSET = Marshal.OffsetOf(nameof(TaskSetOwnerID)).ToInt32(); + public static readonly int ACTIVE_ID_OFFSET = Marshal.OffsetOf(nameof(ActiveID)).ToInt32(); public static bool operator ==(EntityProxyInstanceID lhs, EntityProxyInstanceID rhs) { @@ -34,9 +27,9 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver return !(lhs == rhs); } - [FieldOffset(0)] public readonly Entity Entity; - [FieldOffset(8)] public readonly uint TaskSetOwnerID; - [FieldOffset(12)] public readonly uint ActiveID; + public readonly Entity Entity; + public readonly uint TaskSetOwnerID; + public readonly uint ActiveID; public EntityProxyInstanceID(Entity entity, uint taskSetOwnerID, uint activeID) { diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs index 734f7888..e62e0aad 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs @@ -7,10 +7,11 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver { [BurstCompatible] - [StructLayout(LayoutKind.Explicit)] + [StructLayout(LayoutKind.Sequential)] internal readonly struct EntityProxyInstanceWrapper : IEquatable> where TInstance : unmanaged, IEntityProxyInstance { + public static readonly int INSTANCE_ID_OFFSET = Marshal.OffsetOf>(nameof(InstanceID)).ToInt32(); public static bool operator ==(EntityProxyInstanceWrapper lhs, EntityProxyInstanceWrapper rhs) { //Note that we are not checking if the Payload is equal because the wrapper is only for origin and lookup @@ -24,12 +25,16 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver return !(lhs == rhs); } - [FieldOffset(0)] public readonly EntityProxyInstanceID InstanceID; - [FieldOffset(16)] public readonly TInstance Payload; + public readonly EntityProxyInstanceID InstanceID; + public readonly Entity TestA; + public readonly Entity TestB; + public readonly TInstance Payload; public EntityProxyInstanceWrapper(Entity entity, uint taskSetOwnerID, uint activeID, ref TInstance payload) { InstanceID = new EntityProxyInstanceID(entity, taskSetOwnerID, activeID); + TestA = entity; + TestB = entity; Payload = payload; } @@ -37,6 +42,8 @@ public EntityProxyInstanceWrapper(ref EntityProxyInstanceWrapper orig { EntityProxyInstanceID originalInstanceID = original.InstanceID; InstanceID = new EntityProxyInstanceID(originalInstanceID.Entity, originalInstanceID.TaskSetOwnerID, newActiveID); + TestA = originalInstanceID.Entity; + TestB = originalInstanceID.Entity; Payload = original.Payload; } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs index 4f8b981c..0cdc1b35 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs @@ -10,7 +10,7 @@ public static unsafe void PatchIDs( uint activeID) where TInstance : unmanaged, IEntityProxyInstance { - byte* ptr = (byte*)UnsafeUtility.AddressOf(ref instanceWrapper); + byte* ptr = (byte*)UnsafeUtility.AddressOf(ref instanceWrapper) + EntityProxyInstanceWrapper.INSTANCE_ID_OFFSET; uint* taskSetOwnerIDPtr = (uint*)(ptr + EntityProxyInstanceID.TASK_SET_OWNER_ID_OFFSET); *taskSetOwnerIDPtr = taskSetOwnerID; uint* activeIDPtr = (uint*)(ptr + EntityProxyInstanceID.ACTIVE_ID_OFFSET); From 8c9453934fffc76648fbca6abf69cdb8e9f8c0ef Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Thu, 27 Apr 2023 14:53:38 -0400 Subject: [PATCH 04/16] More migration work --- .../Lifecycle/Migration/IMigrationObserver.cs | 2 +- .../Migration/MigrationReflectionHelper.cs | 100 +++++++++--------- .../Migration/WorldEntityMigrationSystem.cs | 6 +- .../Data/AbstractPersistentData.cs | 7 ++ .../Data/EntityPersistentData.cs | 42 ++++++++ .../Data/ThreadPersistentData.cs | 10 ++ .../PersistentData/PersistentDataSystem.cs | 46 +++++++- .../TaskDriver/TaskDriverManagementSystem.cs | 9 +- .../DataSource/EntityProxyDataSource.cs | 6 +- .../EntityProxyInstanceWrapper.cs | 6 -- .../EntityPersistentDataWriter.cs | 6 ++ 11 files changed, 171 insertions(+), 69 deletions(-) diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs index 9d223e9c..66764250 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs @@ -5,6 +5,6 @@ namespace Anvil.Unity.DOTS.Entities { public interface IMigrationObserver { - public void Migrate(World destinationWorld, ref NativeArray remapArray); + public void MigrateTo(World destinationWorld, ref NativeArray remapArray); } } diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs index 3f73f0db..d6abcff6 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs @@ -1,6 +1,5 @@ -using Anvil.Unity.DOTS.Entities.TaskDriver; +using Anvil.CSharp.Logging; using System; -using System.Reflection; using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; @@ -16,8 +15,6 @@ internal static class MigrationReflectionHelper private static NativeList s_BlobAssetRefOffsetList = new NativeList(Allocator.Persistent); private static NativeList s_WeakAssetRefOffsetList = new NativeList(Allocator.Persistent); - private static readonly Type I_ENTITY_PROXY_INSTANCE = typeof(IEntityProxyInstance); - private static bool s_AppDomainUnloadRegistered; @@ -29,55 +26,53 @@ private static void Init() AppDomain.CurrentDomain.DomainUnload += CurrentDomain_OnDomainUnload; s_AppDomainUnloadRegistered = true; } + } + public static void RegisterTypeForEntityPatching() + where T : struct + { + RegisterTypeForEntityPatching(typeof(T)); + } - Type genericWrapperType = typeof(EntityProxyInstanceWrapper<>); + public static void RegisterTypeForEntityPatching(Type type) + { + if (!type.IsValueType) + { + throw new InvalidOperationException($"Type {type.GetReadableName()} must be a value type in order to register for Entity Patching."); + } - foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + long typeHash = BurstRuntime.GetHashCode64(type); + //We've already added this type + if (s_TypeOffsetsLookup.ContainsKey(typeHash)) { - if (assembly.IsDynamic) - { - return; - } - foreach (Type type in assembly.GetTypes()) - { - if (!type.IsValueType || !I_ENTITY_PROXY_INSTANCE.IsAssignableFrom(type)) - { - continue; - } - - Type concreteType = genericWrapperType.MakeGenericType(type); - long typeHash = BurstRuntime.GetHashCode64(concreteType); - - int entityOffsetStartIndex = s_EntityOffsetList.Length; - int blobOffsetStartIndex = s_BlobAssetRefOffsetList.Length; - int weakAssetStartIndex = s_WeakAssetRefOffsetList.Length; - - EntityRemapUtility.CalculateFieldOffsetsUnmanaged( - concreteType, - out bool hasEntityRefs, - out bool hasBlobRefs, - out bool hasWeakAssetRefs, - ref s_EntityOffsetList, - ref s_BlobAssetRefOffsetList, - ref s_WeakAssetRefOffsetList); - - if (!hasEntityRefs && !hasBlobRefs && !hasWeakAssetRefs) - { - continue; - } - - s_TypeOffsetsLookup.Add( - typeHash, - new TypeOffsetInfo( - entityOffsetStartIndex, - s_EntityOffsetList.Length, - blobOffsetStartIndex, - s_BlobAssetRefOffsetList.Length, - weakAssetStartIndex, - s_WeakAssetRefOffsetList.Length)); - } + return; } + + int entityOffsetStartIndex = s_EntityOffsetList.Length; + int blobOffsetStartIndex = s_BlobAssetRefOffsetList.Length; + int weakAssetStartIndex = s_WeakAssetRefOffsetList.Length; + + EntityRemapUtility.CalculateFieldOffsetsUnmanaged( + type, + out bool hasEntityRefs, + out bool hasBlobRefs, + out bool hasWeakAssetRefs, + ref s_EntityOffsetList, + ref s_BlobAssetRefOffsetList, + ref s_WeakAssetRefOffsetList); + + //We'll allow for a TypeOffset to be registered even if there's nothing to remap so that it's easy to detect + //when you forgot to register a type. + + s_TypeOffsetsLookup.Add( + typeHash, + new TypeOffsetInfo( + entityOffsetStartIndex, + s_EntityOffsetList.Length, + blobOffsetStartIndex, + s_BlobAssetRefOffsetList.Length, + weakAssetStartIndex, + s_WeakAssetRefOffsetList.Length)); } private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) @@ -102,10 +97,13 @@ private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) public static unsafe void PatchEntityReferences(this ref T instance, ref Entity remappedEntity) - where T : unmanaged + where T : struct { long typeHash = BurstRuntime.GetHashCode64(); - TypeOffsetInfo typeOffsetInfo = s_TypeOffsetsLookup[typeHash]; + if (!s_TypeOffsetsLookup.TryGetValue(typeHash, out TypeOffsetInfo typeOffsetInfo)) + { + throw new InvalidOperationException($"Tried to patch type with BurstRuntime hash of {typeHash} but it wasn't registered. Did you call {nameof(RegisterTypeForEntityPatching)}?"); + } byte* instancePtr = (byte*)UnsafeUtility.AddressOf(ref instance); for (int i = typeOffsetInfo.EntityOffsetStartIndex; i < typeOffsetInfo.EntityOffsetEndIndex; ++i) @@ -114,6 +112,8 @@ public static unsafe void PatchEntityReferences(this ref T instance, ref Enti Entity* entityPtr = (Entity*)(instancePtr + entityOffsetInfo.Offset); *entityPtr = remappedEntity; } + + //TODO: Patch for Blobs and Weaks? } diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs index 98c52c7d..4ffe6558 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs @@ -23,11 +23,11 @@ public void RemoveMigrationObserver(IMigrationObserver migrationObserver) m_MigrationObservers.Remove(migrationObserver); } - private void NotifyObservers(World destinationWorld, ref NativeArray remapArray) + private void NotifyObserversToMigrateTo(World destinationWorld, ref NativeArray remapArray) { foreach (IMigrationObserver migrationObserver in m_MigrationObservers) { - migrationObserver.Migrate(destinationWorld, ref remapArray); + migrationObserver.MigrateTo(destinationWorld, ref remapArray); } } @@ -37,7 +37,7 @@ public void MigrateTo(World destinationWorld, EntityQuery entitiesToMigrateQuery NativeArray remapArray = EntityManager.CreateEntityRemapArray(Allocator.TempJob); destinationWorld.EntityManager.MoveEntitiesFrom(EntityManager, entitiesToMigrateQuery, remapArray); - NotifyObservers(destinationWorld, ref remapArray); + NotifyObserversToMigrateTo(destinationWorld, ref remapArray); remapArray.Dispose(); } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs index 1ccd80b3..249ad600 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs @@ -1,6 +1,8 @@ using Anvil.CSharp.Core; using Anvil.Unity.DOTS.Jobs; using System.Runtime.CompilerServices; +using Unity.Collections; +using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities @@ -47,5 +49,10 @@ public void Release() { m_AccessController.Release(); } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + public abstract void MigrateTo(AbstractPersistentData destinationPersistentData, ref NativeArray remapArray); } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs index c7f57c26..f7b67b99 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs @@ -16,6 +16,7 @@ internal class EntityPersistentData : AbstractTypedPersistentData(ChunkUtil.MaxElementsPerChunk(), Allocator.Persistent)) { + MigrationReflectionHelper.RegisterTypeForEntityPatching(); } protected override void DisposeData() @@ -83,5 +84,46 @@ public void ReleaseWriter() { Release(); } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public override void MigrateTo(AbstractPersistentData destinationPersistentData, ref NativeArray remapArray) + { + //Our destination in the other world could be null... TODO: Should we create? + if (destinationPersistentData is not EntityPersistentData destination) + { + return; + } + + EntityPersistentDataWriter currentData = AcquireWriter(); + EntityPersistentDataWriter destinationData = destination.AcquireWriter(); + + NativeKeyValueArrays currentElements = currentData.GetKeyValueArrays(Allocator.Temp); + + for (int i = 0; i < currentElements.Length; ++i) + { + Entity currentEntity = currentElements.Keys[i]; + Entity remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, currentEntity); + //We don't exist in the new world, we should just stay here. + if (remappedEntity == Entity.Null) + { + continue; + } + + //Otherwise, prepare us in the migration data + currentData.Remove(currentEntity); + T currentValue = currentElements.Values[i]; + + currentValue.PatchEntityReferences(ref remappedEntity); + + //TODO: Could this be a problem? Is there data already here that wasn't moved? + destinationData[remappedEntity] = currentValue; + } + + destination.ReleaseWriter(); + ReleaseWriter(); + } } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs index eb5b173d..dcb9cb15 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs @@ -1,7 +1,9 @@ using Anvil.Unity.DOTS.Data; using Anvil.Unity.DOTS.Entities.TaskDriver; using Anvil.Unity.DOTS.Jobs; +using System; using Unity.Collections; +using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities @@ -54,5 +56,13 @@ public ThreadPersistentDataAccessor Acquire() Acquire(AccessType.SharedWrite); return CreateThreadPersistentDataAccessor(); } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + public override void MigrateTo(AbstractPersistentData destinationPersistentData, ref NativeArray remapArray) + { + throw new NotSupportedException($"{nameof(ThreadPersistentData)} isn't supported for migration because it is global to the app."); + } } } diff --git a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs index 93e42cb7..51675531 100644 --- a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs +++ b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs @@ -1,10 +1,14 @@ using Anvil.CSharp.Collections; using System; using System.Collections.Generic; +using System.Diagnostics; +using Unity.Collections; +using Unity.Entities; namespace Anvil.Unity.DOTS.Entities { - internal partial class PersistentDataSystem : AbstractDataSystem + internal partial class PersistentDataSystem : AbstractDataSystem, + IMigrationObserver { private static readonly Dictionary s_ThreadPersistentData = new Dictionary(); private static int s_InstanceCount; @@ -17,6 +21,13 @@ public PersistentDataSystem() m_EntityPersistentData = new Dictionary(); } + protected override void OnCreate() + { + base.OnCreate(); + WorldEntityMigrationSystem worldEntityMigrationSystem = World.GetOrCreateSystem(); + worldEntityMigrationSystem.AddMigrationObserver(this); + } + protected override void OnDestroy() { m_EntityPersistentData.DisposeAllValuesAndClear(); @@ -52,5 +63,38 @@ public EntityPersistentData GetOrCreateEntityPersistentData() return (EntityPersistentData)persistentData; } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public void MigrateTo(World destinationWorld, ref NativeArray remapArray) + { + PersistentDataSystem destinationPersistentDataSystem = destinationWorld.GetOrCreateSystem(); + Debug_EnsureOtherWorldPersistentDataSystemExists(destinationWorld, destinationPersistentDataSystem); + + foreach (KeyValuePair entry in m_EntityPersistentData) + { + if (!destinationPersistentDataSystem.m_EntityPersistentData.TryGetValue(entry.Key, out AbstractPersistentData destinationPersistentData)) + { + continue; + } + + entry.Value.MigrateTo(destinationPersistentData, ref remapArray); + } + } + + //************************************************************************************************************* + // SAFETY + //************************************************************************************************************* + + [Conditional("ANVIL_DEBUG_SAFETY")] + private void Debug_EnsureOtherWorldPersistentDataSystemExists(World destinationWorld, PersistentDataSystem persistentDataSystem) + { + if (persistentDataSystem == null) + { + throw new InvalidOperationException($"Expected World {destinationWorld} to have a {nameof(PersistentDataSystem)} but it does not!"); + } + } } } \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index c4b987a5..725d5f30 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -1,14 +1,11 @@ using Anvil.CSharp.Collections; using Anvil.CSharp.Data; -using Anvil.CSharp.Logging; using Anvil.Unity.DOTS.Jobs; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Unity.Burst; using Unity.Collections; -using Unity.Core; using Unity.Entities; using Unity.Jobs; @@ -232,7 +229,7 @@ protected sealed override void OnUpdate() // MIGRATION //************************************************************************************************************* - public void Migrate(World destinationWorld, ref NativeArray remapArray) + public void MigrateTo(World destinationWorld, ref NativeArray remapArray) { TaskDriverManagementSystem destinationTaskDriverManagementSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldTaskDriverManagementSystemExists(destinationWorld, destinationTaskDriverManagementSystem); @@ -255,7 +252,7 @@ public void Migrate(World destinationWorld, ref NativeArray : AbstractDataSource m_Consolidator; - public EntityProxyDataSource(TaskDriverManagementSystem taskDriverManagementSystem) : base(taskDriverManagementSystem) { } + public EntityProxyDataSource(TaskDriverManagementSystem taskDriverManagementSystem) : base(taskDriverManagementSystem) + { + MigrationReflectionHelper.RegisterTypeForEntityPatching>(); + } protected override void DisposeSelf() { diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs index e62e0aad..faff8b40 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs @@ -26,15 +26,11 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver } public readonly EntityProxyInstanceID InstanceID; - public readonly Entity TestA; - public readonly Entity TestB; public readonly TInstance Payload; public EntityProxyInstanceWrapper(Entity entity, uint taskSetOwnerID, uint activeID, ref TInstance payload) { InstanceID = new EntityProxyInstanceID(entity, taskSetOwnerID, activeID); - TestA = entity; - TestB = entity; Payload = payload; } @@ -42,8 +38,6 @@ public EntityProxyInstanceWrapper(ref EntityProxyInstanceWrapper orig { EntityProxyInstanceID originalInstanceID = original.InstanceID; InstanceID = new EntityProxyInstanceID(originalInstanceID.Entity, originalInstanceID.TaskSetOwnerID, newActiveID); - TestA = originalInstanceID.Entity; - TestB = originalInstanceID.Entity; Payload = original.Payload; } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/JobDataInteraction/EntityPersistentDataWriter.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/JobDataInteraction/EntityPersistentDataWriter.cs index b9b4e04e..1d5df174 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/JobDataInteraction/EntityPersistentDataWriter.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/JobDataInteraction/EntityPersistentDataWriter.cs @@ -80,6 +80,12 @@ public NativeArray GetValueArray(AllocatorManager.AllocatorHandle allocat return m_Lookup.GetValueArray(allocator); } + /// > + public NativeKeyValueArrays GetKeyValueArrays(AllocatorManager.AllocatorHandle allocator) + { + return m_Lookup.GetKeyValueArrays(allocator); + } + /// > public UnsafeParallelHashMap.Enumerator GetEnumerator() { From b4324dddf50242184f0e48fc2e7db83f75ed1834 Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Thu, 27 Apr 2023 15:06:59 -0400 Subject: [PATCH 05/16] Fixing an error with Entity Patching and assuming all entities were the same --- .../Entities/Lifecycle/Migration/MigrationReflectionHelper.cs | 4 ++-- .../Entities/PersistentData/Data/EntityPersistentData.cs | 2 +- .../TaskData/DataStream/DataSource/EntityProxyDataSource.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs index d6abcff6..e5731510 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs @@ -96,7 +96,7 @@ private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) } - public static unsafe void PatchEntityReferences(this ref T instance, ref Entity remappedEntity) + public static unsafe void PatchEntityReferences(this ref T instance, ref NativeArray remapArray) where T : struct { long typeHash = BurstRuntime.GetHashCode64(); @@ -110,7 +110,7 @@ public static unsafe void PatchEntityReferences(this ref T instance, ref Enti { TypeManager.EntityOffsetInfo entityOffsetInfo = s_EntityOffsetList[i]; Entity* entityPtr = (Entity*)(instancePtr + entityOffsetInfo.Offset); - *entityPtr = remappedEntity; + *entityPtr = EntityRemapUtility.RemapEntity(ref remapArray, *entityPtr); } //TODO: Patch for Blobs and Weaks? diff --git a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs index f7b67b99..2f80e026 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs @@ -116,7 +116,7 @@ public override void MigrateTo(AbstractPersistentData destinationPersistentData, currentData.Remove(currentEntity); T currentValue = currentElements.Values[i]; - currentValue.PatchEntityReferences(ref remappedEntity); + currentValue.PatchEntityReferences(ref remapArray); //TODO: Could this be a problem? Is there data already here that wasn't moved? destinationData[remappedEntity] = currentValue; diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index e2193070..9cafbae6 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -88,7 +88,7 @@ public override void MigrateTo( } //If we do have a destination, then we will want to patch the entity references - instance.PatchEntityReferences(ref remappedEntity); + instance.PatchEntityReferences(ref remapArray); //Rewrite the memory for the TaskSetOwnerID and ActiveID instance.PatchIDs( From 9618b6588b5385ceec857b1701c827379c64670b Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Fri, 28 Apr 2023 11:02:38 -0400 Subject: [PATCH 06/16] Working for Burst --- .../Lifecycle/Migration/IMigrationObserver.cs | 3 +- .../Migration/MigrationReflectionHelper.cs | 140 ------------ .../Lifecycle/Migration/MigrationUtil.cs | 200 +++++++++++++++++ ...onHelper.cs.meta => MigrationUtil.cs.meta} | 0 .../Migration/WorldEntityMigrationSystem.cs | 37 +++- .../Data/AbstractPersistentData.cs | 2 +- .../Data/EntityPersistentData.cs | 102 ++++++--- .../Data/ThreadPersistentData.cs | 3 +- .../PersistentData/PersistentDataSystem.cs | 22 +- .../TaskDriver/TaskDriverManagementSystem.cs | 24 +- .../DataSource/AbstractDataSource.cs | 3 +- .../DataSource/CancelProgressDataSource.cs | 11 +- .../DataSource/CancelRequestsDataSource.cs | 8 +- .../DataSource/EntityProxyDataSource.cs | 206 +++++++++++------- .../DataStream/DataSource/IDataSource.cs | 3 +- .../EntityProxyInstanceID.cs | 27 ++- .../EntityProxyInstanceWrapper.cs | 18 +- 17 files changed, 524 insertions(+), 285 deletions(-) delete mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs rename Scripts/Runtime/Entities/Lifecycle/Migration/{MigrationReflectionHelper.cs.meta => MigrationUtil.cs.meta} (100%) diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs index 66764250..ed516b2b 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs @@ -1,10 +1,11 @@ using Unity.Collections; using Unity.Entities; +using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities { public interface IMigrationObserver { - public void MigrateTo(World destinationWorld, ref NativeArray remapArray); + public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray); } } diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs deleted file mode 100644 index e5731510..00000000 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Anvil.CSharp.Logging; -using System; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; -using UnityEngine; - -namespace Anvil.Unity.DOTS.Entities -{ - internal static class MigrationReflectionHelper - { - private static NativeParallelHashMap s_TypeOffsetsLookup = new NativeParallelHashMap(256, Allocator.Persistent); - private static NativeList s_EntityOffsetList = new NativeList(Allocator.Persistent); - private static NativeList s_BlobAssetRefOffsetList = new NativeList(Allocator.Persistent); - private static NativeList s_WeakAssetRefOffsetList = new NativeList(Allocator.Persistent); - - private static bool s_AppDomainUnloadRegistered; - - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] - private static void Init() - { - if (!s_AppDomainUnloadRegistered) - { - AppDomain.CurrentDomain.DomainUnload += CurrentDomain_OnDomainUnload; - s_AppDomainUnloadRegistered = true; - } - } - - public static void RegisterTypeForEntityPatching() - where T : struct - { - RegisterTypeForEntityPatching(typeof(T)); - } - - public static void RegisterTypeForEntityPatching(Type type) - { - if (!type.IsValueType) - { - throw new InvalidOperationException($"Type {type.GetReadableName()} must be a value type in order to register for Entity Patching."); - } - - long typeHash = BurstRuntime.GetHashCode64(type); - //We've already added this type - if (s_TypeOffsetsLookup.ContainsKey(typeHash)) - { - return; - } - - int entityOffsetStartIndex = s_EntityOffsetList.Length; - int blobOffsetStartIndex = s_BlobAssetRefOffsetList.Length; - int weakAssetStartIndex = s_WeakAssetRefOffsetList.Length; - - EntityRemapUtility.CalculateFieldOffsetsUnmanaged( - type, - out bool hasEntityRefs, - out bool hasBlobRefs, - out bool hasWeakAssetRefs, - ref s_EntityOffsetList, - ref s_BlobAssetRefOffsetList, - ref s_WeakAssetRefOffsetList); - - //We'll allow for a TypeOffset to be registered even if there's nothing to remap so that it's easy to detect - //when you forgot to register a type. - - s_TypeOffsetsLookup.Add( - typeHash, - new TypeOffsetInfo( - entityOffsetStartIndex, - s_EntityOffsetList.Length, - blobOffsetStartIndex, - s_BlobAssetRefOffsetList.Length, - weakAssetStartIndex, - s_WeakAssetRefOffsetList.Length)); - } - - private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) - { - if (s_TypeOffsetsLookup.IsCreated) - { - s_TypeOffsetsLookup.Dispose(); - } - if (s_EntityOffsetList.IsCreated) - { - s_EntityOffsetList.Dispose(); - } - if (s_BlobAssetRefOffsetList.IsCreated) - { - s_BlobAssetRefOffsetList.Dispose(); - } - if (s_WeakAssetRefOffsetList.IsCreated) - { - s_WeakAssetRefOffsetList.Dispose(); - } - } - - - public static unsafe void PatchEntityReferences(this ref T instance, ref NativeArray remapArray) - where T : struct - { - long typeHash = BurstRuntime.GetHashCode64(); - if (!s_TypeOffsetsLookup.TryGetValue(typeHash, out TypeOffsetInfo typeOffsetInfo)) - { - throw new InvalidOperationException($"Tried to patch type with BurstRuntime hash of {typeHash} but it wasn't registered. Did you call {nameof(RegisterTypeForEntityPatching)}?"); - } - - byte* instancePtr = (byte*)UnsafeUtility.AddressOf(ref instance); - for (int i = typeOffsetInfo.EntityOffsetStartIndex; i < typeOffsetInfo.EntityOffsetEndIndex; ++i) - { - TypeManager.EntityOffsetInfo entityOffsetInfo = s_EntityOffsetList[i]; - Entity* entityPtr = (Entity*)(instancePtr + entityOffsetInfo.Offset); - *entityPtr = EntityRemapUtility.RemapEntity(ref remapArray, *entityPtr); - } - - //TODO: Patch for Blobs and Weaks? - } - - - public readonly struct TypeOffsetInfo - { - public readonly int EntityOffsetStartIndex; - public readonly int EntityOffsetEndIndex; - public readonly int BlobAssetStartIndex; - public readonly int BlobAssetEndIndex; - public readonly int WeakAssetStartIndex; - public readonly int WeakAssetEndIndex; - - public TypeOffsetInfo(int entityOffsetStartIndex, int entityOffsetEndIndex, int blobAssetStartIndex, int blobAssetEndIndex, int weakAssetStartIndex, int weakAssetEndIndex) - { - EntityOffsetStartIndex = entityOffsetStartIndex; - EntityOffsetEndIndex = entityOffsetEndIndex; - BlobAssetStartIndex = blobAssetStartIndex; - BlobAssetEndIndex = blobAssetEndIndex; - WeakAssetStartIndex = weakAssetStartIndex; - WeakAssetEndIndex = weakAssetEndIndex; - } - } - } -} diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs new file mode 100644 index 00000000..73a64ec5 --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs @@ -0,0 +1,200 @@ +using Anvil.CSharp.Logging; +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using UnityEngine; + +namespace Anvil.Unity.DOTS.Entities +{ + internal static class MigrationUtil + { + private sealed class MigrationUtilContext + { + private MigrationUtilContext() + { + } + } + + private sealed class SharedTypeOffsetInfo + { + public static readonly SharedStatic> REF = SharedStatic>.GetOrCreate(); + } + + private sealed class SharedEntityOffsetInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } + + private sealed class SharedBlobAssetRefInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } + + private sealed class SharedWeakAssetRefInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } + + private static UnsafeParallelHashMap s_TypeOffsetsLookup = new UnsafeParallelHashMap(256, Allocator.Persistent); + private static NativeList s_EntityOffsetList = new NativeList(32, Allocator.Persistent); + private static NativeList s_BlobAssetRefOffsetList = new NativeList(32, Allocator.Persistent); + private static NativeList s_WeakAssetRefOffsetList = new NativeList(32, Allocator.Persistent); + + private static bool s_AppDomainUnloadRegistered; + + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static unsafe void Init() + { + if (s_AppDomainUnloadRegistered) + { + return; + } + AppDomain.CurrentDomain.DomainUnload += CurrentDomain_OnDomainUnload; + s_AppDomainUnloadRegistered = true; + + SharedTypeOffsetInfo.REF.Data = s_TypeOffsetsLookup; + UpdateSharedStatics(); + } + + private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) + { + SharedTypeOffsetInfo.REF.Data = default; + SharedEntityOffsetInfo.REF.Data = default; + SharedBlobAssetRefInfo.REF.Data = default; + SharedWeakAssetRefInfo.REF.Data = default; + + if (s_TypeOffsetsLookup.IsCreated) + { + s_TypeOffsetsLookup.Dispose(); + } + if (s_EntityOffsetList.IsCreated) + { + s_EntityOffsetList.Dispose(); + } + if (s_BlobAssetRefOffsetList.IsCreated) + { + s_BlobAssetRefOffsetList.Dispose(); + } + if (s_WeakAssetRefOffsetList.IsCreated) + { + s_WeakAssetRefOffsetList.Dispose(); + } + } + + public static bool IfEntityIsRemapped( + this Entity currentEntity, + ref NativeArray remapArray, + out Entity remappedEntity) + { + remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, currentEntity); + return remappedEntity != Entity.Null; + } + + public static void RegisterTypeForEntityPatching() + where T : struct + { + RegisterTypeForEntityPatching(typeof(T)); + } + + public static void RegisterTypeForEntityPatching(Type type) + { + if (!type.IsValueType) + { + throw new InvalidOperationException($"Type {type.GetReadableName()} must be a value type in order to register for Entity Patching."); + } + + long typeHash = BurstRuntime.GetHashCode64(type); + //We've already added this type, no need to do so again + if (s_TypeOffsetsLookup.ContainsKey(typeHash)) + { + return; + } + + int entityOffsetStartIndex = s_EntityOffsetList.Length; + + //We'll allow for a TypeOffset to be registered even if there's nothing to remap so that it's easy to detect + //when you forgot to register a type. We'll ignore the bools that this function returns. + EntityRemapUtility.CalculateFieldOffsetsUnmanaged( + type, + out bool hasEntityRefs, + out bool hasBlobRefs, + out bool hasWeakAssetRefs, + ref s_EntityOffsetList, + ref s_BlobAssetRefOffsetList, + ref s_WeakAssetRefOffsetList); + + + //Unity gives us back Blob Asset Refs and Weak Asset Refs as well but for now we're ignoring them. + //When the time comes to use those and do remapping with them, we'll need to add that info here along + //with the utils to actually do the remapping + s_TypeOffsetsLookup.Add( + typeHash, + new TypeOffsetInfo( + entityOffsetStartIndex, + s_EntityOffsetList.Length)); + + UpdateSharedStatics(); + } + + private static unsafe void UpdateSharedStatics() + { + SharedEntityOffsetInfo.REF.Data = new IntPtr(s_EntityOffsetList.GetUnsafePtr()); + SharedBlobAssetRefInfo.REF.Data = new IntPtr(s_BlobAssetRefOffsetList.GetUnsafePtr()); + SharedWeakAssetRefInfo.REF.Data = new IntPtr(s_WeakAssetRefOffsetList.GetUnsafePtr()); + } + + + + + [BurstCompatible] + public static unsafe void PatchEntityReferences(this ref T instance, ref NativeArray remapArray) + where T : struct + { + long typeHash = BurstRuntime.GetHashCode64(); + //Easy way to check if we remembered to register our type. Unfortunately it's a lot harder to figure out which type is missing due to the hash + //but usually you're going to run into this right away and be able to figure it out. Not using the actual Type class so we can Burst this. + if (!SharedTypeOffsetInfo.REF.Data.TryGetValue(typeHash, out TypeOffsetInfo typeOffsetInfo)) + { + throw new InvalidOperationException($"Tried to patch type with BurstRuntime hash of {typeHash} but it wasn't registered. Did you call {nameof(RegisterTypeForEntityPatching)}?"); + } + + //If there's nothing to remap, we'll just return + if (!typeOffsetInfo.CanRemap) + { + return; + } + + //Otherwise we'll get the memory address of the instance and run through all possible entity references + //to remap to the new entity + byte* instancePtr = (byte*)UnsafeUtility.AddressOf(ref instance); + TypeManager.EntityOffsetInfo* entityOffsetInfoPtr = (TypeManager.EntityOffsetInfo*)SharedEntityOffsetInfo.REF.Data; + for (int i = typeOffsetInfo.EntityOffsetStartIndex; i < typeOffsetInfo.EntityOffsetEndIndex; ++i) + { + TypeManager.EntityOffsetInfo* entityOffsetInfo = entityOffsetInfoPtr + i; + Entity* entityPtr = (Entity*)(instancePtr + entityOffsetInfo->Offset); + *entityPtr = EntityRemapUtility.RemapEntity(ref remapArray, *entityPtr); + } + } + + + private readonly struct TypeOffsetInfo + { + public readonly int EntityOffsetStartIndex; + public readonly int EntityOffsetEndIndex; + + public bool CanRemap + { + get => EntityOffsetEndIndex > EntityOffsetStartIndex; + } + + public TypeOffsetInfo(int entityOffsetStartIndex, int entityOffsetEndIndex) + { + EntityOffsetStartIndex = entityOffsetStartIndex; + EntityOffsetEndIndex = entityOffsetEndIndex; + } + } + } +} diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs.meta similarity index 100% rename from Scripts/Runtime/Entities/Lifecycle/Migration/MigrationReflectionHelper.cs.meta rename to Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs index 4ffe6558..8331a504 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs @@ -1,44 +1,71 @@ using System.Collections.Generic; using Unity.Collections; using Unity.Entities; +using Unity.Jobs; +using Unity.Profiling; namespace Anvil.Unity.DOTS.Entities { public class WorldEntityMigrationSystem : AbstractDataSystem { private readonly HashSet m_MigrationObservers; + // ReSharper disable once InconsistentNaming + private NativeList m_Dependencies_ScratchPad; + + private ProfilerMarker m_PrepareProfileMarker = new ProfilerMarker("PREPARE MIGRATION"); public WorldEntityMigrationSystem() { m_MigrationObservers = new HashSet(); + m_Dependencies_ScratchPad = new NativeList(8, Allocator.Persistent); + } + + protected override void OnDestroy() + { + m_Dependencies_ScratchPad.Dispose(); + base.OnDestroy(); } public void AddMigrationObserver(IMigrationObserver migrationObserver) { m_MigrationObservers.Add(migrationObserver); + m_Dependencies_ScratchPad.Resize(m_MigrationObservers.Count, NativeArrayOptions.UninitializedMemory); } public void RemoveMigrationObserver(IMigrationObserver migrationObserver) { m_MigrationObservers.Remove(migrationObserver); + m_Dependencies_ScratchPad.Resize(m_MigrationObservers.Count, NativeArrayOptions.UninitializedMemory); } - private void NotifyObserversToMigrateTo(World destinationWorld, ref NativeArray remapArray) + private JobHandle NotifyObserversToMigrateTo(World destinationWorld, ref NativeArray remapArray) { + int index = 0; foreach (IMigrationObserver migrationObserver in m_MigrationObservers) { - migrationObserver.MigrateTo(destinationWorld, ref remapArray); + m_Dependencies_ScratchPad[index] = migrationObserver.MigrateTo(default, destinationWorld, ref remapArray); + index++; } + return JobHandle.CombineDependencies(m_Dependencies_ScratchPad.AsArray()); } public void MigrateTo(World destinationWorld, EntityQuery entitiesToMigrateQuery) { - // Do move + Logger.Debug($"MIGRATING ON FRAME {UnityEngine.Time.frameCount}"); + m_PrepareProfileMarker.Begin(); + NativeArray remapArray = EntityManager.CreateEntityRemapArray(Allocator.TempJob); + //Do the actual move and get back the remap info destinationWorld.EntityManager.MoveEntitiesFrom(EntityManager, entitiesToMigrateQuery, remapArray); - NotifyObserversToMigrateTo(destinationWorld, ref remapArray); - remapArray.Dispose(); + //Let everyone have a chance to do any additional remapping + JobHandle dependsOn = NotifyObserversToMigrateTo(destinationWorld, ref remapArray); + //Dispose the array based on those remapping jobs being complete + remapArray.Dispose(dependsOn); + //Immediately complete the jobs so migration is complete and the world's state is correct + dependsOn.Complete(); + + m_PrepareProfileMarker.End(); } } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs index 249ad600..5c52b570 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs @@ -53,6 +53,6 @@ public void Release() //************************************************************************************************************* // MIGRATION //************************************************************************************************************* - public abstract void MigrateTo(AbstractPersistentData destinationPersistentData, ref NativeArray remapArray); + public abstract JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem destinationPersistentDataSystem, ref NativeArray remapArray); } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs index 2f80e026..17fa26a6 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs @@ -1,9 +1,12 @@ +using Anvil.CSharp.Logging; using Anvil.Unity.DOTS.Entities.TaskDriver; using Anvil.Unity.DOTS.Jobs; +using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; +using Unity.Profiling; namespace Anvil.Unity.DOTS.Entities { @@ -11,12 +14,16 @@ internal class EntityPersistentData : AbstractTypedPersistentData, ISystemEntityPersistentData, IWorldEntityPersistentData - where T : struct, IEntityPersistentDataInstance + where T : unmanaged, IEntityPersistentDataInstance { + private readonly ProfilerMarker m_ProfilerMarker; + public EntityPersistentData() : base(new UnsafeParallelHashMap(ChunkUtil.MaxElementsPerChunk(), Allocator.Persistent)) { - MigrationReflectionHelper.RegisterTypeForEntityPatching(); + m_ProfilerMarker = new ProfilerMarker(GetType().GetReadableName()); + //We don't know what will be stored in here, but if there are Entity references we want to be able to patch them + MigrationUtil.RegisterTypeForEntityPatching(); } protected override void DisposeData() @@ -89,41 +96,78 @@ public void ReleaseWriter() // MIGRATION //************************************************************************************************************* - public override void MigrateTo(AbstractPersistentData destinationPersistentData, ref NativeArray remapArray) + public override JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem destinationPersistentDataSystem, ref NativeArray remapArray) { - //Our destination in the other world could be null... TODO: Should we create? - if (destinationPersistentData is not EntityPersistentData destination) - { - return; - } + //This ensures there is a target on the other world to go to + EntityPersistentData destinationPersistentData = destinationPersistentDataSystem.GetOrCreateEntityPersistentData(); + + //Launch the migration job to get that burst speed + dependsOn = JobHandle.CombineDependencies(dependsOn, + AcquireWriterAsync(out EntityPersistentDataWriter currentData), + destinationPersistentData.AcquireWriterAsync(out EntityPersistentDataWriter destinationData)); + + MigrateJob migrateJob = new MigrateJob( + currentData, + destinationData, + ref remapArray, + m_ProfilerMarker); + dependsOn = migrateJob.Schedule(dependsOn); + + destinationPersistentData.ReleaseWriterAsync(dependsOn); + ReleaseWriterAsync(dependsOn); + + return dependsOn; + } + + [BurstCompile] + private struct MigrateJob : IJob + { + private EntityPersistentDataWriter m_CurrentData; + private EntityPersistentDataWriter m_DestinationData; + [ReadOnly] private NativeArray m_RemapArray; - EntityPersistentDataWriter currentData = AcquireWriter(); - EntityPersistentDataWriter destinationData = destination.AcquireWriter(); + private ProfilerMarker m_Marker; - NativeKeyValueArrays currentElements = currentData.GetKeyValueArrays(Allocator.Temp); + public MigrateJob( + EntityPersistentDataWriter currentData, + EntityPersistentDataWriter destinationData, + ref NativeArray remapArray, + ProfilerMarker marker) + { + m_CurrentData = currentData; + m_DestinationData = destinationData; + m_RemapArray = remapArray; + m_Marker = marker; + } - for (int i = 0; i < currentElements.Length; ++i) + public void Execute() { - Entity currentEntity = currentElements.Keys[i]; - Entity remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, currentEntity); - //We don't exist in the new world, we should just stay here. - if (remappedEntity == Entity.Null) + m_Marker.Begin(); + //Can't remove while iterating so we collapse to an array first of our current keys/values + NativeKeyValueArrays currentEntries = m_CurrentData.GetKeyValueArrays(Allocator.Temp); + + for (int i = 0; i < currentEntries.Length; ++i) { - continue; + Entity currentEntity = currentEntries.Keys[i]; + //If we don't exist in the new world we can just skip, we stayed in this world + if (!currentEntity.IfEntityIsRemapped(ref m_RemapArray, out Entity remappedEntity)) + { + continue; + } + + //Otherwise, remove us from this world's lookup + m_CurrentData.Remove(currentEntity); + + //Get our data and patch it + T currentValue = currentEntries.Values[i]; + currentValue.PatchEntityReferences(ref m_RemapArray); + + //TODO: Could this be a problem? Is there data already here that wasn't moved? + //Then write the newly remapped data to the new world's lookup + m_DestinationData[remappedEntity] = currentValue; } - - //Otherwise, prepare us in the migration data - currentData.Remove(currentEntity); - T currentValue = currentElements.Values[i]; - - currentValue.PatchEntityReferences(ref remapArray); - - //TODO: Could this be a problem? Is there data already here that wasn't moved? - destinationData[remappedEntity] = currentValue; + m_Marker.End(); } - - destination.ReleaseWriter(); - ReleaseWriter(); } } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs index dcb9cb15..58021ede 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs @@ -60,7 +60,8 @@ public ThreadPersistentDataAccessor Acquire() //************************************************************************************************************* // MIGRATION //************************************************************************************************************* - public override void MigrateTo(AbstractPersistentData destinationPersistentData, ref NativeArray remapArray) + + public override JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem destinationPersistentDataSystem, ref NativeArray remapArray) { throw new NotSupportedException($"{nameof(ThreadPersistentData)} isn't supported for migration because it is global to the app."); } diff --git a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs index 51675531..16846fa8 100644 --- a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs +++ b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using Unity.Collections; using Unity.Entities; +using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities { @@ -14,11 +15,15 @@ internal partial class PersistentDataSystem : AbstractDataSystem, private static int s_InstanceCount; private readonly Dictionary m_EntityPersistentData; - + + // ReSharper disable once InconsistentNaming + private NativeList m_MigrationDependencies_ScratchPad; + public PersistentDataSystem() { s_InstanceCount++; m_EntityPersistentData = new Dictionary(); + m_MigrationDependencies_ScratchPad = new NativeList(8, Allocator.Persistent); } protected override void OnCreate() @@ -30,6 +35,7 @@ protected override void OnCreate() protected override void OnDestroy() { + m_MigrationDependencies_ScratchPad.Dispose(); m_EntityPersistentData.DisposeAllValuesAndClear(); s_InstanceCount--; if (s_InstanceCount <= 0) @@ -59,6 +65,7 @@ public EntityPersistentData GetOrCreateEntityPersistentData() { persistentData = new EntityPersistentData(); m_EntityPersistentData.Add(type, persistentData); + m_MigrationDependencies_ScratchPad.Resize(m_EntityPersistentData.Count, NativeArrayOptions.UninitializedMemory); } return (EntityPersistentData)persistentData; @@ -68,20 +75,19 @@ public EntityPersistentData GetOrCreateEntityPersistentData() // MIGRATION //************************************************************************************************************* - public void MigrateTo(World destinationWorld, ref NativeArray remapArray) + public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { PersistentDataSystem destinationPersistentDataSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldPersistentDataSystemExists(destinationWorld, destinationPersistentDataSystem); + int index = 0; foreach (KeyValuePair entry in m_EntityPersistentData) { - if (!destinationPersistentDataSystem.m_EntityPersistentData.TryGetValue(entry.Key, out AbstractPersistentData destinationPersistentData)) - { - continue; - } - - entry.Value.MigrateTo(destinationPersistentData, ref remapArray); + m_MigrationDependencies_ScratchPad[index] = entry.Value.MigrateTo(dependsOn, destinationPersistentDataSystem, ref remapArray); + index++; } + + return JobHandle.CombineDependencies(m_MigrationDependencies_ScratchPad.AsArray()); } //************************************************************************************************************* diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 725d5f30..4aa296dd 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -28,11 +28,15 @@ internal partial class TaskDriverManagementSystem : AbstractAnvilSystemBase, private readonly Dictionary m_UnityEntityDataAccessControllers; private readonly TaskDriverMigrationData m_TaskDriverMigrationData; + private bool m_IsInitialized; private bool m_IsHardened; private BulkJobScheduler m_EntityProxyDataSourceBulkJobScheduler; private BulkJobScheduler m_CancelProgressFlowBulkJobScheduler; + + // ReSharper disable once InconsistentNaming + private NativeArray m_MigrationDependencies_ScratchPad; private readonly IDProvider m_IDProvider; @@ -49,6 +53,8 @@ public TaskDriverManagementSystem() m_CancelProgressFlows = new List(); m_UnityEntityDataAccessControllers = new Dictionary(); m_TaskDriverMigrationData = new TaskDriverMigrationData(); + + EntityProxyInstanceID.Debug_EnsureOffsetsAreCorrect(); } protected override void OnCreate() @@ -74,6 +80,11 @@ protected override void OnStartRunning() protected sealed override void OnDestroy() { + if (m_MigrationDependencies_ScratchPad.IsCreated) + { + m_MigrationDependencies_ScratchPad.Dispose(); + } + m_EntityProxyDataSourcesByType.DisposeAllValuesAndClear(); m_EntityProxyDataSourceBulkJobScheduler?.Dispose(); m_CancelProgressFlowBulkJobScheduler?.Dispose(); @@ -106,7 +117,8 @@ private void Harden() } m_EntityProxyDataSourceBulkJobScheduler = new BulkJobScheduler(m_EntityProxyDataSourcesByType.Values.ToArray()); - + m_MigrationDependencies_ScratchPad = new NativeArray(m_EntityProxyDataSourcesByType.Count, Allocator.Persistent); + //For all the TaskDrivers, filter to find the ones that don't have Parents. //Those are our top level TaskDrivers m_TopLevelTaskDrivers.AddRange(m_AllTaskDrivers.Where(taskDriver => taskDriver.Parent == null)); @@ -229,23 +241,27 @@ protected sealed override void OnUpdate() // MIGRATION //************************************************************************************************************* - public void MigrateTo(World destinationWorld, ref NativeArray remapArray) + public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { TaskDriverManagementSystem destinationTaskDriverManagementSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldTaskDriverManagementSystemExists(destinationWorld, destinationTaskDriverManagementSystem); - //TODO: Lazy create a World to World mapping lookup for ActiveIDs and TaskSetOwnerIDs + //Lazy create a World to World mapping lookup for ActiveIDs and TaskSetOwnerIDs DestinationWorldDataMap destinationWorldDataMap = m_TaskDriverMigrationData.GetOrCreateDestinationWorldDataMapFor(destinationWorld, destinationTaskDriverManagementSystem.m_TaskDriverMigrationData); Dictionary destinationEntityProxyDataSourcesByType = destinationTaskDriverManagementSystem.m_EntityProxyDataSourcesByType; + int index = 0; foreach (KeyValuePair entry in m_EntityProxyDataSourcesByType) { //We may not have a corresponding destination Data Source in the destination world but we still want to process the migration so that //we remove any references in this world. If we do have the corresponding data source, we'll transfer over to the other world. destinationEntityProxyDataSourcesByType.TryGetValue(entry.Key, out IDataSource destinationDataSource); - entry.Value.MigrateTo(destinationDataSource, ref remapArray, destinationWorldDataMap); + m_MigrationDependencies_ScratchPad[index] = entry.Value.MigrateTo(dependsOn, destinationDataSource, ref remapArray, destinationWorldDataMap); + index++; } + + return JobHandle.CombineDependencies(m_MigrationDependencies_ScratchPad); } //************************************************************************************************************* diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractDataSource.cs index 1ddb2f4c..56509b7d 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractDataSource.cs @@ -138,7 +138,8 @@ protected void AddConsolidationData(AbstractData data, AccessType accessType) // MIGRATION //************************************************************************************************************* - public abstract void MigrateTo( + public abstract JobHandle MigrateTo( + JobHandle dependsOn, IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs index 3b81501c..c5f458d5 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs @@ -13,11 +13,16 @@ protected override JobHandle ConsolidateSelf(JobHandle dependsOn) { throw new InvalidOperationException($"CancelProgress Data Never needs to be consolidated"); } - - - public override void MigrateTo(IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public override JobHandle MigrateTo(JobHandle dependsOn, IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) { //DOES NOTHING RIGHT NOW + //TODO: IMPLEMENT + return dependsOn; } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs index bf3e52b2..b440af46 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs @@ -42,9 +42,15 @@ protected override JobHandle ConsolidateSelf(JobHandle dependsOn) return dependsOn; } - public override void MigrateTo(IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public override JobHandle MigrateTo(JobHandle dependsOn, IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) { //DOES NOTHING RIGHT NOW + //TODO: IMPLEMENT + return dependsOn; } //************************************************************************************************************* diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index 9cafbae6..83430a54 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -1,9 +1,12 @@ +using Anvil.CSharp.Logging; using Anvil.Unity.DOTS.Data; using Anvil.Unity.DOTS.Jobs; using Unity.Burst; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; +using Unity.Profiling; namespace Anvil.Unity.DOTS.Entities.TaskDriver { @@ -12,9 +15,13 @@ internal class EntityProxyDataSource : AbstractDataSource m_Consolidator; + private readonly ProfilerMarker m_ProfilerMarker; + public EntityProxyDataSource(TaskDriverManagementSystem taskDriverManagementSystem) : base(taskDriverManagementSystem) { - MigrationReflectionHelper.RegisterTypeForEntityPatching>(); + m_ProfilerMarker = new ProfilerMarker(GetType().GetReadableName()); + MigrationUtil.RegisterTypeForEntityPatching>(); + EntityProxyInstanceWrapper.Debug_EnsureOffsetsAreCorrect(); } protected override void DisposeSelf() @@ -39,104 +46,135 @@ protected override void HardenSelf() m_Consolidator = new EntityProxyDataSourceConsolidator(PendingData, ActiveDataLookupByID); } - + //************************************************************************************************************* // MIGRATION //************************************************************************************************************* - - public override void MigrateTo( - IDataSource destinationDataSource, + + public override JobHandle MigrateTo( + JobHandle dependsOn, + IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) { EntityProxyDataSource destination = destinationDataSource as EntityProxyDataSource; - - PendingData.Acquire(AccessType.ExclusiveWrite); - destination.PendingData.Acquire(AccessType.ExclusiveWrite); - - UnsafeTypedStream> stream = PendingData.Pending; - NativeArray> instanceArray = stream.ToNativeArray(Allocator.Temp); - stream.Clear(); - - //TEMP Main Thread Writer - UnsafeTypedStream>.LaneWriter laneWriter = stream.AsLaneWriter(ParallelAccessUtil.CollectionIndexForMainThread()); - UnsafeTypedStream>.LaneWriter destinationLaneWriter = destination.PendingData.Pending.AsLaneWriter(ParallelAccessUtil.CollectionIndexForMainThread()); + UnsafeTypedStream>.Writer destinationWriter = default; - - for (int i = 0; i < instanceArray.Length; ++i) + if (destination == null) { - //If we didn't move, we need to rewrite to the stream since we didn't move - EntityProxyInstanceWrapper instance = instanceArray[i]; - EntityProxyInstanceID instanceID = instance.InstanceID; - - //See what our entity was remapped to - Entity remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, instanceID.Entity); - //If we were remapped to null, then we don't exist in the new world, we should just stay here - if (remappedEntity == Entity.Null) - { - laneWriter.Write(instance); - continue; - } - - //If we don't have a destination in the new world, then we can just let these cease to exist - //Check the TaskSetOwnerIDMapping/ActiveIDMapping - if (destination == null - || !destinationWorldDataMap.TaskSetOwnerIDMapping.TryGetValue(instanceID.TaskSetOwnerID, out uint destinationTaskSetOwnerID) - || !destinationWorldDataMap.ActiveIDMapping.TryGetValue(instanceID.ActiveID, out uint destinationActiveID)) - { - continue; - } - - //If we do have a destination, then we will want to patch the entity references - instance.PatchEntityReferences(ref remapArray); - - //Rewrite the memory for the TaskSetOwnerID and ActiveID - instance.PatchIDs( - destinationTaskSetOwnerID, - destinationActiveID); - - //Write to the destination stream - destinationLaneWriter.Write(instance); + dependsOn = JobHandle.CombineDependencies( + dependsOn, + PendingData.AcquireAsync(AccessType.ExclusiveWrite)); } + else + { + dependsOn = JobHandle.CombineDependencies( + dependsOn, + PendingData.AcquireAsync(AccessType.ExclusiveWrite), + destination.PendingData.AcquireAsync(AccessType.ExclusiveWrite)); + + destinationWriter = destination.PendingWriter; + } + + MigrateJob migrateJob = new MigrateJob( + PendingData.Pending, + destinationWriter, + remapArray, + destinationWorldDataMap.TaskSetOwnerIDMapping, + destinationWorldDataMap.ActiveIDMapping, + m_ProfilerMarker); + dependsOn = migrateJob.Schedule(dependsOn); PendingData.Release(); - destination.PendingData.Release(); + destination?.PendingData.Release(); + + return dependsOn; } + [BurstCompile] + private struct MigrateJob : IJob + { + private const int UNSET_ID = -1; + + private UnsafeTypedStream> m_CurrentStream; + private readonly UnsafeTypedStream>.Writer m_DestinationStreamWriter; + [ReadOnly] private NativeArray m_RemapArray; + [ReadOnly] private readonly NativeParallelHashMap m_TaskSetOwnerIDMapping; + [ReadOnly] private readonly NativeParallelHashMap m_ActiveIDMapping; + [NativeSetThreadIndex] private readonly int m_NativeThreadIndex; + + private ProfilerMarker m_Marker; + + public MigrateJob( + UnsafeTypedStream> currentStream, + UnsafeTypedStream>.Writer destinationStreamWriter, + NativeArray remapArray, + NativeParallelHashMap taskSetOwnerIDMapping, + NativeParallelHashMap activeIDMapping, + ProfilerMarker marker) + { + m_CurrentStream = currentStream; + m_DestinationStreamWriter = destinationStreamWriter; + m_RemapArray = remapArray; + m_TaskSetOwnerIDMapping = taskSetOwnerIDMapping; + m_ActiveIDMapping = activeIDMapping; + m_Marker = marker; + + m_NativeThreadIndex = UNSET_ID; + } + + public void Execute() + { + m_Marker.Begin(); + //Can't modify while iterating so we collapse down to a single array and clean the underlying stream. + //We'll build this stream back up if anything should still remain + NativeArray> currentInstanceArray = m_CurrentStream.ToNativeArray(Allocator.Temp); + m_CurrentStream.Clear(); + + int laneIndex = ParallelAccessUtil.CollectionIndexForThread(m_NativeThreadIndex); - // public unsafe void Migrate(NativeArray remapArray) - // { - // PendingData.Acquire(AccessType.ExclusiveWrite); - // //TODO: Need to ensure this is actually the ref - look at NativeParallelHashMap - // foreach (EntityProxyInstanceWrapper entry in PendingData.Pending) - // { - // EntityRemapUtility.CalculateFieldOffsetsUnmanaged( - // typeof(EntityProxyInstanceWrapper), - // out bool hasEntityRefs, - // out bool hasBlobRefs, - // out bool hasWeakAssetRefs, - // ref s_EntityOffsetList, - // ref s_BlobAssetRefOffsetList, - // ref s_WeakAssetRefOffsetList); - // - // EntityProxyInstanceWrapper copy = entry; - // byte* copyPtr = (byte*)UnsafeUtility.AddressOf(ref copy); - // void* startCopyPtr = copyPtr; - // for (int i = 0; i < s_EntityOffsetList.Length; ++i) - // { - // TypeManager.EntityOffsetInfo offsetInfo = s_EntityOffsetList[i]; - // copyPtr += offsetInfo.Offset; - // Entity* offsetEntity = (Entity*)copyPtr; - // *offsetEntity = EntityRemapUtility.RemapEntity(ref remapArray, *offsetEntity); - // } - // - // - // copy = *(EntityProxyInstanceWrapper*)startCopyPtr; - // - // float a = 5.0f; - // } - // PendingData.Release(); - // } + UnsafeTypedStream>.LaneWriter currentLaneWriter = m_CurrentStream.AsLaneWriter(laneIndex); + UnsafeTypedStream>.LaneWriter destinationLaneWriter = + m_DestinationStreamWriter.IsCreated + ? m_DestinationStreamWriter.AsLaneWriter(laneIndex) + : default; + + for (int i = 0; i < currentInstanceArray.Length; ++i) + { + EntityProxyInstanceWrapper instance = currentInstanceArray[i]; + EntityProxyInstanceID instanceID = instance.InstanceID; + + //If we don't exist in the new world then we stayed in this world and we need to rewrite ourselves + //to our own stream + if (!instanceID.Entity.IfEntityIsRemapped(ref m_RemapArray, out Entity remappedEntity)) + { + currentLaneWriter.Write(ref instance); + continue; + } + + //If we don't have a destination in the new world, then we can just let these cease to exist + //Check the TaskSetOwnerIDMapping/ActiveIDMapping + if (!destinationLaneWriter.IsCreated + || !m_TaskSetOwnerIDMapping.TryGetValue(instanceID.TaskSetOwnerID, out uint destinationTaskSetOwnerID) + || !m_ActiveIDMapping.TryGetValue(instanceID.ActiveID, out uint destinationActiveID)) + { + continue; + } + + //If we do have a destination, then we will want to patch the entity references + instance.PatchEntityReferences(ref m_RemapArray); + + //Rewrite the memory for the TaskSetOwnerID and ActiveID + instance.PatchIDs( + destinationTaskSetOwnerID, + destinationActiveID); + + //Write to the destination stream + destinationLaneWriter.Write(instance); + } + m_Marker.End(); + } + } //************************************************************************************************************* // EXECUTION diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs index 4fa8bc75..ac659158 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/IDataSource.cs @@ -14,7 +14,8 @@ internal interface IDataSource : IAnvilDisposable public JobHandle Consolidate(JobHandle dependsOn); - public void MigrateTo( + public JobHandle MigrateTo( + JobHandle dependsOn, IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs index 0ffae2f0..a07f9ba4 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs @@ -1,11 +1,10 @@ using Anvil.Unity.DOTS.Util; using System; -using System.Reflection; +using System.Diagnostics; using System.Runtime.InteropServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; -using UnityEngine; namespace Anvil.Unity.DOTS.Entities.TaskDriver { @@ -14,8 +13,8 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver [StructLayout(LayoutKind.Sequential, Size = 16)] internal readonly struct EntityProxyInstanceID : IEquatable { - public static readonly int TASK_SET_OWNER_ID_OFFSET = Marshal.OffsetOf(nameof(TaskSetOwnerID)).ToInt32(); - public static readonly int ACTIVE_ID_OFFSET = Marshal.OffsetOf(nameof(ActiveID)).ToInt32(); + public static readonly int TASK_SET_OWNER_ID_OFFSET = 8; + public static readonly int ACTIVE_ID_OFFSET = 12; public static bool operator ==(EntityProxyInstanceID lhs, EntityProxyInstanceID rhs) { @@ -77,5 +76,25 @@ public FixedString64Bytes ToFixedString() fs.Append(ActiveID); return fs; } + + //************************************************************************************************************* + // SAFETY + //************************************************************************************************************* + + [Conditional("ANVIL_DEBUG_SAFETY")] + public static void Debug_EnsureOffsetsAreCorrect() + { + int actualOffset = UnsafeUtility.GetFieldOffset(typeof(EntityProxyInstanceID).GetField(nameof(TaskSetOwnerID))); + if (actualOffset != TASK_SET_OWNER_ID_OFFSET) + { + throw new InvalidOperationException($"{nameof(TaskSetOwnerID)} has changed location in the struct. The hardcoded burst compatible offset of {nameof(TASK_SET_OWNER_ID_OFFSET)} = {TASK_SET_OWNER_ID_OFFSET} needs to be changed to {actualOffset}!"); + } + + actualOffset = UnsafeUtility.GetFieldOffset(typeof(EntityProxyInstanceID).GetField(nameof(ActiveID))); + if (actualOffset != ACTIVE_ID_OFFSET) + { + throw new InvalidOperationException($"{nameof(ActiveID)} has changed location in the struct. The hardcoded burst compatible offset of {nameof(ACTIVE_ID_OFFSET)} = {ACTIVE_ID_OFFSET} needs to be changed to {actualOffset}!"); + } + } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs index faff8b40..358bdda3 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs @@ -1,8 +1,11 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; +using Unity.Burst; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; +using UnityEngine; namespace Anvil.Unity.DOTS.Entities.TaskDriver { @@ -11,7 +14,8 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver internal readonly struct EntityProxyInstanceWrapper : IEquatable> where TInstance : unmanaged, IEntityProxyInstance { - public static readonly int INSTANCE_ID_OFFSET = Marshal.OffsetOf>(nameof(InstanceID)).ToInt32(); + public static readonly int INSTANCE_ID_OFFSET = 0; + public static bool operator ==(EntityProxyInstanceWrapper lhs, EntityProxyInstanceWrapper rhs) { //Note that we are not checking if the Payload is equal because the wrapper is only for origin and lookup @@ -83,5 +87,15 @@ private static void Debug_EnsurePayloadsAreTheSame( throw new InvalidOperationException($"Equality check for {typeof(EntityProxyInstanceWrapper)} where the ID's are the same but the Payloads are different. This should never happen!"); } } + + [Conditional("ANVIL_DEBUG_SAFETY")] + public static void Debug_EnsureOffsetsAreCorrect() + { + int actualOffset = UnsafeUtility.GetFieldOffset(typeof(EntityProxyInstanceWrapper).GetField(nameof(InstanceID))); + if (actualOffset != INSTANCE_ID_OFFSET) + { + throw new InvalidOperationException($"{nameof(InstanceID)} has changed location in the struct. The hardcoded burst compatible offset of {nameof(INSTANCE_ID_OFFSET)} = {INSTANCE_ID_OFFSET} needs to be changed to {actualOffset}!"); + } + } } -} \ No newline at end of file +} From 59b135f49337b6a8e60d2ec676c5a1a7bc8beb55 Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Fri, 28 Apr 2023 11:52:34 -0400 Subject: [PATCH 07/16] Docs pass --- .../Lifecycle/Migration/IMigrationObserver.cs | 17 +++ .../Lifecycle/Migration/MigrationUtil.cs | 138 ++++++++++++------ .../Migration/WorldEntityMigrationSystem.cs | 57 +++++--- .../Data/EntityPersistentData.cs | 19 +-- .../Data/ThreadPersistentData.cs | 2 +- .../Entities/TaskDriver/AbstractTaskDriver.cs | 6 +- .../TaskDriver/TaskDriverManagementSystem.cs | 4 +- .../Job/JobConfig/AbstractJobConfig.cs | 3 +- .../DataSource/CancelProgressDataSource.cs | 4 +- .../DataSource/EntityProxyDataSource.cs | 15 +- .../EntityProxyInstanceID.cs | 1 + .../EntityProxyInstanceWrapper.cs | 2 + .../EntityProxyInstanceWrapperExtension.cs | 3 + .../Entities/TaskDriver/TaskSet/TaskSet.cs | 5 +- 14 files changed, 173 insertions(+), 103 deletions(-) diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs index ed516b2b..e14840a4 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs @@ -4,8 +4,25 @@ namespace Anvil.Unity.DOTS.Entities { + /// + /// Implement and register with to receive a notification when + /// Entities are being migrated from one to another. This will allow for scheduling jobs to + /// handle any custom migration for data that refers to s but is not automatically handled by + /// Unity. + /// NOTE: The jobs that are scheduled will be completed immediately, but this allows for taking advantage of + /// multiple cores. + /// public interface IMigrationObserver { + /// + /// Implement to handle any custom migration work. + /// + /// The to wait on. + /// The the entities are moving to. + /// The remapping array for Entities that were in this world and are moving to + /// the next World. See and + /// for more details if custom usage is needed. + /// The that represents all the custom migration work to do. public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray); } } diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs index 73a64ec5..efb7c50e 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs @@ -8,35 +8,11 @@ namespace Anvil.Unity.DOTS.Entities { - internal static class MigrationUtil + /// + /// Helper class for when Entities are migrating from one to another. + /// + public static class MigrationUtil { - private sealed class MigrationUtilContext - { - private MigrationUtilContext() - { - } - } - - private sealed class SharedTypeOffsetInfo - { - public static readonly SharedStatic> REF = SharedStatic>.GetOrCreate(); - } - - private sealed class SharedEntityOffsetInfo - { - public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); - } - - private sealed class SharedBlobAssetRefInfo - { - public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); - } - - private sealed class SharedWeakAssetRefInfo - { - public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); - } - private static UnsafeParallelHashMap s_TypeOffsetsLookup = new UnsafeParallelHashMap(256, Allocator.Persistent); private static NativeList s_EntityOffsetList = new NativeList(32, Allocator.Persistent); private static NativeList s_BlobAssetRefOffsetList = new NativeList(32, Allocator.Persistent); @@ -46,8 +22,10 @@ private sealed class SharedWeakAssetRefInfo [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] - private static unsafe void Init() + private static void Init() { + //This pattern ensures we can setup and dispose properly the static native collections without Unity + //getting upset about memory leaks if (s_AppDomainUnloadRegistered) { return; @@ -83,22 +61,22 @@ private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) s_WeakAssetRefOffsetList.Dispose(); } } - - public static bool IfEntityIsRemapped( - this Entity currentEntity, - ref NativeArray remapArray, - out Entity remappedEntity) - { - remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, currentEntity); - return remappedEntity != Entity.Null; - } - + + /// + /// Registers the Type that may contain Entity references so that it can be used with + /// to remap Entity references. + /// + /// The type to register public static void RegisterTypeForEntityPatching() where T : struct { RegisterTypeForEntityPatching(typeof(T)); } - + + /// + /// + /// Occurs when the Type is not a Value type. + /// public static void RegisterTypeForEntityPatching(Type type) { if (!type.IsValueType) @@ -136,6 +114,8 @@ public static void RegisterTypeForEntityPatching(Type type) entityOffsetStartIndex, s_EntityOffsetList.Length)); + //The size of the underlying data could have changed such that we re-allocated the memory, so we'll update + //our shared statics UpdateSharedStatics(); } @@ -145,10 +125,42 @@ private static unsafe void UpdateSharedStatics() SharedBlobAssetRefInfo.REF.Data = new IntPtr(s_BlobAssetRefOffsetList.GetUnsafePtr()); SharedWeakAssetRefInfo.REF.Data = new IntPtr(s_WeakAssetRefOffsetList.GetUnsafePtr()); } - + //************************************************************************************************************* + // BURST RUNTIME CALLS + //************************************************************************************************************* + + /// + /// Checks if the Entity was remapped by Unity during a world transfer. + /// + /// The current entity in this World + /// The remap array Unity provided. + /// The remapped Entity in the new World if it exists. + /// + /// true if this entity was moved to the new world and remaps to a new entity. + /// false if this entity did not move and stayed in this world. + /// + [BurstCompatible] + public static bool IfEntityIsRemapped( + this Entity currentEntity, + ref NativeArray remapArray, + out Entity remappedEntity) + { + remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, currentEntity); + return remappedEntity != Entity.Null; + } - + /// + /// For a given struct and Unity provided remapping array, all references will be + /// remapped to the new entity reference in the new world. + /// Entities that remained in this world will not be remapped. + /// + /// The struct to patch + /// The Unity provided remap array + /// The type of struct to patch + /// + /// Occurs if this type was not registered via + /// [BurstCompatible] public static unsafe void PatchEntityReferences(this ref T instance, ref NativeArray remapArray) where T : struct @@ -170,15 +182,22 @@ public static unsafe void PatchEntityReferences(this ref T instance, ref Nati //Otherwise we'll get the memory address of the instance and run through all possible entity references //to remap to the new entity byte* instancePtr = (byte*)UnsafeUtility.AddressOf(ref instance); + //Beginning of the list TypeManager.EntityOffsetInfo* entityOffsetInfoPtr = (TypeManager.EntityOffsetInfo*)SharedEntityOffsetInfo.REF.Data; for (int i = typeOffsetInfo.EntityOffsetStartIndex; i < typeOffsetInfo.EntityOffsetEndIndex; ++i) { + //Index into the list TypeManager.EntityOffsetInfo* entityOffsetInfo = entityOffsetInfoPtr + i; + //Get offset info from list and offset into the instance memory Entity* entityPtr = (Entity*)(instancePtr + entityOffsetInfo->Offset); + //Patch *entityPtr = EntityRemapUtility.RemapEntity(ref remapArray, *entityPtr); } } - + + //************************************************************************************************************* + // HELPER TYPES + //************************************************************************************************************* private readonly struct TypeOffsetInfo { @@ -196,5 +215,38 @@ public TypeOffsetInfo(int entityOffsetStartIndex, int entityOffsetEndIndex) EntityOffsetEndIndex = entityOffsetEndIndex; } } + + //************************************************************************************************************* + // SHARED STATIC REQUIREMENTS + //************************************************************************************************************* + + // ReSharper disable once ConvertToStaticClass + // ReSharper disable once ClassNeverInstantiated.Local + private sealed class MigrationUtilContext + { + private MigrationUtilContext() + { + } + } + + private sealed class SharedTypeOffsetInfo + { + public static readonly SharedStatic> REF = SharedStatic>.GetOrCreate(); + } + + private sealed class SharedEntityOffsetInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } + + private sealed class SharedBlobAssetRefInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } + + private sealed class SharedWeakAssetRefInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } } } diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs index 8331a504..38ef651b 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs @@ -2,18 +2,22 @@ using Unity.Collections; using Unity.Entities; using Unity.Jobs; -using Unity.Profiling; namespace Anvil.Unity.DOTS.Entities { + /// + /// World specific system for handling Migration. + /// Register s here to be notified when Migration occurs + /// + /// NOTE: Use on this System instead of directly interfacing with + /// + /// public class WorldEntityMigrationSystem : AbstractDataSystem { private readonly HashSet m_MigrationObservers; // ReSharper disable once InconsistentNaming private NativeList m_Dependencies_ScratchPad; - private ProfilerMarker m_PrepareProfileMarker = new ProfilerMarker("PREPARE MIGRATION"); - public WorldEntityMigrationSystem() { m_MigrationObservers = new HashSet(); @@ -25,35 +29,37 @@ protected override void OnDestroy() m_Dependencies_ScratchPad.Dispose(); base.OnDestroy(); } - + + /// + /// Adds a to be notified when Migration occurs and be given the chance to + /// respond to it. + /// + /// The public void AddMigrationObserver(IMigrationObserver migrationObserver) { m_MigrationObservers.Add(migrationObserver); m_Dependencies_ScratchPad.Resize(m_MigrationObservers.Count, NativeArrayOptions.UninitializedMemory); } - + + /// + /// Removes a if it no longer wishes to be notified of when a Migration occurs. + /// + /// The public void RemoveMigrationObserver(IMigrationObserver migrationObserver) { m_MigrationObservers.Remove(migrationObserver); m_Dependencies_ScratchPad.Resize(m_MigrationObservers.Count, NativeArrayOptions.UninitializedMemory); } - private JobHandle NotifyObserversToMigrateTo(World destinationWorld, ref NativeArray remapArray) - { - int index = 0; - foreach (IMigrationObserver migrationObserver in m_MigrationObservers) - { - m_Dependencies_ScratchPad[index] = migrationObserver.MigrateTo(default, destinationWorld, ref remapArray); - index++; - } - return JobHandle.CombineDependencies(m_Dependencies_ScratchPad.AsArray()); - } - + /// + /// Migrates Entities from this to the destination world with the provided query. + /// This will then handle notifying all s to have the chance to respond with + /// custom migration work. + /// + /// The to move Entities to. + /// The to select the Entities to migrate. public void MigrateTo(World destinationWorld, EntityQuery entitiesToMigrateQuery) { - Logger.Debug($"MIGRATING ON FRAME {UnityEngine.Time.frameCount}"); - m_PrepareProfileMarker.Begin(); - NativeArray remapArray = EntityManager.CreateEntityRemapArray(Allocator.TempJob); //Do the actual move and get back the remap info destinationWorld.EntityManager.MoveEntitiesFrom(EntityManager, entitiesToMigrateQuery, remapArray); @@ -64,8 +70,17 @@ public void MigrateTo(World destinationWorld, EntityQuery entitiesToMigrateQuery remapArray.Dispose(dependsOn); //Immediately complete the jobs so migration is complete and the world's state is correct dependsOn.Complete(); - - m_PrepareProfileMarker.End(); + } + + private JobHandle NotifyObserversToMigrateTo(World destinationWorld, ref NativeArray remapArray) + { + int index = 0; + foreach (IMigrationObserver migrationObserver in m_MigrationObservers) + { + m_Dependencies_ScratchPad[index] = migrationObserver.MigrateTo(default, destinationWorld, ref remapArray); + index++; + } + return JobHandle.CombineDependencies(m_Dependencies_ScratchPad.AsArray()); } } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs index 17fa26a6..503f68d1 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs @@ -1,4 +1,3 @@ -using Anvil.CSharp.Logging; using Anvil.Unity.DOTS.Entities.TaskDriver; using Anvil.Unity.DOTS.Jobs; using Unity.Burst; @@ -6,7 +5,6 @@ using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; -using Unity.Profiling; namespace Anvil.Unity.DOTS.Entities { @@ -16,12 +14,9 @@ internal class EntityPersistentData : AbstractTypedPersistentData where T : unmanaged, IEntityPersistentDataInstance { - private readonly ProfilerMarker m_ProfilerMarker; - public EntityPersistentData() : base(new UnsafeParallelHashMap(ChunkUtil.MaxElementsPerChunk(), Allocator.Persistent)) { - m_ProfilerMarker = new ProfilerMarker(GetType().GetReadableName()); //We don't know what will be stored in here, but if there are Entity references we want to be able to patch them MigrationUtil.RegisterTypeForEntityPatching(); } @@ -109,8 +104,7 @@ public override JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem de MigrateJob migrateJob = new MigrateJob( currentData, destinationData, - ref remapArray, - m_ProfilerMarker); + ref remapArray); dependsOn = migrateJob.Schedule(dependsOn); destinationPersistentData.ReleaseWriterAsync(dependsOn); @@ -125,24 +119,19 @@ private struct MigrateJob : IJob private EntityPersistentDataWriter m_CurrentData; private EntityPersistentDataWriter m_DestinationData; [ReadOnly] private NativeArray m_RemapArray; - - private ProfilerMarker m_Marker; public MigrateJob( EntityPersistentDataWriter currentData, EntityPersistentDataWriter destinationData, - ref NativeArray remapArray, - ProfilerMarker marker) + ref NativeArray remapArray) { m_CurrentData = currentData; m_DestinationData = destinationData; m_RemapArray = remapArray; - m_Marker = marker; } public void Execute() { - m_Marker.Begin(); //Can't remove while iterating so we collapse to an array first of our current keys/values NativeKeyValueArrays currentEntries = m_CurrentData.GetKeyValueArrays(Allocator.Temp); @@ -161,12 +150,10 @@ public void Execute() //Get our data and patch it T currentValue = currentEntries.Values[i]; currentValue.PatchEntityReferences(ref m_RemapArray); - - //TODO: Could this be a problem? Is there data already here that wasn't moved? + //Then write the newly remapped data to the new world's lookup m_DestinationData[remappedEntity] = currentValue; } - m_Marker.End(); } } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs index 58021ede..cc9e79ca 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs @@ -63,7 +63,7 @@ public ThreadPersistentDataAccessor Acquire() public override JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem destinationPersistentDataSystem, ref NativeArray remapArray) { - throw new NotSupportedException($"{nameof(ThreadPersistentData)} isn't supported for migration because it is global to the app."); + throw new NotSupportedException($"{nameof(ThreadPersistentData)} isn't supported for migration because it is global to the app and temporary in nature. No migration should be needed."); } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs index b2cfea4d..dcbf5528 100644 --- a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs +++ b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs @@ -122,7 +122,7 @@ protected override void DisposeSelf() public override string ToString() { - return $"{GetType().GetReadableName()}|{m_ID}"; + return $"{GetType().GetReadableName()}|{m_ID}|{m_UniqueMigrationSuffix}"; } private ComponentSystemGroup GetSystemGroup() @@ -439,7 +439,7 @@ internal void AddToMigrationLookup( //Get our TaskSet to populate all the possible ActiveIDs TaskSet.AddToMigrationLookup(path, migrationActiveIDLookup); - //Try and do the same for our system (there can only be one), will gracefully fail if we have. + //Try and do the same for our system (there can only be one), will gracefully fail if we have already done this string systemPath = $"{typeName}-System"; if (migrationTaskSetOwnerIDLookup.TryAdd(systemPath, TaskDriverSystem.ID)) { @@ -462,7 +462,7 @@ private void Debug_EnsureNoDuplicateMigrationData(string path, Dictionary m_EntityProxyDataSourceBulkJobScheduler; @@ -243,6 +242,9 @@ protected sealed override void OnUpdate() public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { + TODO : SYSTEM AND DRIVER PERSISTENT DATA + TODO: CANCEL PROGRESS and CANCEL REQUEST and CANCEL COMPLETE + TaskDriverManagementSystem destinationTaskDriverManagementSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldTaskDriverManagementSystemExists(destinationWorld, destinationTaskDriverManagementSystem); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/AbstractJobConfig.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/AbstractJobConfig.cs index 74f066bb..0b3df370 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/AbstractJobConfig.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/AbstractJobConfig.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Threading.Tasks; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; @@ -42,7 +41,7 @@ internal static readonly BulkScheduleDelegate PREPARE_AND_SCH BindingFlags.Instance | BindingFlags.NonPublic); private static readonly Usage[] USAGE_TYPES = (Usage[])Enum.GetValues(typeof(Usage)); - + private readonly Dictionary m_AccessWrappers; private readonly List m_SchedulingAccessWrappers; private readonly PersistentDataSystem m_PersistentDataSystem; diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs index c5f458d5..a59d866a 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs @@ -20,8 +20,8 @@ protected override JobHandle ConsolidateSelf(JobHandle dependsOn) public override JobHandle MigrateTo(JobHandle dependsOn, IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) { - //DOES NOTHING RIGHT NOW - //TODO: IMPLEMENT + CancelProgressDataSource destination = destinationDataSource as CancelProgressDataSource; + return dependsOn; } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index 83430a54..625ab30f 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -15,11 +15,8 @@ internal class EntityProxyDataSource : AbstractDataSource m_Consolidator; - private readonly ProfilerMarker m_ProfilerMarker; - public EntityProxyDataSource(TaskDriverManagementSystem taskDriverManagementSystem) : base(taskDriverManagementSystem) { - m_ProfilerMarker = new ProfilerMarker(GetType().GetReadableName()); MigrationUtil.RegisterTypeForEntityPatching>(); EntityProxyInstanceWrapper.Debug_EnsureOffsetsAreCorrect(); } @@ -81,8 +78,7 @@ public override JobHandle MigrateTo( destinationWriter, remapArray, destinationWorldDataMap.TaskSetOwnerIDMapping, - destinationWorldDataMap.ActiveIDMapping, - m_ProfilerMarker); + destinationWorldDataMap.ActiveIDMapping); dependsOn = migrateJob.Schedule(dependsOn); PendingData.Release(); @@ -103,29 +99,24 @@ private struct MigrateJob : IJob [ReadOnly] private readonly NativeParallelHashMap m_ActiveIDMapping; [NativeSetThreadIndex] private readonly int m_NativeThreadIndex; - private ProfilerMarker m_Marker; - public MigrateJob( UnsafeTypedStream> currentStream, UnsafeTypedStream>.Writer destinationStreamWriter, NativeArray remapArray, NativeParallelHashMap taskSetOwnerIDMapping, - NativeParallelHashMap activeIDMapping, - ProfilerMarker marker) + NativeParallelHashMap activeIDMapping) { m_CurrentStream = currentStream; m_DestinationStreamWriter = destinationStreamWriter; m_RemapArray = remapArray; m_TaskSetOwnerIDMapping = taskSetOwnerIDMapping; m_ActiveIDMapping = activeIDMapping; - m_Marker = marker; m_NativeThreadIndex = UNSET_ID; } public void Execute() { - m_Marker.Begin(); //Can't modify while iterating so we collapse down to a single array and clean the underlying stream. //We'll build this stream back up if anything should still remain NativeArray> currentInstanceArray = m_CurrentStream.ToNativeArray(Allocator.Temp); @@ -153,7 +144,6 @@ public void Execute() } //If we don't have a destination in the new world, then we can just let these cease to exist - //Check the TaskSetOwnerIDMapping/ActiveIDMapping if (!destinationLaneWriter.IsCreated || !m_TaskSetOwnerIDMapping.TryGetValue(instanceID.TaskSetOwnerID, out uint destinationTaskSetOwnerID) || !m_ActiveIDMapping.TryGetValue(instanceID.ActiveID, out uint destinationActiveID)) @@ -172,7 +162,6 @@ public void Execute() //Write to the destination stream destinationLaneWriter.Write(instance); } - m_Marker.End(); } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs index a07f9ba4..770b5cf3 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs @@ -13,6 +13,7 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver [StructLayout(LayoutKind.Sequential, Size = 16)] internal readonly struct EntityProxyInstanceID : IEquatable { + //NOTE: Be careful messing with these - See Debug_EnsureOffsetsAreCorrect public static readonly int TASK_SET_OWNER_ID_OFFSET = 8; public static readonly int ACTIVE_ID_OFFSET = 12; diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs index 358bdda3..ab474570 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs @@ -14,6 +14,8 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver internal readonly struct EntityProxyInstanceWrapper : IEquatable> where TInstance : unmanaged, IEntityProxyInstance { + //NOTE: Be careful messing with this - See Debug_EnsureOffsetsAreCorrect + // ReSharper disable once StaticMemberInGenericType public static readonly int INSTANCE_ID_OFFSET = 0; public static bool operator ==(EntityProxyInstanceWrapper lhs, EntityProxyInstanceWrapper rhs) diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs index 0cdc1b35..2e8208e6 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs @@ -10,9 +10,12 @@ public static unsafe void PatchIDs( uint activeID) where TInstance : unmanaged, IEntityProxyInstance { + //Get the address of where the EntityProxyInstanceID is in the wrapper byte* ptr = (byte*)UnsafeUtility.AddressOf(ref instanceWrapper) + EntityProxyInstanceWrapper.INSTANCE_ID_OFFSET; + //Get the address for the TaskSetOwnerID and set it uint* taskSetOwnerIDPtr = (uint*)(ptr + EntityProxyInstanceID.TASK_SET_OWNER_ID_OFFSET); *taskSetOwnerIDPtr = taskSetOwnerID; + //Get the address for the ActiveID and set it uint* activeIDPtr = (uint*)(ptr + EntityProxyInstanceID.ACTIVE_ID_OFFSET); *activeIDPtr = activeID; } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index 73435d3b..9358e331 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -363,7 +363,10 @@ private void Debug_EnsureNoDuplicateMigrationData(string path, Dictionary Date: Fri, 28 Apr 2023 15:05:41 -0400 Subject: [PATCH 08/16] Adding support for Cancel migration --- .../Migration/WorldEntityMigrationSystem.cs | 4 +- .../PersistentData/PersistentDataSystem.cs | 2 +- .../Migration/TaskDriverMigrationData.cs | 38 ++++- .../TaskDriver/TaskDriverManagementSystem.cs | 37 +---- ...AbstractEntityProxyInstanceIDDataSource.cs | 138 ++++++++++++++++ ...actEntityProxyInstanceIDDataSource.cs.meta | 3 + .../DataSource/CancelProgressDataSource.cs | 156 +++++++++++++++++- .../DataSource/CancelRequestsDataSource.cs | 15 +- .../DataSource/EntityProxyDataSource.cs | 16 +- .../DataSource/ICancellableDataStream.cs | 11 ++ .../DataSource/ICancellableDataStream.cs.meta | 3 + .../DataStream/EntityProxyDataStream.cs | 21 ++- .../EntityProxyInstanceID.cs | 4 +- .../EntityProxyInstanceWrapper.cs | 2 +- .../EntityProxyInstanceWrapperExtension.cs | 15 ++ .../Entities/TaskDriver/TaskSet/TaskSet.cs | 38 ++++- 16 files changed, 434 insertions(+), 69 deletions(-) create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs.meta create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/ICancellableDataStream.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/ICancellableDataStream.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs index 38ef651b..e55fd80c 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs @@ -38,7 +38,7 @@ protected override void OnDestroy() public void AddMigrationObserver(IMigrationObserver migrationObserver) { m_MigrationObservers.Add(migrationObserver); - m_Dependencies_ScratchPad.Resize(m_MigrationObservers.Count, NativeArrayOptions.UninitializedMemory); + m_Dependencies_ScratchPad.ResizeUninitialized(m_MigrationObservers.Count); } /// @@ -48,7 +48,7 @@ public void AddMigrationObserver(IMigrationObserver migrationObserver) public void RemoveMigrationObserver(IMigrationObserver migrationObserver) { m_MigrationObservers.Remove(migrationObserver); - m_Dependencies_ScratchPad.Resize(m_MigrationObservers.Count, NativeArrayOptions.UninitializedMemory); + m_Dependencies_ScratchPad.ResizeUninitialized(m_MigrationObservers.Count); } /// diff --git a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs index 16846fa8..715b9019 100644 --- a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs +++ b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs @@ -65,7 +65,7 @@ public EntityPersistentData GetOrCreateEntityPersistentData() { persistentData = new EntityPersistentData(); m_EntityPersistentData.Add(type, persistentData); - m_MigrationDependencies_ScratchPad.Resize(m_EntityPersistentData.Count, NativeArrayOptions.UninitializedMemory); + m_MigrationDependencies_ScratchPad.ResizeUninitialized(m_EntityPersistentData.Count); } return (EntityPersistentData)persistentData; diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs index ca5963cc..1859bd02 100644 --- a/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs @@ -1,8 +1,10 @@ using Anvil.CSharp.Collections; using Anvil.CSharp.Core; +using System; using System.Collections.Generic; using Unity.Collections; using Unity.Entities; +using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities.TaskDriver { @@ -11,6 +13,9 @@ internal class TaskDriverMigrationData : AbstractAnvilBase private readonly Dictionary m_MigrationTaskSetOwnerIDLookup; private readonly Dictionary m_MigrationActiveIDLookup; private readonly Dictionary m_DestinationWorldDataMaps; + private readonly Dictionary m_AllDataSources; + // ReSharper disable once InconsistentNaming + private NativeList m_MigrationDependencies_ScratchPad; public int TaskSetOwnerCount { @@ -27,13 +32,24 @@ public TaskDriverMigrationData() m_MigrationTaskSetOwnerIDLookup = new Dictionary(); m_MigrationActiveIDLookup = new Dictionary(); m_DestinationWorldDataMaps = new Dictionary(); + m_AllDataSources = new Dictionary(); + m_MigrationDependencies_ScratchPad = new NativeList(32, Allocator.Persistent); } protected override void DisposeSelf() { m_DestinationWorldDataMaps.DisposeAllValuesAndClear(); + m_MigrationDependencies_ScratchPad.Dispose(); base.DisposeSelf(); } + + public void AddDataSource(T dataSource) + where T : class, IDataSource + { + Type type = dataSource.GetType(); + m_AllDataSources.Add(type, dataSource); + m_MigrationDependencies_ScratchPad.ResizeUninitialized(m_AllDataSources.Count); + } public void PopulateMigrationLookup(List topLevelTaskDrivers) { @@ -47,7 +63,7 @@ public void PopulateMigrationLookup(List topLevelTaskDrivers } } - public DestinationWorldDataMap GetOrCreateDestinationWorldDataMapFor(World destinationWorld, TaskDriverMigrationData destinationMigrationData) + private DestinationWorldDataMap GetOrCreateDestinationWorldDataMapFor(World destinationWorld, TaskDriverMigrationData destinationMigrationData) { if (!m_DestinationWorldDataMaps.TryGetValue(destinationWorld, out DestinationWorldDataMap destinationWorldDataMap)) { @@ -79,5 +95,25 @@ public DestinationWorldDataMap GetOrCreateDestinationWorldDataMapFor(World desti } return destinationWorldDataMap; } + + public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, TaskDriverMigrationData destinationTaskDriverMigrationData, ref NativeArray remapArray) + { + //Lazy create a World to World mapping lookup for ActiveIDs and TaskSetOwnerIDs + DestinationWorldDataMap destinationWorldDataMap = GetOrCreateDestinationWorldDataMapFor(destinationWorld, destinationTaskDriverMigrationData); + + Dictionary destinationDataSourcesByType = destinationTaskDriverMigrationData.m_AllDataSources; + + int index = 0; + foreach (KeyValuePair entry in m_AllDataSources) + { + //We may not have a corresponding destination Data Source in the destination world but we still want to process the migration so that + //we remove any references in this world. If we do have the corresponding data source, we'll transfer over to the other world. + destinationDataSourcesByType.TryGetValue(entry.Key, out IDataSource destinationDataSource); + m_MigrationDependencies_ScratchPad[index] = entry.Value.MigrateTo(dependsOn, destinationDataSource, ref remapArray, destinationWorldDataMap); + index++; + } + + return JobHandle.CombineDependencies(m_MigrationDependencies_ScratchPad); + } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 33988465..4c151c92 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -33,9 +33,6 @@ internal partial class TaskDriverManagementSystem : AbstractAnvilSystemBase, private bool m_IsHardened; private BulkJobScheduler m_EntityProxyDataSourceBulkJobScheduler; private BulkJobScheduler m_CancelProgressFlowBulkJobScheduler; - - // ReSharper disable once InconsistentNaming - private NativeArray m_MigrationDependencies_ScratchPad; private readonly IDProvider m_IDProvider; @@ -54,6 +51,9 @@ public TaskDriverManagementSystem() m_TaskDriverMigrationData = new TaskDriverMigrationData(); EntityProxyInstanceID.Debug_EnsureOffsetsAreCorrect(); + m_TaskDriverMigrationData.AddDataSource(m_CancelRequestsDataSource); + m_TaskDriverMigrationData.AddDataSource(m_CancelProgressDataSource); + m_TaskDriverMigrationData.AddDataSource(m_CancelCompleteDataSource); } protected override void OnCreate() @@ -79,10 +79,7 @@ protected override void OnStartRunning() protected sealed override void OnDestroy() { - if (m_MigrationDependencies_ScratchPad.IsCreated) - { - m_MigrationDependencies_ScratchPad.Dispose(); - } + m_EntityProxyDataSourcesByType.DisposeAllValuesAndClear(); m_EntityProxyDataSourceBulkJobScheduler?.Dispose(); @@ -116,8 +113,7 @@ private void Harden() } m_EntityProxyDataSourceBulkJobScheduler = new BulkJobScheduler(m_EntityProxyDataSourcesByType.Values.ToArray()); - m_MigrationDependencies_ScratchPad = new NativeArray(m_EntityProxyDataSourcesByType.Count, Allocator.Persistent); - + //For all the TaskDrivers, filter to find the ones that don't have Parents. //Those are our top level TaskDrivers m_TopLevelTaskDrivers.AddRange(m_AllTaskDrivers.Where(taskDriver => taskDriver.Parent == null)); @@ -158,6 +154,7 @@ public EntityProxyDataSource GetOrCreateEntityProxyDataSource(this); m_EntityProxyDataSourcesByType.Add(type, dataSource); + m_TaskDriverMigrationData.AddDataSource(dataSource); } return (EntityProxyDataSource)dataSource; @@ -242,28 +239,12 @@ protected sealed override void OnUpdate() public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { - TODO : SYSTEM AND DRIVER PERSISTENT DATA - TODO: CANCEL PROGRESS and CANCEL REQUEST and CANCEL COMPLETE - + // TODO : SYSTEM AND DRIVER PERSISTENT DATA + TaskDriverManagementSystem destinationTaskDriverManagementSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldTaskDriverManagementSystemExists(destinationWorld, destinationTaskDriverManagementSystem); - - //Lazy create a World to World mapping lookup for ActiveIDs and TaskSetOwnerIDs - DestinationWorldDataMap destinationWorldDataMap = m_TaskDriverMigrationData.GetOrCreateDestinationWorldDataMapFor(destinationWorld, destinationTaskDriverManagementSystem.m_TaskDriverMigrationData); - - Dictionary destinationEntityProxyDataSourcesByType = destinationTaskDriverManagementSystem.m_EntityProxyDataSourcesByType; - - int index = 0; - foreach (KeyValuePair entry in m_EntityProxyDataSourcesByType) - { - //We may not have a corresponding destination Data Source in the destination world but we still want to process the migration so that - //we remove any references in this world. If we do have the corresponding data source, we'll transfer over to the other world. - destinationEntityProxyDataSourcesByType.TryGetValue(entry.Key, out IDataSource destinationDataSource); - m_MigrationDependencies_ScratchPad[index] = entry.Value.MigrateTo(dependsOn, destinationDataSource, ref remapArray, destinationWorldDataMap); - index++; - } - return JobHandle.CombineDependencies(m_MigrationDependencies_ScratchPad); + return m_TaskDriverMigrationData.MigrateTo(dependsOn, destinationWorld, destinationTaskDriverManagementSystem.m_TaskDriverMigrationData, ref remapArray); } //************************************************************************************************************* diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs new file mode 100644 index 00000000..450175a3 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs @@ -0,0 +1,138 @@ +using Anvil.Unity.DOTS.Data; +using Anvil.Unity.DOTS.Jobs; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Jobs; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + internal abstract class AbstractEntityProxyInstanceIDDataSource : AbstractDataSource + { + protected AbstractEntityProxyInstanceIDDataSource(TaskDriverManagementSystem taskDriverManagementSystem) : base(taskDriverManagementSystem) + { + } + + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + public override JobHandle MigrateTo( + JobHandle dependsOn, + IDataSource destinationDataSource, + ref NativeArray remapArray, + DestinationWorldDataMap destinationWorldDataMap) + { + CancelRequestsDataSource destination = destinationDataSource as CancelRequestsDataSource; + UnsafeTypedStream.Writer destinationWriter = default; + + if (destination == null) + { + dependsOn = JobHandle.CombineDependencies( + dependsOn, + PendingData.AcquireAsync(AccessType.ExclusiveWrite)); + } + else + { + dependsOn = JobHandle.CombineDependencies( + dependsOn, + PendingData.AcquireAsync(AccessType.ExclusiveWrite), + destination.PendingData.AcquireAsync(AccessType.ExclusiveWrite)); + + destinationWriter = destination.PendingWriter; + } + + + MigrateJob migrateJob = new MigrateJob( + PendingData.Pending, + destinationWriter, + remapArray, + destinationWorldDataMap.TaskSetOwnerIDMapping, + destinationWorldDataMap.ActiveIDMapping); + dependsOn = migrateJob.Schedule(dependsOn); + + PendingData.ReleaseAsync(dependsOn); + destination?.PendingData.ReleaseAsync(dependsOn); + + return dependsOn; + } + + [BurstCompile] + private struct MigrateJob : IJob + { + private const int UNSET_ID = -1; + + private UnsafeTypedStream m_CurrentStream; + private readonly UnsafeTypedStream.Writer m_DestinationStreamWriter; + [ReadOnly] private NativeArray m_RemapArray; + [ReadOnly] private readonly NativeParallelHashMap m_TaskSetOwnerIDMapping; + [ReadOnly] private readonly NativeParallelHashMap m_ActiveIDMapping; + [NativeSetThreadIndex] private readonly int m_NativeThreadIndex; + + public MigrateJob( + UnsafeTypedStream currentStream, + UnsafeTypedStream.Writer destinationStreamWriter, + NativeArray remapArray, + NativeParallelHashMap taskSetOwnerIDMapping, + NativeParallelHashMap activeIDMapping) + { + m_CurrentStream = currentStream; + m_DestinationStreamWriter = destinationStreamWriter; + m_RemapArray = remapArray; + m_TaskSetOwnerIDMapping = taskSetOwnerIDMapping; + m_ActiveIDMapping = activeIDMapping; + + m_NativeThreadIndex = UNSET_ID; + } + + public void Execute() + { + //Can't modify while iterating so we collapse down to a single array and clean the underlying stream. + //We'll build this stream back up if anything should still remain + NativeArray currentInstanceArray = m_CurrentStream.ToNativeArray(Allocator.Temp); + m_CurrentStream.Clear(); + + int laneIndex = ParallelAccessUtil.CollectionIndexForThread(m_NativeThreadIndex); + + UnsafeTypedStream.LaneWriter currentLaneWriter = m_CurrentStream.AsLaneWriter(laneIndex); + UnsafeTypedStream.LaneWriter destinationLaneWriter = + m_DestinationStreamWriter.IsCreated + ? m_DestinationStreamWriter.AsLaneWriter(laneIndex) + : default; + + for (int i = 0; i < currentInstanceArray.Length; ++i) + { + EntityProxyInstanceID instanceID = currentInstanceArray[i]; + + //If we don't exist in the new world then we stayed in this world and we need to rewrite ourselves + //to our own stream + if (!instanceID.Entity.IfEntityIsRemapped(ref m_RemapArray, out Entity remappedEntity)) + { + currentLaneWriter.Write(ref instanceID); + continue; + } + + //If we don't have a destination in the new world, then we can just let these cease to exist + if (!destinationLaneWriter.IsCreated + || !m_TaskSetOwnerIDMapping.TryGetValue(instanceID.TaskSetOwnerID, out uint destinationTaskSetOwnerID) + || !m_ActiveIDMapping.TryGetValue(instanceID.ActiveID, out uint destinationActiveID)) + { + continue; + } + + //If we do have a destination, then we will want to patch the entity references + instanceID.PatchEntityReferences(ref m_RemapArray); + + //Rewrite the memory for the TaskSetOwnerID and ActiveID + instanceID.PatchIDs( + destinationTaskSetOwnerID, + destinationActiveID); + + //Write to the destination stream + destinationLaneWriter.Write(instanceID); + } + } + } + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs.meta new file mode 100644 index 00000000..5d4abfc5 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b05cb314a49b4946a34dea034a4c7ea9 +timeCreated: 1682706186 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs index a59d866a..eb1fe390 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs @@ -1,28 +1,176 @@ +using Anvil.Unity.DOTS.Jobs; using System; +using System.Collections.Generic; +using Unity.Burst; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities.TaskDriver { - internal class CancelProgressDataSource : AbstractDataSource + internal class CancelProgressDataSource : AbstractEntityProxyInstanceIDDataSource { + + // ReSharper disable once InconsistentNaming + private NativeArray m_MigrationDependencies_ScratchPad; + public CancelProgressDataSource(TaskDriverManagementSystem taskDriverManagementSystem) : base(taskDriverManagementSystem) { } + protected override void DisposeSelf() + { + if (m_MigrationDependencies_ScratchPad.IsCreated) + { + m_MigrationDependencies_ScratchPad.Dispose(); + } + base.DisposeSelf(); + } + + protected override void HardenSelf() + { + base.HardenSelf(); + + //One extra for base dependency + m_MigrationDependencies_ScratchPad = new NativeArray(ActiveDataLookupByID.Count + 1, Allocator.Persistent); + } + protected override JobHandle ConsolidateSelf(JobHandle dependsOn) { throw new InvalidOperationException($"CancelProgress Data Never needs to be consolidated"); } - + //************************************************************************************************************* // MIGRATION //************************************************************************************************************* - public override JobHandle MigrateTo(JobHandle dependsOn, IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) + public override JobHandle MigrateTo( + JobHandle dependsOn, + IDataSource destinationDataSource, + ref NativeArray remapArray, + DestinationWorldDataMap destinationWorldDataMap) + { + int index = 0; + foreach (KeyValuePair entry in ActiveDataLookupByID) + { + ActiveLookupData activeLookupData = (ActiveLookupData)entry.Value; + + m_MigrationDependencies_ScratchPad[index] = MigrateTo( + dependsOn, + activeLookupData, + destinationDataSource, + ref remapArray, + destinationWorldDataMap); + index++; + } + m_MigrationDependencies_ScratchPad[index] = base.MigrateTo( + dependsOn, + destinationDataSource, + ref remapArray, + destinationWorldDataMap); + + return JobHandle.CombineDependencies(m_MigrationDependencies_ScratchPad); + } + + private JobHandle MigrateTo( + JobHandle dependsOn, + ActiveLookupData currentLookupData, + IDataSource destinationDataSource, + ref NativeArray remapArray, + DestinationWorldDataMap destinationWorldDataMap) { - CancelProgressDataSource destination = destinationDataSource as CancelProgressDataSource; + ActiveLookupData destinationLookupData = null; + + //If we don't have a destination or a mapping to a destination active ID... + if (destinationDataSource is not CancelProgressDataSource destination + || !destinationWorldDataMap.ActiveIDMapping.TryGetValue( + currentLookupData.ID, + out uint destinationActiveID)) + { + //Then we can only deal with ourselves + dependsOn = JobHandle.CombineDependencies( + dependsOn, + currentLookupData.AcquireAsync(AccessType.ExclusiveWrite)); + } + else + { + destinationLookupData = (ActiveLookupData)destination.ActiveDataLookupByID[destinationActiveID]; + + dependsOn = JobHandle.CombineDependencies( + dependsOn, + currentLookupData.AcquireAsync(AccessType.ExclusiveWrite), + destinationLookupData.AcquireAsync(AccessType.ExclusiveWrite)); + } + + MigrateJob migrateJob = new MigrateJob( + currentLookupData.Lookup, + destinationLookupData?.Lookup ?? default, + ref remapArray, + destinationWorldDataMap.TaskSetOwnerIDMapping, + destinationWorldDataMap.ActiveIDMapping); + + dependsOn = migrateJob.Schedule(dependsOn); + + currentLookupData.ReleaseAsync(dependsOn); + destinationLookupData?.ReleaseAsync(dependsOn); return dependsOn; } + + [BurstCompile] + private struct MigrateJob : IJob + { + private UnsafeParallelHashMap m_CurrentLookup; + private UnsafeParallelHashMap m_DestinationLookup; + [ReadOnly] private NativeArray m_RemapArray; + [ReadOnly] private readonly NativeParallelHashMap m_TaskSetOwnerIDMapping; + [ReadOnly] private readonly NativeParallelHashMap m_ActiveIDMapping; + + public MigrateJob( + UnsafeParallelHashMap currentLookup, + UnsafeParallelHashMap destinationLookup, + ref NativeArray remapArray, + NativeParallelHashMap taskSetOwnerIDMapping, + NativeParallelHashMap activeIDMapping) + { + m_CurrentLookup = currentLookup; + m_DestinationLookup = destinationLookup; + m_RemapArray = remapArray; + m_TaskSetOwnerIDMapping = taskSetOwnerIDMapping; + m_ActiveIDMapping = activeIDMapping; + } + + public void Execute() + { + //Can't remove while iterating so we collapse to an array first of our current keys/values + NativeKeyValueArrays currentEntries = m_CurrentLookup.GetKeyValueArrays(Allocator.Temp); + + for (int i = 0; i < currentEntries.Length; ++i) + { + EntityProxyInstanceID currentID = currentEntries.Keys[i]; + //If we don't exist in the new world, we can just skip, we stayed in this world + if (!currentID.Entity.IfEntityIsRemapped(ref m_RemapArray, out Entity remappedEntity)) + { + continue; + } + + //Otherwise, remove us from this world's lookup + m_CurrentLookup.Remove(currentID); + + //If we don't have a destination in the new world, then we can just let these cease to exist + if (!m_TaskSetOwnerIDMapping.TryGetValue(currentID.TaskSetOwnerID, out uint destinationTaskSetOwnerID) + || !m_ActiveIDMapping.TryGetValue(currentID.ActiveID, out uint destinationActiveID)) + { + continue; + } + + //Patch our ID with new values + currentID.PatchEntityReferences(ref m_RemapArray); + currentID.PatchIDs(destinationTaskSetOwnerID, destinationActiveID); + + //Write to the destination lookup + m_DestinationLookup.Add(currentID, currentEntries.Values[i]); + } + } + } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs index b440af46..9d4e4711 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelRequestsDataSource.cs @@ -1,12 +1,10 @@ using Anvil.Unity.DOTS.Jobs; using Unity.Burst; -using Unity.Collections; -using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities.TaskDriver { - internal class CancelRequestsDataSource : AbstractDataSource + internal class CancelRequestsDataSource : AbstractEntityProxyInstanceIDDataSource { private CancelRequestsDataSourceConsolidator m_Consolidator; @@ -42,17 +40,6 @@ protected override JobHandle ConsolidateSelf(JobHandle dependsOn) return dependsOn; } - //************************************************************************************************************* - // MIGRATION - //************************************************************************************************************* - - public override JobHandle MigrateTo(JobHandle dependsOn, IDataSource destinationDataSource, ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) - { - //DOES NOTHING RIGHT NOW - //TODO: IMPLEMENT - return dependsOn; - } - //************************************************************************************************************* // JOBS //************************************************************************************************************* diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index 625ab30f..2ebcd5e5 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -1,4 +1,3 @@ -using Anvil.CSharp.Logging; using Anvil.Unity.DOTS.Data; using Anvil.Unity.DOTS.Jobs; using Unity.Burst; @@ -6,7 +5,6 @@ using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; -using Unity.Profiling; namespace Anvil.Unity.DOTS.Entities.TaskDriver { @@ -76,13 +74,13 @@ public override JobHandle MigrateTo( MigrateJob migrateJob = new MigrateJob( PendingData.Pending, destinationWriter, - remapArray, + ref remapArray, destinationWorldDataMap.TaskSetOwnerIDMapping, destinationWorldDataMap.ActiveIDMapping); dependsOn = migrateJob.Schedule(dependsOn); - PendingData.Release(); - destination?.PendingData.Release(); + PendingData.ReleaseAsync(dependsOn); + destination?.PendingData.ReleaseAsync(dependsOn); return dependsOn; } @@ -102,7 +100,7 @@ private struct MigrateJob : IJob public MigrateJob( UnsafeTypedStream> currentStream, UnsafeTypedStream>.Writer destinationStreamWriter, - NativeArray remapArray, + ref NativeArray remapArray, NativeParallelHashMap taskSetOwnerIDMapping, NativeParallelHashMap activeIDMapping) { @@ -125,9 +123,9 @@ public void Execute() int laneIndex = ParallelAccessUtil.CollectionIndexForThread(m_NativeThreadIndex); UnsafeTypedStream>.LaneWriter currentLaneWriter = m_CurrentStream.AsLaneWriter(laneIndex); - UnsafeTypedStream>.LaneWriter destinationLaneWriter = - m_DestinationStreamWriter.IsCreated - ? m_DestinationStreamWriter.AsLaneWriter(laneIndex) + UnsafeTypedStream>.LaneWriter destinationLaneWriter = + m_DestinationStreamWriter.IsCreated + ? m_DestinationStreamWriter.AsLaneWriter(laneIndex) : default; for (int i = 0; i < currentInstanceArray.Length; ++i) diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/ICancellableDataStream.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/ICancellableDataStream.cs new file mode 100644 index 00000000..60610866 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/ICancellableDataStream.cs @@ -0,0 +1,11 @@ +using System; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + internal interface ICancellableDataStream + { + public uint PendingCancelActiveID { get; } + + public Type InstanceType { get; } + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/ICancellableDataStream.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/ICancellableDataStream.cs.meta new file mode 100644 index 00000000..5cd19245 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/ICancellableDataStream.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ee4f9b67da8f41dcaefff7e66e8f5122 +timeCreated: 1682703596 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs index 88676971..b393b50c 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/EntityProxyDataStream.cs @@ -1,5 +1,6 @@ using Anvil.Unity.DOTS.Data; using Anvil.Unity.DOTS.Jobs; +using System; using System.Runtime.CompilerServices; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; @@ -7,7 +8,10 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver { //TODO: #137 - Too much complexity that is not needed - internal class EntityProxyDataStream : AbstractDataStream, IDriverDataStream, ISystemDataStream + internal class EntityProxyDataStream : AbstractDataStream, + IDriverDataStream, + ISystemDataStream, + ICancellableDataStream where TInstance : unmanaged, IEntityProxyInstance { public static readonly int MAX_ELEMENTS_PER_CHUNK = ChunkUtil.MaxElementsPerChunk>(); @@ -24,6 +28,13 @@ public override uint ActiveID get => m_ActiveArrayData.ID; } + public uint PendingCancelActiveID + { + get => m_PendingCancelActiveArrayData.ID; + } + + public Type InstanceType { get; } + //TODO: #136 - Not good to expose these just for the CancelComplete case. public UnsafeTypedStream>.Writer PendingWriter { get; } public PendingData> PendingData { get; } @@ -44,6 +55,8 @@ public EntityProxyDataStream(ITaskSetOwner taskSetOwner, CancelRequestBehaviour } ScheduleInfo = m_ActiveArrayData.ScheduleInfo; + + InstanceType = typeof(TInstance); } public EntityProxyDataStream(AbstractTaskDriver taskDriver, EntityProxyDataStream systemDataStream) @@ -58,6 +71,8 @@ public EntityProxyDataStream(AbstractTaskDriver taskDriver, EntityProxyDataStrea //TODO: #136 - Not good to expose these just for the CancelComplete case. PendingData = systemDataStream.PendingData; PendingWriter = systemDataStream.PendingWriter; + + InstanceType = typeof(TInstance); } //TODO: #137 - Gross!!! This is a special case only for CancelComplete @@ -71,6 +86,8 @@ protected EntityProxyDataStream(ITaskSetOwner taskSetOwner) : base(taskSetOwner) //TODO: #136 - Not good to expose these just for the CancelComplete case. PendingWriter = m_DataSource.PendingWriter; PendingData = m_DataSource.PendingData; + + InstanceType = typeof(TInstance); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -221,4 +238,4 @@ public void ReleasePendingWriter() ReleasePending(); } } -} \ No newline at end of file +} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs index 770b5cf3..9fb9f49c 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceID.cs @@ -14,8 +14,8 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver internal readonly struct EntityProxyInstanceID : IEquatable { //NOTE: Be careful messing with these - See Debug_EnsureOffsetsAreCorrect - public static readonly int TASK_SET_OWNER_ID_OFFSET = 8; - public static readonly int ACTIVE_ID_OFFSET = 12; + public const int TASK_SET_OWNER_ID_OFFSET = 8; + public const int ACTIVE_ID_OFFSET = 12; public static bool operator ==(EntityProxyInstanceID lhs, EntityProxyInstanceID rhs) { diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs index ab474570..8c859d54 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapper.cs @@ -16,7 +16,7 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver { //NOTE: Be careful messing with this - See Debug_EnsureOffsetsAreCorrect // ReSharper disable once StaticMemberInGenericType - public static readonly int INSTANCE_ID_OFFSET = 0; + public const int INSTANCE_ID_OFFSET = 0; public static bool operator ==(EntityProxyInstanceWrapper lhs, EntityProxyInstanceWrapper rhs) { diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs index 2e8208e6..ecda988b 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/EntityProxyInstance/EntityProxyInstanceWrapperExtension.cs @@ -19,5 +19,20 @@ public static unsafe void PatchIDs( uint* activeIDPtr = (uint*)(ptr + EntityProxyInstanceID.ACTIVE_ID_OFFSET); *activeIDPtr = activeID; } + + public static unsafe void PatchIDs( + this ref EntityProxyInstanceID instanceID, + uint taskSetOwnerID, + uint activeID) + { + //Get the address of the instance ID + byte* ptr = (byte*)UnsafeUtility.AddressOf(ref instanceID); + //Get the address for the TaskSetOwnerID and set it + uint* taskSetOwnerIDPtr = (uint*)(ptr + EntityProxyInstanceID.TASK_SET_OWNER_ID_OFFSET); + *taskSetOwnerIDPtr = taskSetOwnerID; + //Get the address for the ActiveID and set it + uint* activeIDPtr = (uint*)(ptr + EntityProxyInstanceID.ACTIVE_ID_OFFSET); + *activeIDPtr = activeID; + } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index 9358e331..970ef425 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -15,7 +15,7 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver //TODO: #138 - Maybe we should have DriverTaskSet vs SystemTaskSet that extend AbstractTaskSet internal class TaskSet : AbstractAnvilBase { - private readonly List m_DataStreamsWithExplicitCancellation; + private readonly List m_DataStreamsWithExplicitCancellation; private readonly Dictionary m_PublicDataStreamsByType; private readonly Dictionary m_EntityPersistentDataByType; @@ -44,7 +44,7 @@ public TaskSet(ITaskSetOwner taskSetOwner) m_JobConfigs = new List(); m_JobConfigSchedulingDelegates = new HashSet(); - m_DataStreamsWithExplicitCancellation = new List(); + m_DataStreamsWithExplicitCancellation = new List(); m_PublicDataStreamsByType = new Dictionary(); m_EntityPersistentDataByType = new Dictionary(); @@ -341,10 +341,38 @@ public void AddToMigrationLookup(string parentPath, Dictionary mig { foreach (KeyValuePair entry in m_PublicDataStreamsByType) { - string path = $"{parentPath}-{entry.Key.GetReadableName()}"; - Debug_EnsureNoDuplicateMigrationData(path, migrationActiveIDLookup); - migrationActiveIDLookup.Add(path, entry.Value.ActiveID); + AddToMigrationLookup(parentPath, entry.Key.GetReadableName(), entry.Value.ActiveID, migrationActiveIDLookup); } + + foreach (ICancellableDataStream entry in m_DataStreamsWithExplicitCancellation) + { + AddToMigrationLookup(parentPath, $"{entry.InstanceType.GetReadableName()}-ExplicitCancel", entry.PendingCancelActiveID, migrationActiveIDLookup); + } + + AddToMigrationLookup( + parentPath, + typeof(CancelRequestsDataStream).GetReadableName(), + CancelRequestsDataStream.ActiveID, + migrationActiveIDLookup); + + AddToMigrationLookup( + parentPath, + typeof(CancelProgressDataStream).GetReadableName(), + CancelProgressDataStream.ActiveID, + migrationActiveIDLookup); + + AddToMigrationLookup( + parentPath, + typeof(CancelCompleteDataStream).GetReadableName(), + CancelCompleteDataStream.ActiveID, + migrationActiveIDLookup); + } + + private void AddToMigrationLookup(string parentPath, string name, uint activeID, Dictionary migrationActiveIDLookup) + { + string path = $"{parentPath}-{name}"; + Debug_EnsureNoDuplicateMigrationData(path, migrationActiveIDLookup); + migrationActiveIDLookup.Add(path, activeID); } //************************************************************************************************************* From 9a72efa518f20bc76e98b4ca77dafcb8544d5b5a Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Fri, 28 Apr 2023 16:30:02 -0400 Subject: [PATCH 09/16] Ensuring we get all Persistent Data --- .../Data/AbstractPersistentData.cs | 2 +- .../Data/EntityPersistentData.cs | 11 +++---- .../Data/ThreadPersistentData.cs | 2 +- .../PersistentData/PersistentDataSystem.cs | 31 +++++++++++++++++-- .../Entities/TaskDriver/AbstractTaskDriver.cs | 9 +++--- .../Migration/DestinationWorldDataMap.cs | 1 + .../Migration/TaskDriverMigrationData.cs | 6 ++-- .../TaskDriver/TaskDriverManagementSystem.cs | 11 +++---- .../Entities/TaskDriver/TaskSet/TaskSet.cs | 15 +++++++-- 9 files changed, 62 insertions(+), 26 deletions(-) diff --git a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs index 5c52b570..371f14e7 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs @@ -53,6 +53,6 @@ public void Release() //************************************************************************************************************* // MIGRATION //************************************************************************************************************* - public abstract JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem destinationPersistentDataSystem, ref NativeArray remapArray); + public abstract JobHandle MigrateTo(JobHandle dependsOn, AbstractPersistentData destinationPersistentData, ref NativeArray remapArray); } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs index 503f68d1..e97dd88e 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs @@ -91,15 +91,14 @@ public void ReleaseWriter() // MIGRATION //************************************************************************************************************* - public override JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem destinationPersistentDataSystem, ref NativeArray remapArray) + public override JobHandle MigrateTo(JobHandle dependsOn, AbstractPersistentData destinationPersistentData, ref NativeArray remapArray) { - //This ensures there is a target on the other world to go to - EntityPersistentData destinationPersistentData = destinationPersistentDataSystem.GetOrCreateEntityPersistentData(); - + EntityPersistentData destinationEntityPersistentData = (EntityPersistentData)destinationPersistentData; + //Launch the migration job to get that burst speed dependsOn = JobHandle.CombineDependencies(dependsOn, AcquireWriterAsync(out EntityPersistentDataWriter currentData), - destinationPersistentData.AcquireWriterAsync(out EntityPersistentDataWriter destinationData)); + destinationEntityPersistentData.AcquireWriterAsync(out EntityPersistentDataWriter destinationData)); MigrateJob migrateJob = new MigrateJob( currentData, @@ -107,7 +106,7 @@ public override JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem de ref remapArray); dependsOn = migrateJob.Schedule(dependsOn); - destinationPersistentData.ReleaseWriterAsync(dependsOn); + destinationEntityPersistentData.ReleaseWriterAsync(dependsOn); ReleaseWriterAsync(dependsOn); return dependsOn; diff --git a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs index cc9e79ca..153cb870 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs @@ -61,7 +61,7 @@ public ThreadPersistentDataAccessor Acquire() // MIGRATION //************************************************************************************************************* - public override JobHandle MigrateTo(JobHandle dependsOn, PersistentDataSystem destinationPersistentDataSystem, ref NativeArray remapArray) + public override JobHandle MigrateTo(JobHandle dependsOn, AbstractPersistentData destinationPersistentData, ref NativeArray remapArray) { throw new NotSupportedException($"{nameof(ThreadPersistentData)} isn't supported for migration because it is global to the app and temporary in nature. No migration should be needed."); } diff --git a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs index 715b9019..ca4376d5 100644 --- a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs +++ b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs @@ -1,4 +1,5 @@ using Anvil.CSharp.Collections; +using Anvil.CSharp.Logging; using System; using System.Collections.Generic; using System.Diagnostics; @@ -11,11 +12,13 @@ namespace Anvil.Unity.DOTS.Entities internal partial class PersistentDataSystem : AbstractDataSystem, IMigrationObserver { + private const string WORLD_PATH = "World"; private static readonly Dictionary s_ThreadPersistentData = new Dictionary(); private static int s_InstanceCount; private readonly Dictionary m_EntityPersistentData; + private readonly Dictionary m_MigrationPersistentDataLookup; // ReSharper disable once InconsistentNaming private NativeList m_MigrationDependencies_ScratchPad; @@ -24,6 +27,7 @@ public PersistentDataSystem() s_InstanceCount++; m_EntityPersistentData = new Dictionary(); m_MigrationDependencies_ScratchPad = new NativeList(8, Allocator.Persistent); + m_MigrationPersistentDataLookup = new Dictionary(); } protected override void OnCreate() @@ -65,7 +69,7 @@ public EntityPersistentData GetOrCreateEntityPersistentData() { persistentData = new EntityPersistentData(); m_EntityPersistentData.Add(type, persistentData); - m_MigrationDependencies_ScratchPad.ResizeUninitialized(m_EntityPersistentData.Count); + AddToMigrationLookup(WORLD_PATH, persistentData); } return (EntityPersistentData)persistentData; @@ -81,14 +85,26 @@ public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref Nati Debug_EnsureOtherWorldPersistentDataSystemExists(destinationWorld, destinationPersistentDataSystem); int index = 0; - foreach (KeyValuePair entry in m_EntityPersistentData) + foreach (KeyValuePair entry in m_MigrationPersistentDataLookup) { - m_MigrationDependencies_ScratchPad[index] = entry.Value.MigrateTo(dependsOn, destinationPersistentDataSystem, ref remapArray); + if (!destinationPersistentDataSystem.m_MigrationPersistentDataLookup.TryGetValue(entry.Key, out AbstractPersistentData destinationPersistentData)) + { + throw new InvalidOperationException($"Current World {World} has Entity Persistent Data of {entry.Key} but it doesn't exist in the destination world {destinationWorld}!"); + } + m_MigrationDependencies_ScratchPad[index] = entry.Value.MigrateTo(dependsOn, destinationPersistentData, ref remapArray); index++; } return JobHandle.CombineDependencies(m_MigrationDependencies_ScratchPad.AsArray()); } + + public void AddToMigrationLookup(string parentPath, AbstractPersistentData entityPersistentData) + { + string path = $"{parentPath}-{entityPersistentData.GetType().GetReadableName()}"; + Debug_EnsureNoDuplicateMigrationData(path); + m_MigrationPersistentDataLookup.Add(path, entityPersistentData); + m_MigrationDependencies_ScratchPad.ResizeUninitialized(m_MigrationPersistentDataLookup.Count); + } //************************************************************************************************************* // SAFETY @@ -102,5 +118,14 @@ private void Debug_EnsureOtherWorldPersistentDataSystemExists(World destinationW throw new InvalidOperationException($"Expected World {destinationWorld} to have a {nameof(PersistentDataSystem)} but it does not!"); } } + + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + private void Debug_EnsureNoDuplicateMigrationData(string path) + { + if (m_MigrationPersistentDataLookup.ContainsKey(path)) + { + throw new InvalidOperationException($"Trying to add Entity Persistent Data migration data for {this} but {path} is already in the lookup!"); + } + } } } \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs index dcbf5528..7ca08c7f 100644 --- a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs +++ b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs @@ -428,7 +428,8 @@ void ITaskSetOwner.AddResolvableDataStreamsTo(Type type, List migrationTaskSetOwnerIDLookup, - Dictionary migrationActiveIDLookup) + Dictionary migrationActiveIDLookup, + PersistentDataSystem persistentDataSystem) { //Construct the unique path for this TaskDriver and ensure we don't need a user provided suffix string typeName = GetType().GetReadableName(); @@ -437,19 +438,19 @@ internal void AddToMigrationLookup( migrationTaskSetOwnerIDLookup.Add(path, m_ID); //Get our TaskSet to populate all the possible ActiveIDs - TaskSet.AddToMigrationLookup(path, migrationActiveIDLookup); + TaskSet.AddToMigrationLookup(path, migrationActiveIDLookup, persistentDataSystem); //Try and do the same for our system (there can only be one), will gracefully fail if we have already done this string systemPath = $"{typeName}-System"; if (migrationTaskSetOwnerIDLookup.TryAdd(systemPath, TaskDriverSystem.ID)) { - TaskDriverSystem.TaskSet.AddToMigrationLookup(systemPath, migrationActiveIDLookup); + TaskDriverSystem.TaskSet.AddToMigrationLookup(systemPath, migrationActiveIDLookup, persistentDataSystem); } //Then recurse downward to catch all the sub task drivers foreach (AbstractTaskDriver subTaskDriver in m_SubTaskDrivers) { - subTaskDriver.AddToMigrationLookup(path, migrationTaskSetOwnerIDLookup, migrationActiveIDLookup); + subTaskDriver.AddToMigrationLookup(path, migrationTaskSetOwnerIDLookup, migrationActiveIDLookup, persistentDataSystem); } } diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs b/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs index d5392ff1..4d9f4715 100644 --- a/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs @@ -1,4 +1,5 @@ using Anvil.CSharp.Core; +using System.Collections.Generic; using Unity.Collections; namespace Anvil.Unity.DOTS.Entities.TaskDriver diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs index 1859bd02..2860edb4 100644 --- a/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs @@ -12,6 +12,7 @@ internal class TaskDriverMigrationData : AbstractAnvilBase { private readonly Dictionary m_MigrationTaskSetOwnerIDLookup; private readonly Dictionary m_MigrationActiveIDLookup; + private readonly Dictionary m_DestinationWorldDataMaps; private readonly Dictionary m_AllDataSources; // ReSharper disable once InconsistentNaming @@ -51,7 +52,7 @@ public void AddDataSource(T dataSource) m_MigrationDependencies_ScratchPad.ResizeUninitialized(m_AllDataSources.Count); } - public void PopulateMigrationLookup(List topLevelTaskDrivers) + public void PopulateMigrationLookup(World world, List topLevelTaskDrivers) { //Generate a World ID foreach (AbstractTaskDriver topLevelTaskDriver in topLevelTaskDrivers) @@ -59,7 +60,8 @@ public void PopulateMigrationLookup(List topLevelTaskDrivers topLevelTaskDriver.AddToMigrationLookup( string.Empty, m_MigrationTaskSetOwnerIDLookup, - m_MigrationActiveIDLookup); + m_MigrationActiveIDLookup, + world.GetOrCreateSystem()); } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 4c151c92..1dc68766 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -48,12 +48,13 @@ public TaskDriverManagementSystem() m_CancelCompleteDataSource = new CancelCompleteDataSource(this); m_CancelProgressFlows = new List(); m_UnityEntityDataAccessControllers = new Dictionary(); - m_TaskDriverMigrationData = new TaskDriverMigrationData(); - EntityProxyInstanceID.Debug_EnsureOffsetsAreCorrect(); + m_TaskDriverMigrationData = new TaskDriverMigrationData(); m_TaskDriverMigrationData.AddDataSource(m_CancelRequestsDataSource); m_TaskDriverMigrationData.AddDataSource(m_CancelProgressDataSource); m_TaskDriverMigrationData.AddDataSource(m_CancelCompleteDataSource); + + EntityProxyInstanceID.Debug_EnsureOffsetsAreCorrect(); } protected override void OnCreate() @@ -79,8 +80,6 @@ protected override void OnStartRunning() protected sealed override void OnDestroy() { - - m_EntityProxyDataSourcesByType.DisposeAllValuesAndClear(); m_EntityProxyDataSourceBulkJobScheduler?.Dispose(); m_CancelProgressFlowBulkJobScheduler?.Dispose(); @@ -93,7 +92,7 @@ protected sealed override void OnDestroy() m_CancelProgressDataSource.Dispose(); m_IDProvider.Dispose(); - + base.OnDestroy(); } @@ -143,7 +142,7 @@ private void Harden() m_CancelProgressFlowBulkJobScheduler = new BulkJobScheduler(m_CancelProgressFlows.ToArray()); //Build the Migration Data for this world - m_TaskDriverMigrationData.PopulateMigrationLookup(m_TopLevelTaskDrivers); + m_TaskDriverMigrationData.PopulateMigrationLookup(World, m_TopLevelTaskDrivers); } public EntityProxyDataSource GetOrCreateEntityProxyDataSource() diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index 970ef425..ae8a8e69 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -119,7 +119,6 @@ public EntityPersistentData GetOrCreateEntityPersistentData() if (!m_EntityPersistentDataByType.TryGetValue(type, out AbstractPersistentData persistentData)) { persistentData = CreateEntityPersistentData(); - m_EntityPersistentDataByType.Add(type, persistentData); } return (EntityPersistentData)persistentData; @@ -129,6 +128,7 @@ public EntityPersistentData CreateEntityPersistentData() where T : unmanaged, IEntityPersistentDataInstance { EntityPersistentData entityPersistentData = new EntityPersistentData(); + m_EntityPersistentDataByType.Add(typeof(T), entityPersistentData); return entityPersistentData; } @@ -337,7 +337,10 @@ public void ReleaseCancelRequestsWriter() // MIGRATION //************************************************************************************************************* - public void AddToMigrationLookup(string parentPath, Dictionary migrationActiveIDLookup) + public void AddToMigrationLookup( + string parentPath, + Dictionary migrationActiveIDLookup, + PersistentDataSystem persistentDataSystem) { foreach (KeyValuePair entry in m_PublicDataStreamsByType) { @@ -366,6 +369,11 @@ public void AddToMigrationLookup(string parentPath, Dictionary mig typeof(CancelCompleteDataStream).GetReadableName(), CancelCompleteDataStream.ActiveID, migrationActiveIDLookup); + + foreach (AbstractPersistentData entry in m_EntityPersistentDataByType.Values) + { + persistentDataSystem.AddToMigrationLookup(parentPath, entry); + } } private void AddToMigrationLookup(string parentPath, string name, uint activeID, Dictionary migrationActiveIDLookup) @@ -375,6 +383,7 @@ private void AddToMigrationLookup(string parentPath, string name, uint activeID, migrationActiveIDLookup.Add(path, activeID); } + //************************************************************************************************************* // SAFETY //************************************************************************************************************* @@ -384,7 +393,7 @@ private void Debug_EnsureNoDuplicateMigrationData(string path, Dictionary Date: Fri, 28 Apr 2023 16:37:55 -0400 Subject: [PATCH 10/16] Removing TODO --- .../Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 1dc68766..9c2cc6e7 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -238,8 +238,6 @@ protected sealed override void OnUpdate() public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { - // TODO : SYSTEM AND DRIVER PERSISTENT DATA - TaskDriverManagementSystem destinationTaskDriverManagementSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldTaskDriverManagementSystemExists(destinationWorld, destinationTaskDriverManagementSystem); From 2fafb2ea800eb18b0c349229ab5b0de9c4e8bbe2 Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Mon, 1 May 2023 14:49:28 -0400 Subject: [PATCH 11/16] Merging in main --- .../Entities/TaskDriver/AbstractTaskDriver.cs | 211 ++++-------------- .../System/AbstractTaskDriverSystem.cs | 62 ++--- .../System/ContextTaskDriverSystemWrapper.cs | 77 +++++++ .../ContextTaskDriverSystemWrapper.cs.meta | 3 + .../TaskDriver/System/ITaskDriverSystem.cs | 84 +++++++ .../System/ITaskDriverSystem.cs.meta | 3 + .../TaskDriver/TaskDriverManagementSystem.cs | 2 +- .../Job/JobConfig/CancelCompleteJobConfig.cs | 8 - .../JobConfig/CancelCompleteJobConfig.cs.meta | 3 - .../TaskSet/Job/JobConfigFactory.cs | 19 -- .../Job/JobData/CancelCompleteJobData.cs | 16 -- .../Job/JobData/CancelCompleteJobData.cs.meta | 3 - .../CancelCompleteScheduleInfo.cs | 15 -- .../CancelCompleteScheduleInfo.cs.meta | 3 - .../Cancellation/CancelRequestsDataStream.cs | 27 ++- .../IAbstractCancelRequestDataStream.cs | 39 ++++ .../IAbstractCancelRequestDataStream.cs.meta | 3 + .../IDriverCancelRequestDataStream.cs | 13 ++ .../IDriverCancelRequestDataStream.cs.meta | 3 + .../ISystemCancelRequestDataStream.cs | 13 ++ .../ISystemCancelRequestDataStream.cs.meta | 3 + .../Entities/TaskDriver/TaskSet/TaskSet.cs | 69 +----- 22 files changed, 341 insertions(+), 338 deletions(-) create mode 100644 Scripts/Runtime/Entities/TaskDriver/System/ContextTaskDriverSystemWrapper.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/System/ContextTaskDriverSystemWrapper.cs.meta create mode 100644 Scripts/Runtime/Entities/TaskDriver/System/ITaskDriverSystem.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/System/ITaskDriverSystem.cs.meta delete mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/CancelCompleteJobConfig.cs delete mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/CancelCompleteJobConfig.cs.meta delete mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobData/CancelCompleteJobData.cs delete mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobData/CancelCompleteJobData.cs.meta delete mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/Scheduling/ScheduleInfo/CancelCompleteScheduleInfo.cs delete mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/Scheduling/ScheduleInfo/CancelCompleteScheduleInfo.cs.meta create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IAbstractCancelRequestDataStream.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IAbstractCancelRequestDataStream.cs.meta create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IDriverCancelRequestDataStream.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IDriverCancelRequestDataStream.cs.meta create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/ISystemCancelRequestDataStream.cs create mode 100644 Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/ISystemCancelRequestDataStream.cs.meta diff --git a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs index 7ca08c7f..a872c869 100644 --- a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs +++ b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Reflection; using Unity.Entities; -using Unity.Jobs; using Debug = UnityEngine.Debug; namespace Anvil.Unity.DOTS.Entities.TaskDriver @@ -43,17 +42,30 @@ public abstract class AbstractTaskDriver : AbstractAnvilBase, ITaskSetOwner public World World { get; } internal AbstractTaskDriver Parent { get; private set; } - internal AbstractTaskDriverSystem TaskDriverSystem { get; } + + internal AbstractTaskDriverSystem System { get; } + internal TaskSet TaskSet { get; } - protected AbstractAnvilSystemBase System + /// + /// Data Stream representing requests to Cancel an + /// + public IDriverCancelRequestDataStream CancelRequestDataStream { - get => TaskDriverSystem; + get => TaskSet.CancelRequestsDataStream; + } + + /// + /// Data Stream representing when Cancel Requests are Complete + /// + public IDriverDataStream CancelCompleteDataStream + { + get => TaskSet.CancelCompleteDataStream; } AbstractTaskDriverSystem ITaskSetOwner.TaskDriverSystem { - get => TaskDriverSystem; + get => System; } TaskSet ITaskSetOwner.TaskSet @@ -79,6 +91,11 @@ bool ITaskSetOwner.HasCancellableData return m_HasCancellableData; } } + + protected ITaskDriverSystem TaskDriverSystem + { + get => new ContextTaskDriverSystemWrapper(System, this); + } protected AbstractTaskDriver(World world, string uniqueMigrationSuffix = null) { @@ -94,17 +111,17 @@ protected AbstractTaskDriver(World world, string uniqueMigrationSuffix = null) Type taskDriverSystemType = TASK_DRIVER_SYSTEM_TYPE.MakeGenericType(taskDriverType); //If this is the first TaskDriver of this type, then the System will have been created for this World. - TaskDriverSystem = (AbstractTaskDriverSystem)World.GetExistingSystem(taskDriverSystemType); + System = (AbstractTaskDriverSystem)World.GetExistingSystem(taskDriverSystemType); //If not, then we will want to explicitly create it and ensure it is part of the lifecycle. - if (TaskDriverSystem == null) + if (System == null) { - TaskDriverSystem = (AbstractTaskDriverSystem)Activator.CreateInstance(taskDriverSystemType, World); - World.AddSystem(TaskDriverSystem); + System = (AbstractTaskDriverSystem)Activator.CreateInstance(taskDriverSystemType, World); + World.AddSystem(System); ComponentSystemGroup systemGroup = GetSystemGroup(); - systemGroup.AddSystemToUpdateList(TaskDriverSystem); + systemGroup.AddSystemToUpdateList(System); } - TaskDriverSystem.RegisterTaskDriver(this); + System.RegisterTaskDriver(this); m_ID = taskDriverManagementSystem.GetNextID(); taskDriverManagementSystem.RegisterTaskDriver(this); @@ -156,17 +173,8 @@ protected TTaskDriver AddSubTaskDriver(TTaskDriver subTaskDriver) return subTaskDriver; } - - protected ISystemDataStream CreateSystemDataStream(CancelRequestBehaviour cancelRequestBehaviour = CancelRequestBehaviour.Delete) - where TInstance : unmanaged, IEntityProxyInstance - { - ISystemDataStream dataStream - = TaskDriverSystem.GetOrCreateDataStream(this, cancelRequestBehaviour); - - return dataStream; - } - - protected IDriverDataStream CreateDriverDataStream(CancelRequestBehaviour cancelRequestBehaviour = CancelRequestBehaviour.Delete) + + protected IDriverDataStream CreateDataStream(CancelRequestBehaviour cancelRequestBehaviour = CancelRequestBehaviour.Delete) where TInstance : unmanaged, IEntityProxyInstance { IDriverDataStream dataStream = TaskSet.CreateDataStream(cancelRequestBehaviour); @@ -174,20 +182,13 @@ protected IDriverDataStream CreateDriverDataStream(CancelR return dataStream; } - protected IDriverEntityPersistentData CreateDriverEntityPersistentData() + protected IDriverEntityPersistentData CreateEntityPersistentData() where T : unmanaged, IEntityPersistentDataInstance { EntityPersistentData entityPersistentData = TaskSet.CreateEntityPersistentData(); return entityPersistentData; } - protected ISystemEntityPersistentData CreateSystemEntityPersistentData() - where T : unmanaged, IEntityPersistentDataInstance - { - EntityPersistentData entityPersistentData = TaskDriverSystem.GetOrCreateEntityPersistentData(); - return entityPersistentData; - } - protected IWorldEntityPersistentData GetOrCreateWorldEntityPersistentData() where T : unmanaged, IEntityPersistentDataInstance { @@ -202,39 +203,6 @@ protected IThreadPersistentData GetOrCreateThreadPersistentData() return threadPersistentData; } - //************************************************************************************************************* - // JOB CONFIGURATION - SYSTEM LEVEL - //************************************************************************************************************* - - protected IResolvableJobConfigRequirements ConfigureSystemJobToCancel( - ISystemDataStream dataStream, - JobConfigScheduleDelegates.ScheduleCancelJobDelegate scheduleJobFunction, - BatchStrategy batchStrategy) - where TInstance : unmanaged, IEntityProxyInstance - { - return TaskDriverSystem.ConfigureSystemJobToCancel( - dataStream, - scheduleJobFunction, - batchStrategy); - } - - protected IResolvableJobConfigRequirements ConfigureSystemJobToUpdate( - ISystemDataStream dataStream, - JobConfigScheduleDelegates.ScheduleUpdateJobDelegate scheduleJobFunction, - BatchStrategy batchStrategy) - where TInstance : unmanaged, IEntityProxyInstance - { - return TaskDriverSystem.ConfigureSystemJobToUpdate( - dataStream, - scheduleJobFunction, - batchStrategy); - } - - protected EntityQuery GetEntityQuery(params ComponentType[] componentTypes) - { - return TaskDriverSystem.GetEntityQuery(componentTypes); - } - //************************************************************************************************************* // JOB CONFIGURATION - DRIVER LEVEL //************************************************************************************************************* @@ -247,7 +215,7 @@ protected EntityQuery GetEntityQuery(params ComponentType[] componentTypes) /// The to use for executing the job. /// The type of instance contained in the /// A to allow for chaining more configuration options. - public IJobConfig ConfigureDriverJobTriggeredBy( + protected IJobConfig ConfigureJobTriggeredBy( IDriverDataStream dataStream, JobConfigScheduleDelegates.ScheduleDataStreamJobDelegate scheduleJobFunction, BatchStrategy batchStrategy) @@ -267,7 +235,7 @@ public IJobConfig ConfigureDriverJobTriggeredBy( /// The scheduling function to call to schedule the job. /// The to use for executing the job. /// A to allow for chaining more configuration options. - public IJobConfig ConfigureDriverJobTriggeredBy( + protected IJobConfig ConfigureJobTriggeredBy( EntityQuery entityQuery, JobConfigScheduleDelegates.ScheduleEntityQueryJobDelegate scheduleJobFunction, BatchStrategy batchStrategy) @@ -278,111 +246,8 @@ public IJobConfig ConfigureDriverJobTriggeredBy( batchStrategy); } - /// - /// Configures a Job that is triggered by the cancellation of instances in this - /// completing. - /// - /// The scheduling function to call to schedule the job. - /// The to use for executing the job. - /// A to allow for chaining more configuration options. - public IJobConfig ConfigureDriverJobWhenCancelComplete( - JobConfigScheduleDelegates.ScheduleDataStreamJobDelegate scheduleJobFunction, - BatchStrategy batchStrategy) - { - return TaskSet.ConfigureJobWhenCancelComplete( - scheduleJobFunction, - batchStrategy); - } - - //TODO: #73 - Implement other job types - //************************************************************************************************************* - // EXTERNAL USAGE - //************************************************************************************************************* - - /// - /// Gets a for use in a job outside the Task Driver context. - /// Requires a call to after scheduling the job. - /// - /// The - /// A to wait on - public JobHandle AcquireCancelCompleteReaderAsync(out DataStreamActiveReader cancelCompleteReader) - { - return TaskSet.AcquireCancelCompleteReaderAsync(out cancelCompleteReader); - } - - /// - /// Allows other jobs to use the underlying data for the - /// and ensures data integrity across those other usages. - /// - /// The that used this data. - public void ReleaseCancelCompleteReaderAsync(JobHandle dependsOn) - { - TaskSet.ReleaseCancelCompleteReaderAsync(dependsOn); - } - - /// - /// Gets a for use on the main thread outside the Task Driver - /// context. - /// Requires a call to when done. - /// - /// The - public DataStreamActiveReader AcquireCancelCompleteReader() - { - return TaskSet.AcquireCancelCompleteReader(); - } - - /// - /// Allows other jobs or code to use to underlying data for the - /// and ensures data integrity across those other usages. - /// - public void ReleaseCancelCompleteReader() - { - TaskSet.ReleaseCancelCompleteReader(); - } - - /// - /// Gets a for use in a job outside the Task Driver context. - /// Requires a call to after scheduling the job. - /// - /// The - /// A to wait on - public JobHandle AcquireCancelRequestsWriterAsync(out CancelRequestsWriter cancelRequestsWriter) - { - return TaskSet.AcquireCancelRequestsWriterAsync(out cancelRequestsWriter); - } - - /// - /// Allows other jobs to use the underlying data for the - /// and ensures data integrity across those other usages. - /// - /// The that used this data. - public void ReleaseCancelRequestsWriterAsync(JobHandle dependsOn) - { - TaskSet.ReleaseCancelRequestsWriterAsync(dependsOn); - } - - /// - /// Gets a for use on the main thread outside the Task Driver - /// context. - /// Requires a call to when done. - /// - /// The - public CancelRequestsWriter AcquireCancelRequestsWriter() - { - return TaskSet.AcquireCancelRequestsWriter(); - } - - /// - /// Allows other jobs or code to use to underlying data for the - /// and ensures data integrity across those other usages. - /// - public void ReleaseCancelRequestsWriter() - { - TaskSet.ReleaseCancelRequestsWriter(); - } - //************************************************************************************************************* // HARDENING //************************************************************************************************************* @@ -400,14 +265,14 @@ internal void Harden() } //Harden our TaskDriverSystem if it hasn't been already - TaskDriverSystem.Harden(); + System.Harden(); //Harden our own TaskSet TaskSet.Harden(); //TODO: #138 - Can we consolidate this into the TaskSet and have TaskSets aware of parenting instead m_HasCancellableData = TaskSet.ExplicitCancellationCount > 0 - || TaskDriverSystem.HasCancellableData + || System.HasCancellableData || m_SubTaskDrivers.Any(subtaskDriver => subtaskDriver.m_HasCancellableData); } @@ -442,9 +307,9 @@ internal void AddToMigrationLookup( //Try and do the same for our system (there can only be one), will gracefully fail if we have already done this string systemPath = $"{typeName}-System"; - if (migrationTaskSetOwnerIDLookup.TryAdd(systemPath, TaskDriverSystem.ID)) + if (migrationTaskSetOwnerIDLookup.TryAdd(systemPath, System.ID)) { - TaskDriverSystem.TaskSet.AddToMigrationLookup(systemPath, migrationActiveIDLookup, persistentDataSystem); + System.TaskSet.AddToMigrationLookup(systemPath, migrationActiveIDLookup, persistentDataSystem); } //Then recurse downward to catch all the sub task drivers @@ -485,4 +350,4 @@ private void Debug_EnsureHardened() } } } -} \ No newline at end of file +} diff --git a/Scripts/Runtime/Entities/TaskDriver/System/AbstractTaskDriverSystem.cs b/Scripts/Runtime/Entities/TaskDriver/System/AbstractTaskDriverSystem.cs index f03b6618..8ffb6d4c 100644 --- a/Scripts/Runtime/Entities/TaskDriver/System/AbstractTaskDriverSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/System/AbstractTaskDriverSystem.cs @@ -7,49 +7,42 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver { - //TODO: #188 - /// Document public API public abstract partial class AbstractTaskDriverSystem : AbstractAnvilSystemBase, ITaskSetOwner { private static readonly NoOpJobConfig NO_OP_JOB_CONFIG = new NoOpJobConfig(); private static readonly List EMPTY_SUB_TASK_DRIVERS = new List(); - + private readonly List m_TaskDrivers; private BulkJobScheduler m_BulkJobScheduler; private bool m_IsHardened; private bool m_IsUpdatePhaseHardened; private bool m_HasCancellableData; - - AbstractTaskDriverSystem ITaskSetOwner.TaskDriverSystem - { - get => this; - } - + //Note - This represents the World that was passed in by the TaskDriver during this system's construction. //Normally a system doesn't get a World until OnCreate is called and the System.World will return null. //We need a valid World in the constructor so we get one and assign it to this property instead. public new World World { get; } - internal TaskSet TaskSet { get; } - - TaskSet ITaskSetOwner.TaskSet - { - get => TaskSet; - } - + internal uint ID { get; } - + uint ITaskSetOwner.ID { get => ID; } - - List ITaskSetOwner.SubTaskDrivers + + internal ISystemCancelRequestDataStream CancelRequestDataStream { - get => EMPTY_SUB_TASK_DRIVERS; + get => TaskSet.CancelRequestsDataStream; } - + + internal ISystemDataStream CancelCompleteDataStream + { + get => TaskSet.CancelCompleteDataStream; + } + internal bool HasCancellableData { get @@ -64,6 +57,21 @@ bool ITaskSetOwner.HasCancellableData get => HasCancellableData; } + AbstractTaskDriverSystem ITaskSetOwner.TaskDriverSystem + { + get => this; + } + + TaskSet ITaskSetOwner.TaskSet + { + get => TaskSet; + } + + List ITaskSetOwner.SubTaskDrivers + { + get => EMPTY_SUB_TASK_DRIVERS; + } + protected AbstractTaskDriverSystem(World world) { World = world; @@ -106,17 +114,15 @@ internal void RegisterTaskDriver(AbstractTaskDriver taskDriver) m_TaskDrivers.Add(taskDriver); } - internal ISystemDataStream GetOrCreateDataStream( - AbstractTaskDriver taskDriver, - CancelRequestBehaviour cancelRequestBehaviour = CancelRequestBehaviour.Delete) + internal ISystemDataStream CreateDataStream(AbstractTaskDriver taskDriver, CancelRequestBehaviour cancelRequestBehaviour = CancelRequestBehaviour.Delete) where TInstance : unmanaged, IEntityProxyInstance { EntityProxyDataStream dataStream = TaskSet.GetOrCreateDataStream(cancelRequestBehaviour); //Create a proxy DataStream that references the same data owned by the system but gives it the TaskDriver context return new EntityProxyDataStream(taskDriver, dataStream); } - - internal EntityPersistentData GetOrCreateEntityPersistentData() + + internal ISystemEntityPersistentData CreateEntityPersistentData() where T : unmanaged, IEntityPersistentDataInstance { EntityPersistentData entityPersistentData = TaskSet.GetOrCreateEntityPersistentData(); @@ -134,7 +140,7 @@ private bool HaveSystemLevelJobsBeenConfigured() // JOB CONFIGURATION - SYSTEM LEVEL //************************************************************************************************************* - internal IResolvableJobConfigRequirements ConfigureSystemJobToUpdate( + internal IResolvableJobConfigRequirements ConfigureJobToUpdate( ISystemDataStream dataStream, JobConfigScheduleDelegates.ScheduleUpdateJobDelegate scheduleJobFunction, BatchStrategy batchStrategy) @@ -153,7 +159,7 @@ internal IResolvableJobConfigRequirements ConfigureSystemJobToUpdate( batchStrategy); } - internal IResolvableJobConfigRequirements ConfigureSystemJobToCancel( + internal IResolvableJobConfigRequirements ConfigureJobToCancel( ISystemDataStream dataStream, JobConfigScheduleDelegates.ScheduleCancelJobDelegate scheduleJobFunction, BatchStrategy batchStrategy) @@ -276,4 +282,4 @@ private void Debug_AssertWorldUniqueConstraint(AbstractTaskDriver taskDriver) } } } -} \ No newline at end of file +} diff --git a/Scripts/Runtime/Entities/TaskDriver/System/ContextTaskDriverSystemWrapper.cs b/Scripts/Runtime/Entities/TaskDriver/System/ContextTaskDriverSystemWrapper.cs new file mode 100644 index 00000000..fcb1df10 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/System/ContextTaskDriverSystemWrapper.cs @@ -0,0 +1,77 @@ +using Unity.Entities; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + /// + /// Helper class to wrap a so that it has the context of the calling + /// . + /// + /// + /// These functions just pipe directly to the system but handle passing along the context. This avoids errors of + /// passing along the wrong context but also keeps the API the same as the Driver API. + /// The consumer gets an to interface with and isn't aware of this wrapper. + /// The corresponding methods are marked internal so that they can't be + /// accessed without going through here. + /// + internal class ContextTaskDriverSystemWrapper : ITaskDriverSystem + { + private readonly AbstractTaskDriverSystem m_TaskDriverSystem; + private readonly AbstractTaskDriver m_ContextTaskDriver; + + public ISystemCancelRequestDataStream CancelRequestDataStream + { + get => m_TaskDriverSystem.CancelRequestDataStream; + } + + public ISystemDataStream CancelCompleteDataStream + { + get => m_TaskDriverSystem.CancelCompleteDataStream; + } + + public ContextTaskDriverSystemWrapper(AbstractTaskDriverSystem taskDriverSystem, AbstractTaskDriver contextTaskDriver) + { + m_TaskDriverSystem = taskDriverSystem; + m_ContextTaskDriver = contextTaskDriver; + } + + public ISystemDataStream CreateDataStream(CancelRequestBehaviour cancelRequestBehaviour = CancelRequestBehaviour.Delete) + where TInstance : unmanaged, IEntityProxyInstance + { + return m_TaskDriverSystem.CreateDataStream(m_ContextTaskDriver, cancelRequestBehaviour); + } + + public ISystemEntityPersistentData CreateEntityPersistentData() + where T : unmanaged, IEntityPersistentDataInstance + { + return m_TaskDriverSystem.CreateEntityPersistentData(); + } + + public IResolvableJobConfigRequirements ConfigureJobToUpdate( + ISystemDataStream dataStream, + JobConfigScheduleDelegates.ScheduleUpdateJobDelegate scheduleJobFunction, + BatchStrategy batchStrategy) + where TInstance : unmanaged, IEntityProxyInstance + { + return m_TaskDriverSystem.ConfigureJobToUpdate(dataStream, scheduleJobFunction, batchStrategy); + } + + public IResolvableJobConfigRequirements ConfigureJobToCancel( + ISystemDataStream dataStream, + JobConfigScheduleDelegates.ScheduleCancelJobDelegate scheduleJobFunction, + BatchStrategy batchStrategy) + where TInstance : unmanaged, IEntityProxyInstance + { + return m_TaskDriverSystem.ConfigureJobToCancel(dataStream, scheduleJobFunction, batchStrategy); + } + + public EntityQuery GetEntityQuery(params ComponentType[] componentTypes) + { + return m_TaskDriverSystem.GetEntityQuery(componentTypes); + } + + public override string ToString() + { + return $"{m_TaskDriverSystem} via context {m_ContextTaskDriver}"; + } + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/System/ContextTaskDriverSystemWrapper.cs.meta b/Scripts/Runtime/Entities/TaskDriver/System/ContextTaskDriverSystemWrapper.cs.meta new file mode 100644 index 00000000..b6613b51 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/System/ContextTaskDriverSystemWrapper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 57a7cb6c70e744ffa00348ea91a8d433 +timeCreated: 1682948665 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/System/ITaskDriverSystem.cs b/Scripts/Runtime/Entities/TaskDriver/System/ITaskDriverSystem.cs new file mode 100644 index 00000000..f5582674 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/System/ITaskDriverSystem.cs @@ -0,0 +1,84 @@ +using Unity.Entities; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + /// + /// Represents a through the context of the calling + /// . + /// + public interface ITaskDriverSystem + { + /// + /// Data Stream representing requests to Cancel an on the system + /// + public ISystemCancelRequestDataStream CancelRequestDataStream { get; } + + /// + /// Data Stream representing when Cancel Requests are Complete on the system + /// + public ISystemDataStream CancelCompleteDataStream { get; } + + /// + /// Creates an for use in jobs. + /// + /// The type of this stream should use. + /// The type of in the stream. + /// The instance + public ISystemDataStream CreateDataStream(CancelRequestBehaviour cancelRequestBehaviour = CancelRequestBehaviour.Delete) + where TInstance : unmanaged, IEntityProxyInstance; + + /// + /// Creates an instance that is bound to the lifecycle of the + /// system instance. + /// + /// The type of + /// The instance. + public ISystemEntityPersistentData CreateEntityPersistentData() + where T : unmanaged, IEntityPersistentDataInstance; + + /// + /// Configures an job to be run on the system. This will + /// process the data to move it's progress forward and ultimately resolve into another data type. + /// + /// The to schedule the job on. + /// The callback function to perform the scheduling + /// The to use for scheduling + /// The type of in the stream + /// + /// A reference to the instance passed in to continue chaining + /// configuration methods. + /// + public IResolvableJobConfigRequirements ConfigureJobToUpdate( + ISystemDataStream dataStream, + JobConfigScheduleDelegates.ScheduleUpdateJobDelegate scheduleJobFunction, + BatchStrategy batchStrategy) + where TInstance : unmanaged, IEntityProxyInstance; + + /// + /// Configures an job to be run on the system. This will operate + /// on data in a stream that has been requested to cancel with . It + /// provides the opportunity for the job to do the unwinding for however long that takes and to eventually + /// resolve into a new data type and inherently notify of a + /// + /// The to schedule the job on. + /// The callback function to perform the scheduling + /// The to use for scheduling + /// The type of in the stream + /// + /// A reference to the instance passed in to continue chaining + /// configuration methods. + /// + public IResolvableJobConfigRequirements ConfigureJobToCancel( + ISystemDataStream dataStream, + JobConfigScheduleDelegates.ScheduleCancelJobDelegate scheduleJobFunction, + BatchStrategy batchStrategy) + where TInstance : unmanaged, IEntityProxyInstance; + + /// + /// Gets or Creates an tied to this system. + /// + /// The s to construct the query. + /// The instance + public EntityQuery GetEntityQuery(params ComponentType[] componentTypes); + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/System/ITaskDriverSystem.cs.meta b/Scripts/Runtime/Entities/TaskDriver/System/ITaskDriverSystem.cs.meta new file mode 100644 index 00000000..a05f1e95 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/System/ITaskDriverSystem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cbb8a41f7eaf4dedb83b025c9d3b9c74 +timeCreated: 1682948536 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 9c2cc6e7..24058cc8 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -178,7 +178,7 @@ public void RegisterTaskDriver(AbstractTaskDriver taskDriver) { Debug_EnsureNotHardened(); m_AllTaskDrivers.Add(taskDriver); - m_AllTaskDriverSystems.Add(taskDriver.TaskDriverSystem); + m_AllTaskDriverSystems.Add(taskDriver.System); } public AccessController GetOrCreateCDFEAccessController() diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/CancelCompleteJobConfig.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/CancelCompleteJobConfig.cs deleted file mode 100644 index 584edb75..00000000 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/CancelCompleteJobConfig.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Anvil.Unity.DOTS.Entities.TaskDriver -{ - internal class CancelCompleteJobConfig : DataStreamJobConfig - { - public CancelCompleteJobConfig(ITaskSetOwner taskSetOwner, CancelCompleteDataStream cancelCompleteDataStream) - : base(taskSetOwner, cancelCompleteDataStream) { } - } -} \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/CancelCompleteJobConfig.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/CancelCompleteJobConfig.cs.meta deleted file mode 100644 index e90e1d44..00000000 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfig/CancelCompleteJobConfig.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: e508d984d5ec48fabaf68177308a303e -timeCreated: 1667588470 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfigFactory.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfigFactory.cs index 24fd830f..7194f130 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfigFactory.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobConfigFactory.cs @@ -12,25 +12,6 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver /// internal static class JobConfigFactory { - public static CancelCompleteJobConfig CreateCancelCompleteJobConfig( - ITaskSetOwner taskSetOwner, - CancelCompleteDataStream cancelCompleteDataStream, - JobConfigScheduleDelegates.ScheduleDataStreamJobDelegate scheduleJobFunction, - BatchStrategy batchStrategy) - { - CancelCompleteJobConfig jobConfig = new CancelCompleteJobConfig(taskSetOwner, cancelCompleteDataStream); - - CancelCompleteJobData jobData = new CancelCompleteJobData(jobConfig); - - CancelCompleteScheduleInfo scheduleInfo = new CancelCompleteScheduleInfo( - jobData, - cancelCompleteDataStream, - batchStrategy, - scheduleJobFunction); - - return FinalizeJobConfig(jobConfig, scheduleInfo); - } - public static UpdateJobConfig CreateUpdateJobConfig( ITaskSetOwner taskSetOwner, EntityProxyDataStream dataStream, diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobData/CancelCompleteJobData.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobData/CancelCompleteJobData.cs deleted file mode 100644 index 75a2da93..00000000 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobData/CancelCompleteJobData.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Anvil.Unity.DOTS.Entities.TaskDriver -{ - /// - /// Specific for Jobs that have been triggered by the completion of cancelling - /// instances in an . - /// - public class CancelCompleteJobData : DataStreamJobData - { - private readonly CancelCompleteJobConfig m_JobConfig; - - internal CancelCompleteJobData(CancelCompleteJobConfig jobConfig) : base(jobConfig) - { - m_JobConfig = jobConfig; - } - } -} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobData/CancelCompleteJobData.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobData/CancelCompleteJobData.cs.meta deleted file mode 100644 index de417ae2..00000000 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/JobData/CancelCompleteJobData.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 041a0cc502124f71b62703716a4f226d -timeCreated: 1667589203 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/Scheduling/ScheduleInfo/CancelCompleteScheduleInfo.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/Scheduling/ScheduleInfo/CancelCompleteScheduleInfo.cs deleted file mode 100644 index 01a6a7cc..00000000 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/Scheduling/ScheduleInfo/CancelCompleteScheduleInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Anvil.Unity.DOTS.Entities.TaskDriver -{ - /// - /// Specific scheduling information for a - /// - public class CancelCompleteScheduleInfo : DataStreamScheduleInfo - { - internal CancelCompleteScheduleInfo( - CancelCompleteJobData jobData, - CancelCompleteDataStream cancelCompleteDataStream, - BatchStrategy batchStrategy, - JobConfigScheduleDelegates.ScheduleDataStreamJobDelegate scheduleJobFunction) - : base(jobData, cancelCompleteDataStream, batchStrategy, scheduleJobFunction) { } - } -} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/Scheduling/ScheduleInfo/CancelCompleteScheduleInfo.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/Scheduling/ScheduleInfo/CancelCompleteScheduleInfo.cs.meta deleted file mode 100644 index 1502b1b0..00000000 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/Job/Scheduling/ScheduleInfo/CancelCompleteScheduleInfo.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 6e9493c40cf84c2c894555422918c3a1 -timeCreated: 1667588351 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/Cancellation/CancelRequestsDataStream.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/Cancellation/CancelRequestsDataStream.cs index cc289829..b165377e 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/Cancellation/CancelRequestsDataStream.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/Cancellation/CancelRequestsDataStream.cs @@ -4,7 +4,9 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver { - internal class CancelRequestsDataStream : AbstractDataStream + internal class CancelRequestsDataStream : AbstractDataStream, + IDriverCancelRequestDataStream, + ISystemCancelRequestDataStream { private readonly CancelRequestsDataSource m_DataSource; public ActiveLookupData ActiveLookupData { get; } @@ -50,5 +52,28 @@ public CancelRequestsWriter CreateCancelRequestsWriter() { return new CancelRequestsWriter(m_DataSource.PendingWriter, TaskSetOwner.TaskSet.CancelRequestsContexts); } + + public JobHandle AcquireCancelRequestsWriterAsync(out CancelRequestsWriter cancelRequestsWriter) + { + JobHandle dependsOn = AcquirePendingAsync(AccessType.SharedWrite); + cancelRequestsWriter = CreateCancelRequestsWriter(); + return dependsOn; + } + + public void ReleaseCancelRequestsWriterAsync(JobHandle dependsOn) + { + ReleasePendingAsync(dependsOn); + } + + public CancelRequestsWriter AcquireCancelRequestsWriter() + { + AcquirePending(AccessType.SharedWrite); + return CreateCancelRequestsWriter(); + } + + public void ReleaseCancelRequestsWriter() + { + ReleasePending(); + } } } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IAbstractCancelRequestDataStream.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IAbstractCancelRequestDataStream.cs new file mode 100644 index 00000000..89dfb235 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IAbstractCancelRequestDataStream.cs @@ -0,0 +1,39 @@ +using Unity.Entities; +using Unity.Jobs; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + /// + /// An that represents a cancel request for an + /// + public interface IAbstractCancelRequestDataStream : IAbstractDataStream + { + /// + /// Gets a for use in a job. + /// Requires a call to after scheduling the job. + /// + /// The + /// A to wait on + public JobHandle AcquireCancelRequestsWriterAsync(out CancelRequestsWriter cancelRequestsWriter); + + /// + /// Allows other jobs to use the underlying data for the + /// and ensures data integrity across those other usages. + /// + /// The that used this data. + public void ReleaseCancelRequestsWriterAsync(JobHandle dependsOn); + + /// + /// Gets a for use on the main thread. + /// Requires a call to when done. + /// + /// The + public CancelRequestsWriter AcquireCancelRequestsWriter(); + + /// + /// Allows other jobs or code to use to underlying data for the + /// and ensures data integrity across those other usages. + /// + public void ReleaseCancelRequestsWriter(); + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IAbstractCancelRequestDataStream.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IAbstractCancelRequestDataStream.cs.meta new file mode 100644 index 00000000..274b3803 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IAbstractCancelRequestDataStream.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ca5bfe7d76ec4bbf97dcfdec8283b999 +timeCreated: 1682885974 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IDriverCancelRequestDataStream.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IDriverCancelRequestDataStream.cs new file mode 100644 index 00000000..484519a9 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IDriverCancelRequestDataStream.cs @@ -0,0 +1,13 @@ +using Unity.Entities; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + /// + /// An that is owned by a . + /// It represents a cancel request for an + /// + public interface IDriverCancelRequestDataStream : IAbstractCancelRequestDataStream + { + + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IDriverCancelRequestDataStream.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IDriverCancelRequestDataStream.cs.meta new file mode 100644 index 00000000..22214c4e --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/IDriverCancelRequestDataStream.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a4247eb6135e496daf2ad34010fd3340 +timeCreated: 1682885754 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/ISystemCancelRequestDataStream.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/ISystemCancelRequestDataStream.cs new file mode 100644 index 00000000..a53cc8c5 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/ISystemCancelRequestDataStream.cs @@ -0,0 +1,13 @@ +using Unity.Entities; + +namespace Anvil.Unity.DOTS.Entities.TaskDriver +{ + /// + /// An that is owned by a . + /// It represents a cancel request for an + /// + public interface ISystemCancelRequestDataStream : IAbstractCancelRequestDataStream + { + + } +} diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/ISystemCancelRequestDataStream.cs.meta b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/ISystemCancelRequestDataStream.cs.meta new file mode 100644 index 00000000..442617e1 --- /dev/null +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/ISystemCancelRequestDataStream.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: adcf120d3fff4f75bb3901369279c821 +timeCreated: 1682885816 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index ae8a8e69..3b599f24 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -7,8 +7,6 @@ using System.Diagnostics; using Unity.Collections; using Unity.Entities; -using Unity.Jobs; -using UnityEngine.UI; namespace Anvil.Unity.DOTS.Entities.TaskDriver { @@ -230,23 +228,6 @@ EntityQueryComponentJobConfig entityQueryComponentJobConfig return entityQueryComponentJobConfig; } - public IJobConfig ConfigureJobWhenCancelComplete( - in JobConfigScheduleDelegates.ScheduleDataStreamJobDelegate scheduleJobFunction, - BatchStrategy batchStrategy) - { - Debug_EnsureNoDuplicateJobSchedulingDelegates(scheduleJobFunction); - - CancelCompleteJobConfig cancelCompleteJobConfig - = JobConfigFactory.CreateCancelCompleteJobConfig( - TaskSetOwner, - CancelCompleteDataStream, - scheduleJobFunction, - batchStrategy); - m_JobConfigs.Add(cancelCompleteJobConfig); - return cancelCompleteJobConfig; - } - - public void Harden() { Debug_EnsureNotHardened(); @@ -285,53 +266,6 @@ private void AddCancelRequestContextsTo(List contexts) taskDriver.TaskSet.AddCancelRequestContextsTo(contexts); } } - - - public JobHandle AcquireCancelCompleteReaderAsync(out DataStreamActiveReader cancelCompleteReader) - { - JobHandle dependsOn = CancelCompleteDataStream.AcquireActiveAsync(AccessType.SharedRead); - cancelCompleteReader = CancelCompleteDataStream.CreateDataStreamActiveReader(); - return dependsOn; - } - - public void ReleaseCancelCompleteReaderAsync(JobHandle dependsOn) - { - CancelCompleteDataStream.ReleaseActiveAsync(dependsOn); - } - - public DataStreamActiveReader AcquireCancelCompleteReader() - { - CancelCompleteDataStream.AcquireActive(AccessType.SharedRead); - return CancelCompleteDataStream.CreateDataStreamActiveReader(); - } - - public void ReleaseCancelCompleteReader() - { - CancelCompleteDataStream.ReleaseActive(); - } - - public JobHandle AcquireCancelRequestsWriterAsync(out CancelRequestsWriter cancelRequestsWriter) - { - JobHandle dependsOn = CancelRequestsDataStream.AcquirePendingAsync(AccessType.SharedWrite); - cancelRequestsWriter = CancelRequestsDataStream.CreateCancelRequestsWriter(); - return dependsOn; - } - - public void ReleaseCancelRequestsWriterAsync(JobHandle dependsOn) - { - CancelRequestsDataStream.ReleasePendingAsync(dependsOn); - } - - public CancelRequestsWriter AcquireCancelRequestsWriter() - { - CancelRequestsDataStream.AcquirePending(AccessType.SharedWrite); - return CancelRequestsDataStream.CreateCancelRequestsWriter(); - } - - public void ReleaseCancelRequestsWriter() - { - CancelRequestsDataStream.ReleasePending(); - } //************************************************************************************************************* // MIGRATION @@ -383,7 +317,6 @@ private void AddToMigrationLookup(string parentPath, string name, uint activeID, migrationActiveIDLookup.Add(path, activeID); } - //************************************************************************************************************* // SAFETY //************************************************************************************************************* @@ -418,4 +351,4 @@ private void Debug_EnsureNoDuplicateJobSchedulingDelegates(Delegate jobSchedulin } } } -} \ No newline at end of file +} From 8f00359a1dbd8b87ca52617cddc54fb6a79dd7b8 Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Mon, 1 May 2023 14:51:50 -0400 Subject: [PATCH 12/16] Renaming the public facing reference for Task Driver System to just be "System" --- .../Entities/TaskDriver/AbstractTaskDriver.cs | 49 +++++++++---------- .../System/AbstractTaskDriverSystem.cs | 18 +++---- .../TaskDriver/TaskDriverManagementSystem.cs | 2 +- .../Entities/TaskDriver/TaskSet/TaskSet.cs | 29 ++++++----- 4 files changed, 48 insertions(+), 50 deletions(-) diff --git a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs index a872c869..27ed6b80 100644 --- a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs +++ b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs @@ -26,8 +26,7 @@ public abstract class AbstractTaskDriver : AbstractAnvilBase, ITaskSetOwner private static readonly Type TASK_DRIVER_SYSTEM_TYPE = typeof(TaskDriverSystem<>); private static readonly Type COMPONENT_SYSTEM_GROUP_TYPE = typeof(ComponentSystemGroup); - - + private readonly PersistentDataSystem m_PersistentDataSystem; private readonly List m_SubTaskDrivers; private readonly uint m_ID; @@ -42,9 +41,9 @@ public abstract class AbstractTaskDriver : AbstractAnvilBase, ITaskSetOwner public World World { get; } internal AbstractTaskDriver Parent { get; private set; } - - internal AbstractTaskDriverSystem System { get; } - + + internal AbstractTaskDriverSystem TaskDriverSystem { get; } + internal TaskSet TaskSet { get; } /// @@ -65,7 +64,7 @@ public IDriverDataStream CancelCompleteDataStream AbstractTaskDriverSystem ITaskSetOwner.TaskDriverSystem { - get => System; + get => TaskDriverSystem; } TaskSet ITaskSetOwner.TaskSet @@ -91,10 +90,10 @@ bool ITaskSetOwner.HasCancellableData return m_HasCancellableData; } } - - protected ITaskDriverSystem TaskDriverSystem + + protected ITaskDriverSystem System { - get => new ContextTaskDriverSystemWrapper(System, this); + get => new ContextTaskDriverSystemWrapper(TaskDriverSystem, this); } protected AbstractTaskDriver(World world, string uniqueMigrationSuffix = null) @@ -111,17 +110,17 @@ protected AbstractTaskDriver(World world, string uniqueMigrationSuffix = null) Type taskDriverSystemType = TASK_DRIVER_SYSTEM_TYPE.MakeGenericType(taskDriverType); //If this is the first TaskDriver of this type, then the System will have been created for this World. - System = (AbstractTaskDriverSystem)World.GetExistingSystem(taskDriverSystemType); + TaskDriverSystem = (AbstractTaskDriverSystem)World.GetExistingSystem(taskDriverSystemType); //If not, then we will want to explicitly create it and ensure it is part of the lifecycle. - if (System == null) + if (TaskDriverSystem == null) { - System = (AbstractTaskDriverSystem)Activator.CreateInstance(taskDriverSystemType, World); - World.AddSystem(System); + TaskDriverSystem = (AbstractTaskDriverSystem)Activator.CreateInstance(taskDriverSystemType, World); + World.AddSystem(TaskDriverSystem); ComponentSystemGroup systemGroup = GetSystemGroup(); - systemGroup.AddSystemToUpdateList(System); + systemGroup.AddSystemToUpdateList(TaskDriverSystem); } - System.RegisterTaskDriver(this); + TaskDriverSystem.RegisterTaskDriver(this); m_ID = taskDriverManagementSystem.GetNextID(); taskDriverManagementSystem.RegisterTaskDriver(this); @@ -173,7 +172,7 @@ protected TTaskDriver AddSubTaskDriver(TTaskDriver subTaskDriver) return subTaskDriver; } - + protected IDriverDataStream CreateDataStream(CancelRequestBehaviour cancelRequestBehaviour = CancelRequestBehaviour.Delete) where TInstance : unmanaged, IEntityProxyInstance { @@ -265,14 +264,14 @@ internal void Harden() } //Harden our TaskDriverSystem if it hasn't been already - System.Harden(); + TaskDriverSystem.Harden(); //Harden our own TaskSet TaskSet.Harden(); //TODO: #138 - Can we consolidate this into the TaskSet and have TaskSets aware of parenting instead m_HasCancellableData = TaskSet.ExplicitCancellationCount > 0 - || System.HasCancellableData + || TaskDriverSystem.HasCancellableData || m_SubTaskDrivers.Any(subtaskDriver => subtaskDriver.m_HasCancellableData); } @@ -285,13 +284,13 @@ void ITaskSetOwner.AddResolvableDataStreamsTo(Type type, List migrationTaskSetOwnerIDLookup, Dictionary migrationActiveIDLookup, PersistentDataSystem persistentDataSystem) @@ -304,12 +303,12 @@ internal void AddToMigrationLookup( //Get our TaskSet to populate all the possible ActiveIDs TaskSet.AddToMigrationLookup(path, migrationActiveIDLookup, persistentDataSystem); - + //Try and do the same for our system (there can only be one), will gracefully fail if we have already done this string systemPath = $"{typeName}-System"; - if (migrationTaskSetOwnerIDLookup.TryAdd(systemPath, System.ID)) + if (migrationTaskSetOwnerIDLookup.TryAdd(systemPath, TaskDriverSystem.ID)) { - System.TaskSet.AddToMigrationLookup(systemPath, migrationActiveIDLookup, persistentDataSystem); + TaskDriverSystem.TaskSet.AddToMigrationLookup(systemPath, migrationActiveIDLookup, persistentDataSystem); } //Then recurse downward to catch all the sub task drivers @@ -318,7 +317,7 @@ internal void AddToMigrationLookup( subTaskDriver.AddToMigrationLookup(path, migrationTaskSetOwnerIDLookup, migrationActiveIDLookup, persistentDataSystem); } } - + //************************************************************************************************************* // SAFETY //************************************************************************************************************* @@ -331,7 +330,7 @@ private void Debug_EnsureNoDuplicateMigrationData(string path, Dictionary EMPTY_SUB_TASK_DRIVERS = new List(); - + private readonly List m_TaskDrivers; private BulkJobScheduler m_BulkJobScheduler; private bool m_IsHardened; private bool m_IsUpdatePhaseHardened; private bool m_HasCancellableData; - + //Note - This represents the World that was passed in by the TaskDriver during this system's construction. //Normally a system doesn't get a World until OnCreate is called and the System.World will return null. //We need a valid World in the constructor so we get one and assign it to this property instead. public new World World { get; } internal TaskSet TaskSet { get; } - + internal uint ID { get; } - + uint ITaskSetOwner.ID { get => ID; } - + internal ISystemCancelRequestDataStream CancelRequestDataStream { get => TaskSet.CancelRequestsDataStream; } - + internal ISystemDataStream CancelCompleteDataStream { get => TaskSet.CancelCompleteDataStream; } - + internal bool HasCancellableData { get @@ -66,7 +66,7 @@ TaskSet ITaskSetOwner.TaskSet { get => TaskSet; } - + List ITaskSetOwner.SubTaskDrivers { get => EMPTY_SUB_TASK_DRIVERS; @@ -121,7 +121,7 @@ internal ISystemDataStream CreateDataStream(AbstractTaskDr //Create a proxy DataStream that references the same data owned by the system but gives it the TaskDriver context return new EntityProxyDataStream(taskDriver, dataStream); } - + internal ISystemEntityPersistentData CreateEntityPersistentData() where T : unmanaged, IEntityPersistentDataInstance { diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 24058cc8..9c2cc6e7 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -178,7 +178,7 @@ public void RegisterTaskDriver(AbstractTaskDriver taskDriver) { Debug_EnsureNotHardened(); m_AllTaskDrivers.Add(taskDriver); - m_AllTaskDriverSystems.Add(taskDriver.System); + m_AllTaskDriverSystems.Add(taskDriver.TaskDriverSystem); } public AccessController GetOrCreateCDFEAccessController() diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index 3b599f24..2427cba8 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -1,7 +1,6 @@ using Anvil.CSharp.Collections; using Anvil.CSharp.Core; using Anvil.CSharp.Logging; -using Anvil.Unity.DOTS.Jobs; using System; using System.Collections.Generic; using System.Diagnostics; @@ -266,13 +265,13 @@ private void AddCancelRequestContextsTo(List contexts) taskDriver.TaskSet.AddCancelRequestContextsTo(contexts); } } - + //************************************************************************************************************* // MIGRATION //************************************************************************************************************* public void AddToMigrationLookup( - string parentPath, + string parentPath, Dictionary migrationActiveIDLookup, PersistentDataSystem persistentDataSystem) { @@ -285,23 +284,23 @@ public void AddToMigrationLookup( { AddToMigrationLookup(parentPath, $"{entry.InstanceType.GetReadableName()}-ExplicitCancel", entry.PendingCancelActiveID, migrationActiveIDLookup); } - + AddToMigrationLookup( - parentPath, - typeof(CancelRequestsDataStream).GetReadableName(), - CancelRequestsDataStream.ActiveID, + parentPath, + typeof(CancelRequestsDataStream).GetReadableName(), + CancelRequestsDataStream.ActiveID, migrationActiveIDLookup); - + AddToMigrationLookup( - parentPath, - typeof(CancelProgressDataStream).GetReadableName(), - CancelProgressDataStream.ActiveID, + parentPath, + typeof(CancelProgressDataStream).GetReadableName(), + CancelProgressDataStream.ActiveID, migrationActiveIDLookup); - + AddToMigrationLookup( - parentPath, - typeof(CancelCompleteDataStream).GetReadableName(), - CancelCompleteDataStream.ActiveID, + parentPath, + typeof(CancelCompleteDataStream).GetReadableName(), + CancelCompleteDataStream.ActiveID, migrationActiveIDLookup); foreach (AbstractPersistentData entry in m_EntityPersistentDataByType.Values) From 39865379614b5bbae24b762515dff3cda05ac94a Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Mon, 1 May 2023 16:08:44 -0400 Subject: [PATCH 13/16] PR Comments --- ...Observer.cs => IWorldMigrationObserver.cs} | 2 +- ...s.meta => IWorldMigrationObserver.cs.meta} | 0 .../Lifecycle/Migration/MigrationUtil.cs | 252 ------------------ .../Migration/WorldEntityMigrationHelper.cs | 105 ++++++++ ...eta => WorldEntityMigrationHelper.cs.meta} | 0 .../Migration/WorldEntityMigrationSystem.cs | 223 ++++++++++++++-- .../Data/AbstractPersistentData.cs | 5 - .../Data/EntityPersistentData.cs | 11 +- .../Interface/IMigratablePersistentData.cs | 12 + .../IMigratablePersistentData.cs.meta | 3 + .../Data/ThreadPersistentData.cs | 11 - .../PersistentData/PersistentDataSystem.cs | 23 +- .../Entities/TaskDriver/AbstractTaskDriver.cs | 18 +- .../Migration/DestinationWorldDataMap.cs | 30 ++- .../Migration/TaskDriverMigrationData.cs | 28 +- .../TaskDriver/TaskDriverManagementSystem.cs | 6 +- ...AbstractEntityProxyInstanceIDDataSource.cs | 2 +- .../DataSource/CancelProgressDataSource.cs | 3 +- .../DataSource/EntityProxyDataSource.cs | 4 +- .../Entities/TaskDriver/TaskSet/TaskSet.cs | 12 +- 20 files changed, 405 insertions(+), 345 deletions(-) rename Scripts/Runtime/Entities/Lifecycle/Migration/{IMigrationObserver.cs => IWorldMigrationObserver.cs} (97%) rename Scripts/Runtime/Entities/Lifecycle/Migration/{IMigrationObserver.cs.meta => IWorldMigrationObserver.cs.meta} (100%) delete mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs create mode 100644 Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs rename Scripts/Runtime/Entities/Lifecycle/Migration/{MigrationUtil.cs.meta => WorldEntityMigrationHelper.cs.meta} (100%) create mode 100644 Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs create mode 100644 Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs similarity index 97% rename from Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs rename to Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs index e14840a4..b7cbcde4 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs @@ -12,7 +12,7 @@ namespace Anvil.Unity.DOTS.Entities /// NOTE: The jobs that are scheduled will be completed immediately, but this allows for taking advantage of /// multiple cores. /// - public interface IMigrationObserver + public interface IWorldMigrationObserver { /// /// Implement to handle any custom migration work. diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs.meta similarity index 100% rename from Scripts/Runtime/Entities/Lifecycle/Migration/IMigrationObserver.cs.meta rename to Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs deleted file mode 100644 index efb7c50e..00000000 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs +++ /dev/null @@ -1,252 +0,0 @@ -using Anvil.CSharp.Logging; -using System; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; -using UnityEngine; - -namespace Anvil.Unity.DOTS.Entities -{ - /// - /// Helper class for when Entities are migrating from one to another. - /// - public static class MigrationUtil - { - private static UnsafeParallelHashMap s_TypeOffsetsLookup = new UnsafeParallelHashMap(256, Allocator.Persistent); - private static NativeList s_EntityOffsetList = new NativeList(32, Allocator.Persistent); - private static NativeList s_BlobAssetRefOffsetList = new NativeList(32, Allocator.Persistent); - private static NativeList s_WeakAssetRefOffsetList = new NativeList(32, Allocator.Persistent); - - private static bool s_AppDomainUnloadRegistered; - - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] - private static void Init() - { - //This pattern ensures we can setup and dispose properly the static native collections without Unity - //getting upset about memory leaks - if (s_AppDomainUnloadRegistered) - { - return; - } - AppDomain.CurrentDomain.DomainUnload += CurrentDomain_OnDomainUnload; - s_AppDomainUnloadRegistered = true; - - SharedTypeOffsetInfo.REF.Data = s_TypeOffsetsLookup; - UpdateSharedStatics(); - } - - private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) - { - SharedTypeOffsetInfo.REF.Data = default; - SharedEntityOffsetInfo.REF.Data = default; - SharedBlobAssetRefInfo.REF.Data = default; - SharedWeakAssetRefInfo.REF.Data = default; - - if (s_TypeOffsetsLookup.IsCreated) - { - s_TypeOffsetsLookup.Dispose(); - } - if (s_EntityOffsetList.IsCreated) - { - s_EntityOffsetList.Dispose(); - } - if (s_BlobAssetRefOffsetList.IsCreated) - { - s_BlobAssetRefOffsetList.Dispose(); - } - if (s_WeakAssetRefOffsetList.IsCreated) - { - s_WeakAssetRefOffsetList.Dispose(); - } - } - - /// - /// Registers the Type that may contain Entity references so that it can be used with - /// to remap Entity references. - /// - /// The type to register - public static void RegisterTypeForEntityPatching() - where T : struct - { - RegisterTypeForEntityPatching(typeof(T)); - } - - /// - /// - /// Occurs when the Type is not a Value type. - /// - public static void RegisterTypeForEntityPatching(Type type) - { - if (!type.IsValueType) - { - throw new InvalidOperationException($"Type {type.GetReadableName()} must be a value type in order to register for Entity Patching."); - } - - long typeHash = BurstRuntime.GetHashCode64(type); - //We've already added this type, no need to do so again - if (s_TypeOffsetsLookup.ContainsKey(typeHash)) - { - return; - } - - int entityOffsetStartIndex = s_EntityOffsetList.Length; - - //We'll allow for a TypeOffset to be registered even if there's nothing to remap so that it's easy to detect - //when you forgot to register a type. We'll ignore the bools that this function returns. - EntityRemapUtility.CalculateFieldOffsetsUnmanaged( - type, - out bool hasEntityRefs, - out bool hasBlobRefs, - out bool hasWeakAssetRefs, - ref s_EntityOffsetList, - ref s_BlobAssetRefOffsetList, - ref s_WeakAssetRefOffsetList); - - - //Unity gives us back Blob Asset Refs and Weak Asset Refs as well but for now we're ignoring them. - //When the time comes to use those and do remapping with them, we'll need to add that info here along - //with the utils to actually do the remapping - s_TypeOffsetsLookup.Add( - typeHash, - new TypeOffsetInfo( - entityOffsetStartIndex, - s_EntityOffsetList.Length)); - - //The size of the underlying data could have changed such that we re-allocated the memory, so we'll update - //our shared statics - UpdateSharedStatics(); - } - - private static unsafe void UpdateSharedStatics() - { - SharedEntityOffsetInfo.REF.Data = new IntPtr(s_EntityOffsetList.GetUnsafePtr()); - SharedBlobAssetRefInfo.REF.Data = new IntPtr(s_BlobAssetRefOffsetList.GetUnsafePtr()); - SharedWeakAssetRefInfo.REF.Data = new IntPtr(s_WeakAssetRefOffsetList.GetUnsafePtr()); - } - - //************************************************************************************************************* - // BURST RUNTIME CALLS - //************************************************************************************************************* - - /// - /// Checks if the Entity was remapped by Unity during a world transfer. - /// - /// The current entity in this World - /// The remap array Unity provided. - /// The remapped Entity in the new World if it exists. - /// - /// true if this entity was moved to the new world and remaps to a new entity. - /// false if this entity did not move and stayed in this world. - /// - [BurstCompatible] - public static bool IfEntityIsRemapped( - this Entity currentEntity, - ref NativeArray remapArray, - out Entity remappedEntity) - { - remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, currentEntity); - return remappedEntity != Entity.Null; - } - - /// - /// For a given struct and Unity provided remapping array, all references will be - /// remapped to the new entity reference in the new world. - /// Entities that remained in this world will not be remapped. - /// - /// The struct to patch - /// The Unity provided remap array - /// The type of struct to patch - /// - /// Occurs if this type was not registered via - /// - [BurstCompatible] - public static unsafe void PatchEntityReferences(this ref T instance, ref NativeArray remapArray) - where T : struct - { - long typeHash = BurstRuntime.GetHashCode64(); - //Easy way to check if we remembered to register our type. Unfortunately it's a lot harder to figure out which type is missing due to the hash - //but usually you're going to run into this right away and be able to figure it out. Not using the actual Type class so we can Burst this. - if (!SharedTypeOffsetInfo.REF.Data.TryGetValue(typeHash, out TypeOffsetInfo typeOffsetInfo)) - { - throw new InvalidOperationException($"Tried to patch type with BurstRuntime hash of {typeHash} but it wasn't registered. Did you call {nameof(RegisterTypeForEntityPatching)}?"); - } - - //If there's nothing to remap, we'll just return - if (!typeOffsetInfo.CanRemap) - { - return; - } - - //Otherwise we'll get the memory address of the instance and run through all possible entity references - //to remap to the new entity - byte* instancePtr = (byte*)UnsafeUtility.AddressOf(ref instance); - //Beginning of the list - TypeManager.EntityOffsetInfo* entityOffsetInfoPtr = (TypeManager.EntityOffsetInfo*)SharedEntityOffsetInfo.REF.Data; - for (int i = typeOffsetInfo.EntityOffsetStartIndex; i < typeOffsetInfo.EntityOffsetEndIndex; ++i) - { - //Index into the list - TypeManager.EntityOffsetInfo* entityOffsetInfo = entityOffsetInfoPtr + i; - //Get offset info from list and offset into the instance memory - Entity* entityPtr = (Entity*)(instancePtr + entityOffsetInfo->Offset); - //Patch - *entityPtr = EntityRemapUtility.RemapEntity(ref remapArray, *entityPtr); - } - } - - //************************************************************************************************************* - // HELPER TYPES - //************************************************************************************************************* - - private readonly struct TypeOffsetInfo - { - public readonly int EntityOffsetStartIndex; - public readonly int EntityOffsetEndIndex; - - public bool CanRemap - { - get => EntityOffsetEndIndex > EntityOffsetStartIndex; - } - - public TypeOffsetInfo(int entityOffsetStartIndex, int entityOffsetEndIndex) - { - EntityOffsetStartIndex = entityOffsetStartIndex; - EntityOffsetEndIndex = entityOffsetEndIndex; - } - } - - //************************************************************************************************************* - // SHARED STATIC REQUIREMENTS - //************************************************************************************************************* - - // ReSharper disable once ConvertToStaticClass - // ReSharper disable once ClassNeverInstantiated.Local - private sealed class MigrationUtilContext - { - private MigrationUtilContext() - { - } - } - - private sealed class SharedTypeOffsetInfo - { - public static readonly SharedStatic> REF = SharedStatic>.GetOrCreate(); - } - - private sealed class SharedEntityOffsetInfo - { - public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); - } - - private sealed class SharedBlobAssetRefInfo - { - public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); - } - - private sealed class SharedWeakAssetRefInfo - { - public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); - } - } -} diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs new file mode 100644 index 00000000..a6245842 --- /dev/null +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs @@ -0,0 +1,105 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; + +namespace Anvil.Unity.DOTS.Entities +{ + /// + /// Helper class for when Entities are migrating from one to another. + /// + public static class WorldEntityMigrationHelper + { + //************************************************************************************************************* + // MIGRATION + //************************************************************************************************************* + + /// + /// Migrates Entities from this to the destination world with the provided query. + /// This will then handle notifying all s to have the chance to respond with + /// custom migration work. + /// NOTE: Use this instead of in order for migration callbacks + /// to occur in non IComponentData + /// + /// The source 's Entity Manager + /// The to move Entities to. + /// The to select the Entities to migrate. + public static void MoveEntitiesAndMigratableDataTo(this EntityManager srcEntityManager, World destinationWorld, EntityQuery entitiesToMigrateQuery) + { + WorldEntityMigrationSystem worldEntityMigrationSystem = srcEntityManager.World.GetOrCreateSystem(); + worldEntityMigrationSystem.MoveEntitiesAndMigratableDataTo(destinationWorld, entitiesToMigrateQuery); + } + + //************************************************************************************************************* + // BURST RUNTIME CALLS + //************************************************************************************************************* + + /// + /// Checks if the Entity was remapped by Unity during a world transfer. + /// + /// The current entity in this World + /// The remap array Unity provided. + /// The remapped Entity in the new World if it exists. + /// + /// true if this entity was moved to the new world and remaps to a new entity. + /// false if this entity did not move and stayed in this world. + /// + [BurstCompatible] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetRemappedEntity( + this Entity currentEntity, + ref NativeArray remapArray, + out Entity remappedEntity) + { + remappedEntity = EntityRemapUtility.RemapEntity(ref remapArray, currentEntity); + return remappedEntity != Entity.Null; + } + + /// + /// For a given struct and Unity provided remapping array, all references will be + /// remapped to the new entity reference in the new world. + /// Entities that remained in this world will not be remapped. + /// + /// The struct to patch + /// The Unity provided remap array + /// The type of struct to patch + /// + /// Occurs if this type was not registered via + /// + [BurstCompatible] + public static unsafe void PatchEntityReferences(this ref T instance, ref NativeArray remapArray) + where T : struct + { + long typeHash = BurstRuntime.GetHashCode64(); + //Easy way to check if we remembered to register our type. Unfortunately it's a lot harder to figure out which type is missing due to the hash + //but usually you're going to run into this right away and be able to figure it out. Not using the actual Type class so we can Burst this. + if (!WorldEntityMigrationSystem.SharedTypeOffsetInfo.REF.Data.TryGetValue(typeHash, out WorldEntityMigrationSystem.TypeOffsetInfo typeOffsetInfo)) + { + throw new InvalidOperationException($"Tried to patch type with BurstRuntime hash of {typeHash} but it wasn't registered. Did you call {nameof(WorldEntityMigrationSystem.RegisterForEntityPatching)}?"); + } + + //If there's nothing to remap, we'll just return + if (!typeOffsetInfo.CanRemap) + { + return; + } + + //Otherwise we'll get the memory address of the instance and run through all possible entity references + //to remap to the new entity + byte* instancePtr = (byte*)UnsafeUtility.AddressOf(ref instance); + //Beginning of the list + TypeManager.EntityOffsetInfo* entityOffsetInfoPtr = (TypeManager.EntityOffsetInfo*)WorldEntityMigrationSystem.SharedEntityOffsetInfo.REF.Data; + for (int i = typeOffsetInfo.EntityOffsetStartIndex; i < typeOffsetInfo.EntityOffsetEndIndex; ++i) + { + //Index into the list + TypeManager.EntityOffsetInfo* entityOffsetInfo = entityOffsetInfoPtr + i; + //Get offset info from list and offset into the instance memory + Entity* entityPtr = (Entity*)(instancePtr + entityOffsetInfo->Offset); + //Patch + *entityPtr = EntityRemapUtility.RemapEntity(ref remapArray, *entityPtr); + } + } + } +} diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs.meta similarity index 100% rename from Scripts/Runtime/Entities/Lifecycle/Migration/MigrationUtil.cs.meta rename to Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs index e55fd80c..6a68d4f8 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs @@ -1,26 +1,32 @@ +using Anvil.CSharp.Logging; +using System; using System.Collections.Generic; +using Unity.Burst; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.Jobs; +using UnityEngine; namespace Anvil.Unity.DOTS.Entities { /// /// World specific system for handling Migration. - /// Register s here to be notified when Migration occurs + /// Register s here to be notified when Migration occurs /// - /// NOTE: Use on this System instead of directly interfacing with + /// NOTE: Use on this System instead of directly interfacing with /// /// public class WorldEntityMigrationSystem : AbstractDataSystem { - private readonly HashSet m_MigrationObservers; + private readonly HashSet m_MigrationObservers; + // ReSharper disable once InconsistentNaming private NativeList m_Dependencies_ScratchPad; public WorldEntityMigrationSystem() { - m_MigrationObservers = new HashSet(); + m_MigrationObservers = new HashSet(); m_Dependencies_ScratchPad = new NativeList(8, Allocator.Persistent); } @@ -29,58 +35,233 @@ protected override void OnDestroy() m_Dependencies_ScratchPad.Dispose(); base.OnDestroy(); } - + /// - /// Adds a to be notified when Migration occurs and be given the chance to + /// Adds a to be notified when Migration occurs and be given the chance to /// respond to it. /// - /// The - public void AddMigrationObserver(IMigrationObserver migrationObserver) + /// The + public void RegisterMigrationObserver(IWorldMigrationObserver worldMigrationObserver) { - m_MigrationObservers.Add(migrationObserver); + m_MigrationObservers.Add(worldMigrationObserver); m_Dependencies_ScratchPad.ResizeUninitialized(m_MigrationObservers.Count); } - + /// - /// Removes a if it no longer wishes to be notified of when a Migration occurs. + /// Removes a if it no longer wishes to be notified of when a Migration occurs. /// - /// The - public void RemoveMigrationObserver(IMigrationObserver migrationObserver) + /// The + public void UnregisterMigrationObserver(IWorldMigrationObserver worldMigrationObserver) { - m_MigrationObservers.Remove(migrationObserver); + //We've already been destroyed, no need to unregister + if (!m_Dependencies_ScratchPad.IsCreated) + { + return; + } + m_MigrationObservers.Remove(worldMigrationObserver); m_Dependencies_ScratchPad.ResizeUninitialized(m_MigrationObservers.Count); } /// /// Migrates Entities from this to the destination world with the provided query. - /// This will then handle notifying all s to have the chance to respond with + /// This will then handle notifying all s to have the chance to respond with /// custom migration work. /// /// The to move Entities to. /// The to select the Entities to migrate. - public void MigrateTo(World destinationWorld, EntityQuery entitiesToMigrateQuery) + public void MoveEntitiesAndMigratableDataTo(World destinationWorld, EntityQuery entitiesToMigrateQuery) { NativeArray remapArray = EntityManager.CreateEntityRemapArray(Allocator.TempJob); //Do the actual move and get back the remap info destinationWorld.EntityManager.MoveEntitiesFrom(EntityManager, entitiesToMigrateQuery, remapArray); - + //Let everyone have a chance to do any additional remapping - JobHandle dependsOn = NotifyObserversToMigrateTo(destinationWorld, ref remapArray); + JobHandle dependsOn = NotifyObserversOfMigrateTo(destinationWorld, ref remapArray); //Dispose the array based on those remapping jobs being complete remapArray.Dispose(dependsOn); //Immediately complete the jobs so migration is complete and the world's state is correct dependsOn.Complete(); } - - private JobHandle NotifyObserversToMigrateTo(World destinationWorld, ref NativeArray remapArray) + + private JobHandle NotifyObserversOfMigrateTo(World destinationWorld, ref NativeArray remapArray) { int index = 0; - foreach (IMigrationObserver migrationObserver in m_MigrationObservers) + foreach (IWorldMigrationObserver migrationObserver in m_MigrationObservers) { m_Dependencies_ScratchPad[index] = migrationObserver.MigrateTo(default, destinationWorld, ref remapArray); index++; } return JobHandle.CombineDependencies(m_Dependencies_ScratchPad.AsArray()); } + + //************************************************************************************************************* + // STATIC REGISTRATION + //************************************************************************************************************* + + private static UnsafeParallelHashMap s_TypeOffsetsLookup = new UnsafeParallelHashMap(256, Allocator.Persistent); + private static NativeList s_EntityOffsetList = new NativeList(32, Allocator.Persistent); + private static NativeList s_BlobAssetRefOffsetList = new NativeList(32, Allocator.Persistent); + private static NativeList s_WeakAssetRefOffsetList = new NativeList(32, Allocator.Persistent); + + private static bool s_AppDomainUnloadRegistered; + + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void Init() + { + //This pattern ensures we can setup and dispose properly the static native collections without Unity + //getting upset about memory leaks + if (s_AppDomainUnloadRegistered) + { + return; + } + AppDomain.CurrentDomain.DomainUnload += CurrentDomain_OnDomainUnload; + s_AppDomainUnloadRegistered = true; + + SharedTypeOffsetInfo.REF.Data = s_TypeOffsetsLookup; + UpdateSharedStatics(); + } + + private static void CurrentDomain_OnDomainUnload(object sender, EventArgs e) + { + SharedTypeOffsetInfo.REF.Data = default; + SharedEntityOffsetInfo.REF.Data = default; + SharedBlobAssetRefInfo.REF.Data = default; + SharedWeakAssetRefInfo.REF.Data = default; + + if (s_TypeOffsetsLookup.IsCreated) + { + s_TypeOffsetsLookup.Dispose(); + } + if (s_EntityOffsetList.IsCreated) + { + s_EntityOffsetList.Dispose(); + } + if (s_BlobAssetRefOffsetList.IsCreated) + { + s_BlobAssetRefOffsetList.Dispose(); + } + if (s_WeakAssetRefOffsetList.IsCreated) + { + s_WeakAssetRefOffsetList.Dispose(); + } + } + + private static unsafe void UpdateSharedStatics() + { + SharedEntityOffsetInfo.REF.Data = new IntPtr(s_EntityOffsetList.GetUnsafePtr()); + SharedBlobAssetRefInfo.REF.Data = new IntPtr(s_BlobAssetRefOffsetList.GetUnsafePtr()); + SharedWeakAssetRefInfo.REF.Data = new IntPtr(s_WeakAssetRefOffsetList.GetUnsafePtr()); + } + + /// + /// Registers the Type that may contain Entity references so that it can be used with + /// to remap Entity references. + /// + /// The type to register + public static void RegisterForEntityPatching() + where T : struct + { + RegisterForEntityPatching(typeof(T)); + } + + /// + /// + /// Occurs when the Type is not a Value type. + /// + public static void RegisterForEntityPatching(Type type) + { + if (!type.IsValueType) + { + throw new InvalidOperationException($"Type {type.GetReadableName()} must be a value type in order to register for Entity Patching."); + } + + long typeHash = BurstRuntime.GetHashCode64(type); + //We've already added this type, no need to do so again + if (s_TypeOffsetsLookup.ContainsKey(typeHash)) + { + return; + } + + int entityOffsetStartIndex = s_EntityOffsetList.Length; + + //We'll allow for a TypeOffset to be registered even if there's nothing to remap so that it's easy to detect + //when you forgot to register a type. We'll ignore the bools that this function returns. + EntityRemapUtility.CalculateFieldOffsetsUnmanaged( + type, + out bool hasEntityRefs, + out bool hasBlobRefs, + out bool hasWeakAssetRefs, + ref s_EntityOffsetList, + ref s_BlobAssetRefOffsetList, + ref s_WeakAssetRefOffsetList); + + + //Unity gives us back Blob Asset Refs and Weak Asset Refs as well but for now we're ignoring them. + //When the time comes to use those and do remapping with them, we'll need to add that info here along + //with the utils to actually do the remapping + s_TypeOffsetsLookup.Add( + typeHash, + new TypeOffsetInfo( + entityOffsetStartIndex, + s_EntityOffsetList.Length)); + + //The size of the underlying data could have changed such that we re-allocated the memory, so we'll update + //our shared statics + UpdateSharedStatics(); + } + + //************************************************************************************************************* + // HELPER TYPES + //************************************************************************************************************* + + internal readonly struct TypeOffsetInfo + { + public readonly int EntityOffsetStartIndex; + public readonly int EntityOffsetEndIndex; + + public bool CanRemap + { + get => EntityOffsetEndIndex > EntityOffsetStartIndex; + } + + public TypeOffsetInfo(int entityOffsetStartIndex, int entityOffsetEndIndex) + { + EntityOffsetStartIndex = entityOffsetStartIndex; + EntityOffsetEndIndex = entityOffsetEndIndex; + } + } + + + //************************************************************************************************************* + // SHARED STATIC REQUIREMENTS + //************************************************************************************************************* + + // ReSharper disable once ConvertToStaticClass + // ReSharper disable once ClassNeverInstantiated.Local + private sealed class MigrationUtilContext + { + private MigrationUtilContext() { } + } + + internal sealed class SharedTypeOffsetInfo + { + public static readonly SharedStatic> REF = SharedStatic>.GetOrCreate(); + } + + internal sealed class SharedEntityOffsetInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } + + internal sealed class SharedBlobAssetRefInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } + + internal sealed class SharedWeakAssetRefInfo + { + public static readonly SharedStatic REF = SharedStatic.GetOrCreate(); + } } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs index 371f14e7..3b475a35 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs @@ -49,10 +49,5 @@ public void Release() { m_AccessController.Release(); } - - //************************************************************************************************************* - // MIGRATION - //************************************************************************************************************* - public abstract JobHandle MigrateTo(JobHandle dependsOn, AbstractPersistentData destinationPersistentData, ref NativeArray remapArray); } } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs index e97dd88e..6d7f8e44 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs @@ -11,14 +11,15 @@ namespace Anvil.Unity.DOTS.Entities internal class EntityPersistentData : AbstractTypedPersistentData>, IDriverEntityPersistentData, ISystemEntityPersistentData, - IWorldEntityPersistentData + IWorldEntityPersistentData, + IMigratablePersistentData where T : unmanaged, IEntityPersistentDataInstance { public EntityPersistentData() : base(new UnsafeParallelHashMap(ChunkUtil.MaxElementsPerChunk(), Allocator.Persistent)) { //We don't know what will be stored in here, but if there are Entity references we want to be able to patch them - MigrationUtil.RegisterTypeForEntityPatching(); + WorldEntityMigrationSystem.RegisterForEntityPatching(); } protected override void DisposeData() @@ -91,7 +92,7 @@ public void ReleaseWriter() // MIGRATION //************************************************************************************************************* - public override JobHandle MigrateTo(JobHandle dependsOn, AbstractPersistentData destinationPersistentData, ref NativeArray remapArray) + public JobHandle MigrateTo(JobHandle dependsOn, IMigratablePersistentData destinationPersistentData, ref NativeArray remapArray) { EntityPersistentData destinationEntityPersistentData = (EntityPersistentData)destinationPersistentData; @@ -131,6 +132,8 @@ public MigrateJob( public void Execute() { + //TODO: Optimization: Could pass through the array of entities that were moving to avoid the copy. See: https://github.com/decline-cookies/anvil-unity-dots/pull/232#discussion_r1181697951 + //Can't remove while iterating so we collapse to an array first of our current keys/values NativeKeyValueArrays currentEntries = m_CurrentData.GetKeyValueArrays(Allocator.Temp); @@ -138,7 +141,7 @@ public void Execute() { Entity currentEntity = currentEntries.Keys[i]; //If we don't exist in the new world we can just skip, we stayed in this world - if (!currentEntity.IfEntityIsRemapped(ref m_RemapArray, out Entity remappedEntity)) + if (!currentEntity.TryGetRemappedEntity(ref m_RemapArray, out Entity remappedEntity)) { continue; } diff --git a/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs new file mode 100644 index 00000000..046f7bbe --- /dev/null +++ b/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs @@ -0,0 +1,12 @@ +using System; +using Unity.Collections; +using Unity.Entities; +using Unity.Jobs; + +namespace Anvil.Unity.DOTS.Entities +{ + public interface IMigratablePersistentData : IDisposable + { + public JobHandle MigrateTo(JobHandle dependsOn, IMigratablePersistentData destinationPersistentData, ref NativeArray remapArray); + } +} diff --git a/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs.meta b/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs.meta new file mode 100644 index 00000000..b8081593 --- /dev/null +++ b/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 935dd787a0bf43a79fa8853abe712ebe +timeCreated: 1682969502 \ No newline at end of file diff --git a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs index 153cb870..eb5b173d 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/ThreadPersistentData.cs @@ -1,9 +1,7 @@ using Anvil.Unity.DOTS.Data; using Anvil.Unity.DOTS.Entities.TaskDriver; using Anvil.Unity.DOTS.Jobs; -using System; using Unity.Collections; -using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities @@ -56,14 +54,5 @@ public ThreadPersistentDataAccessor Acquire() Acquire(AccessType.SharedWrite); return CreateThreadPersistentDataAccessor(); } - - //************************************************************************************************************* - // MIGRATION - //************************************************************************************************************* - - public override JobHandle MigrateTo(JobHandle dependsOn, AbstractPersistentData destinationPersistentData, ref NativeArray remapArray) - { - throw new NotSupportedException($"{nameof(ThreadPersistentData)} isn't supported for migration because it is global to the app and temporary in nature. No migration should be needed."); - } } } diff --git a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs index ca4376d5..a07c68cf 100644 --- a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs +++ b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs @@ -10,7 +10,7 @@ namespace Anvil.Unity.DOTS.Entities { internal partial class PersistentDataSystem : AbstractDataSystem, - IMigrationObserver + IWorldMigrationObserver { private const string WORLD_PATH = "World"; private static readonly Dictionary s_ThreadPersistentData = new Dictionary(); @@ -18,23 +18,24 @@ internal partial class PersistentDataSystem : AbstractDataSystem, private readonly Dictionary m_EntityPersistentData; - private readonly Dictionary m_MigrationPersistentDataLookup; + private readonly Dictionary m_MigrationPersistentDataLookup; // ReSharper disable once InconsistentNaming private NativeList m_MigrationDependencies_ScratchPad; + private WorldEntityMigrationSystem m_WorldEntityMigrationSystem; public PersistentDataSystem() { s_InstanceCount++; m_EntityPersistentData = new Dictionary(); m_MigrationDependencies_ScratchPad = new NativeList(8, Allocator.Persistent); - m_MigrationPersistentDataLookup = new Dictionary(); + m_MigrationPersistentDataLookup = new Dictionary(); } protected override void OnCreate() { base.OnCreate(); - WorldEntityMigrationSystem worldEntityMigrationSystem = World.GetOrCreateSystem(); - worldEntityMigrationSystem.AddMigrationObserver(this); + m_WorldEntityMigrationSystem = World.GetOrCreateSystem(); + m_WorldEntityMigrationSystem.RegisterMigrationObserver(this); } protected override void OnDestroy() @@ -46,6 +47,8 @@ protected override void OnDestroy() { s_ThreadPersistentData.DisposeAllValuesAndClear(); } + + m_WorldEntityMigrationSystem.UnregisterMigrationObserver(this); base.OnDestroy(); } @@ -69,7 +72,7 @@ public EntityPersistentData GetOrCreateEntityPersistentData() { persistentData = new EntityPersistentData(); m_EntityPersistentData.Add(type, persistentData); - AddToMigrationLookup(WORLD_PATH, persistentData); + AddToMigrationLookup(WORLD_PATH, (EntityPersistentData)persistentData); } return (EntityPersistentData)persistentData; @@ -79,15 +82,15 @@ public EntityPersistentData GetOrCreateEntityPersistentData() // MIGRATION //************************************************************************************************************* - public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) + JobHandle IWorldMigrationObserver.MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { PersistentDataSystem destinationPersistentDataSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldPersistentDataSystemExists(destinationWorld, destinationPersistentDataSystem); int index = 0; - foreach (KeyValuePair entry in m_MigrationPersistentDataLookup) + foreach (KeyValuePair entry in m_MigrationPersistentDataLookup) { - if (!destinationPersistentDataSystem.m_MigrationPersistentDataLookup.TryGetValue(entry.Key, out AbstractPersistentData destinationPersistentData)) + if (!destinationPersistentDataSystem.m_MigrationPersistentDataLookup.TryGetValue(entry.Key, out IMigratablePersistentData destinationPersistentData)) { throw new InvalidOperationException($"Current World {World} has Entity Persistent Data of {entry.Key} but it doesn't exist in the destination world {destinationWorld}!"); } @@ -98,7 +101,7 @@ public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref Nati return JobHandle.CombineDependencies(m_MigrationDependencies_ScratchPad.AsArray()); } - public void AddToMigrationLookup(string parentPath, AbstractPersistentData entityPersistentData) + public void AddToMigrationLookup(string parentPath, IMigratablePersistentData entityPersistentData) { string path = $"{parentPath}-{entityPersistentData.GetType().GetReadableName()}"; Debug_EnsureNoDuplicateMigrationData(path); diff --git a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs index 27ed6b80..9d1b5595 100644 --- a/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs +++ b/Scripts/Runtime/Entities/TaskDriver/AbstractTaskDriver.cs @@ -96,6 +96,21 @@ protected ITaskDriverSystem System get => new ContextTaskDriverSystemWrapper(TaskDriverSystem, this); } + /// + /// Creates a new instance of a + /// + /// The this Task Driver is a part of. + /// + /// An optional unique suffix to identify this TaskDriver by. This is necessary when there are two or more of the + /// same type of TaskDrivers at the same level in the hierarchy. + /// Ex. + /// ShootTaskDriver + /// - TimerTaskDriver (for time between shots) + /// - TimerTaskDriver (for reloading) + /// + /// Both TimerTaskDriver's would conflict as being siblings of the ShootTaskDriver so they would need a unique + /// migration suffix to distinguish them for ensuring migration happens properly between worlds. + /// protected AbstractTaskDriver(World world, string uniqueMigrationSuffix = null) { m_UniqueMigrationSuffix = uniqueMigrationSuffix ?? string.Empty; @@ -295,7 +310,8 @@ internal void AddToMigrationLookup( Dictionary migrationActiveIDLookup, PersistentDataSystem persistentDataSystem) { - //Construct the unique path for this TaskDriver and ensure we don't need a user provided suffix + //Construct the unique path for this TaskDriver. By default, out unique migration suffix is empty but if we + //conflict with another, then we'll need to get the user to provide one. string typeName = GetType().GetReadableName(); string path = $"{parentPath}{typeName}{m_UniqueMigrationSuffix}-"; Debug_EnsureNoDuplicateMigrationData(path, migrationTaskSetOwnerIDLookup); diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs b/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs index 4d9f4715..913cbed8 100644 --- a/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/DestinationWorldDataMap.cs @@ -10,11 +10,33 @@ internal class DestinationWorldDataMap : AbstractAnvilBase public NativeParallelHashMap ActiveIDMapping; public DestinationWorldDataMap( - NativeParallelHashMap taskSetOwnerIDMapping, - NativeParallelHashMap activeIDMapping) + Dictionary srcTaskSetOwnerMapping, + Dictionary dstTaskSetOwnerMapping, + Dictionary srcActiveIDMapping, + Dictionary dstActiveIDMapping) { - TaskSetOwnerIDMapping = taskSetOwnerIDMapping; - ActiveIDMapping = activeIDMapping; + //TODO: Optimization - Could instead pass in the list of entities that moved and use that as the upper limit. In most cases it will be less than the dstMapping counts unless its a full world move. + //We're going to the Destination World so we can't have more than they have + TaskSetOwnerIDMapping = new NativeParallelHashMap(dstTaskSetOwnerMapping.Count, Allocator.Persistent); + ActiveIDMapping = new NativeParallelHashMap(dstActiveIDMapping.Count, Allocator.Persistent); + + foreach (KeyValuePair entry in srcTaskSetOwnerMapping) + { + if (!dstTaskSetOwnerMapping.TryGetValue(entry.Key, out uint dstTaskSetOwnerID)) + { + continue; + } + TaskSetOwnerIDMapping.Add(entry.Value, dstTaskSetOwnerID); + } + + foreach (KeyValuePair entry in srcActiveIDMapping) + { + if (!dstActiveIDMapping.TryGetValue(entry.Key, out uint dstActiveID)) + { + continue; + } + ActiveIDMapping.Add(entry.Value, dstActiveID); + } } protected override void DisposeSelf() diff --git a/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs index 2860edb4..cb8de93b 100644 --- a/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs +++ b/Scripts/Runtime/Entities/TaskDriver/Migration/TaskDriverMigrationData.cs @@ -67,32 +67,14 @@ public void PopulateMigrationLookup(World world, List topLev private DestinationWorldDataMap GetOrCreateDestinationWorldDataMapFor(World destinationWorld, TaskDriverMigrationData destinationMigrationData) { + //TODO: Optimization: Might be able to jobify if we switch to fixed strings and UnsafeWorlds? if (!m_DestinationWorldDataMaps.TryGetValue(destinationWorld, out DestinationWorldDataMap destinationWorldDataMap)) { - //We're going to the Destination World so we can't have more than they have - NativeParallelHashMap taskSetOwnerIDMapping = new NativeParallelHashMap(destinationMigrationData.TaskSetOwnerCount, Allocator.Persistent); - NativeParallelHashMap activeIDMapping = new NativeParallelHashMap(destinationMigrationData.ActiveIDCount, Allocator.Persistent); - - foreach (KeyValuePair entry in m_MigrationTaskSetOwnerIDLookup) - { - if (!destinationMigrationData.m_MigrationTaskSetOwnerIDLookup.TryGetValue(entry.Key, out uint dstTaskSetOwnerID)) - { - continue; - } - taskSetOwnerIDMapping.Add(entry.Value, dstTaskSetOwnerID); - } - - foreach (KeyValuePair entry in m_MigrationActiveIDLookup) - { - if (!destinationMigrationData.m_MigrationActiveIDLookup.TryGetValue(entry.Key, out uint dstActiveID)) - { - continue; - } - activeIDMapping.Add(entry.Value, dstActiveID); - } - + destinationWorldDataMap = new DestinationWorldDataMap(m_MigrationTaskSetOwnerIDLookup, + destinationMigrationData.m_MigrationTaskSetOwnerIDLookup, + m_MigrationActiveIDLookup, + destinationMigrationData.m_MigrationActiveIDLookup); - destinationWorldDataMap = new DestinationWorldDataMap(taskSetOwnerIDMapping, activeIDMapping); m_DestinationWorldDataMaps.Add(destinationWorld, destinationWorldDataMap); } return destinationWorldDataMap; diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 9c2cc6e7..4970875f 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -15,7 +15,7 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver //TODO: #86 - Revisit with Entities 1.0 for "Create Before/After" [UpdateInGroup(typeof(InitializationSystemGroup), OrderFirst = true)] internal partial class TaskDriverManagementSystem : AbstractAnvilSystemBase, - IMigrationObserver + IWorldMigrationObserver { private readonly Dictionary m_EntityProxyDataSourcesByType; private readonly HashSet m_AllTaskDrivers; @@ -61,7 +61,7 @@ protected override void OnCreate() { base.OnCreate(); WorldEntityMigrationSystem worldEntityMigrationSystem = World.GetOrCreateSystem(); - worldEntityMigrationSystem.AddMigrationObserver(this); + worldEntityMigrationSystem.RegisterMigrationObserver(this); } protected override void OnStartRunning() @@ -236,7 +236,7 @@ protected sealed override void OnUpdate() // MIGRATION //************************************************************************************************************* - public JobHandle MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) + JobHandle IWorldMigrationObserver.MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { TaskDriverManagementSystem destinationTaskDriverManagementSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldTaskDriverManagementSystemExists(destinationWorld, destinationTaskDriverManagementSystem); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs index 450175a3..b61762f3 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs @@ -107,7 +107,7 @@ public void Execute() //If we don't exist in the new world then we stayed in this world and we need to rewrite ourselves //to our own stream - if (!instanceID.Entity.IfEntityIsRemapped(ref m_RemapArray, out Entity remappedEntity)) + if (!instanceID.Entity.TryGetRemappedEntity(ref m_RemapArray, out Entity remappedEntity)) { currentLaneWriter.Write(ref instanceID); continue; diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs index eb1fe390..8c20625b 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/CancelProgressDataSource.cs @@ -49,6 +49,7 @@ public override JobHandle MigrateTo( ref NativeArray remapArray, DestinationWorldDataMap destinationWorldDataMap) { + //TODO: Optimization by using a list of entities that moved and iterating through that instead. See: https://github.com/decline-cookies/anvil-unity-dots/pull/232#discussion_r1181717999 int index = 0; foreach (KeyValuePair entry in ActiveDataLookupByID) { @@ -148,7 +149,7 @@ public void Execute() { EntityProxyInstanceID currentID = currentEntries.Keys[i]; //If we don't exist in the new world, we can just skip, we stayed in this world - if (!currentID.Entity.IfEntityIsRemapped(ref m_RemapArray, out Entity remappedEntity)) + if (!currentID.Entity.TryGetRemappedEntity(ref m_RemapArray, out Entity remappedEntity)) { continue; } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index 2ebcd5e5..a19df4cf 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -15,7 +15,7 @@ internal class EntityProxyDataSource : AbstractDataSource>(); + WorldEntityMigrationSystem.RegisterForEntityPatching>(); EntityProxyInstanceWrapper.Debug_EnsureOffsetsAreCorrect(); } @@ -135,7 +135,7 @@ public void Execute() //If we don't exist in the new world then we stayed in this world and we need to rewrite ourselves //to our own stream - if (!instanceID.Entity.IfEntityIsRemapped(ref m_RemapArray, out Entity remappedEntity)) + if (!instanceID.Entity.TryGetRemappedEntity(ref m_RemapArray, out Entity remappedEntity)) { currentLaneWriter.Write(ref instance); continue; diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index 2427cba8..e6075a82 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -14,7 +14,7 @@ internal class TaskSet : AbstractAnvilBase { private readonly List m_DataStreamsWithExplicitCancellation; private readonly Dictionary m_PublicDataStreamsByType; - private readonly Dictionary m_EntityPersistentDataByType; + private readonly Dictionary m_MigratableEntityPersistentDataByType; private readonly List m_JobConfigs; private readonly HashSet m_JobConfigSchedulingDelegates; @@ -43,7 +43,7 @@ public TaskSet(ITaskSetOwner taskSetOwner) m_DataStreamsWithExplicitCancellation = new List(); m_PublicDataStreamsByType = new Dictionary(); - m_EntityPersistentDataByType = new Dictionary(); + m_MigratableEntityPersistentDataByType = new Dictionary(); //TODO: #138 - Move all Cancellation aspects into one class to make it easier/nicer to work with @@ -59,7 +59,7 @@ protected override void DisposeSelf() { CancelRequestsContexts.Dispose(); } - m_EntityPersistentDataByType.DisposeAllValuesAndClear(); + m_MigratableEntityPersistentDataByType.DisposeAllValuesAndClear(); base.DisposeSelf(); } @@ -113,7 +113,7 @@ public EntityPersistentData GetOrCreateEntityPersistentData() where T : unmanaged, IEntityPersistentDataInstance { Type type = typeof(T); - if (!m_EntityPersistentDataByType.TryGetValue(type, out AbstractPersistentData persistentData)) + if (!m_MigratableEntityPersistentDataByType.TryGetValue(type, out IMigratablePersistentData persistentData)) { persistentData = CreateEntityPersistentData(); } @@ -125,7 +125,7 @@ public EntityPersistentData CreateEntityPersistentData() where T : unmanaged, IEntityPersistentDataInstance { EntityPersistentData entityPersistentData = new EntityPersistentData(); - m_EntityPersistentDataByType.Add(typeof(T), entityPersistentData); + m_MigratableEntityPersistentDataByType.Add(typeof(T), entityPersistentData); return entityPersistentData; } @@ -303,7 +303,7 @@ public void AddToMigrationLookup( CancelCompleteDataStream.ActiveID, migrationActiveIDLookup); - foreach (AbstractPersistentData entry in m_EntityPersistentDataByType.Values) + foreach (IMigratablePersistentData entry in m_MigratableEntityPersistentDataByType.Values) { persistentDataSystem.AddToMigrationLookup(parentPath, entry); } From fe2784b32da7cd80a1ce4b38e81777f59510aa48 Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Mon, 1 May 2023 16:18:09 -0400 Subject: [PATCH 14/16] Ready for re-review --- .../Entities/PersistentData/Data/AbstractPersistentData.cs | 2 -- .../PersistentData/Data/Interface/IMigratablePersistentData.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs index 3b475a35..1ccd80b3 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/AbstractPersistentData.cs @@ -1,8 +1,6 @@ using Anvil.CSharp.Core; using Anvil.Unity.DOTS.Jobs; using System.Runtime.CompilerServices; -using Unity.Collections; -using Unity.Entities; using Unity.Jobs; namespace Anvil.Unity.DOTS.Entities diff --git a/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs index 046f7bbe..ff125f04 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/Interface/IMigratablePersistentData.cs @@ -5,7 +5,7 @@ namespace Anvil.Unity.DOTS.Entities { - public interface IMigratablePersistentData : IDisposable + internal interface IMigratablePersistentData : IDisposable { public JobHandle MigrateTo(JobHandle dependsOn, IMigratablePersistentData destinationPersistentData, ref NativeArray remapArray); } From 21d5b1c706ee01cce8391db020b330c5583a0525 Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Tue, 2 May 2023 10:55:57 -0400 Subject: [PATCH 15/16] PR Comments --- ...er.cs => EntityWorldMigrationExtension.cs} | 16 +++++----- ... => EntityWorldMigrationExtension.cs.meta} | 0 ...ystem.cs => EntityWorldMigrationSystem.cs} | 32 +++++++++---------- ...eta => EntityWorldMigrationSystem.cs.meta} | 0 ...er.cs => IEntityWorldMigrationObserver.cs} | 4 +-- ... => IEntityWorldMigrationObserver.cs.meta} | 0 .../Data/EntityPersistentData.cs | 2 +- .../PersistentData/PersistentDataSystem.cs | 12 +++---- .../TaskDriver/TaskDriverManagementSystem.cs | 8 ++--- ...AbstractEntityProxyInstanceIDDataSource.cs | 4 +++ .../DataSource/EntityProxyDataSource.cs | 2 +- .../Entities/TaskDriver/TaskSet/TaskSet.cs | 10 +++--- 12 files changed, 48 insertions(+), 42 deletions(-) rename Scripts/Runtime/Entities/Lifecycle/Migration/{WorldEntityMigrationHelper.cs => EntityWorldMigrationExtension.cs} (88%) rename Scripts/Runtime/Entities/Lifecycle/Migration/{WorldEntityMigrationHelper.cs.meta => EntityWorldMigrationExtension.cs.meta} (100%) rename Scripts/Runtime/Entities/Lifecycle/Migration/{WorldEntityMigrationSystem.cs => EntityWorldMigrationSystem.cs} (87%) rename Scripts/Runtime/Entities/Lifecycle/Migration/{WorldEntityMigrationSystem.cs.meta => EntityWorldMigrationSystem.cs.meta} (100%) rename Scripts/Runtime/Entities/Lifecycle/Migration/{IWorldMigrationObserver.cs => IEntityWorldMigrationObserver.cs} (92%) rename Scripts/Runtime/Entities/Lifecycle/Migration/{IWorldMigrationObserver.cs.meta => IEntityWorldMigrationObserver.cs.meta} (100%) diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationExtension.cs similarity index 88% rename from Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs rename to Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationExtension.cs index a6245842..b25859a6 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationExtension.cs @@ -10,7 +10,7 @@ namespace Anvil.Unity.DOTS.Entities /// /// Helper class for when Entities are migrating from one to another. /// - public static class WorldEntityMigrationHelper + public static class EntityWorldMigrationExtension { //************************************************************************************************************* // MIGRATION @@ -18,7 +18,7 @@ public static class WorldEntityMigrationHelper /// /// Migrates Entities from this to the destination world with the provided query. - /// This will then handle notifying all s to have the chance to respond with + /// This will then handle notifying all s to have the chance to respond with /// custom migration work. /// NOTE: Use this instead of in order for migration callbacks /// to occur in non IComponentData @@ -28,8 +28,8 @@ public static class WorldEntityMigrationHelper /// The to select the Entities to migrate. public static void MoveEntitiesAndMigratableDataTo(this EntityManager srcEntityManager, World destinationWorld, EntityQuery entitiesToMigrateQuery) { - WorldEntityMigrationSystem worldEntityMigrationSystem = srcEntityManager.World.GetOrCreateSystem(); - worldEntityMigrationSystem.MoveEntitiesAndMigratableDataTo(destinationWorld, entitiesToMigrateQuery); + EntityWorldMigrationSystem entityWorldMigrationSystem = srcEntityManager.World.GetOrCreateSystem(); + entityWorldMigrationSystem.MoveEntitiesAndMigratableDataTo(destinationWorld, entitiesToMigrateQuery); } //************************************************************************************************************* @@ -66,7 +66,7 @@ public static bool TryGetRemappedEntity( /// The Unity provided remap array /// The type of struct to patch /// - /// Occurs if this type was not registered via + /// Occurs if this type was not registered via /// [BurstCompatible] public static unsafe void PatchEntityReferences(this ref T instance, ref NativeArray remapArray) @@ -75,9 +75,9 @@ public static unsafe void PatchEntityReferences(this ref T instance, ref Nati long typeHash = BurstRuntime.GetHashCode64(); //Easy way to check if we remembered to register our type. Unfortunately it's a lot harder to figure out which type is missing due to the hash //but usually you're going to run into this right away and be able to figure it out. Not using the actual Type class so we can Burst this. - if (!WorldEntityMigrationSystem.SharedTypeOffsetInfo.REF.Data.TryGetValue(typeHash, out WorldEntityMigrationSystem.TypeOffsetInfo typeOffsetInfo)) + if (!EntityWorldMigrationSystem.SharedTypeOffsetInfo.REF.Data.TryGetValue(typeHash, out EntityWorldMigrationSystem.TypeOffsetInfo typeOffsetInfo)) { - throw new InvalidOperationException($"Tried to patch type with BurstRuntime hash of {typeHash} but it wasn't registered. Did you call {nameof(WorldEntityMigrationSystem.RegisterForEntityPatching)}?"); + throw new InvalidOperationException($"Tried to patch type with BurstRuntime hash of {typeHash} but it wasn't registered. Did you call {nameof(EntityWorldMigrationSystem.RegisterForEntityPatching)}?"); } //If there's nothing to remap, we'll just return @@ -90,7 +90,7 @@ public static unsafe void PatchEntityReferences(this ref T instance, ref Nati //to remap to the new entity byte* instancePtr = (byte*)UnsafeUtility.AddressOf(ref instance); //Beginning of the list - TypeManager.EntityOffsetInfo* entityOffsetInfoPtr = (TypeManager.EntityOffsetInfo*)WorldEntityMigrationSystem.SharedEntityOffsetInfo.REF.Data; + TypeManager.EntityOffsetInfo* entityOffsetInfoPtr = (TypeManager.EntityOffsetInfo*)EntityWorldMigrationSystem.SharedEntityOffsetInfo.REF.Data; for (int i = typeOffsetInfo.EntityOffsetStartIndex; i < typeOffsetInfo.EntityOffsetEndIndex; ++i) { //Index into the list diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationExtension.cs.meta similarity index 100% rename from Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationHelper.cs.meta rename to Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationExtension.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationSystem.cs similarity index 87% rename from Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs rename to Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationSystem.cs index 6a68d4f8..63540411 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationSystem.cs @@ -12,21 +12,21 @@ namespace Anvil.Unity.DOTS.Entities { /// /// World specific system for handling Migration. - /// Register s here to be notified when Migration occurs + /// Register s here to be notified when Migration occurs /// /// NOTE: Use on this System instead of directly interfacing with /// /// - public class WorldEntityMigrationSystem : AbstractDataSystem + public class EntityWorldMigrationSystem : AbstractDataSystem { - private readonly HashSet m_MigrationObservers; + private readonly HashSet m_MigrationObservers; // ReSharper disable once InconsistentNaming private NativeList m_Dependencies_ScratchPad; - public WorldEntityMigrationSystem() + public EntityWorldMigrationSystem() { - m_MigrationObservers = new HashSet(); + m_MigrationObservers = new HashSet(); m_Dependencies_ScratchPad = new NativeList(8, Allocator.Persistent); } @@ -37,34 +37,34 @@ protected override void OnDestroy() } /// - /// Adds a to be notified when Migration occurs and be given the chance to + /// Adds a to be notified when Migration occurs and be given the chance to /// respond to it. /// - /// The - public void RegisterMigrationObserver(IWorldMigrationObserver worldMigrationObserver) + /// The + public void RegisterMigrationObserver(IEntityWorldMigrationObserver entityWorldMigrationObserver) { - m_MigrationObservers.Add(worldMigrationObserver); + m_MigrationObservers.Add(entityWorldMigrationObserver); m_Dependencies_ScratchPad.ResizeUninitialized(m_MigrationObservers.Count); } /// - /// Removes a if it no longer wishes to be notified of when a Migration occurs. + /// Removes a if it no longer wishes to be notified of when a Migration occurs. /// - /// The - public void UnregisterMigrationObserver(IWorldMigrationObserver worldMigrationObserver) + /// The + public void UnregisterMigrationObserver(IEntityWorldMigrationObserver entityWorldMigrationObserver) { //We've already been destroyed, no need to unregister if (!m_Dependencies_ScratchPad.IsCreated) { return; } - m_MigrationObservers.Remove(worldMigrationObserver); + m_MigrationObservers.Remove(entityWorldMigrationObserver); m_Dependencies_ScratchPad.ResizeUninitialized(m_MigrationObservers.Count); } /// /// Migrates Entities from this to the destination world with the provided query. - /// This will then handle notifying all s to have the chance to respond with + /// This will then handle notifying all s to have the chance to respond with /// custom migration work. /// /// The to move Entities to. @@ -86,7 +86,7 @@ public void MoveEntitiesAndMigratableDataTo(World destinationWorld, EntityQuery private JobHandle NotifyObserversOfMigrateTo(World destinationWorld, ref NativeArray remapArray) { int index = 0; - foreach (IWorldMigrationObserver migrationObserver in m_MigrationObservers) + foreach (IEntityWorldMigrationObserver migrationObserver in m_MigrationObservers) { m_Dependencies_ScratchPad[index] = migrationObserver.MigrateTo(default, destinationWorld, ref remapArray); index++; @@ -156,7 +156,7 @@ private static unsafe void UpdateSharedStatics() /// /// Registers the Type that may contain Entity references so that it can be used with - /// to remap Entity references. + /// to remap Entity references. /// /// The type to register public static void RegisterForEntityPatching() diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationSystem.cs.meta similarity index 100% rename from Scripts/Runtime/Entities/Lifecycle/Migration/WorldEntityMigrationSystem.cs.meta rename to Scripts/Runtime/Entities/Lifecycle/Migration/EntityWorldMigrationSystem.cs.meta diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs b/Scripts/Runtime/Entities/Lifecycle/Migration/IEntityWorldMigrationObserver.cs similarity index 92% rename from Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs rename to Scripts/Runtime/Entities/Lifecycle/Migration/IEntityWorldMigrationObserver.cs index b7cbcde4..bd4361cf 100644 --- a/Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs +++ b/Scripts/Runtime/Entities/Lifecycle/Migration/IEntityWorldMigrationObserver.cs @@ -5,14 +5,14 @@ namespace Anvil.Unity.DOTS.Entities { /// - /// Implement and register with to receive a notification when + /// Implement and register with to receive a notification when /// Entities are being migrated from one to another. This will allow for scheduling jobs to /// handle any custom migration for data that refers to s but is not automatically handled by /// Unity. /// NOTE: The jobs that are scheduled will be completed immediately, but this allows for taking advantage of /// multiple cores. /// - public interface IWorldMigrationObserver + public interface IEntityWorldMigrationObserver { /// /// Implement to handle any custom migration work. diff --git a/Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs.meta b/Scripts/Runtime/Entities/Lifecycle/Migration/IEntityWorldMigrationObserver.cs.meta similarity index 100% rename from Scripts/Runtime/Entities/Lifecycle/Migration/IWorldMigrationObserver.cs.meta rename to Scripts/Runtime/Entities/Lifecycle/Migration/IEntityWorldMigrationObserver.cs.meta diff --git a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs index 6d7f8e44..d2bd8c8e 100644 --- a/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs +++ b/Scripts/Runtime/Entities/PersistentData/Data/EntityPersistentData.cs @@ -19,7 +19,7 @@ public EntityPersistentData() : base(new UnsafeParallelHashMap(ChunkUtil.MaxElementsPerChunk(), Allocator.Persistent)) { //We don't know what will be stored in here, but if there are Entity references we want to be able to patch them - WorldEntityMigrationSystem.RegisterForEntityPatching(); + EntityWorldMigrationSystem.RegisterForEntityPatching(); } protected override void DisposeData() diff --git a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs index a07c68cf..1bf9e0ed 100644 --- a/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs +++ b/Scripts/Runtime/Entities/PersistentData/PersistentDataSystem.cs @@ -10,7 +10,7 @@ namespace Anvil.Unity.DOTS.Entities { internal partial class PersistentDataSystem : AbstractDataSystem, - IWorldMigrationObserver + IEntityWorldMigrationObserver { private const string WORLD_PATH = "World"; private static readonly Dictionary s_ThreadPersistentData = new Dictionary(); @@ -21,7 +21,7 @@ internal partial class PersistentDataSystem : AbstractDataSystem, private readonly Dictionary m_MigrationPersistentDataLookup; // ReSharper disable once InconsistentNaming private NativeList m_MigrationDependencies_ScratchPad; - private WorldEntityMigrationSystem m_WorldEntityMigrationSystem; + private EntityWorldMigrationSystem m_EntityWorldMigrationSystem; public PersistentDataSystem() { @@ -34,8 +34,8 @@ public PersistentDataSystem() protected override void OnCreate() { base.OnCreate(); - m_WorldEntityMigrationSystem = World.GetOrCreateSystem(); - m_WorldEntityMigrationSystem.RegisterMigrationObserver(this); + m_EntityWorldMigrationSystem = World.GetOrCreateSystem(); + m_EntityWorldMigrationSystem.RegisterMigrationObserver(this); } protected override void OnDestroy() @@ -48,7 +48,7 @@ protected override void OnDestroy() s_ThreadPersistentData.DisposeAllValuesAndClear(); } - m_WorldEntityMigrationSystem.UnregisterMigrationObserver(this); + m_EntityWorldMigrationSystem.UnregisterMigrationObserver(this); base.OnDestroy(); } @@ -82,7 +82,7 @@ public EntityPersistentData GetOrCreateEntityPersistentData() // MIGRATION //************************************************************************************************************* - JobHandle IWorldMigrationObserver.MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) + JobHandle IEntityWorldMigrationObserver.MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { PersistentDataSystem destinationPersistentDataSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldPersistentDataSystemExists(destinationWorld, destinationPersistentDataSystem); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs index 4970875f..4addc767 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskDriverManagementSystem.cs @@ -15,7 +15,7 @@ namespace Anvil.Unity.DOTS.Entities.TaskDriver //TODO: #86 - Revisit with Entities 1.0 for "Create Before/After" [UpdateInGroup(typeof(InitializationSystemGroup), OrderFirst = true)] internal partial class TaskDriverManagementSystem : AbstractAnvilSystemBase, - IWorldMigrationObserver + IEntityWorldMigrationObserver { private readonly Dictionary m_EntityProxyDataSourcesByType; private readonly HashSet m_AllTaskDrivers; @@ -60,8 +60,8 @@ public TaskDriverManagementSystem() protected override void OnCreate() { base.OnCreate(); - WorldEntityMigrationSystem worldEntityMigrationSystem = World.GetOrCreateSystem(); - worldEntityMigrationSystem.RegisterMigrationObserver(this); + EntityWorldMigrationSystem entityWorldMigrationSystem = World.GetOrCreateSystem(); + entityWorldMigrationSystem.RegisterMigrationObserver(this); } protected override void OnStartRunning() @@ -236,7 +236,7 @@ protected sealed override void OnUpdate() // MIGRATION //************************************************************************************************************* - JobHandle IWorldMigrationObserver.MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) + JobHandle IEntityWorldMigrationObserver.MigrateTo(JobHandle dependsOn, World destinationWorld, ref NativeArray remapArray) { TaskDriverManagementSystem destinationTaskDriverManagementSystem = destinationWorld.GetOrCreateSystem(); Debug_EnsureOtherWorldTaskDriverManagementSystemExists(destinationWorld, destinationTaskDriverManagementSystem); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs index b61762f3..75dfcfb3 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/AbstractEntityProxyInstanceIDDataSource.cs @@ -88,6 +88,10 @@ public MigrateJob( public void Execute() { + //TODO: Optimization - Look into adding a RemoveSwapBack like function the UnsafeTypedStream. We could then avoid + //this copy to the array and the clear and instead just iterate through the stream and remove the instances we don't need. + //See: https://github.com/decline-cookies/anvil-unity-dots/pull/232#discussion_r1181714399 + //Can't modify while iterating so we collapse down to a single array and clean the underlying stream. //We'll build this stream back up if anything should still remain NativeArray currentInstanceArray = m_CurrentStream.ToNativeArray(Allocator.Temp); diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs index a19df4cf..52001d67 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/EntityProxyDataSource.cs @@ -15,7 +15,7 @@ internal class EntityProxyDataSource : AbstractDataSource>(); + EntityWorldMigrationSystem.RegisterForEntityPatching>(); EntityProxyInstanceWrapper.Debug_EnsureOffsetsAreCorrect(); } diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index e6075a82..b97c38e1 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -275,9 +275,11 @@ public void AddToMigrationLookup( Dictionary migrationActiveIDLookup, PersistentDataSystem persistentDataSystem) { + //TODO: Instead of the AssemblyQualifiedName, maybe just hash the AssemblyQualifiedName to get smaller paths. + foreach (KeyValuePair entry in m_PublicDataStreamsByType) { - AddToMigrationLookup(parentPath, entry.Key.GetReadableName(), entry.Value.ActiveID, migrationActiveIDLookup); + AddToMigrationLookup(parentPath, entry.Key.AssemblyQualifiedName, entry.Value.ActiveID, migrationActiveIDLookup); } foreach (ICancellableDataStream entry in m_DataStreamsWithExplicitCancellation) @@ -287,19 +289,19 @@ public void AddToMigrationLookup( AddToMigrationLookup( parentPath, - typeof(CancelRequestsDataStream).GetReadableName(), + typeof(CancelRequestsDataStream).AssemblyQualifiedName, CancelRequestsDataStream.ActiveID, migrationActiveIDLookup); AddToMigrationLookup( parentPath, - typeof(CancelProgressDataStream).GetReadableName(), + typeof(CancelProgressDataStream).AssemblyQualifiedName, CancelProgressDataStream.ActiveID, migrationActiveIDLookup); AddToMigrationLookup( parentPath, - typeof(CancelCompleteDataStream).GetReadableName(), + typeof(CancelCompleteDataStream).AssemblyQualifiedName, CancelCompleteDataStream.ActiveID, migrationActiveIDLookup); From 24e55f5a9d815016649c25a3db3db3e06789e50c Mon Sep 17 00:00:00 2001 From: Jon Keon Date: Tue, 2 May 2023 11:01:38 -0400 Subject: [PATCH 16/16] Fixing migration uniqueness --- .../Entities/TaskDriver/TaskSet/TaskSet.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs index b97c38e1..6f48bdd5 100644 --- a/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs +++ b/Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskSet.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Unity.Burst; using Unity.Collections; using Unity.Entities; +using UnityEngine.UI; namespace Anvil.Unity.DOTS.Entities.TaskDriver { @@ -275,33 +277,31 @@ public void AddToMigrationLookup( Dictionary migrationActiveIDLookup, PersistentDataSystem persistentDataSystem) { - //TODO: Instead of the AssemblyQualifiedName, maybe just hash the AssemblyQualifiedName to get smaller paths. - foreach (KeyValuePair entry in m_PublicDataStreamsByType) { - AddToMigrationLookup(parentPath, entry.Key.AssemblyQualifiedName, entry.Value.ActiveID, migrationActiveIDLookup); + AddToMigrationLookup(parentPath, BurstRuntime.GetHashCode64(entry.Key), entry.Value.ActiveID, migrationActiveIDLookup); } foreach (ICancellableDataStream entry in m_DataStreamsWithExplicitCancellation) { - AddToMigrationLookup(parentPath, $"{entry.InstanceType.GetReadableName()}-ExplicitCancel", entry.PendingCancelActiveID, migrationActiveIDLookup); + AddToMigrationLookup(parentPath, BurstRuntime.GetHashCode64(entry.InstanceType) ^ BurstRuntime.GetHashCode64(), entry.PendingCancelActiveID, migrationActiveIDLookup); } AddToMigrationLookup( parentPath, - typeof(CancelRequestsDataStream).AssemblyQualifiedName, + BurstRuntime.GetHashCode64(typeof(CancelRequestsDataStream)), CancelRequestsDataStream.ActiveID, migrationActiveIDLookup); AddToMigrationLookup( parentPath, - typeof(CancelProgressDataStream).AssemblyQualifiedName, + BurstRuntime.GetHashCode64(typeof(CancelProgressDataStream)), CancelProgressDataStream.ActiveID, migrationActiveIDLookup); AddToMigrationLookup( parentPath, - typeof(CancelCompleteDataStream).AssemblyQualifiedName, + BurstRuntime.GetHashCode64(typeof(CancelCompleteDataStream)), CancelCompleteDataStream.ActiveID, migrationActiveIDLookup); @@ -311,9 +311,9 @@ public void AddToMigrationLookup( } } - private void AddToMigrationLookup(string parentPath, string name, uint activeID, Dictionary migrationActiveIDLookup) + private void AddToMigrationLookup(string parentPath, long typeHash, uint activeID, Dictionary migrationActiveIDLookup) { - string path = $"{parentPath}-{name}"; + string path = $"{parentPath}-{typeHash}"; Debug_EnsureNoDuplicateMigrationData(path, migrationActiveIDLookup); migrationActiveIDLookup.Add(path, activeID); }