static void CheckAgainstFullConversion(World destinationWorld) { var dstEntityManager = destinationWorld.EntityManager; using (var fullConversionWorld = new World("FullConversion")) { using (var blobAssetStore = new BlobAssetStore()) { var conversionSettings = GameObjectConversionSettings.FromWorld(fullConversionWorld, blobAssetStore); conversionSettings.ConversionFlags = ConversionFlags; GameObjectConversionUtility.ConvertScene(SceneManager.GetActiveScene(), conversionSettings); const EntityManagerDifferOptions options = EntityManagerDifferOptions.IncludeForwardChangeSet | EntityManagerDifferOptions.ValidateUniqueEntityGuid; using (var blobAssetCache = new BlobAssetCache(Allocator.TempJob)) { EntityDiffer.PrecomputeBlobAssetCache(fullConversionWorld.EntityManager, EntityManagerDiffer.EntityGuidQueryDesc, blobAssetCache); using (var changes = EntityDiffer.GetChanges( dstEntityManager, fullConversionWorld.EntityManager, options, EntityManagerDiffer.EntityGuidQueryDesc, blobAssetCache, Allocator.TempJob )) { Assert.IsFalse(changes.AnyChanges, "Full conversion and incremental conversion do not match!"); } } } } }
public void EntityDiffer_GetChanges_CreateEntityAndSetComponentData_WithoutFastForward_ManagedComponents() { using (var differ = new EntityManagerDiffer(SrcEntityManager, Allocator.TempJob)) { var entity = SrcEntityManager.CreateEntity(typeof(EntityGuid), typeof(EcsTestData), typeof(EcsTestManagedComponent)); SrcEntityManager.SetComponentData(entity, CreateEntityGuid()); SrcEntityManager.SetComponentData(entity, new EcsTestData { value = 9 }); SrcEntityManager.SetComponentData(entity, new EcsTestManagedComponent { value = "SomeString" }); const EntityManagerDifferOptions options = EntityManagerDifferOptions.IncludeForwardChangeSet | EntityManagerDifferOptions.IncludeReverseChangeSet; using (var changes = differ.GetChanges(options, Allocator.Temp)) { // ForwardChanges defines all operations needed to go from the shadow state to the current state. Assert.IsTrue(changes.HasForwardChangeSet); Assert.AreEqual(0, changes.ForwardChangeSet.DestroyedEntityCount); Assert.AreEqual(1, changes.ForwardChangeSet.CreatedEntityCount); Assert.AreEqual(3, changes.ForwardChangeSet.AddComponents.Length); Assert.AreEqual(2, changes.ForwardChangeSet.SetComponents.Length); Assert.AreEqual(1, changes.ForwardChangeSet.SetManagedComponents.Length); // ReverseChanges defines all operations needed to go from the current state back to the last shadow state. (i.e. Undo) Assert.IsTrue(changes.HasReverseChangeSet); Assert.AreEqual(1, changes.ReverseChangeSet.DestroyedEntityCount); Assert.AreEqual(0, changes.ReverseChangeSet.CreatedEntityCount); Assert.AreEqual(0, changes.ReverseChangeSet.AddComponents.Length); Assert.AreEqual(0, changes.ReverseChangeSet.SetComponents.Length); Assert.AreEqual(0, changes.ReverseChangeSet.SetManagedComponents.Length); } // Since we did not fast forward the inner shadow world. We should be able to generate the exact same changes again. using (var changes = differ.GetChanges(options, Allocator.Temp)) { Assert.IsTrue(changes.HasForwardChangeSet); Assert.AreEqual(0, changes.ForwardChangeSet.DestroyedEntityCount); Assert.AreEqual(1, changes.ForwardChangeSet.CreatedEntityCount); Assert.AreEqual(3, changes.ForwardChangeSet.AddComponents.Length); Assert.AreEqual(2, changes.ForwardChangeSet.SetComponents.Length); Assert.AreEqual(1, changes.ForwardChangeSet.SetManagedComponents.Length); Assert.IsTrue(changes.HasReverseChangeSet); Assert.AreEqual(1, changes.ReverseChangeSet.DestroyedEntityCount); Assert.AreEqual(0, changes.ReverseChangeSet.CreatedEntityCount); Assert.AreEqual(0, changes.ReverseChangeSet.AddComponents.Length); Assert.AreEqual(0, changes.ReverseChangeSet.SetComponents.Length); Assert.AreEqual(0, changes.ReverseChangeSet.SetManagedComponents.Length); } } }
/// <summary> /// Generates a detailed change set for the world. /// All entities to be considered for diffing must have the <see cref="EntityGuid"/> component with a unique value. /// </summary> /// <remarks> /// The resulting <see cref="EntityChanges"/> must be disposed when no longer needed. /// </remarks> /// <param name="options">A set of options which can be toggled.</param> /// <param name="allocator">The allocator to use for the results object.</param> /// <returns>A set of changes for the world since the last fast-forward.</returns> public EntityChanges GetChanges(EntityManagerDifferOptions options, Allocator allocator) { var changes = EntityDiffer.GetChanges( srcEntityManager: m_SourceEntityManager, dstEntityManager: m_ShadowEntityManager, options, m_EntityQueryDesc, m_BlobAssetCache, allocator); return(changes); }
public void EntityDiffer_GetChanges_CreateEntityAndSetComponentData_WithFastForward_ManagedComponents() { using (var differ = new EntityManagerDiffer(SrcEntityManager, Allocator.TempJob)) { var entity = SrcEntityManager.CreateEntity(typeof(EntityGuid), typeof(EcsTestData), typeof(EcsTestManagedComponent)); var entityGuid = CreateEntityGuid(); SrcEntityManager.SetComponentData(entity, entityGuid); SrcEntityManager.SetComponentData(entity, new EcsTestData { value = 9 }); SrcEntityManager.SetComponentData(entity, new EcsTestManagedComponent { value = "SomeString" }); const EntityManagerDifferOptions options = EntityManagerDifferOptions.IncludeForwardChangeSet | EntityManagerDifferOptions.IncludeReverseChangeSet | EntityManagerDifferOptions.FastForwardShadowWorld; using (var changes = differ.GetChanges(options, Allocator.Temp)) { // Forward changes is all changes needed to go from the shadow state to the current state. Assert.IsTrue(changes.HasForwardChangeSet); Assert.AreEqual(0, changes.ForwardChangeSet.DestroyedEntityCount); Assert.AreEqual(1, changes.ForwardChangeSet.CreatedEntityCount); Assert.AreEqual(3, changes.ForwardChangeSet.AddComponents.Length); Assert.AreEqual(2, changes.ForwardChangeSet.SetComponents.Length); Assert.AreEqual(1, changes.ForwardChangeSet.SetManagedComponents.Length); // Reverse changes is all changes needed to go from the current state back to the last shadow state. (i.e. Undo) Assert.IsTrue(changes.HasReverseChangeSet); Assert.AreEqual(1, changes.ReverseChangeSet.DestroyedEntityCount); Assert.AreEqual(0, changes.ReverseChangeSet.CreatedEntityCount); Assert.AreEqual(0, changes.ReverseChangeSet.AddComponents.Length); Assert.AreEqual(0, changes.ReverseChangeSet.SetComponents.Length); Assert.AreEqual(0, changes.ReverseChangeSet.SetManagedComponents.Length); } // The inner shadow world was updated during the last call which means no new changes should be found. using (var changes = differ.GetChanges(options, Allocator.Temp)) { Assert.IsFalse(changes.AnyChanges); } } }
/// <summary> /// Generates a detailed change set for the world. /// All entities to be considered for diffing must have the <see cref="EntityGuid"/> component with a unique value. /// The resulting <see cref="EntityChanges"/> must be disposed when no longer needed. /// </summary> /// <param name="options">A set of options which can be toggled.</param> /// <param name="allocator">The allocator to use for the results object.</param> /// <returns>A Change set containing the differences between the two worlds.</returns> public EntityChanges GetChanges(EntityManagerDifferOptions options, Allocator allocator) { s_GetChangesMarker.Begin(); var changes = EntityManagerDifferUtility.GetChanges( m_SrcWorld.EntityManager, m_SrcWorldEntityQuery, m_ShadowWorld.EntityManager, m_ShadowWorldEntityQuery, m_TypeInfoStream, options, allocator); s_GetChangesMarker.End(); return(changes); }
/// <summary> /// Generates a detailed change set for the world. /// All entities to be considered for diffing must have the <see cref="EntityGuid"/> component with a unique value. /// </summary> /// <remarks> /// The resulting <see cref="EntityChanges"/> must be disposed when no longer needed. /// </remarks> /// <param name="options">A set of options which can be toggled.</param> /// <param name="allocator">The allocator to use for the results object.</param> /// <returns>A set of changes for the world since the last fast-forward.</returns> public EntityChanges GetChanges(EntityManagerDifferOptions options, Allocator allocator) { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (null == m_SourceEntityManager || null == m_ShadowEntityManager) { throw new ArgumentException($"The {nameof(EntityManagerDiffer)} has already been Disposed."); } #endif var changes = EntityDiffer.GetChanges( srcEntityManager: m_SourceEntityManager, dstEntityManager: m_ShadowEntityManager, options, m_EntityQueryDesc, m_BlobAssetCache, allocator); return(changes); }
/// <summary> /// Generates a change set between the given entity managers using the given options. /// </summary> /// <remarks> /// No assumptions are made about the relationship between the given worlds. This means a full /// deep compare is done to generate the change set. /// </remarks> /// <param name="srcEntityManager">The src world to compute changes from.</param> /// <param name="dstEntityManager">The dst world to compute changes to.</param> /// <param name="options">Options to specify to tailor the diffing based on specific requirements. See <see cref="EntityManagerDifferOptions"/> for more information.</param> /// <param name="allocator">Allocator to use for the returned set.</param> /// <returns>A set of changes between src and dst worlds.</returns> public static EntityChangeSet GetChanges( EntityManager srcEntityManager, EntityManager dstEntityManager, EntityManagerDifferOptions options, Allocator allocator) { using (var typeInfoStream = new TypeInfoStream(Allocator.TempJob)) { var changes = GetChanges( srcEntityManager, CreateQuery(srcEntityManager), dstEntityManager, CreateQuery(dstEntityManager), typeInfoStream, options, allocator); // @NOTE We are not disposing the changes object itself. // In this case since we only allocated the forward and we are returning it to the caller. return(changes.ForwardChangeSet); } }
/// <summary> /// Generates a detailed change set between <see cref="srcEntityManager"/> and <see cref="dstEntityManager"/>. /// All entities to be considered must have the <see cref="EntityGuid"/> component with a unique value. /// The resulting <see cref="Entities.EntityChanges"/> must be disposed when no longer needed. /// </summary> /// <remarks> /// When using the <see cref="EntityManagerDifferOptions.FastForwardShadowWorld"/> the destination world must be a direct ancestor to /// the source world, and must only be updated using this call or similar methods. There should be no direct changes to destination world. /// </remarks> internal static EntityChanges GetChanges( EntityManager srcEntityManager, EntityManager dstEntityManager, EntityManagerDifferOptions options, EntityQueryDesc entityQueryDesc, BlobAssetCache blobAssetCache, Allocator allocator) { #if ENABLE_PROFILER || UNITY_EDITOR s_GetChangesProfilerMarker.Begin(); #endif CheckEntityGuidComponent(entityQueryDesc); var changes = default(EntityChanges); if (options == EntityManagerDifferOptions.None) { return(changes); } srcEntityManager.CompleteAllJobs(); dstEntityManager.CompleteAllJobs(); var srcEntityQuery = srcEntityManager.CreateEntityQuery(entityQueryDesc); var dstEntityQuery = dstEntityManager.CreateEntityQuery(entityQueryDesc); // Gather a set of a chunks to consider for diffing in both the src and dst worlds. using (var srcChunks = srcEntityQuery.CreateArchetypeChunkArray(Allocator.TempJob, out var srcChunksJob)) using (var dstChunks = dstEntityQuery.CreateArchetypeChunkArray(Allocator.TempJob, out var dstChunksJob)) { JobHandle clearMissingReferencesJob = default; if (CheckOption(options, EntityManagerDifferOptions.ClearMissingReferences)) { // Opt-in feature. // This is a special user case for references to destroyed entities. // If entity is destroyed, any references to that entity will remain set but become invalid (i.e. broken). // This option ensures that references to non-existent entities will be explicitly set to Entity.Null which // will force it to be picked up in the change set. ClearMissingReferences(srcEntityManager, srcChunks, out clearMissingReferencesJob, srcChunksJob); } // @TODO NET_DOTS does not support JobHandle.CombineDependencies with 3 arguments. #if NET_DOTS var archetypeChunkChangesJobDependencies = CombineDependencies(srcChunksJob, dstChunksJob, clearMissingReferencesJob); #else var archetypeChunkChangesJobDependencies = JobHandle.CombineDependencies(srcChunksJob, dstChunksJob, clearMissingReferencesJob); #endif // Broad phased chunk comparison. using (var archetypeChunkChanges = GetArchetypeChunkChanges( srcChunks: srcChunks, dstChunks: dstChunks, allocator: Allocator.TempJob, jobHandle: out var archetypeChunkChangesJob, dependsOn: archetypeChunkChangesJobDependencies)) { // Explicitly sync at this point to parallelize subsequent jobs by chunk. archetypeChunkChangesJob.Complete(); // Gather a sorted set of entities based on which chunks have changes. using (var srcEntities = GetSortedEntitiesInChunk( archetypeChunkChanges.CreatedSrcChunks, Allocator.TempJob, jobHandle: out var srcEntitiesJob)) using (var dstEntities = GetSortedEntitiesInChunk( archetypeChunkChanges.DestroyedDstChunks, Allocator.TempJob, jobHandle: out var dstEntitiesJob)) using (var srcBlobAssets = GetReferencedBlobAssets( srcChunks, Allocator.TempJob, jobHandle: out var srcBlobAssetsJob)) using (var dstBlobAssets = blobAssetCache.BlobAssetBatch->ToNativeList(Allocator.TempJob)) { var duplicateEntityGuids = default(NativeList <DuplicateEntityGuid>); var forwardEntityChanges = default(EntityInChunkChanges); var reverseEntityChanges = default(EntityInChunkChanges); var forwardComponentChanges = default(ComponentChanges); var reverseComponentChanges = default(ComponentChanges); var forwardBlobAssetChanges = default(BlobAssetChanges); var reverseBlobAssetChanges = default(BlobAssetChanges); try { JobHandle getDuplicateEntityGuidsJob = default; JobHandle forwardChangesJob = default; JobHandle reverseChangesJob = default; if (CheckOption(options, EntityManagerDifferOptions.ValidateUniqueEntityGuid)) { // Guid validation will happen incrementally and only consider changed entities in the source world. duplicateEntityGuids = GetDuplicateEntityGuids( srcEntities, Allocator.TempJob, jobHandle: out getDuplicateEntityGuidsJob, dependsOn: srcEntitiesJob); } if (CheckOption(options, EntityManagerDifferOptions.IncludeForwardChangeSet)) { forwardEntityChanges = GetEntityInChunkChanges( srcEntityManager, dstEntityManager, srcEntities, dstEntities, Allocator.TempJob, jobHandle: out var forwardEntityChangesJob, dependsOn: JobHandle.CombineDependencies(srcEntitiesJob, dstEntitiesJob)); forwardComponentChanges = GetComponentChanges( forwardEntityChanges,
public static LiveLinkChangeSet UpdateLiveLink(Scene scene, Hash128 sceneGUID, ref LiveLinkDiffGenerator liveLinkData, int sceneDirtyID, LiveLinkMode mode, GUID configGUID, BuildConfiguration config) { //Debug.Log("ApplyLiveLink: " + scene.SceneName); int framesToRetainBlobAssets = RetainBlobAssetsSetting.GetFramesToRetainBlobAssets(config); var liveLinkEnabled = mode != LiveLinkMode.Disabled; if (liveLinkData != null && liveLinkData._LiveLinkEnabled != liveLinkEnabled) { liveLinkData.Dispose(); liveLinkData = null; } var unloadAllPreviousEntities = liveLinkData == null; if (liveLinkData == null) { liveLinkData = new LiveLinkDiffGenerator(scene, sceneGUID, configGUID, config, liveLinkEnabled); } else if (liveLinkData._Scene != scene || !ReferenceEquals(liveLinkData._buildConfiguration, config) || liveLinkData._buildConfigurationGUID != configGUID) { liveLinkData._Scene = scene; liveLinkData._buildConfigurationGUID = configGUID; liveLinkData._buildConfiguration = config; liveLinkData._RequestCleanConversion = true; } if (!liveLinkEnabled) { return(new LiveLinkChangeSet { UnloadAllPreviousEntities = unloadAllPreviousEntities, SceneName = scene.name, SceneGUID = sceneGUID, FramesToRetainBlobAssets = framesToRetainBlobAssets }); } var flags = GameObjectConversionUtility.ConversionFlags.AddEntityGUID | GameObjectConversionUtility.ConversionFlags.AssignName | GameObjectConversionUtility.ConversionFlags.GameViewLiveLink; if (mode == LiveLinkMode.LiveConvertSceneView) { flags |= GameObjectConversionUtility.ConversionFlags.SceneViewLiveLink; } if (mode == LiveLinkMode.LiveConvertStandalonePlayer) { flags |= GameObjectConversionUtility.ConversionFlags.IsBuildingForPlayer; } liveLinkData.Convert(flags); const EntityManagerDifferOptions options = EntityManagerDifferOptions.IncludeForwardChangeSet | EntityManagerDifferOptions.FastForwardShadowWorld | EntityManagerDifferOptions.ValidateUniqueEntityGuid | EntityManagerDifferOptions.ClearMissingReferences; var changes = new LiveLinkChangeSet { Changes = liveLinkData._LiveLinkDiffer.GetChanges(options, Allocator.TempJob).ForwardChangeSet, UnloadAllPreviousEntities = unloadAllPreviousEntities, SceneName = scene.name, SceneGUID = sceneGUID, FramesToRetainBlobAssets = framesToRetainBlobAssets }; #if !UNITY_2020_2_OR_NEWER liveLinkData.LiveLinkDirtyID = sceneDirtyID; #endif // convertedEntityManager.Debug.CheckInternalConsistency(); return(changes); }
internal void DebugIncrementalConversion() { if (!_IncrementalConversionDebug.NeedsUpdate) { return; } _IncrementalConversionDebug.NeedsUpdate = false; var flags = _IncrementalConversionDebug.LastConversionFlags; using (DebugConversionMarker.Auto()) { // use this to compare the results of incremental conversion with the results of a clean conversion. var settings = PrepareConversion(_IncrementalConversionDebug.World, flags, _buildConfigurationGUID, _buildConfiguration); GameObjectConversionUtility.InitializeIncrementalConversion(_Scene, settings).Dispose(); AddMissingData(_IncrementalConversionDebug.World, _IncrementalConversionDebug.MissingSceneQuery, _IncrementalConversionDebug.MissingRenderDataQuery); const EntityManagerDifferOptions options = EntityManagerDifferOptions.IncludeForwardChangeSet | EntityManagerDifferOptions.ValidateUniqueEntityGuid | EntityManagerDifferOptions.UseReferentialEquality; unsafe { if (_IncrementalConversionDebug.BlobAssets.BlobAssetBatch != null) { _IncrementalConversionDebug.BlobAssets.Dispose(); } } _IncrementalConversionDebug.BlobAssets = new BlobAssetCache(Allocator.Persistent); EntityDiffer.PrecomputeBlobAssetCache(_ConvertedWorld.EntityManager, EntityManagerDiffer.EntityGuidQueryDesc, _IncrementalConversionDebug.BlobAssets); var changes = EntityDiffer.GetChanges( _IncrementalConversionDebug.World.EntityManager, _ConvertedWorld.EntityManager, options, EntityManagerDiffer.EntityGuidQueryDesc, _IncrementalConversionDebug.BlobAssets, Allocator.TempJob ); using (changes) { if (!changes.AnyChanges) { return; } var fwdChanges = changes.ForwardChangeSet; #if !UNITY_DISABLE_MANAGED_COMPONENTS { // Remove all companion link object changes. // Companion objects will always be different between different conversions, so this is // absolutely expected. // It is unlikely that a diff will ever only consist of changes to hybrid components, and even // in that case pointing out that the companion link changed is not exactly helpful for the user // either. var managedComponents = fwdChanges.SetManagedComponents; int numCompanionLinkObjects = 0; var types = fwdChanges.TypeHashes; var companionLinkIndex = TypeManager.GetTypeIndex <CompanionLink>(); int last = managedComponents.Length - 1; for (int i = last; i >= 0; i--) { // We need to go through the type index to correctly handle null Companion Links int packedTypeIdx = managedComponents[i].Component.PackedTypeIndex; var idx = TypeManager.GetTypeIndexFromStableTypeHash(types[packedTypeIdx].StableTypeHash); if (idx == companionLinkIndex) { managedComponents[i] = managedComponents[last - numCompanionLinkObjects]; numCompanionLinkObjects += 1; } } if (numCompanionLinkObjects > 0) { // throw away the companion link changes Array.Resize(ref managedComponents, last + 1 - numCompanionLinkObjects); fwdChanges = new EntityChangeSet(fwdChanges.CreatedEntityCount, fwdChanges.DestroyedEntityCount, fwdChanges.Entities, fwdChanges.TypeHashes, fwdChanges.Names, fwdChanges.AddComponents, fwdChanges.RemoveComponents, fwdChanges.SetComponents, fwdChanges.ComponentData, fwdChanges.EntityReferenceChanges, fwdChanges.BlobAssetReferenceChanges, managedComponents, // <- this changes fwdChanges.SetSharedComponents, fwdChanges.LinkedEntityGroupAdditions, fwdChanges.LinkedEntityGroupRemovals, fwdChanges.CreatedBlobAssets, fwdChanges.DestroyedBlobAssets, fwdChanges.BlobAssetData); if (!fwdChanges.HasChanges) { return; } } } #endif _RequestCleanConversion = true; var sb = new StringBuilder(); fwdChanges.PrintSummary(sb); var errorString = "The result of incrementally converting changes and a clean conversion didn't match, are you missing some dependencies?\n" + "This is what was added/removed/changed by the clean conversion relative to the incremental conversion:\n" + sb; if (LiveConversionSettings.TreatIncrementalConversionFailureAsError) { throw new Exception(errorString); } Debug.LogWarning(errorString); } } }
public void UndoRedoPrefabInstancesWithRelationship() { const int instanceCount = 10; var srcPrefabRootGuid = CreateEntityGuid(); var srcPrefabLevel0Guid = CreateEntityGuid(); var prefabLevel1Guid = CreateEntityGuid(); const EntityManagerDifferOptions options = EntityManagerDifferOptions.IncludeForwardChangeSet | EntityManagerDifferOptions.FastForwardShadowWorld; using (var differ = new EntityManagerDiffer(SrcEntityManager, Allocator.TempJob)) { // Create Root Prefab entity with a Guid component var srcPrefabRoot = SrcEntityManager.CreateEntity(typeof(HierarchyComponent), typeof(Prefab), typeof(LinkedEntityGroup)); SrcEntityManager.AddComponentData(srcPrefabRoot, srcPrefabRootGuid); SrcEntityManager.GetBuffer <LinkedEntityGroup>(srcPrefabRoot).Add(srcPrefabRoot); PushChanges(differ, DstEntityManager); using (var changes = differ.GetChanges(options, Allocator.TempJob)) { EntityPatcher.ApplyChangeSet(DstEntityManager, changes.ForwardChangeSet); } // Instantiate root prefab in dst world 10 times var dstPrefabRoot = GetEntity(DstEntityManager, srcPrefabRootGuid); var dstInstanceRoots = new Entity[instanceCount]; for (var i = 0; i != dstInstanceRoots.Length; i++) { var dstInstanceRoot = DstEntityManager.Instantiate(dstPrefabRoot); dstInstanceRoots[i] = dstInstanceRoot; Assert.AreEqual(1, DstEntityManager.GetBuffer <LinkedEntityGroup>(dstInstanceRoot).Length); Assert.AreEqual(dstInstanceRoot, DstEntityManager.GetBuffer <LinkedEntityGroup>(dstInstanceRoot)[0].Value); } // Add level 0 entity to the prefab var srcLevel0Prefab = SrcEntityManager.CreateEntity(typeof(HierarchyComponent), typeof(Prefab)); SrcEntityManager.AddComponentData(srcLevel0Prefab, srcPrefabLevel0Guid); SrcEntityManager.GetBuffer <LinkedEntityGroup>(srcPrefabRoot).Add(srcLevel0Prefab); SrcEntityManager.SetComponentData(srcPrefabRoot, new HierarchyComponent { Parent = Entity.Null, Child = srcLevel0Prefab }); SrcEntityManager.SetComponentData(srcLevel0Prefab, new HierarchyComponent { Parent = srcPrefabRoot, Child = Entity.Null }); // Synchronize worlds, we now should have 10 instances in the world along with a level0 for each // and hierarchy matching the relationships create PushChanges(differ, DstEntityManager); for (var i = 0; i != dstInstanceRoots.Length; i++) { var dstInstanceRoot = dstInstanceRoots[i]; var dstInstanceGroup = DstEntityManager.GetBuffer <LinkedEntityGroup>(dstInstanceRoot); var dstInstanceLevel0 = dstInstanceGroup[1].Value; Assert.AreEqual(2, dstInstanceGroup.Length); Assert.AreEqual(dstInstanceRoot, dstInstanceGroup[0].Value); Assert.AreEqual(dstInstanceLevel0, dstInstanceGroup[1].Value); var hierarchyLevel0 = DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel0); var hierarchyRoot = DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceRoot); Assert.AreEqual(dstInstanceRoot, hierarchyLevel0.Parent); Assert.AreEqual(dstInstanceLevel0, hierarchyRoot.Child); } // Add level 1 entity to the prefab var srcLevel1Prefab = SrcEntityManager.CreateEntity(typeof(HierarchyComponent), typeof(Prefab)); SrcEntityManager.AddComponentData(srcLevel1Prefab, prefabLevel1Guid); SrcEntityManager.GetBuffer <LinkedEntityGroup>(srcPrefabRoot).Add(srcLevel1Prefab); SrcEntityManager.SetComponentData(srcLevel0Prefab, new HierarchyComponent { Parent = srcPrefabRoot, Child = srcLevel1Prefab }); SrcEntityManager.SetComponentData(srcLevel1Prefab, new HierarchyComponent { Parent = srcLevel0Prefab, Child = Entity.Null }); // Synchronize worlds, we now should have 10 instances of level 1 linked to their matching level 0 PushChanges(differ, DstEntityManager); for (var i = 0; i != dstInstanceRoots.Length; i++) { var dstRootInstance = dstInstanceRoots[i]; var dstInstanceGroup = DstEntityManager.GetBuffer <LinkedEntityGroup>(dstRootInstance); Assert.AreEqual(3, dstInstanceGroup.Length); Assert.AreEqual(dstRootInstance, dstInstanceGroup[0].Value); var dstInstanceLevel0 = dstInstanceGroup[1].Value; var dstInstanceLevel1 = dstInstanceGroup[2].Value; Assert.AreEqual(dstInstanceLevel0, DstEntityManager.GetComponentData <HierarchyComponent>(dstRootInstance).Child); Assert.AreEqual(dstRootInstance, DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel0).Parent); Assert.AreEqual(dstInstanceLevel1, DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel0).Child); Assert.AreEqual(dstInstanceLevel0, DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel1).Parent); } // Remove level 1 entity from the prefab SrcEntityManager.GetBuffer <LinkedEntityGroup>(srcPrefabRoot).RemoveAt(2); SrcEntityManager.DestroyEntity(srcLevel1Prefab); // Fix the hierarchy of level 0 to remove the link to level 1 SrcEntityManager.SetComponentData(srcLevel0Prefab, new HierarchyComponent { Parent = srcPrefabRoot, Child = Entity.Null }); // Synchronize worlds, destination world should no longer have instances of level 1 PushChanges(differ, DstEntityManager); for (var i = 0; i != dstInstanceRoots.Length; i++) { var dstRootInstance = dstInstanceRoots[i]; var dstInstanceGroup = DstEntityManager.GetBuffer <LinkedEntityGroup>(dstRootInstance); Assert.AreEqual(2, dstInstanceGroup.Length); Assert.AreEqual(dstRootInstance, dstInstanceGroup[0].Value); var dstInstanceLevel1 = dstInstanceGroup[1].Value; Assert.AreEqual(dstInstanceLevel1, DstEntityManager.GetComponentData <HierarchyComponent>(dstRootInstance).Child); Assert.AreEqual(dstRootInstance, DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel1).Parent); Assert.AreEqual(Entity.Null, DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel1).Child); } // Add again level 1 as a Child of level 0 srcLevel1Prefab = SrcEntityManager.CreateEntity(typeof(HierarchyComponent), typeof(Prefab)); SrcEntityManager.AddComponentData(srcLevel1Prefab, prefabLevel1Guid); SrcEntityManager.GetBuffer <LinkedEntityGroup>(srcPrefabRoot).Add(srcLevel1Prefab); SrcEntityManager.SetComponentData(srcLevel0Prefab, new HierarchyComponent { Parent = srcPrefabRoot, Child = srcLevel1Prefab }); SrcEntityManager.SetComponentData(srcLevel1Prefab, new HierarchyComponent { Parent = srcLevel0Prefab, Child = Entity.Null }); PushChanges(differ, DstEntityManager); for (var i = 0; i != dstInstanceRoots.Length; i++) { var dstRootInstance = dstInstanceRoots[i]; var dstInstanceGroup = DstEntityManager.GetBuffer <LinkedEntityGroup>(dstRootInstance); Assert.AreEqual(3, dstInstanceGroup.Length); Assert.AreEqual(dstRootInstance, dstInstanceGroup[0].Value); var dstInstanceLevel0 = dstInstanceGroup[1].Value; var dstInstanceLevel1 = dstInstanceGroup[2].Value; Assert.AreEqual(dstInstanceLevel0, DstEntityManager.GetComponentData <HierarchyComponent>(dstRootInstance).Child); Assert.AreEqual(dstRootInstance, DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel0).Parent); Assert.AreEqual(dstInstanceLevel1, DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel0).Child); Assert.AreEqual(dstInstanceLevel0, DstEntityManager.GetComponentData <HierarchyComponent>(dstInstanceLevel1).Parent); } } }
/// <summary> /// Generates a detailed change set between <see cref="srcEntityManager"/> and <see cref="dstEntityManager"/>. /// All entities to be considered must have the <see cref="EntityGuid"/> component with a unique value. /// The resulting <see cref="EntityChanges"/> must be disposed when no longer needed. /// </summary> /// <remarks> /// For performance the <see cref="dstToSrcSequenceNumbers"/> is used to map chunks between the given worlds. If this is not provided, or is empty /// the tracker must evaluate all chunks, entities, and component data. /// /// When using the <see cref="EntityManagerDifferOptions.FastForwardShadowWorld"/> the destination world must be a direct ancestor to /// the source world, and must only be updated using this call or similar methods. There should be no direct changes to destination world. /// </remarks> internal static unsafe EntityChanges GetChanges( EntityManager srcEntityManager, EntityQuery srcEntityQuery, EntityManager dstEntityManager, EntityQuery dstEntityQuery, TypeInfoStream typeInfoStream, EntityManagerDifferOptions options, Allocator allocator) { var changes = new EntityChanges(); srcEntityManager.CompleteAllJobs(); dstEntityManager.CompleteAllJobs(); if (options != EntityManagerDifferOptions.None) { var includeForwardChangeSet = (options & EntityManagerDifferOptions.IncludeForwardChangeSet) == EntityManagerDifferOptions.IncludeForwardChangeSet; var includeReverseChangeSet = (options & EntityManagerDifferOptions.IncludeReverseChangeSet) == EntityManagerDifferOptions.IncludeReverseChangeSet; var fastForwardShadowWorld = (options & EntityManagerDifferOptions.FastForwardShadowWorld) == EntityManagerDifferOptions.FastForwardShadowWorld; var clearMissingReferences = (options & EntityManagerDifferOptions.ClearMissingReferences) == EntityManagerDifferOptions.ClearMissingReferences; var validateUniqueEntityGuids = (options & EntityManagerDifferOptions.ValidateUniqueEntityGuid) == EntityManagerDifferOptions.ValidateUniqueEntityGuid; // Query chunks that should be considered for change tracking. using (var srcChunks = srcEntityQuery.CreateArchetypeChunkArray(Allocator.TempJob)) using (var dstChunks = dstEntityQuery.CreateArchetypeChunkArray(Allocator.TempJob)) { if (clearMissingReferences) { ArchetypeChunkChangeUtility.ClearMissingReferences(srcChunks, srcEntityManager.EntityComponentStore, srcEntityManager.GlobalSystemVersion, typeInfoStream); } // Compare the chunks and get a set of all changed chunks. // @NOTE A modified chunk will appear as destroyed and then created. using (var archetypeChunkChanges = ArchetypeChunkChangeUtility.GetArchetypeChunkChanges(srcChunks, dstChunks, Allocator.TempJob)) { // If we have no chunk-level changes then there is no work to be done. if (archetypeChunkChanges.HasChanges) { if (includeForwardChangeSet || includeReverseChangeSet) { BuildComponentDataToEntityLookupTask <EntityGuid> buildSrcEntityGuidToEntityLookupTask = default; try { using (var buildSrcEntitiesTask = new BuildEntityInChunkWithComponentTask <EntityGuid>( archetypeChunkChanges.CreatedSrcChunks, Allocator.TempJob)) using (var buildDstEntitiesTask = new BuildEntityInChunkWithComponentTask <EntityGuid>( archetypeChunkChanges.DestroyedDstChunks, Allocator.TempJob)) { var handle = JobHandle.CombineDependencies(buildSrcEntitiesTask.Schedule(), buildDstEntitiesTask.Schedule()); if (validateUniqueEntityGuids) { // Validation is expensive since we must run over the entire world. This is opt in. buildSrcEntityGuidToEntityLookupTask = new BuildComponentDataToEntityLookupTask <EntityGuid>( srcEntityQuery.CalculateEntityCount(), Allocator.TempJob); handle = JobHandle.CombineDependencies(handle, buildSrcEntityGuidToEntityLookupTask.Schedule(srcChunks)); } handle.Complete(); if (validateUniqueEntityGuids && TryGetDuplicateComponents( buildSrcEntityGuidToEntityLookupTask.GetComponentDataToEntityMap(), out var duplicates)) { throw new DuplicateEntityGuidException(message: $"Found {duplicates.Length} {nameof(EntityGuid)} components that are shared by more than one Entity, " + $"see the {nameof(DuplicateEntityGuidException.DuplicateEntityGuids)} property for more information.") { DuplicateEntityGuids = duplicates }; } var srcState = new WorldState( srcEntityManager, buildSrcEntitiesTask.GetEntities() ); var dstState = new WorldState( dstEntityManager, buildDstEntitiesTask.GetEntities() ); BuildChangeSetTask buildForwardChangeSetTask = default; BuildChangeSetTask buildReverseChangeSetTask = default; try { JobHandle buildForwardChangeSetJob = default; JobHandle buildReverseChangeSetJob = default; if (includeForwardChangeSet) { buildForwardChangeSetTask = new BuildChangeSetTask(dstState, srcState, typeInfoStream, Allocator.TempJob); buildForwardChangeSetJob = buildForwardChangeSetTask.Schedule(); } if (includeReverseChangeSet) { buildReverseChangeSetTask = new BuildChangeSetTask(srcState, dstState, typeInfoStream, Allocator.TempJob); buildReverseChangeSetJob = buildReverseChangeSetTask.Schedule(); } JobHandle.CombineDependencies(buildForwardChangeSetJob, buildReverseChangeSetJob).Complete(); changes = new EntityChanges( includeForwardChangeSet ? buildForwardChangeSetTask.GetChangeSet(allocator) : default, includeReverseChangeSet ? buildReverseChangeSetTask.GetChangeSet(allocator) : default