#if MODULE_ENTITIES
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
namespace Pathfinding.ECS {
/// Helper for EntityAccess
static class EntityAccessHelper {
public static readonly int GlobalSystemVersionOffset = UnsafeUtility.GetFieldOffset(typeof(ComponentTypeHandle).GetField("m_GlobalSystemVersion", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
}
///
/// Wrapper for a pointer.
///
/// Very similar to the entities package RefRW struct. But unfortunately that one cannot be used because the required constructor is not exposed.
///
public ref struct ComponentRef where T : unmanaged {
unsafe byte* ptr;
public unsafe ComponentRef(byte* ptr) {
this.ptr = ptr;
}
public ref T value {
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get {
unsafe {
return ref *(T*)ptr;
}
}
}
}
/// Utility for efficient random access to entity storage data from the main thread
public struct EntityStorageCache {
EntityStorageInfo storage;
Entity entity;
int lastWorldHash;
///
/// Retrieves the storage for a given entity.
///
/// This method is very fast if the entity is the same as the last call to this method.
/// If the entity is different, it will be slower.
///
/// Returns: True if the entity exists, and false if it does not.
///
// Inlining makes this method about 20% faster. It's hot when accessing properties on the FollowerEntity component.
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool Update (World world, Entity entity, out EntityManager entityManager, out EntityStorageInfo storage) {
entityManager = default;
storage = this.storage;
if (world == null) return false;
entityManager = world.EntityManager;
// We must use entityManager.EntityOrderVersion here, not GlobalSystemVersion, because
// the GlobalSystemVersion does not necessarily update when structural changes happen.
var worldHash = entityManager.EntityOrderVersion ^ ((int)world.SequenceNumber << 8);
if (worldHash != lastWorldHash || entity != this.entity) {
if (!entityManager.Exists(entity)) return false;
this.storage = storage = entityManager.GetStorageInfo(entity);
this.entity = entity;
lastWorldHash = worldHash;
}
return true;
}
///
/// Retrieves a component for a given entity.
///
/// This is a convenience method to call on this object and update on the access object, and then retrieve the component data.
///
/// This method is very fast if the entity is the same as the last call to this method.
/// If the entity is different, it will be slower.
///
/// Warning: You must not store the returned reference past a structural change in the ECS world.
///
/// Returns: True if the entity exists, and false if it does not.
/// Throws: An exception if the entity does not have the given component.
///
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool GetComponentData(Entity entity, ref EntityAccess access, out ComponentRef value) where A : unmanaged, IComponentData {
if (Update(World.DefaultGameObjectInjectionWorld, entity, out var entityManager, out var storage)) {
access.Update(entityManager);
unsafe {
value = new ComponentRef((byte*)UnsafeUtility.AddressOf(ref access[storage]));
}
return true;
} else {
value = default;
return false;
}
}
}
///
/// Utility for efficient random access to entity component data from the main thread.
///
/// Since this struct returns a reference to the component data, it is faster than using EntityManager.GetComponentData,
/// in particular for larger component types.
///
/// Warning: Some checks are not enforced by this API. It is the user's responsibility to ensure that
/// this struct does not survive past an ECS system update. If you only use this struct from the main thread
/// and only store it locally on the stack, this should not be a problem.
/// This struct also does not enforce that you only read to the component data if the readOnly flag is set.
///
public struct EntityAccess where T : unmanaged, IComponentData {
public ComponentTypeHandle handle;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
SystemHandle systemHandle;
#endif
uint lastSystemVersion;
ulong worldSequenceNumber;
bool readOnly;
public EntityAccess(bool readOnly) {
handle = default;
this.readOnly = readOnly;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
systemHandle = default;
#endif
// Version 0 is never used by EntityManager.GlobalSystemVersion
lastSystemVersion = 0;
worldSequenceNumber = 0;
}
///
/// Update the component type handle if necessary.
///
/// This must be called if any jobs or system might have been scheduled since the struct was created or since the last call to Update.
///
public void Update (EntityManager entityManager) {
// If the global system version has changed, jobs may have been scheduled which writes
// to the component data. Therefore we need to complete all dependencies before we can
// safely read or write to the component data.
var systemVersion = entityManager.GlobalSystemVersion;
var sequenceNumber = entityManager.WorldUnmanaged.SequenceNumber;
if (systemVersion != lastSystemVersion || worldSequenceNumber != sequenceNumber) {
if (lastSystemVersion == 0 || worldSequenceNumber != sequenceNumber) {
handle = entityManager.GetComponentTypeHandle(readOnly);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
entityManager.WorldUnmanaged.IsSystemValid(systemHandle);
systemHandle = entityManager.WorldUnmanaged.GetExistingUnmanagedSystem();
#endif
}
lastSystemVersion = systemVersion;
worldSequenceNumber = sequenceNumber;
if (readOnly) entityManager.CompleteDependencyBeforeRO();
else entityManager.CompleteDependencyBeforeRW();
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
handle.Update(ref entityManager.WorldUnmanaged.ResolveSystemStateRef(systemHandle));
#else
// handle.Update just does the same thing as this unsafe code, but in a much more roundabout way
unsafe {
var ptr = (byte*)UnsafeUtility.AddressOf(ref handle);
*(uint*)(ptr + EntityAccessHelper.GlobalSystemVersionOffset) = systemVersion;
}
#endif
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public bool HasComponent (EntityStorageInfo storage) {
return storage.Chunk.Has(ref handle);
}
public ref T this[EntityStorageInfo storage] {
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get {
unsafe {
var ptr = readOnly ? ((T*)storage.Chunk.GetRequiredComponentDataPtrRO(ref handle) + storage.IndexInChunk) : ((T*)storage.Chunk.GetRequiredComponentDataPtrRW(ref handle) + storage.IndexInChunk);
return ref *ptr;
}
}
}
}
///
/// Utility for efficient random access to managed entity component data from the main thread.
///
/// Warning: Some checks are not enforced by this API. It is the user's responsibility to ensure that
/// this struct does not survive past an ECS system update. If you only use this struct from the main thread
/// and only store it locally on the stack, this should not be a problem.
/// This struct also does not enforce that you only read to the component data if the readOnly flag is set.
///
public struct ManagedEntityAccess where T : class, IComponentData {
EntityManager entityManager;
ComponentTypeHandle handle;
bool readOnly;
public ManagedEntityAccess(bool readOnly) {
entityManager = default;
handle = default;
this.readOnly = readOnly;
}
public ManagedEntityAccess(EntityManager entityManager, bool readOnly) : this(readOnly) {
Update(entityManager);
}
public void Update (EntityManager entityManager) {
if (readOnly) entityManager.CompleteDependencyBeforeRO();
else entityManager.CompleteDependencyBeforeRW();
handle = entityManager.GetComponentTypeHandle(readOnly);
this.entityManager = entityManager;
}
public T this[EntityStorageInfo storage] {
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
get {
return storage.Chunk.GetManagedComponentAccessor(ref handle, entityManager)[storage.IndexInChunk];
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
set {
var accessor = storage.Chunk.GetManagedComponentAccessor(ref handle, entityManager);
accessor[storage.IndexInChunk] = value;
}
}
}
}
#endif