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!");
                        }
                    }
                }
            }
        }
Esempio n. 2
0
        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);
                }
            }
        }
Esempio n. 3
0
        /// <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);
        }
Esempio n. 4
0
        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);
                }
            }
        }
Esempio n. 5
0
        /// <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);
        }
Esempio n. 7
0
        /// <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);
            }
        }
Esempio n. 8
0
        /// <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);
                }
            }
        }
Esempio n. 12
0
        /// <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