/// <summary> /// Creates a stateful change tracker over the given world. /// </summary> /// <param name="srcWorld">The input world to track changes for.</param> /// <param name="allocator">Allocator used for the cached state.</param> public EntityManagerDiffer(World srcWorld, Allocator allocator) { m_SrcWorld = srcWorld ?? throw new ArgumentNullException(nameof(srcWorld)); m_ShadowWorld = new World(srcWorld.Name + " (Shadow)"); m_SrcWorldEntityQuery = EntityManagerDifferUtility.CreateQuery(srcWorld.EntityManager); m_ShadowWorldEntityQuery = EntityManagerDifferUtility.CreateQuery(m_ShadowWorld.EntityManager); m_TypeInfoStream = new TypeInfoStream(allocator); }
/// <summary> /// Clears out any references to non-existent entities. /// </summary> /// <remarks> /// This can potentially mutate the world and increment the changed version of chunks. /// </remarks> public static unsafe void ClearMissingReferences( NativeArray <ArchetypeChunk> chunks, EntityComponentStore *entityComponentStore, uint globalSystemVersion, TypeInfoStream typeInfoStream ) { new SetMissingEntityReferencesToNull { GlobalSystemVersion = globalSystemVersion, Chunks = chunks, EntityComponentStore = entityComponentStore, TypeInfoStream = typeInfoStream }.Schedule(chunks.Length, 64).Complete(); }
public unsafe BuildChangeSetTask(WorldState beforeState, WorldState afterState, TypeInfoStream typeInfoStream, Allocator allocator) { m_BeforeState = beforeState; m_AfterState = afterState; m_TypeInfoStream = typeInfoStream; m_CreatedEntities = new NativeList <EntityInChunkWithComponent <EntityGuid> >(1, allocator); m_ModifiedEntities = new NativeList <ModifiedEntity>(1, allocator); m_DestroyedEntities = new NativeList <EntityInChunkWithComponent <EntityGuid> >(1, allocator); m_PackedEntities = new PackedCollection <EntityGuid>(1, allocator); m_PackedStableTypeHashes = new PackedCollection <ComponentTypeHash>(1, allocator); m_AddComponents = new NativeList <PackedComponent>(1, allocator); m_RemoveComponents = new NativeList <PackedComponent>(1, allocator); m_ComponentDataStream = new ComponentDataStream(allocator); m_EntityReference = new NativeList <EntityReferenceChange>(1, allocator); m_LinkedEntityGroupAdditions = new NativeList <LinkedEntityGroupChange>(1, allocator); m_LinkedEntityGroupRemovals = new NativeList <LinkedEntityGroupChange>(1, allocator); m_BeforeEntityToEntityGuid = new EntityToComponentMap <EntityGuid>(m_BeforeState.EntityComponentStore, allocator); m_AfterEntityToEntityGuid = new EntityToComponentMap <EntityGuid>(m_AfterState.EntityComponentStore, allocator); IsCreated = true; }
/// <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="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