Added new RHI abstraction layer;
Added new console debug page to UnitTest;
This commit is contained in:
@@ -217,8 +217,8 @@ internal class ScriptComponentPool : IComponentPool<ScriptComponent>
|
||||
private Dictionary<Entity, List<ScriptComponent>>? _scriptComponents;
|
||||
private List<ScriptComponent>? _executionList;
|
||||
|
||||
internal Dictionary<Entity, List<ScriptComponent>>? ScriptComponents => _scriptComponents;
|
||||
internal List<ScriptComponent>? ExecutionList => _executionList;
|
||||
internal IReadOnlyDictionary<Entity, List<ScriptComponent>>? ScriptComponents => _scriptComponents;
|
||||
internal IReadOnlyList<ScriptComponent>? ExecutionList => _executionList;
|
||||
|
||||
public bool IsInitialized => _scriptComponents != null;
|
||||
public int Count => _scriptComponents?.Keys.Count ?? 0;
|
||||
@@ -461,10 +461,22 @@ internal class ScriptComponentPool : IComponentPool<ScriptComponent>
|
||||
}
|
||||
|
||||
[SkipLocalsInit]
|
||||
internal readonly struct ComponentStorage : IDisposable
|
||||
internal struct ComponentStorage : IDisposable
|
||||
{
|
||||
private readonly Dictionary<TypeHandle, IComponentPool> _componentPools = new();
|
||||
private readonly Dictionary<TypeHandle, BitSet> _componentEntityMasks = new();
|
||||
private static int s_nextId = 0;
|
||||
private static class TypeID<T>
|
||||
{
|
||||
public static readonly int value = s_nextId++;
|
||||
}
|
||||
|
||||
private int _currentCapacity = 16;
|
||||
|
||||
private IComponentPool?[] _componentPools = new IComponentPool[16];
|
||||
private BitSet?[] _componentEntityMasks = new BitSet[16];
|
||||
|
||||
private readonly Dictionary<TypeHandle, int> _typeIDMap = new(16);
|
||||
private readonly Dictionary<int, TypeHandle> _typeHandleMap = new(16);
|
||||
|
||||
private readonly ScriptComponentPool _scriptComponentPool = new();
|
||||
|
||||
private readonly World _world;
|
||||
@@ -474,92 +486,225 @@ internal readonly struct ComponentStorage : IDisposable
|
||||
_world = world;
|
||||
}
|
||||
|
||||
internal Dictionary<TypeHandle, IComponentPool> ComponentPools => _componentPools;
|
||||
internal Dictionary<TypeHandle, BitSet> ComponentEntityMasks => _componentEntityMasks;
|
||||
internal ScriptComponentPool ScriptComponentPool => _scriptComponentPool;
|
||||
internal readonly IReadOnlyList<IComponentPool?> ComponentPools => _componentPools;
|
||||
internal readonly IReadOnlyList<BitSet?> ComponentEntityMasks => _componentEntityMasks;
|
||||
internal readonly ScriptComponentPool ScriptComponentPool => _scriptComponentPool;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetPool(TypeHandle typeHandle, [MaybeNullWhen(false)] out IComponentPool pool)
|
||||
private readonly int GetTypeID(TypeHandle typeHandle)
|
||||
{
|
||||
return _componentPools.TryGetValue(typeHandle, out pool);
|
||||
if (_typeIDMap.TryGetValue(typeHandle, out var id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
return typeof(TypeID<>).MakeGenericType(typeHandle!)
|
||||
.GetField("value", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
|
||||
?.GetValue(null) as int? ?? -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetPool(Type type, [MaybeNullWhen(false)] out IComponentPool pool)
|
||||
private readonly int GetTypeID<T>()
|
||||
{
|
||||
return TypeID<T>.value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Resize(int newCapacity)
|
||||
{
|
||||
Array.Resize(ref _componentPools, newCapacity);
|
||||
Array.Resize(ref _componentEntityMasks, newCapacity);
|
||||
_currentCapacity = newCapacity;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal readonly TypeHandle GetComponentPoolType(int poolIndex)
|
||||
{
|
||||
if (poolIndex < 0 || poolIndex >= _currentCapacity)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(poolIndex), "Invalid pool index.");
|
||||
}
|
||||
|
||||
return _typeHandleMap[poolIndex];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryGetPool(TypeHandle typeHandle, [NotNullWhen(true)] out IComponentPool? pool)
|
||||
{
|
||||
var result = _typeIDMap.TryGetValue(typeHandle, out var id);
|
||||
if (!result || id >= _currentCapacity)
|
||||
{
|
||||
pool = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
pool = _componentPools[id];
|
||||
return pool != null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryGetPool(Type type, [NotNullWhen(true)] out IComponentPool? pool)
|
||||
{
|
||||
return TryGetPool(TypeHandle.Get(type), out pool);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetPool<T>([MaybeNullWhen(false)] out ComponentPool<T> pool)
|
||||
public readonly bool TryGetPool<T>([NotNullWhen(true)] out ComponentPool<T>? pool)
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
var result = TryGetPool(TypeHandle.Get<T>(), out var obj);
|
||||
pool = (ComponentPool<T>?)obj ?? default;
|
||||
return result;
|
||||
var id = TypeID<T>.value;
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
pool = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
pool = (ComponentPool<T>?)_componentPools[id];
|
||||
return pool != null;
|
||||
}
|
||||
|
||||
public IComponentPool GetOrCreateComponentPool(Type type)
|
||||
{
|
||||
var typeHandle = TypeHandle.Get(type);
|
||||
if (_typeIDMap.TryGetValue(typeHandle, out var id))
|
||||
{
|
||||
if (id < _currentCapacity && _componentPools[id] is IComponentPool existingPool)
|
||||
{
|
||||
return existingPool;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
id = GetTypeID(typeHandle);
|
||||
}
|
||||
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
Resize(_currentCapacity * 2);
|
||||
_typeIDMap[typeHandle] = id;
|
||||
_typeHandleMap[id] = typeHandle;
|
||||
}
|
||||
else if (_componentPools[id] is IComponentPool existingPool)
|
||||
{
|
||||
return existingPool;
|
||||
}
|
||||
|
||||
var pool = Activator.CreateInstance(typeof(ComponentPool<>).MakeGenericType(type)) as IComponentPool
|
||||
?? throw new InvalidOperationException($"Failed to create component pool for type {type.FullName}");
|
||||
|
||||
_componentPools[id] = pool;
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
public ComponentPool<T> GetOrCreateComponentPool<T>()
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
var key = TypeHandle.Get<T>();
|
||||
if (!_componentPools.TryGetValue(key, out var obj))
|
||||
var id = TypeID<T>.value;
|
||||
var typeHandle = TypeHandle.Get<T>();
|
||||
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
var pool = new ComponentPool<T>(16);
|
||||
_componentPools[key] = pool;
|
||||
return pool;
|
||||
Resize(_currentCapacity * 2);
|
||||
_typeIDMap[typeHandle] = id;
|
||||
_typeHandleMap[id] = typeHandle;
|
||||
}
|
||||
else if (_componentPools[id] is ComponentPool<T> existingPool)
|
||||
{
|
||||
return existingPool;
|
||||
}
|
||||
|
||||
return (ComponentPool<T>)obj;
|
||||
var pool = new ComponentPool<T>();
|
||||
_componentPools[id] = pool;
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetMask(TypeHandle typeHandle, [MaybeNullWhen(false)] out BitSet bitSet)
|
||||
public readonly bool TryGetMask(TypeHandle typeHandle, [NotNullWhen(true)] out BitSet? bitSet)
|
||||
{
|
||||
return _componentEntityMasks.TryGetValue(typeHandle, out bitSet);
|
||||
if (!_typeIDMap.TryGetValue(typeHandle, out var id)
|
||||
|| id >= _currentCapacity)
|
||||
{
|
||||
bitSet = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bitSet = _componentEntityMasks[id];
|
||||
return bitSet != null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetMask(Type type, [MaybeNullWhen(false)] out BitSet bitSet)
|
||||
{
|
||||
return TryGetMask(TypeHandle.Get(type), out bitSet);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetMask<T>([MaybeNullWhen(false)] out BitSet bitSet)
|
||||
public readonly bool TryGetMask<T>([NotNullWhen(true)] out BitSet? bitSet)
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
return TryGetMask(TypeHandle.Get<T>(), out bitSet);
|
||||
}
|
||||
|
||||
public BitSet GetOrCreateMask(TypeHandle typeHandle)
|
||||
public BitSet GetOrCreateMask<T>()
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
if (!_componentEntityMasks.TryGetValue(typeHandle, out var mask))
|
||||
var typeHandle = TypeHandle.Get<T>();
|
||||
if (!_typeIDMap.TryGetValue(typeHandle, out var id))
|
||||
{
|
||||
mask = new BitSet();
|
||||
_componentEntityMasks[typeHandle] = mask;
|
||||
id = GetTypeID<T>();
|
||||
_typeIDMap[typeHandle] = id;
|
||||
_typeHandleMap[id] = typeHandle;
|
||||
}
|
||||
return mask;
|
||||
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
Resize(_currentCapacity * 2);
|
||||
}
|
||||
|
||||
ref var set = ref _componentEntityMasks[id];
|
||||
set ??= new BitSet();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public BitSet GetOrCreateMask(Type type)
|
||||
{
|
||||
var typeHandle = TypeHandle.Get(type);
|
||||
if (!_typeIDMap.TryGetValue(typeHandle, out var id))
|
||||
{
|
||||
id = GetTypeID(typeHandle);
|
||||
_typeIDMap[typeHandle] = id;
|
||||
_typeHandleMap[id] = typeHandle;
|
||||
}
|
||||
|
||||
if (id >= _currentCapacity)
|
||||
{
|
||||
Resize(_currentCapacity * 2);
|
||||
}
|
||||
|
||||
ref var set = ref _componentEntityMasks[id];
|
||||
set ??= new BitSet();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildExecutionList()
|
||||
public readonly void RebuildExecutionList()
|
||||
{
|
||||
_scriptComponentPool.RebuildExecutionList();
|
||||
}
|
||||
|
||||
public void Remove(Entity entity)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void Remove(Entity entity)
|
||||
{
|
||||
_scriptComponentPool.Remove(entity);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly void Dispose()
|
||||
{
|
||||
foreach (var pool in _componentPools.Values)
|
||||
foreach (var pool in _componentPools)
|
||||
{
|
||||
pool.Dispose();
|
||||
pool?.Dispose();
|
||||
}
|
||||
_componentPools.Clear();
|
||||
|
||||
Array.Clear(_componentPools);
|
||||
_scriptComponentPool.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ public readonly struct EntityManager : IDisposable
|
||||
return entity;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal readonly void AddEntityInternal(Entity entity)
|
||||
{
|
||||
_entities.Add(entity);
|
||||
@@ -87,18 +88,19 @@ public readonly struct EntityManager : IDisposable
|
||||
return _entities[entity.ID].Generation == entity.Generation;
|
||||
}
|
||||
|
||||
public readonly void AddComponent(Entity entity, IComponentData component, Type type)
|
||||
/// <summary>
|
||||
/// Adds a component of the specified type to the given entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method use reflection to determine the type of the component being added. Use generic as much as possible.
|
||||
/// </remarks>
|
||||
/// <param name="entity">The entity to which the component will be added.</param>
|
||||
/// <param name="component">The component data to associate with the entity.</param>
|
||||
/// <param name="componentType">The type of the component being added. This must match the type of <paramref name="component"/>.</param>
|
||||
public readonly void AddComponent(Entity entity, IComponentData component, Type componentType)
|
||||
{
|
||||
var typeHandle = TypeHandle.Get(type);
|
||||
ref var pool = ref CollectionsMarshal.GetValueRefOrAddDefault(_world.ComponentStorage.ComponentPools, typeHandle, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
var poolType = typeof(ComponentPool<>).MakeGenericType(type);
|
||||
pool = (IComponentPool)(Activator.CreateInstance(poolType) ?? throw new InvalidOperationException($"Failed to create component pool for type {type}."));
|
||||
}
|
||||
|
||||
pool!.Add(entity, component);
|
||||
_world.ComponentStorage.GetOrCreateMask(typeHandle).SetBit(entity.ID);
|
||||
_world.ComponentStorage.GetOrCreateComponentPool(componentType).Add(entity, component);
|
||||
_world.ComponentStorage.GetOrCreateMask(componentType).SetBit(entity.ID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,7 +114,7 @@ public readonly struct EntityManager : IDisposable
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
_world.ComponentStorage.GetOrCreateComponentPool<T>().Add(entity, component);
|
||||
_world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).SetBit(entity.ID);
|
||||
_world.ComponentStorage.GetOrCreateMask<T>().SetBit(entity.ID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -134,7 +136,7 @@ public readonly struct EntityManager : IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
_world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).ClearBit(entity.ID);
|
||||
_world.ComponentStorage.GetOrCreateMask<T>().ClearBit(entity.ID);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -219,8 +221,13 @@ public readonly struct EntityManager : IDisposable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly IEnumerable<IComponentData> GetComponents(Entity entity)
|
||||
{
|
||||
foreach (var pool in _world.ComponentStorage.ComponentPools.Values)
|
||||
foreach (var pool in _world.ComponentStorage.ComponentPools)
|
||||
{
|
||||
if (pool == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pool.Has(entity))
|
||||
{
|
||||
yield return pool.Get(entity);
|
||||
@@ -241,11 +248,17 @@ public readonly struct EntityManager : IDisposable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly IEnumerable<(TypeHandle, IntPtr)> GetComponentsUnsafe(Entity entity)
|
||||
{
|
||||
foreach (var (typeHandle, pool) in _world.ComponentStorage.ComponentPools)
|
||||
for (var i = 0; i < _world.ComponentStorage.ComponentPools.Count; i++)
|
||||
{
|
||||
var pool = _world.ComponentStorage.ComponentPools[i];
|
||||
if (pool == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pool.Has(entity))
|
||||
{
|
||||
yield return (typeHandle, pool.GetUnsafe(entity));
|
||||
yield return (_world.ComponentStorage.GetComponentPoolType(i), pool.GetUnsafe(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,26 @@
|
||||
|
||||
namespace Ghost.Entities.Systems;
|
||||
|
||||
internal class SystemDependencyBuilder(List<Type> allSystemTypes)
|
||||
internal class SystemDependencyBuilder
|
||||
{
|
||||
private Dictionary<Type, List<Type>> _dependencies = new();
|
||||
private readonly Dictionary<Type, List<Type>> _dependencies = new();
|
||||
private readonly List<Type> _systemTypes;
|
||||
|
||||
public SystemDependencyBuilder(List<Type> allSystemTypes)
|
||||
{
|
||||
_systemTypes = allSystemTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a dependency graph for all system types that implement the <see cref="ISystem"/> interface.
|
||||
/// </summary>
|
||||
/// <remarks>This method analyzes all system types and their dependencies, as defined by the <see
|
||||
/// cref="DependsOnAttribute"/>. It validates that each system type is a concrete implementation of <see
|
||||
/// cref="ISystem"/> and constructs a mapping of each system type to its direct dependencies.</remarks>
|
||||
/// <exception cref="ArgumentException">Thrown if a type in <c>allSystemTypes</c> is not a concrete implementation of <see cref="ISystem"/>.</exception>
|
||||
public void BuildDependencyGraph()
|
||||
{
|
||||
foreach (var systemType in allSystemTypes)
|
||||
foreach (var systemType in _systemTypes)
|
||||
{
|
||||
if (!typeof(ISystem).IsAssignableFrom(systemType) || systemType.IsAbstract || systemType.IsInterface)
|
||||
{
|
||||
@@ -45,7 +58,7 @@ internal class SystemDependencyBuilder(List<Type> allSystemTypes)
|
||||
foreach (var dependencyType in directDependencies)
|
||||
{
|
||||
// Ensure the dependency is a registered system type
|
||||
if (!allSystemTypes.Contains(dependencyType))
|
||||
if (!_systemTypes.Contains(dependencyType))
|
||||
{
|
||||
throw new InvalidOperationException($"System {systemType.Name} depends on unregistered system {dependencyType.Name}.");
|
||||
}
|
||||
@@ -66,12 +79,12 @@ internal class SystemDependencyBuilder(List<Type> allSystemTypes)
|
||||
/// <exception cref="InvalidOperationException">Thrown if a circular dependency is detected.</exception>"
|
||||
public List<Type> BuildExecutionOrder()
|
||||
{
|
||||
var executionOrder = new List<Type>(allSystemTypes.Count);
|
||||
var executionOrder = new List<Type>(_systemTypes.Count);
|
||||
var visited = new HashSet<Type>(); // Tracks visited nodes in the current DFS path (for cycle detection)
|
||||
var permanentMark = new HashSet<Type>(); // Tracks nodes whose dependencies have been fully resolved
|
||||
|
||||
// Initialize dependencies for all registered systems, even those without explicit attributes
|
||||
foreach (var sysType in allSystemTypes)
|
||||
foreach (var sysType in _systemTypes)
|
||||
{
|
||||
if (!_dependencies.ContainsKey(sysType))
|
||||
{
|
||||
@@ -79,7 +92,7 @@ internal class SystemDependencyBuilder(List<Type> allSystemTypes)
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var systemType in allSystemTypes)
|
||||
foreach (var systemType in _systemTypes)
|
||||
{
|
||||
if (!permanentMark.Contains(systemType))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user