Added new RHI abstraction layer;

Added new console debug page to UnitTest;
This commit is contained in:
2025-08-25 10:48:59 +09:00
parent eafbfb2fa1
commit 5385141f14
44 changed files with 3473 additions and 357 deletions

View File

@@ -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();
}
}

View File

@@ -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));
}
}
}

View File

@@ -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))
{