Boat Attack使用了Universal RP的许多新图形功能,可以用于探索 Universal RP 的使用方式和技巧。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

1892 行
91 KiB

//#define LOG_DIFF_ALL
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Unity.Assertions;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Profiling;
namespace Unity.Entities
{
public static class HashMapUtility
{
public static bool TryRemove<K, V>(this NativeMultiHashMap<K, V> h, K k, V v) where K : struct, IEquatable<K> where V : struct, IEquatable<V>
{
if (!h.TryGetFirstValue(k, out var candidate, out var iterator))
return false;
do
{
if (candidate.Equals(v))
{
h.Remove(iterator);
return true;
}
} while (h.TryGetNextValue(out candidate, ref iterator));
return false;
}
}
public struct ComponentDiff
{
public int EntityIndex;
public int TypeHashIndex;
}
[Serializable]
public struct EntityGuid : IComponentData, IEquatable<EntityGuid>, IComparable<EntityGuid>
{
public ulong a;
public ulong b;
public static readonly EntityGuid Null = new EntityGuid();
public static bool operator ==(EntityGuid lhs, EntityGuid rhs)
{
return lhs.a == rhs.a && lhs.b == rhs.b;
}
public static bool operator !=(EntityGuid lhs, EntityGuid rhs)
{
return !(lhs == rhs);
}
public override bool Equals(object obj)
{
return obj is EntityGuid entityGuid ? Equals(entityGuid) : false;
}
public bool Equals(EntityGuid other)
{
return a == other.a && b == other.b;
}
public override int GetHashCode()
{
unchecked
{
return (a.GetHashCode() * 397) ^ b.GetHashCode();
}
}
public int CompareTo(EntityGuid other)
{
if (a != other.a)
return a > other.a ? 1 : -1;
if (b != other.b)
return b > other.b ? 1 : -1;
return 0;
}
public override string ToString()
{
return $"{a:x16}{b:x16}";
}
}
public struct DataDiff
{
public int EntityIndex;
public int TypeHashIndex;
public int Offset;
public int SizeBytes;
public override string ToString()
{
return $"{EntityIndex} {TypeHashIndex} {Offset} {SizeBytes}";
}
}
public struct LinkedEntityGroupAddition
{
public EntityGuid RootGuid;
public EntityGuid ChildGuid;
}
public struct LinkedEntityGroupRemoval
{
public EntityGuid RootGuid;
public EntityGuid ChildGuid;
}
public struct DiffEntityPatch
{
public int EntityIndex;
public int TypeHashIndex;
public EntityGuid Guid;
public int Offset;
}
public struct SetSharedComponentDiff
{
public int EntityIndex;
public int TypeHashIndex;
public object BoxedSharedValue;
}
public static class DiffUtil
{
public static EntityQuery CreateAllChunksQuery(EntityManager manager)
{
var guidQuery = new[]
{
new EntityQueryDesc
{
All = new ComponentType[] {typeof(EntityGuid)},
Options = EntityQueryOptions.IncludePrefab | EntityQueryOptions.IncludeDisabled
}
};
return manager.CreateEntityQuery(guidQuery);
}
public static NativeArray<ArchetypeChunk> GetAllChunks(EntityManager manager)
{
var query = CreateAllChunksQuery(manager);
var chunks = query.CreateArchetypeChunkArray(Allocator.TempJob);
query.Dispose();
return chunks;
}
}
public struct WorldDiff : IDisposable
{
public NativeArray<ulong> TypeHashes;
public NativeArray<EntityGuid> Entities;
public NativeArray<NativeString64> EntityNames;
public NativeArray<byte> ComponentPayload;
// As consecutive counts in Entities
public int NewEntityCount;
public int DeletedEntityCount;
public NativeArray<ComponentDiff> AddComponents;
public NativeArray<ComponentDiff> RemoveComponents;
public NativeArray<DataDiff> SetCommands;
public NativeArray<DiffEntityPatch> EntityPatches;
public SetSharedComponentDiff[] SharedSetCommands;
public NativeArray<LinkedEntityGroupAddition> LinkedEntityGroupAdditions;
public NativeArray<LinkedEntityGroupRemoval> LinkedEntityGroupRemovals;
public void Dispose()
{
TypeHashes.Dispose();
Entities.Dispose();
EntityNames.Dispose();
ComponentPayload.Dispose();
AddComponents.Dispose();
RemoveComponents.Dispose();
SetCommands.Dispose();
EntityPatches.Dispose();
LinkedEntityGroupAdditions.Dispose();
LinkedEntityGroupRemovals.Dispose();
}
public bool HasChanges
{
get
{
return NewEntityCount != 0 || DeletedEntityCount != 0 || AddComponents.Length != 0 || RemoveComponents.Length != 0
|| SetCommands.Length != 0 || EntityPatches.Length != 0 || SharedSetCommands.Length != 0 || LinkedEntityGroupAdditions.Length != 0
|| LinkedEntityGroupRemovals.Length != 0;
}
}
}
internal unsafe struct DiffEntityData
{
public Chunk* Chunk;
public int Index;
}
internal unsafe struct DiffCreationData
{
public EntityGuid Guid;
public Chunk* Chunk;
public int Index;
}
internal unsafe struct DiffModificationData
{
public EntityGuid Guid;
public Chunk* BeforeChunk;
public int BeforeIndex;
public Chunk* AfterChunk;
public int AfterIndex;
}
public static unsafe class WorldDiffer
{
static ProfilerMarker m_Diff = new ProfilerMarker("Diff");
static ProfilerMarker m_ApplyDiff = new ProfilerMarker("ApplyDiff");
/// <summary>
/// Calculate differences between previousStateShadowWorld and newState, and apply them to dstEntityWorld.
/// All entities to be considered for diffing must have an EntityGuid component with a unique GUID.
/// </summary>
/// <remarks>
/// previousShadowWorld must be a direct ancestor to the newState world, and must only be updated
/// using this call or similar WorldDifffer methods. No direct changes to previousStateShadowWorld are
/// allowed. After this call, previousShadowWorld will be updated to exactly match newState.
/// </remarks>
/// <param name="newState">The state to be updated to.</param>
/// <param name="previousStateShadowWorld">The shadow world (previous state) of the current state world.</param>
/// <param name="dstEntityWorld">The world to which to apply the updates to.</param>
public static void DiffAndApply(World newState, World previousStateShadowWorld, World dstEntityWorld)
{
m_Diff.Begin();
using (var diff = WorldDiffer.UpdateDiff(newState, previousStateShadowWorld, Allocator.TempJob))
{
m_Diff.End();
m_ApplyDiff.Begin();
ApplyDiff(dstEntityWorld, diff);
m_ApplyDiff.End();
}
}
/// <summary>
/// Create a diff of changes between beforeWorld and afterWorld. All entities to be considered for diffing
/// must have an EntityGuid component with a unique GUID so that they can be matched between worlds. The
/// resulting WorldDiff must be Disposed when no longer needed.
/// </summary>
/// <param name="afterWorld">The world containing the new state.</param>
/// <param name="beforeWorld">The world containing the old state.</param>
/// <param name="resultAllocator">The allocator to use when allocating WorldDiff data.</param>
/// <returns>A WorldDiff containing the differences between the two worlds.</returns>
public static WorldDiff CreateDiff(World afterWorld, World beforeWorld, Allocator resultAllocator)
{
return CreateDiffAndMaybeUpdate(afterWorld, beforeWorld, calculateDiff: true, updateBeforeWorld: false, resultAllocator);
}
/// <summary>
/// Create a diff of changes between beforeWorld and afterWorld, and efficiently update beforeWorld to
/// match afterWorld.
/// </summary>
/// <remarks>
/// UpdateDiff is only valid if beforeWorld was only ever updated via one of the WorldDiffer
/// methods that updates the "before" world and no other changes have been done to it. All entities to be
/// considered for diffing must have an EntityGuid component with a unique GUID so that they can be matched
/// between worlds. The resulting WorldDiff must be Disposed when no longer needed.
/// </remarks>
/// <param name="afterWorld">The world containing the new state.</param>
/// <param name="beforeWorld">The world containing the old state.</param>
/// <param name="resultAllocator">The allocator to use when allocating WorldDiff data.</param>
/// <returns>A WorldDiff containing the differences between the two worlds.</returns>
public static WorldDiff UpdateDiff(World afterWorld, World beforeWorld, Allocator resultAllocator)
{
return CreateDiffAndMaybeUpdate(afterWorld, beforeWorld, true, updateBeforeWorld: true, resultAllocator);
}
// Compute a diff between beforeWorld and afterWorld (afterWorld == Source, beforeWorld == ShadowWorld/dest)
// If updateBeforeWorld is true, beforeWorld must have only ever been updated via UpdateDiff or FastForward.
internal static WorldDiff CreateDiffAndMaybeUpdate(World afterWorld, World beforeWorld, bool calculateDiff,
bool updateBeforeWorld, Allocator resultAllocator)
{
if (afterWorld == null)
throw new ArgumentNullException("afterWorld");
if (beforeWorld == null)
throw new ArgumentNullException("beforeWorld");
if (resultAllocator == Allocator.Temp)
throw new ArgumentException("Allocator can not be Allocator.Temp. Use Allocator.TempJob instead.");
var afterEM = afterWorld.EntityManager;
var beforeEM = beforeWorld.EntityManager;
var afterChunks = DiffUtil.GetAllChunks(afterEM);
var beforeChunks = DiffUtil.GetAllChunks(beforeEM);
var visited = new NativeHashMap<ulong, byte>(afterChunks.Length * 3, Allocator.TempJob);
var cloneList = new NativeList<IntPtr>(afterChunks.Length, Allocator.TempJob);
var dropList = new NativeList<IntPtr>(afterChunks.Length, Allocator.TempJob);
var afterState = new NativeHashMap<EntityGuid, DiffEntityData>(4096, Allocator.TempJob);
var beforeState = new NativeHashMap<EntityGuid, DiffEntityData>(4096, Allocator.TempJob);
var afterEntityToGuid = new NativeHashMap<Entity, EntityGuid>(4096, Allocator.TempJob);
var afterGuidToEntity = new NativeHashMap<EntityGuid, Entity>(4096, Allocator.TempJob);
var beforeEntityToGuid = new NativeHashMap<Entity, EntityGuid>(4096, Allocator.TempJob);
var beforeChunkBySequenceNumber = new NativeHashMap<ulong, ArchetypeChunk>(4096, Allocator.TempJob);
for (int i = 0; i < beforeChunks.Length; ++i)
{
beforeChunkBySequenceNumber.TryAdd(beforeChunks[i].m_Chunk->SequenceNumber, beforeChunks[i]);
}
for (int i = 0; i < afterChunks.Length; ++i)
{
Chunk* afterChunk = afterChunks[i].m_Chunk;
ArchetypeChunk beforeArchetypeChunk;
beforeChunkBySequenceNumber.TryGetValue(afterChunk->SequenceNumber, out beforeArchetypeChunk);
Chunk* beforeChunk = beforeArchetypeChunk.m_Chunk;
bool b = visited.TryAdd(afterChunk->SequenceNumber, 1);
UnityEngine.Assertions.Assert.IsTrue(b);
if (calculateDiff)
{
CreateEntityToGuidLookup(afterChunk, afterEntityToGuid);
CreateGuidToEntityLookup(afterChunk, afterGuidToEntity);
}
if (beforeChunk == null)
{
cloneList.Add((IntPtr) afterChunk);
if (calculateDiff)
{
AddChunkEntitiesToState(afterChunk, afterState);
}
}
else if (ChunkChanged(afterChunk, beforeChunk))
{
cloneList.Add((IntPtr) afterChunk);
dropList.Add((IntPtr) beforeChunk);
if (calculateDiff)
{
AddChunkEntitiesToState(afterChunk, afterState);
AddChunkEntitiesToState(beforeChunk, beforeState);
}
}
}
beforeChunkBySequenceNumber.Dispose();
for (int i = 0; i < beforeChunks.Length; ++i)
{
Chunk* beforeChunk = beforeChunks[i].m_Chunk;
if (calculateDiff)
{
CreateEntityToGuidLookup(beforeChunk, beforeEntityToGuid);
}
byte ignored;
if (!visited.TryGetValue(beforeChunk->SequenceNumber, out ignored))
{
dropList.Add((IntPtr) beforeChunk);
if (calculateDiff)
{
AddChunkEntitiesToState(beforeChunk, beforeState);
}
}
}
WorldDiff result = default;
if (calculateDiff)
{
var removedEntities = new NativeList<EntityGuid>(1, Allocator.TempJob);
var createdEntities = new NativeList<DiffCreationData>(1, Allocator.TempJob);
var modifiedEntities = new NativeList<DiffModificationData>(1, Allocator.TempJob);
ComputeEntityDiff(beforeState, afterState, removedEntities, createdEntities, modifiedEntities);
var typeHashes = new NativeList<ulong>(1, Allocator.TempJob);
var typeHashLookup = new NativeHashMap<int, int>(1, Allocator.TempJob);
var entityList = new NativeList<EntityGuid>(1, Allocator.TempJob);
var entityNameList = new NativeList<NativeString64>(1, Allocator.TempJob);
var entityLookup = new NativeHashMap<EntityGuid, int>(1, Allocator.TempJob);
var removeComponents = new NativeList<ComponentDiff>(1, Allocator.TempJob);
var addComponents = new NativeList<ComponentDiff>(1, Allocator.TempJob);
var dataDiffs = new NativeList<DataDiff>(1, Allocator.TempJob);
var byteDiffs = new NativeList<byte>(1, Allocator.TempJob);
var sharedDiffs = new List<SetSharedComponentDiff>();
var entityPatches = new NativeList<DiffEntityPatch>(1, Allocator.TempJob);
using (var linkedEntityGroupAdds = new NativeList<LinkedEntityGroupAddition>(1, Allocator.TempJob))
using (var linkedEntityGroupRemoves = new NativeList<LinkedEntityGroupRemoval>(1, Allocator.TempJob))
{
BuildAddComponents(createdEntities, typeHashes, typeHashLookup, addComponents, dataDiffs, byteDiffs,
entityList, entityLookup, sharedDiffs, afterEM.ManagedComponentStore, entityPatches,
afterEntityToGuid, linkedEntityGroupAdds);
BuildComponentDiff(modifiedEntities, typeHashes, typeHashLookup, removeComponents, addComponents,
linkedEntityGroupAdds, linkedEntityGroupRemoves, dataDiffs, sharedDiffs, byteDiffs, entityList,
entityLookup, afterEM.ManagedComponentStore, beforeEM.ManagedComponentStore,
entityPatches,
afterEntityToGuid, beforeEntityToGuid);
// Add removed entities to back of entity list
entityList.AddRange(removedEntities);
for (var i = 0; i < entityList.Length; ++i)
{
afterGuidToEntity.TryGetValue(entityList[i], out var entity);
var truncatedName = new NativeString64();
#if UNITY_EDITOR
var untruncatedName = afterEM.GetName(entity);
truncatedName.CopyFrom(untruncatedName);
#endif
entityNameList.Add(truncatedName);
}
// Build output data
result.TypeHashes = typeHashes.ToArray(resultAllocator);
result.Entities = entityList.ToArray(resultAllocator);
result.EntityNames = entityNameList.ToArray(resultAllocator);
result.NewEntityCount = createdEntities.Length;
result.DeletedEntityCount = removedEntities.Length;
result.AddComponents = addComponents.ToArray(resultAllocator);
result.RemoveComponents = removeComponents.ToArray(resultAllocator);
result.SetCommands = dataDiffs.ToArray(resultAllocator);
result.EntityPatches = entityPatches.ToArray(resultAllocator);
result.ComponentPayload = byteDiffs.ToArray(resultAllocator);
result.SharedSetCommands = sharedDiffs.ToArray();
result.LinkedEntityGroupAdditions = linkedEntityGroupAdds.ToArray(resultAllocator);
result.LinkedEntityGroupRemovals = linkedEntityGroupRemoves.ToArray(resultAllocator);
entityPatches.Dispose();
entityLookup.Dispose();
entityNameList.Dispose();
entityList.Dispose();
byteDiffs.Dispose();
dataDiffs.Dispose();
addComponents.Dispose();
removeComponents.Dispose();
typeHashLookup.Dispose();
typeHashes.Dispose();
modifiedEntities.Dispose();
createdEntities.Dispose();
removedEntities.Dispose();
}
}
// If we're supposed to do the efficient update of the before world, perform
// the chunk-level operations to do so.
if (updateBeforeWorld)
{
var archetypeChanges = beforeEM.EntityComponentStore->BeginArchetypeChangeTracking();
// Drop all unused and modified chunks
for (int i = 0; i < dropList.Length; ++i)
{
EntityManagerMoveEntitiesUtility.DestroyChunkForDiffing((Chunk*) dropList[i],
beforeEM.EntityComponentStore, beforeEM.ManagedComponentStore);
}
// Clone all new and modified chunks
for (int i = 0; i < cloneList.Length; ++i)
{
EntityManagerMoveEntitiesUtility.CloneChunkForDiffing((Chunk*) cloneList[i],
afterEM.ManagedComponentStore,
beforeEM.EntityComponentStore, beforeEM.ManagedComponentStore);
}
var changedArchetypes = beforeEM.EntityComponentStore->EndArchetypeChangeTracking(archetypeChanges);
beforeEM.EntityGroupManager.AddAdditionalArchetypes(changedArchetypes);
}
afterGuidToEntity.Dispose();
afterEntityToGuid.Dispose();
beforeEntityToGuid.Dispose();
afterState.Dispose();
beforeState.Dispose();
visited.Dispose();
dropList.Dispose();
cloneList.Dispose();
beforeChunks.Dispose();
afterChunks.Dispose();
return result;
}
public static UInt64 Ordinal(Entity e)
{
UInt64 result = 0;
result |= (UInt32)e.Version;
result <<= 32;
result |= (UInt32)e.Index;
return result;
}
struct EntitySorter : IComparer<Entity>
{
public int Compare(Entity x, Entity y)
{
if(Ordinal(x) < Ordinal(y))
return -1;
if(Ordinal(x) > Ordinal(y))
return 1;
return 0;
}
}
static void BuildComponentDiff(NativeList<DiffModificationData> modifiedEntities,
NativeList<ulong> typeHashes,
NativeHashMap<int, int> typeHashLookup,
NativeList<ComponentDiff> removeComponents,
NativeList<ComponentDiff> addComponents,
NativeList<LinkedEntityGroupAddition> linkedEntityGroupAdds,
NativeList<LinkedEntityGroupRemoval> linkedEntityGroupRemoves,
NativeList<DataDiff> dataDiffs,
List<SetSharedComponentDiff> sharedComponentDiffs,
NativeList<byte> byteDiffs,
NativeList<EntityGuid> guidList,
NativeHashMap<EntityGuid, int> guidToIndex,
ManagedComponentStore ssharedComponentStore,
ManagedComponentStore dsharedComponentStore,
NativeList<DiffEntityPatch> entityPatches,
NativeHashMap<Entity, EntityGuid> SrcWorldEntityToGuid,
NativeHashMap<Entity, EntityGuid> DstWorldEntityToGuid)
{
var LinkedEntityGroupTypeIndex = TypeManager.GetTypeIndex<LinkedEntityGroup>();
for (int i = 0; i < modifiedEntities.Length; ++i)
{
var mod = modifiedEntities[i];
var beforeArch = mod.BeforeChunk->Archetype;
var afterArch = mod.AfterChunk->Archetype;
var entityIndex = GetExternalIndex(mod.Guid, guidList, guidToIndex);
for (int afterType = 1; afterType < afterArch->TypesCount; ++afterType)
{
var at = afterArch->Types[afterType];
int targetTypeIndex = at.TypeIndex;
var typeHashIndex = GetTypeHashIndex(targetTypeIndex, typeHashes, typeHashLookup);
int beforeType = ChunkDataUtility.GetIndexInTypeArray(beforeArch, afterArch->Types[afterType].TypeIndex);
if (-1 == beforeType)
{
addComponents.Add(new ComponentDiff {EntityIndex = entityIndex, TypeHashIndex = typeHashIndex});
if (at.IsSharedComponent)
{
// Also push through initial data wholesale.
object afterObject = GetSharedComponentObject(ssharedComponentStore, mod.AfterChunk, afterType, targetTypeIndex);
sharedComponentDiffs.Add(new SetSharedComponentDiff
{
BoxedSharedValue = afterObject,
EntityIndex = entityIndex,
TypeHashIndex = typeHashIndex,
});
}
else if (at.IsBuffer)
{
// Also push through initial data wholesale.
var afterHeader = (BufferHeader*)(mod.AfterChunk->Buffer + afterArch->Offsets[afterType] + afterArch->SizeOfs[afterType] * mod.AfterIndex);
var afterElements = afterHeader->Length;
var bytesPerElement = TypeManager.GetTypeInfo(at.TypeIndex).ElementSize;
var afterBytes = bytesPerElement * afterElements;
var afterElementPointer = BufferHeader.GetElementPointer(afterHeader);
if (at.TypeIndex == LinkedEntityGroupTypeIndex)
{
// magic in addcomponent already put a self-reference at the top of the buffer, so there's no need for us to add it.
// the rest of the elements should be interpreted as LinkedEntityGroupAdditions.
for(var a = 1; a < afterElements; ++a)
{
var childEntity = ((Entity*)afterElementPointer)[a];
var childEntityHasGuid = SrcWorldEntityToGuid.TryGetValue(childEntity, out var childEntityGuid);
// if the child entity doesn't have a guid, there's no way for us to communicate its identity to the destination world.
Assert.IsTrue(childEntityHasGuid);
linkedEntityGroupAdds.Add(new LinkedEntityGroupAddition{RootGuid = mod.Guid, ChildGuid = childEntityGuid});
}
}
else
{
dataDiffs.Add(new DataDiff
{
EntityIndex = entityIndex,
Offset = 0,
SizeBytes = afterBytes,
TypeHashIndex = typeHashIndex
});
byteDiffs.AddRange(afterElementPointer, afterBytes);
ExtractGuidPatches(entityPatches, SrcWorldEntityToGuid, at, afterElementPointer, afterElements, entityIndex, typeHashIndex);
}
}
else
{
// Also push through initial data wholesale.
dataDiffs.Add(new DataDiff
{
EntityIndex = entityIndex,
Offset = 0,
SizeBytes = afterArch->SizeOfs[afterType],
TypeHashIndex = typeHashIndex
});
byte* afterAddress = mod.AfterChunk->Buffer + afterArch->Offsets[afterType] + afterArch->SizeOfs[afterType] * mod.AfterIndex;
byteDiffs.AddRange(afterAddress, afterArch->SizeOfs[afterType]);
ExtractGuidPatches(entityPatches, SrcWorldEntityToGuid, at, afterAddress, 1, entityIndex, typeHashIndex);
}
// Also check value
continue;
}
if (!at.IsSharedComponent && !at.IsBuffer && !at.IsZeroSized && !at.IsSystemStateComponent)
{
Assert.AreEqual(beforeArch->SizeOfs[beforeType], afterArch->SizeOfs[afterType]);
byte* beforeAddress = mod.BeforeChunk->Buffer + beforeArch->Offsets[beforeType] + beforeArch->SizeOfs[beforeType] * mod.BeforeIndex;
byte* afterAddress = mod.AfterChunk->Buffer + afterArch->Offsets[afterType] + afterArch->SizeOfs[afterType] * mod.AfterIndex;
var equals = ComponentEqualsWithEntityGuids(afterAddress, beforeAddress, targetTypeIndex, SrcWorldEntityToGuid, DstWorldEntityToGuid);
if (!equals)
{
// For now do full component replacement, because we need to dig into field information to make this work.
dataDiffs.Add(new DataDiff
{
EntityIndex = entityIndex,
Offset = 0,
SizeBytes = afterArch->SizeOfs[afterType],
TypeHashIndex = typeHashIndex
});
byteDiffs.AddRange(afterAddress, afterArch->SizeOfs[afterType]);
ExtractGuidPatches(entityPatches, SrcWorldEntityToGuid, at, afterAddress, 1, entityIndex, typeHashIndex);
}
continue;
}
if (at.IsSharedComponent)
{
object beforeObject = GetSharedComponentObject(dsharedComponentStore, mod.BeforeChunk, beforeType, targetTypeIndex);
object afterObject = GetSharedComponentObject(ssharedComponentStore, mod.AfterChunk, afterType, targetTypeIndex);
if (!TypeManager.Equals(beforeObject, afterObject, targetTypeIndex))
{
sharedComponentDiffs.Add(new SetSharedComponentDiff
{
BoxedSharedValue = afterObject,
EntityIndex = entityIndex,
TypeHashIndex = typeHashIndex,
});
}
continue;
}
if (at.IsBuffer)
{
Assert.AreEqual(beforeArch->SizeOfs[beforeType], afterArch->SizeOfs[afterType]);
var beforeHeader = (BufferHeader*) (mod.BeforeChunk->Buffer + beforeArch->Offsets[beforeType] + beforeArch->SizeOfs[beforeType] * mod.BeforeIndex);
var afterHeader = (BufferHeader*) (mod.AfterChunk->Buffer + afterArch->Offsets[afterType] + afterArch->SizeOfs[afterType] * mod.AfterIndex);
byte* beforeElementPointer = BufferHeader.GetElementPointer(beforeHeader);
byte* afterElementPointer = BufferHeader.GetElementPointer(afterHeader);
var beforeElements = beforeHeader->Length;
var afterElements = afterHeader->Length;
if (at.TypeIndex == LinkedEntityGroupTypeIndex)
{
using(var befores = new NativeArray<Entity>(beforeElements, Allocator.TempJob))
using (var afters = new NativeArray<Entity>(afterElements, Allocator.TempJob))
{
UnsafeUtility.MemCpy(befores.GetUnsafePtr(), beforeElementPointer, beforeElements * sizeof(Entity));
UnsafeUtility.MemCpy(afters.GetUnsafePtr(), afterElementPointer,afterElements * sizeof(Entity));
befores.Sort(new EntitySorter());
afters.Sort(new EntitySorter());
int b = 0;
int a = 0;
while (b < befores.Length && a < afters.Length)
{
if (Ordinal(befores[b]) == Ordinal(afters[a])) // if Entity is in both "before" and "after", it's not added or deleted
{
++a;
++b;
}
else if (Ordinal(befores[b]) < Ordinal(afters[a])) // if Entity is in "before" but not "after", it's been removed
{
var childHasGuid = SrcWorldEntityToGuid.TryGetValue(befores[b++], out var childGuid);
Assert.IsTrue(childHasGuid);
linkedEntityGroupRemoves.Add(new LinkedEntityGroupRemoval{RootGuid = mod.Guid, ChildGuid = childGuid});
}
else // if Entity is in "after" but not "before", it's been added
{
var childHasGuid = SrcWorldEntityToGuid.TryGetValue(afters[a++], out var childGuid);
Assert.IsTrue(childHasGuid);
linkedEntityGroupAdds.Add(new LinkedEntityGroupAddition{RootGuid = mod.Guid, ChildGuid = childGuid});
}
}
while (b < befores.Length) // if Entity is in "before" but not "after", it's been removed
{
var childHasGuid = SrcWorldEntityToGuid.TryGetValue(befores[b++], out var childGuid);
Assert.IsTrue(childHasGuid);
linkedEntityGroupRemoves.Add(new LinkedEntityGroupRemoval{RootGuid = mod.Guid, ChildGuid = childGuid});
}
while (a < afters.Length) // if Entity is in "after" but not "before", it's been added
{
var childHasGuid = SrcWorldEntityToGuid.TryGetValue(afters[a++], out var childGuid);
Assert.IsTrue(childHasGuid);
linkedEntityGroupAdds.Add(new LinkedEntityGroupAddition{RootGuid = mod.Guid, ChildGuid = childGuid});
}
}
}
else
{
var bytesPerElement = TypeManager.GetTypeInfo(at.TypeIndex).ElementSize;
var afterElementBytes = afterElements * bytesPerElement;
if (beforeElements != afterElements ||
!ManagedComponentStore.FastEquality_CompareElements(beforeElementPointer,
afterElementPointer, afterElements, at.TypeIndex))
{
dataDiffs.Add(new DataDiff
{
EntityIndex = entityIndex,
Offset = 0,
SizeBytes = afterElementBytes,
TypeHashIndex = typeHashIndex
});
byteDiffs.AddRange(afterElementPointer, afterElementBytes);
ExtractGuidPatches(entityPatches, SrcWorldEntityToGuid, at, afterElementPointer,
afterElements,
entityIndex, typeHashIndex);
}
}
continue;
}
}
for (int beforeType = 1; beforeType < beforeArch->TypesCount; ++beforeType)
{
int targetTypeIndex = beforeArch->Types[beforeType].TypeIndex;
if (-1 == ChunkDataUtility.GetIndexInTypeArray(afterArch, targetTypeIndex))
{
var typeHashIndex = GetTypeHashIndex(targetTypeIndex, typeHashes, typeHashLookup);
removeComponents.Add(new ComponentDiff {EntityIndex = entityIndex, TypeHashIndex = typeHashIndex});
}
}
}
}
// Compare two component instances; if they have internal Entity fields,
// compare those by GUID to see if they point to the same entity or not.
private static bool ComponentEqualsWithEntityGuids(
byte* afterCompPtr, byte* beforeCompPtr, int typeIndex,
NativeHashMap<Entity, EntityGuid> AfterWorldEntityToGuid,
NativeHashMap<Entity, EntityGuid> BeforeWorldEntityToGuid)
{
// if the type has no entity references, then this is simple
if (!TypeManager.HasEntityReferences(typeIndex))
{
return TypeManager.Equals(afterCompPtr, beforeCompPtr, typeIndex);
}
// If it does have entity references, first compare the data by ignoring the Entity fields.
// This is done by saving them to savedEntities and temporarily setting them to null,
// then redoing the comparison.
var ft = TypeManager.GetTypeInfo(typeIndex);
int AfterOffset = 0;
int BeforeOffset = ft.EntityOffsetCount;
var savedEntitiesArray = stackalloc Entity[ft.EntityOffsetCount * 2];
{
Entity* savedEntities = (Entity*) savedEntitiesArray;
int runOffset = 0;
int lastEntityOffset = 0;
int sizeLeft = ft.SizeInChunk;
byte* aptr = afterCompPtr;
byte* bptr = beforeCompPtr;
for (int ei = 0; ei < ft.EntityOffsetCount; ++ei)
{
var entityOffset = ft.EntityOffsets[ei].Offset;
Assert.IsTrue(entityOffset >= lastEntityOffset);
int runSize = entityOffset - runOffset;
if (runSize > 0)
{
if (UnsafeUtility.MemCmp(afterCompPtr + runOffset, beforeCompPtr + runOffset, runSize) != 0)
return false;
}
savedEntities[AfterOffset + ei] = *(Entity*) (afterCompPtr + entityOffset);
savedEntities[BeforeOffset + ei] = *(Entity*) (beforeCompPtr + entityOffset);
// we just compared a run, plus skip the entity
sizeLeft -= (runSize + sizeof(Entity));
// next run start is just past the Entity field
runOffset = entityOffset + sizeof(Entity);
}
if (sizeLeft > 0)
{
if (UnsafeUtility.MemCmp(afterCompPtr + runOffset, beforeCompPtr + runOffset, sizeLeft) != 0)
return false;
}
// everything is equal other than the entities. Compare guids to see if they actually changed.
for (int ei = 0; ei < ft.EntityOffsetCount; ++ei)
{
EntityGuid afterGuid = EntityGuid.Null;
EntityGuid beforeGuid = EntityGuid.Null;
var afterEntity = savedEntities[AfterOffset + ei];
var beforeEntity = savedEntities[BeforeOffset + ei];
if (afterEntity != Entity.Null)
{
bool srcHasEntity = AfterWorldEntityToGuid.TryGetValue(afterEntity, out afterGuid);
// Bogus internal entity reference. Return false to indicate a difference.
if (!srcHasEntity)
return false;
}
if (beforeEntity != Entity.Null)
{
bool dstHasEntity = BeforeWorldEntityToGuid.TryGetValue(beforeEntity, out beforeGuid);
// Bogus internal entity reference. Return false to indicate a difference.
if (!dstHasEntity)
return false;
}
if (!afterGuid.Equals(beforeGuid))
{
return false;
}
}
}
return true;
}
private static void ExtractGuidPatches(NativeList<DiffEntityPatch> entityPatches, NativeHashMap<Entity, EntityGuid> allEntities, ComponentTypeInArchetype at, byte* afterAddress, int elementCount, int entityIndex, int typeHashIndex)
{
var ft = TypeManager.GetTypeInfo(at.TypeIndex);
if(at.TypeIndex == TypeManager.GetTypeIndex<LinkedEntityGroup>())
Debug.LogWarning("We should never attempt to extract guid patches from a LinkedEntityGroup.");
if (ft.EntityOffsets == null)
return;
int elementOffset = 0;
for (var element = 0; element < elementCount; ++element)
{
#if !NET_DOTS
foreach (var eo in ft.EntityOffsets)
{
#else
for(int i = 0; i < ft.EntityOffsetCount; ++i)
{
var eo = UnsafeUtility.ReadArrayElement<TypeManager.EntityOffsetInfo>(ft.EntityOffsets, i);
#endif
var offset = elementOffset + eo.Offset;
Entity* e = (Entity*) (afterAddress + offset);
EntityGuid guid;
if (!allEntities.TryGetValue(*e, out guid)) // if *e has no guid, then guid will be null (desired)
guid = EntityGuid.Null;
entityPatches.Add(new DiffEntityPatch
{
EntityIndex = entityIndex,
TypeHashIndex = typeHashIndex,
Offset = offset,
Guid = guid
});
}
elementOffset += ft.ElementSize;
}
}
#if !NET_DOTS
static object GetSharedComponentObject(ManagedComponentStore managedComponentStore, Chunk* chunk, int typeIndexInArchetype, int targetTypeIndex)
{
int off = typeIndexInArchetype - chunk->Archetype->FirstSharedComponent;
Assert.IsTrue((0 <= off) && (off < chunk->Archetype->NumSharedComponents));
int sharedComponentIndex = chunk->GetSharedComponentValue(off);
return managedComponentStore.GetSharedComponentDataBoxed(sharedComponentIndex, targetTypeIndex);
}
#else
static object GetSharedComponentObject(ManagedComponentStore managedComponentStore, Chunk* chunk, int typeIndexInArchetype, int targetTypeIndex)
{
return null;
}
#endif
static private void BuildAddComponents(NativeList<DiffCreationData> newEntities,
NativeList<ulong> typeHashes,
NativeHashMap<int, int> typeHashLookup,
NativeList<ComponentDiff> addComponents,
NativeList<DataDiff> dataDiffs,
NativeList<byte> byteDiffs,
NativeList<EntityGuid> guidList,
NativeHashMap<EntityGuid, int> guidToIndex,
List<SetSharedComponentDiff> sharedDiffs,
ManagedComponentStore managedComponentStore,
NativeList<DiffEntityPatch> entityPatches,
NativeHashMap<Entity, EntityGuid> SrcWorldEntityToGuid,
NativeList<LinkedEntityGroupAddition> LinkedEntityGroupAdds)
{
var LinkedEntityGroupTypeIndex = TypeManager.GetTypeIndex<LinkedEntityGroup>();
for (int i = 0; i < newEntities.Length; ++i)
{
var ent = newEntities[i];
var afterArch = ent.Chunk->Archetype;
var entityIndex = GetExternalIndex(ent.Guid, guidList, guidToIndex);
for (int afterType = 1; afterType < afterArch->TypesCount; ++afterType)
{
var at = afterArch->Types[afterType];
if (at.IsSystemStateComponent)
continue;
int targetTypeIndex = at.TypeIndex;
var typeHashIndex = GetTypeHashIndex(targetTypeIndex, typeHashes, typeHashLookup);
addComponents.Add(new ComponentDiff {EntityIndex = entityIndex, TypeHashIndex = typeHashIndex});
//@TODO: BuildComponentDiff is copy pasta..
if (!at.IsBuffer && !at.IsSharedComponent && !at.IsZeroSized)
{
// Also push through initial data wholesale.
dataDiffs.Add(new DataDiff
{
EntityIndex = entityIndex,
Offset = 0,
SizeBytes = afterArch->SizeOfs[afterType],
TypeHashIndex = typeHashIndex
});
byte* src = ent.Chunk->Buffer + afterArch->Offsets[afterType] + ent.Index * afterArch->SizeOfs[afterType];
byteDiffs.AddRange(src, afterArch->SizeOfs[afterType]);
ExtractGuidPatches(entityPatches, SrcWorldEntityToGuid, at, src, 1, entityIndex, typeHashIndex);
}
else if (at.IsSharedComponent)
{
object o = GetSharedComponentObject(managedComponentStore, ent.Chunk, afterType, targetTypeIndex);
sharedDiffs.Add(new SetSharedComponentDiff
{
EntityIndex = entityIndex,
TypeHashIndex = typeHashIndex,
BoxedSharedValue = o
});
}
else if (at.IsBuffer)
{
BufferHeader* afterHeader = (BufferHeader*)(ent.Chunk->Buffer + afterArch->Offsets[afterType] + ent.Index * afterArch->SizeOfs[afterType]);
var bytesPerElement = TypeManager.GetTypeInfo(at.TypeIndex).ElementSize;
var afterElements = afterHeader->Length;
var afterElementBytes = bytesPerElement * afterElements;
byte* afterElementPointer = BufferHeader.GetElementPointer(afterHeader);
if (at.TypeIndex == LinkedEntityGroupTypeIndex)
{
// magic in addcomponent already put a self-reference at the top of the buffer, so there's no need for us to add it.
// the rest of the elements should be interpreted as LinkedEntityGroupAdditions.
for(var a = 1; a < afterElements; ++a)
{
var childEntity = ((Entity*)afterElementPointer)[a];
var childEntityHasGuid = SrcWorldEntityToGuid.TryGetValue(childEntity, out var childEntityGuid);
// if the child entity doesn't have a guid, there's no way for us to communicate its identity to the destination world.
Assert.IsTrue(childEntityHasGuid);
LinkedEntityGroupAdds.Add(new LinkedEntityGroupAddition{RootGuid = ent.Guid, ChildGuid = childEntityGuid});
}
}
else
{
dataDiffs.Add(new DataDiff
{
EntityIndex = entityIndex,
Offset = 0,
SizeBytes = afterElementBytes,
TypeHashIndex = typeHashIndex
});
byteDiffs.AddRange(afterElementPointer, afterElementBytes);
ExtractGuidPatches(entityPatches, SrcWorldEntityToGuid, at, afterElementPointer,
afterElements, entityIndex, typeHashIndex);
}
}
}
}
}
static int GetTypeHashIndex(int targetTypeIndex, NativeList<ulong> typeHashes, NativeHashMap<int, int> typeHashLookup)
{
int result;
if (!typeHashLookup.TryGetValue(targetTypeIndex, out result))
{
result = typeHashes.Length;
typeHashes.Add(TypeManager.GetTypeInfo(targetTypeIndex).StableTypeHash);
typeHashLookup.TryAdd(targetTypeIndex, result);
}
return result;
}
static int GetExternalIndex(EntityGuid guid, NativeList<EntityGuid> guidList, NativeHashMap<EntityGuid, int> guidToIndex)
{
int result;
if (!guidToIndex.TryGetValue(guid, out result))
{
result = guidList.Length;
guidList.Add(guid);
guidToIndex.TryAdd(guid, result);
}
return result;
}
static void ComputeEntityDiff(
NativeHashMap<EntityGuid, DiffEntityData> beforeState,
NativeHashMap<EntityGuid, DiffEntityData> afterState,
NativeList<EntityGuid> removedEntities,
NativeList<DiffCreationData> createdEntities,
NativeList<DiffModificationData> modifiedEntities)
{
NativeArray<EntityGuid> beforeGuids = beforeState.GetKeyArray(Allocator.Temp);
NativeArray<EntityGuid> afterGuids = afterState.GetKeyArray(Allocator.Temp);
Assert.AreEqual(beforeGuids.Length, beforeState.Length);
Assert.AreEqual(afterGuids.Length, afterState.Length);
beforeGuids.Sort();
afterGuids.Sort();
int ai = 0;
int bi = 0;
while (ai < afterGuids.Length && bi < beforeGuids.Length)
{
EntityGuid aguid = afterGuids[ai];
EntityGuid bguid = beforeGuids[bi];
int c = bguid.CompareTo(aguid);
if (c < 0)
{
removedEntities.Add(bguid);
++bi;
}
else if (c == 0)
{
DiffEntityData afterData;
DiffEntityData beforeData;
bool b = afterState.TryGetValue(aguid, out afterData);
Assert.IsTrue(b);
b = beforeState.TryGetValue(bguid, out beforeData);
Assert.IsTrue(b);
modifiedEntities.Add(new DiffModificationData
{
Guid = bguid,
AfterChunk = afterData.Chunk,
AfterIndex = afterData.Index,
BeforeChunk = beforeData.Chunk,
BeforeIndex = beforeData.Index,
});
++ai;
++bi;
}
else
{
DiffEntityData d;
bool b = afterState.TryGetValue(aguid, out d);
Assert.IsTrue(b);
createdEntities.Add(new DiffCreationData {Chunk = d.Chunk, Guid = aguid, Index = d.Index});
++ai;
}
}
while (bi < beforeGuids.Length)
{
removedEntities.Add(beforeGuids[bi]);
++bi;
}
while (ai < afterGuids.Length)
{
DiffEntityData d;
bool b = afterState.TryGetValue(afterGuids[ai], out d);
Assert.IsTrue(b);
createdEntities.Add(new DiffCreationData {Chunk = d.Chunk, Guid = afterGuids[ai], Index = d.Index});
++ai;
}
afterGuids.Dispose();
beforeGuids.Dispose();
}
static void CreateEntityToGuidLookup(Chunk* c, NativeHashMap<Entity, EntityGuid> lookup)
{
var arch = c->Archetype;
Entity* entities = (Entity*) (c->Buffer + arch->Offsets[0]);
// Find EntityGuid type index
int guidTypeIndex = TypeManager.GetTypeIndex<EntityGuid>();
int guidIndex = 0;
for (int i = 1; i < arch->TypesCount; ++i)
{
if (arch->Types[i].TypeIndex == guidTypeIndex)
{
guidIndex = i;
break;
}
}
Assert.IsTrue(guidIndex > 0);
EntityGuid* guids = (EntityGuid*) (c->Buffer + arch->Offsets[guidIndex]);
for (int i = 0; i < c->Count; ++i)
{
lookup.TryAdd(entities[i], guids[i]);
}
}
static void CreateGuidToEntityLookup(Chunk* c, NativeHashMap<EntityGuid, Entity> lookup)
{
var arch = c->Archetype;
Entity* entities = (Entity*) (c->Buffer + arch->Offsets[0]);
// Find EntityGuid type index
int guidTypeIndex = TypeManager.GetTypeIndex<EntityGuid>();
int guidIndex = 0;
for (int i = 1; i < arch->TypesCount; ++i)
{
if (arch->Types[i].TypeIndex == guidTypeIndex)
{
guidIndex = i;
break;
}
}
Assert.IsTrue(guidIndex > 0);
EntityGuid* guids = (EntityGuid*) (c->Buffer + arch->Offsets[guidIndex]);
for (int i = 0; i < c->Count; ++i)
{
lookup.TryAdd(guids[i], entities[i]);
}
}
static void AddChunkEntitiesToState(Chunk* c, NativeHashMap<EntityGuid, DiffEntityData> state)
{
var arch = c->Archetype;
// Find EntityGuid type index
int guidTypeIndex = TypeManager.GetTypeIndex<EntityGuid>();
int guidIndex = 0;
for (int i = 1; i < arch->TypesCount; ++i)
{
if (arch->Types[i].TypeIndex == guidTypeIndex)
{
guidIndex = i;
break;
}
}
Assert.IsTrue(guidIndex > 0);
EntityGuid* ptr = (EntityGuid*) (c->Buffer + arch->Offsets[guidIndex]);
for (int i = 0; i < c->Count; ++i)
{
var item = new DiffEntityData { Chunk = c, Index = i };
state.TryAdd(ptr[i], item);
}
}
static bool ChunkChanged(Chunk* sourceChunk, Chunk* destChunk)
{
int archCount = sourceChunk->Archetype->TypesCount;
for (int ti = 0; ti < archCount; ++ti)
{
if (sourceChunk->GetChangeVersion(ti) != destChunk->GetChangeVersion(ti))
{
return true;
}
}
return false;
}
[BurstCompile]
struct BuildDestWorldGuidLookups : IJobChunk
{
public NativeMultiHashMap<EntityGuid, Entity>.Concurrent GuidToDestWorldEntity;
public NativeHashMap<Entity,EntityGuid>.Concurrent DestWorldEntityToGuid;
[ReadOnly] public ArchetypeChunkComponentType<EntityGuid> EntityGUID;
[ReadOnly] public ArchetypeChunkEntityType Entity;
public void Execute(ArchetypeChunk chunk, int entityIndex, int chunkIndex)
{
var guids = chunk.GetNativeArray(EntityGUID);
var entities = chunk.GetNativeArray(Entity);
for (int i = 0; i != entities.Length; i++)
{
GuidToDestWorldEntity.Add(guids[i], entities[i]);
DestWorldEntityToGuid.TryAdd(entities[i], guids[i]);
}
}
}
[BurstCompile]
struct BuildDestWorldPrefabLookups : IJobChunk
{
public NativeHashMap<EntityGuid, Entity>.Concurrent GuidToDestWorldPrefabEntity;
[ReadOnly] public ArchetypeChunkComponentType<EntityGuid> EntityGUID;
[ReadOnly] public ArchetypeChunkEntityType Entity;
public void Execute(ArchetypeChunk chunk, int entityIndex, int chunkIndex)
{
var guids = chunk.GetNativeArray(EntityGUID);
var entities = chunk.GetNativeArray(Entity);
for (int i = 0; i != entities.Length; i++)
GuidToDestWorldPrefabEntity.TryAdd(guids[i], entities[i]);
}
}
struct BuildEntityToRootEntityLookup : IJobChunk
{
public NativeHashMap<Entity,Entity>.Concurrent EntityToRootEntity;
[ReadOnly] public ArchetypeChunkBufferType<LinkedEntityGroup> LinkedEntityGroup;
public void Execute(ArchetypeChunk chunk, int entityIndex, int chunkIndex)
{
var linkedEntityGroups = chunk.GetBufferAccessor(LinkedEntityGroup);
for (int i = 0; i != linkedEntityGroups.Length; i++)
{
var linkedEntityGroup = linkedEntityGroups[i];
for (int j = 0; j != linkedEntityGroup.Length; ++j)
EntityToRootEntity.TryAdd(linkedEntityGroup[j].Value, linkedEntityGroup[0].Value);
}
}
}
#if !UNITY_DOTSPLAYER
[DebuggerTypeProxy(typeof(DiffApplierDebugView))]
#endif
internal struct DiffApplier : IDisposable
{
static ProfilerMarker s_AllocateLookups = new ProfilerMarker("DiffApplier.AllocateLookups");
static ProfilerMarker s_BuildDestWorldLookups = new ProfilerMarker("DiffApplier.BuildDestWorldLookups");
static ProfilerMarker s_BuildDiffToDestWorldLookups = new ProfilerMarker("DiffApplier.BuildDiffToDestWorldLookups");
static ProfilerMarker s_CreateEntities = new ProfilerMarker("DiffApplier.CreateEntities");
static ProfilerMarker s_DestroyEntities = new ProfilerMarker("DiffApplier.DestroyEntities");
static ProfilerMarker s_AddComponents = new ProfilerMarker("DiffApplier.AddComponents");
static ProfilerMarker s_RemoveComponents = new ProfilerMarker("DiffApplier.RemoveComponents");
static ProfilerMarker s_SetSharedComponents = new ProfilerMarker("DiffApplier.SetSharedComponents");
static ProfilerMarker s_SetComponents = new ProfilerMarker("DiffApplier.SetComponents");
static ProfilerMarker s_ApplyLinkedEntityGroupAdds = new ProfilerMarker("DiffApplier.ApplyLinkedEntityGroupAdds");
static ProfilerMarker s_ApplyLinkedEntityGroupRemoves = new ProfilerMarker("DiffApplier.ApplyLinkedEntityGroupRemoves");
static ProfilerMarker s_PatchEntities = new ProfilerMarker("DiffApplier.PatchEntities");
// World to which we apply the diff
internal World destWorld;
// Diff which we apply to World dest
internal WorldDiff diff;
// EntityManager for the dest World
internal EntityManager DestWorldManager;
// For each diff entity index, which entities in the dest world have the same guid?
internal NativeMultiHashMap<int, Entity> DiffIndexToDestWorldEntities;
// For each diff type index, which dest world component type corresponds to it?
internal NativeArray<ComponentType> DiffIndexToDestWorldTypes;
// For each dest world entity that has a guid, which guid does it have?
internal NativeHashMap<Entity, EntityGuid> DestWorldEntityToGuid;
// For each guid in the dest world, which entities have that guid (there may be many!)?
internal NativeMultiHashMap<EntityGuid, Entity> GuidToDestWorldEntity;
// For each dest world entity, what is its root entity?
internal NativeHashMap<Entity, Entity> DestWorldEntityToRootEntity;
// For a given GUID, what is the Prefab in the dest world, if any, that corresponds to it?
internal NativeHashMap<EntityGuid, Entity> GuidToDestWorldPrefabEntity;
internal int DestWorldEntitiesWithGuids;
internal int DestWorldEntitiesWithLinkedEntityGroups;
public void Dispose()
{
DiffIndexToDestWorldEntities.Dispose();
DiffIndexToDestWorldTypes.Dispose();
GuidToDestWorldEntity.Dispose();
DestWorldEntityToGuid.Dispose();
DestWorldEntityToRootEntity.Dispose();
GuidToDestWorldPrefabEntity.Dispose();
}
public DiffApplier(World dest_, WorldDiff diff_)
{
destWorld = dest_;
diff = diff_;
DestWorldManager = destWorld.EntityManager;
DestWorldManager.CompleteAllJobs();
DiffIndexToDestWorldEntities = default;
DiffIndexToDestWorldTypes = default;
DestWorldEntityToGuid = default;
GuidToDestWorldEntity = default;
GuidToDestWorldPrefabEntity = default;
DestWorldEntityToRootEntity = default;
DestWorldEntitiesWithGuids = 0;
DestWorldEntitiesWithLinkedEntityGroups = 0;
}
public void Apply()
{
AllocateLookups();
BuildDestWorldLookups();
BuildDiffToDestWorldLookups();
CreateEntities();
SetDebugNames();
DestroyEntities();
AddComponents();
RemoveComponents();
SetSharedComponents();
SetComponents();
BuildDestWorldLookups();
ApplyLinkedEntityGroupAdds();
ApplyLinkedEntityGroupRemoves();
PatchEntities();
}
void AllocateLookups()
{
s_AllocateLookups.Begin();
var factor = 4;
DiffIndexToDestWorldEntities = new NativeMultiHashMap<int, Entity>(DestWorldEntitiesWithGuids * factor, Allocator.TempJob);
DiffIndexToDestWorldTypes = new NativeArray<ComponentType>(diff.TypeHashes.Length, Allocator.TempJob);
DestWorldEntityToGuid = new NativeHashMap<Entity, EntityGuid>(DestWorldEntitiesWithGuids * factor, Allocator.TempJob);
GuidToDestWorldEntity = new NativeMultiHashMap<EntityGuid, Entity>(DestWorldEntitiesWithGuids * factor, Allocator.TempJob);
GuidToDestWorldPrefabEntity = new NativeHashMap<EntityGuid, Entity>(DestWorldEntitiesWithGuids * factor, Allocator.TempJob);
DestWorldEntityToRootEntity = new NativeHashMap<Entity, Entity>(DestWorldEntitiesWithLinkedEntityGroups * factor, Allocator.TempJob);
s_AllocateLookups.End();
}
// we need O(1) lookup for entity->guid and guid->entity in the dest world,
// and for now this insanely expensive lookup table generator is how we get it.
// no matter how small the diff is, this thing has a cost proportional to the
// number of entities in the dest world that have guids.
void BuildDestWorldLookups()
{
s_BuildDestWorldLookups.Begin();
var guidQuery = new EntityQueryDesc
{
All = new ComponentType[] {typeof(EntityGuid)},
Options = EntityQueryOptions.IncludeDisabled | EntityQueryOptions.IncludePrefab
};
var linkedEntityGroupQuery = new EntityQueryDesc
{
All = new ComponentType[] {typeof(EntityGuid), typeof(LinkedEntityGroup)},
Options = EntityQueryOptions.IncludeDisabled | EntityQueryOptions.IncludePrefab
};
var guidPrefabQuery = new EntityQueryDesc
{
All = new ComponentType[] {typeof(EntityGuid), typeof(Prefab)},
Options = EntityQueryOptions.IncludeDisabled | EntityQueryOptions.IncludePrefab
};
using (EntityQuery destChunksWithGuids = DestWorldManager.CreateEntityQuery(guidQuery))
using (EntityQuery destChunksWithLinkedEntityGroups = DestWorldManager.CreateEntityQuery(linkedEntityGroupQuery))
using (EntityQuery destchunksWithGuidAndPrefab = DestWorldManager.CreateEntityQuery(guidPrefabQuery))
{
DestWorldEntitiesWithGuids = destChunksWithGuids.CalculateLength();
DestWorldEntitiesWithLinkedEntityGroups = destChunksWithLinkedEntityGroups.CalculateLength();
DestWorldEntityToGuid.Dispose();
GuidToDestWorldEntity.Dispose();
GuidToDestWorldPrefabEntity.Dispose();
DestWorldEntityToRootEntity.Dispose();
var factor = 4;
DestWorldEntityToGuid = new NativeHashMap<Entity, EntityGuid>(DestWorldEntitiesWithGuids * factor, Allocator.TempJob);
GuidToDestWorldEntity = new NativeMultiHashMap<EntityGuid, Entity>(DestWorldEntitiesWithGuids * factor, Allocator.TempJob);
GuidToDestWorldPrefabEntity = new NativeHashMap<EntityGuid, Entity>(DestWorldEntitiesWithGuids * factor, Allocator.TempJob);
DestWorldEntityToRootEntity = new NativeHashMap<Entity, Entity>(DestWorldEntitiesWithGuids * factor, Allocator.TempJob);
var buildDestWorldGuidLookups = new BuildDestWorldGuidLookups
{
GuidToDestWorldEntity = GuidToDestWorldEntity.ToConcurrent(),
DestWorldEntityToGuid = DestWorldEntityToGuid.ToConcurrent(),
Entity = DestWorldManager.GetArchetypeChunkEntityType(),
EntityGUID = DestWorldManager.GetArchetypeChunkComponentType<EntityGuid>(true)
};
var handle0 = buildDestWorldGuidLookups.Schedule(destChunksWithGuids);
var buildDestWorldGuidPrefabLookups = new BuildDestWorldPrefabLookups
{
GuidToDestWorldPrefabEntity = GuidToDestWorldPrefabEntity.ToConcurrent(),
Entity = DestWorldManager.GetArchetypeChunkEntityType(),
EntityGUID = DestWorldManager.GetArchetypeChunkComponentType<EntityGuid>(true)
};
var handle1 = buildDestWorldGuidPrefabLookups.Schedule(destchunksWithGuidAndPrefab);
var buildEntityToRootEntityLookup = new BuildEntityToRootEntityLookup
{
EntityToRootEntity = DestWorldEntityToRootEntity.ToConcurrent(),
LinkedEntityGroup = DestWorldManager.GetArchetypeChunkBufferType<LinkedEntityGroup>(true)
};
var handle2 = buildEntityToRootEntityLookup.Schedule(destChunksWithLinkedEntityGroups);
handle0.Complete();
handle1.Complete();
handle2.Complete();
}
DiffIndexToDestWorldEntities.Clear();
for(var i = 0; i < diff.Entities.Length; ++i)
{
var guid = diff.Entities[i];
if (GuidToDestWorldEntity.TryGetFirstValue(guid, out var destWorldEntity, out var iterator))
{
do
{
DiffIndexToDestWorldEntities.Add(i, destWorldEntity);
} while (GuidToDestWorldEntity.TryGetNextValue(out destWorldEntity, ref iterator));
}
}
s_BuildDestWorldLookups.End();
}
// here we build lookups to go from diff index, to dest world thing.
// first, a lookup to go from diff guid index to dest world entities with that guid,
// and then a lookup to go from diff type index to dest world componenttype.
void BuildDiffToDestWorldLookups()
{
s_BuildDiffToDestWorldLookups.Begin();
for (var i = 0; i < diff.TypeHashes.Length; ++i)
{
var stableTypeHash = diff.TypeHashes[i];
var typeIndex = TypeManager.GetTypeIndexFromStableTypeHash(stableTypeHash);
var type = TypeManager.GetType(typeIndex);
DiffIndexToDestWorldTypes[i] = new ComponentType(type);
}
s_BuildDiffToDestWorldLookups.End();
}
void CreateEntities()
{
s_CreateEntities.Begin();
var EntityGuidArchetype = DestWorldManager.CreateArchetype();
using (var NewEntities = new NativeArray<Entity>(diff.NewEntityCount, Allocator.Temp))
{
DestWorldManager.CreateEntity(EntityGuidArchetype, NewEntities);
for (var i = 0; i < diff.NewEntityCount; ++i)
{
DiffIndexToDestWorldEntities.Add(i, NewEntities[i]);
#if LOG_DIFF_ALL
Debug.Log($"CreateEntity({diff.Entities[i]}) -> {NewEntities[i]}");
#endif
}
}
s_CreateEntities.End();
}
void SetDebugNames()
{
for (var i = 0; i < diff.Entities.Length; ++i)
{
if (DiffIndexToDestWorldEntities.TryGetFirstValue(i, out var entity, out var it))
{
do
{
#if UNITY_EDITOR
DestWorldManager.SetName(entity, diff.EntityNames[i].ToString());
#endif
} while (DiffIndexToDestWorldEntities.TryGetNextValue(out entity, ref it));
}
}
}
void DestroyEntities()
{
s_DestroyEntities.Begin();
int firstIndex = diff.Entities.Length - diff.DeletedEntityCount;
for (var i = 0; i < diff.DeletedEntityCount; ++i)
{
if (DiffIndexToDestWorldEntities.TryGetFirstValue(firstIndex + i, out var entity, out var iterator))
{
do
{
if (DestWorldManager.EntityComponentStore->Exists(entity))
{
#if LOG_DIFF_ALL
Debug.Log($"DestroyEntity({diff.Entities[firstIndex + i]})");
#endif
DestWorldManager.DestroyEntity(entity);
}
else
{
Debug.LogWarning($"DestroyEntity({diff.Entities[firstIndex + i]}) but it does not exist.");
}
} while (DiffIndexToDestWorldEntities.TryGetNextValue(out entity, ref iterator));
}
}
s_DestroyEntities.End();
}
void AddComponents()
{
s_AddComponents.Begin();
var linkedEntityGroupTypeIndex = TypeManager.GetTypeIndex<LinkedEntityGroup>();
foreach (var addition in diff.AddComponents)
{
var componentType = DiffIndexToDestWorldTypes[addition.TypeHashIndex];
if (DiffIndexToDestWorldEntities.TryGetFirstValue(addition.EntityIndex, out var entity, out var iterator))
{
do
{
if (!DestWorldManager.EntityComponentStore->HasComponent(entity, componentType))
{
DestWorldManager.AddComponent(entity, componentType);
// magic is required to force the first entity in the LinkedEntityGroup to be the entity
// that owns the component. this magic doesn't seem to exist at a lower level, so let's
// shim it in here. we'll probably need to move the magic lower someday.
if (componentType.TypeIndex == linkedEntityGroupTypeIndex)
{
var buffer = DestWorldManager.GetBuffer<LinkedEntityGroup>(entity);
buffer.Add(entity);
}
}
else
Debug.LogWarning($"AddComponent({diff.Entities[addition.EntityIndex]}, {componentType}) but the component already exists.");
#if LOG_DIFF_ALL
Debug.Log($"AddComponent<{componentType}>({diff.Entities[addition.EntityIndex]}, {DestWorldManager.Debug.GetComponentBoxed(entity, componentType)})");
#endif
} while (DiffIndexToDestWorldEntities.TryGetNextValue(out entity, ref iterator));
}
}
s_AddComponents.End();
}
void RemoveComponents()
{
s_RemoveComponents.Begin();
foreach (var removal in diff.RemoveComponents)
{
var componentType = DiffIndexToDestWorldTypes[removal.TypeHashIndex];
if (DiffIndexToDestWorldEntities.TryGetFirstValue(removal.EntityIndex, out var entity, out var iterator))
{
do
{
DestWorldManager.RemoveComponent(entity, componentType);
#if LOG_DIFF_ALL
Debug.Log($"AddComponent<{componentType}>({diff.Entities[removal.EntityIndex]}).");
#endif
} while (DiffIndexToDestWorldEntities.TryGetNextValue(out entity, ref iterator));
}
}
s_RemoveComponents.End();
}
void SetSharedComponents()
{
s_SetSharedComponents.Begin();
foreach (var shared in diff.SharedSetCommands)
{
var componentType = DiffIndexToDestWorldTypes[shared.TypeHashIndex];
var componentData = shared.BoxedSharedValue;
if (DiffIndexToDestWorldEntities.TryGetFirstValue(shared.EntityIndex, out var entity, out var iterator))
{
do
{
if (!DestWorldManager.Exists(entity))
Debug.LogWarning($"SetComponent<{componentType}>({diff.Entities[shared.EntityIndex]}) but entity does not exist.");
else if (!DestWorldManager.HasComponent(entity, componentType))
Debug.LogWarning($"SetComponent<{componentType}>({diff.Entities[shared.EntityIndex]}) but component does not exist.");
else
{
DestWorldManager.SetSharedComponentDataBoxed(entity, componentType.TypeIndex, componentData);
#if LOG_DIFF_ALL
Debug.Log($"SetComponent<{componentType}>({diff.Entities[shared.EntityIndex]}, {componentData})");
#endif
}
} while (DiffIndexToDestWorldEntities.TryGetNextValue(out entity, ref iterator));
}
}
s_SetSharedComponents.End();
}
void SetComponents()
{
s_SetComponents.Begin();
long readOffset = 0;
foreach (var setting in diff.SetCommands)
{
var data = (byte*) diff.ComponentPayload.GetUnsafePtr() + readOffset;
var size = setting.SizeBytes;
var componentType = DiffIndexToDestWorldTypes[setting.TypeHashIndex];
ComponentTypeInArchetype ctia = new ComponentTypeInArchetype(componentType);
if (DiffIndexToDestWorldEntities.TryGetFirstValue(setting.EntityIndex, out var entity, out var iterator))
{
do
{
if (!DestWorldManager.Exists(entity))
Debug.LogWarning($"SetComponent<{componentType}>({diff.Entities[setting.EntityIndex]}) but entity does not exist.");
else if (!DestWorldManager.HasComponent(entity, componentType))
Debug.LogWarning($"SetComponent<{componentType}>({diff.Entities[setting.EntityIndex]}) but component does not exist.");
else
{
if (ctia.IsZeroSized)
{
// Nothing to set.
}
else if (!ctia.IsBuffer)
{
var target = (byte*)DestWorldManager.GetComponentDataRawRW(entity, componentType.TypeIndex);
UnsafeUtility.MemCpy(target + setting.Offset, data, size);
}
else
{
var typeInfo = TypeManager.GetTypeInfo(ctia.TypeIndex);
var elementSize = typeInfo.ElementSize;
var lengthInElements = size / elementSize;
var header = (BufferHeader*)DestWorldManager.GetComponentDataRawRW(entity, componentType.TypeIndex);
BufferHeader.Assign(header, data, lengthInElements, elementSize, 16);
}
#if LOG_DIFF_ALL
Debug.Log($"SetComponent<{componentType}>({diff.Entities[setting.EntityIndex]}, {DestWorldManager.Debug.GetComponentBoxed(entity, componentType)})");
#endif
}
} while (DiffIndexToDestWorldEntities.TryGetNextValue(out entity, ref iterator));
}
readOffset += size;
}
s_SetComponents.End();
}
void PatchEntities()
{
s_PatchEntities.Begin();
foreach (var patch in diff.EntityPatches)
{
var typeOfComponentToPatch = DiffIndexToDestWorldTypes[patch.TypeHashIndex];
var guidToPatchTo = patch.Guid;
var byteOffsetInComponent = patch.Offset;
Entity entityToPatchTo;
var multipleEntitiesWithGuidToPatchToExistInDest = false;
if (guidToPatchTo.Equals(EntityGuid.Null))
entityToPatchTo = Entity.Null;
else
{
var guidToPatchToExistsInDestWorld = GuidToDestWorldEntity.TryGetFirstValue(guidToPatchTo, out entityToPatchTo, out var patchSourceIterator);
if (!guidToPatchToExistsInDestWorld)
{
Debug.LogWarning($"PatchEntities<{typeOfComponentToPatch}>({diff.Entities[patch.EntityIndex]}) but entity with guid-to-patch-to does not exist.");
continue;
}
multipleEntitiesWithGuidToPatchToExistInDest = GuidToDestWorldEntity.TryGetNextValue(out _, ref patchSourceIterator);
}
if (DiffIndexToDestWorldEntities.TryGetFirstValue(patch.EntityIndex, out var entityWhereComponentToPatchIs, out var patchDestinationIterator))
{
do
{
if (!DestWorldManager.Exists(entityWhereComponentToPatchIs))
Debug.LogWarning($"PatchEntities<{typeOfComponentToPatch}>({diff.Entities[patch.EntityIndex]}) but entity to patch does not exist.");
else if (!DestWorldManager.HasComponent(entityWhereComponentToPatchIs, typeOfComponentToPatch))
Debug.LogWarning($"PatchEntities<{typeOfComponentToPatch}>({diff.Entities[patch.EntityIndex]}) but component in entity to patch does not exist.");
else
{
// if just one entity has the GUID we're patching to, we can just use that entity.
// but if multiple entities have that GUID, we need to patch to the (one) entity that's in the destination entity's "group."
// that group is defined by a LinkedEntityGroup component on the destination entity's "root entity," which contains an array of entity references.
// the destination entity's "root entity" is defined by whatever entity owns the (one) LinkedEntityGroup that refers to the destination entity.
// so, we had to build a lookup table earlier, to take us from "destination entity" to "root entity of my group," so we can find this LinkedEntityGroup
// component, and riffle through it to find the (one) entity with the GUID we're looking for.
if (multipleEntitiesWithGuidToPatchToExistInDest)
{
var entityWhereComponentToPatchIsHasARoot = DestWorldEntityToRootEntity.TryGetValue(entityWhereComponentToPatchIs, out var rootOfEntityWhereComponentToPatchIs);
if (!entityWhereComponentToPatchIsHasARoot)
{
Debug.LogWarning(
$"PatchEntities<{typeOfComponentToPatch}>({diff.Entities[patch.EntityIndex]}) but 2+ entities for GUID of entity-to-patch-to, and no root for entity-to-patch is, so we can't disambiguate.");
continue;
}
var groupOfRootOfEntityWhereComponentToPatchIs = DestWorldManager.GetBuffer<LinkedEntityGroup>(rootOfEntityWhereComponentToPatchIs);
for (var g = 0; g < groupOfRootOfEntityWhereComponentToPatchIs.Length; ++g)
if(DestWorldEntityToGuid.TryGetValue(groupOfRootOfEntityWhereComponentToPatchIs[g].Value, out var guidOfEntityInRootGroup))
if (guidOfEntityInRootGroup.Equals(guidToPatchTo))
{
entityToPatchTo = groupOfRootOfEntityWhereComponentToPatchIs[g].Value;
break;
}
}
if (typeOfComponentToPatch.IsBuffer)
{
byte* pointer = (byte*) DestWorldManager.GetBufferRawRW(entityWhereComponentToPatchIs, typeOfComponentToPatch.TypeIndex);
UnsafeUtility.MemCpy(pointer + byteOffsetInComponent, &entityToPatchTo, sizeof(Entity));
}
else
{
byte* pointer = (byte*) DestWorldManager.GetComponentDataRawRW(entityWhereComponentToPatchIs, typeOfComponentToPatch.TypeIndex);
UnsafeUtility.MemCpy(pointer + byteOffsetInComponent, &entityToPatchTo, sizeof(Entity));
}
#if LOG_DIFF_ALL
Debug.Log($"SetComponent_EntityPatch<{typeOfComponentToPatch}>({diff.Entities[patch.EntityIndex]}, {DestWorldManager.Debug.GetComponentBoxed(entityWhereComponentToPatchIs, typeOfComponentToPatch)})");
#endif
}
} while (DiffIndexToDestWorldEntities.TryGetNextValue(out entityWhereComponentToPatchIs, ref patchDestinationIterator));
}
}
s_PatchEntities.End();
}
struct Child
{
public Entity childEntity;
public Entity rootEntity;
public EntityGuid rootGuid;
public EntityGuid childGuid;
}
void AddInstanceChildToTables(Child addition)
{
for(var i = 0; i < diff.Entities.Length; ++i)
if (diff.Entities[i].Equals(addition.childGuid))
{
DiffIndexToDestWorldEntities.Add(i, addition.childEntity);
break;
}
DestWorldEntityToGuid.TryAdd(addition.childEntity,addition.childGuid);
GuidToDestWorldEntity.Add(addition.childGuid, addition.childEntity);
DestWorldEntityToRootEntity.TryAdd(addition.childEntity, addition.rootEntity);
}
void AddPrefabChildToTables(Child addition)
{
DestWorldEntityToRootEntity.TryAdd(addition.childEntity, addition.rootEntity);
}
void RemoveChildFromTables(Child removal)
{
for(var i = 0; i < diff.Entities.Length; ++i)
if (diff.Entities[i].Equals(removal.childGuid))
{
DiffIndexToDestWorldEntities.TryRemove(i, removal.childEntity);
break;
}
DestWorldEntityToGuid.Remove(removal.childEntity);
GuidToDestWorldEntity.TryRemove(removal.childGuid, removal.childEntity);
DestWorldEntityToRootEntity.Remove(removal.childEntity);
}
void ApplyLinkedEntityGroupAdds()
{
s_ApplyLinkedEntityGroupAdds.Begin();
using (var prefabChildren = new NativeList<Child>(Allocator.TempJob))
using (var instanceChildren = new NativeList<Child>(Allocator.TempJob))
{
for (var a = 0; a < diff.LinkedEntityGroupAdditions.Length; ++a)
{
var add = diff.LinkedEntityGroupAdditions[a];
// If we are asked to add a child to a linked entity group, then that child's guid must correspond to
// exactly one entity in the destination world that also has a Prefab component. Since we made a lookup
// from GUID to Prefab entity before, we can use it to find the specific entity we want.
if (GuidToDestWorldPrefabEntity.TryGetValue(add.ChildGuid, out var prefabEntityToInstantiate))
{
if (GuidToDestWorldEntity.TryGetFirstValue(add.RootGuid, out var rootEntity, out var iterator))
{
do
{
if (rootEntity == prefabEntityToInstantiate)
{
Debug.LogWarning($"Trying to instantiate self as child???");
continue;
}
if (DestWorldManager.HasComponent<Prefab>(rootEntity))
{
prefabChildren.Add(new Child{childEntity = prefabEntityToInstantiate, rootEntity = rootEntity, childGuid = add.ChildGuid, rootGuid = add.RootGuid});
var group = DestWorldManager.GetBuffer<LinkedEntityGroup>(rootEntity);
group.Add(prefabEntityToInstantiate);
}
else
{
var instantiatedEntity = DestWorldManager.Instantiate(prefabEntityToInstantiate);
instanceChildren.Add(new Child{childEntity = instantiatedEntity, rootEntity = rootEntity, childGuid = add.ChildGuid, rootGuid = add.RootGuid});
var group = DestWorldManager.GetBuffer<LinkedEntityGroup>(rootEntity);
group.Add(instantiatedEntity);
}
} while (GuidToDestWorldEntity.TryGetNextValue(out rootEntity, ref iterator));
}
else
{
Debug.LogWarning($"Tried to add a child to a linked entity group, but root entity didn't exist in destination world.");
}
}
else
{
Debug.LogWarning($"Tried to add a child to a linked entity group, but no such prefab exists in destination world.");
}
}
for (var a = 0; a < instanceChildren.Length; ++a)
AddInstanceChildToTables(instanceChildren[a]);
for (var a = 0; a < prefabChildren.Length; ++a)
AddPrefabChildToTables(prefabChildren[a]);
}
s_ApplyLinkedEntityGroupAdds.End();
}
void ApplyLinkedEntityGroupRemoves()
{
s_ApplyLinkedEntityGroupRemoves.Begin();
using (var pending = new NativeList<Child>(Allocator.TempJob))
{
for (var r = 0; r < diff.LinkedEntityGroupRemovals.Length; ++r)
{
var remove = diff.LinkedEntityGroupRemovals[r];
if (GuidToDestWorldEntity.TryGetFirstValue(remove.RootGuid, out var rootEntity, out var iterator))
{
do
{
var group = DestWorldManager.GetBuffer<LinkedEntityGroup>(rootEntity);
for (var g = 0; g < group.Length; ++g)
{
var childEntity = group[g].Value;
if (DestWorldEntityToGuid.TryGetValue(childEntity, out var childGuid) &&
childGuid.Equals(remove.ChildGuid))
{
group.RemoveAt(g);
pending.Add(new Child {childEntity = childEntity, rootEntity = rootEntity, childGuid = remove.ChildGuid, rootGuid = remove.RootGuid});
DestWorldManager.DestroyEntity(childEntity);
break;
}
}
// if we got here without destroying an entity, then maybe the destination world destroyed it before we synced?
// not sure if that is a fatal error, or what.
} while (GuidToDestWorldEntity.TryGetNextValue(out rootEntity, ref iterator));
}
}
for (var a = 0; a < pending.Length; ++a)
RemoveChildFromTables(pending[a]);
}
s_ApplyLinkedEntityGroupRemoves.End();
}
}
public static void ApplyDiff(World dest, WorldDiff diff)
{
#if LOG_DIFF_ALL
Debug.Log("--- Begin Apply diff ---- ");
#endif
using(var applier = new DiffApplier(dest, diff))
applier.Apply();
#if LOG_DIFF_ALL
Debug.Log("--- End Apply diff ---- ");
#endif
}
}
}