为系统循环创建高性能组件池
create performant component pooling for system loops
我正在设置一个非常小的实体-组件-系统示例,但组件池存在一些问题。
目前我的实体只是 ID (GUID),这很好。
每个系统都必须实现 ISystem
接口
internal interface ISystem
{
void Run();
}
并存储在KeyedByTypeCollection
中。这个集合确保每个系统都是独一无二的。
每个组件都必须实现 IComponent
接口。
internal interface IComponent
{
}
通过这样做,我可以将所有不同的组件类型存储到它们匹配的组件池中。每个池是一个Dictionary<Guid, IComponent>
。键表示实体的 ID,值是该实体的组件。每个池都存储在 KeyedByTypeCollection
中以确保组件池是唯一的。
目前我的EntityManager
class包含核心逻辑。我不知道是否需要将其设为静态,但目前我的系统需要从中获取一些信息。
处理组件池的重要核心方法有:
internal static class EntityManager
{
public static List<Guid> activeEntities = new List<Guid>();
private static KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools = new KeyedByTypeCollection<Dictionary<Guid, IComponent>>();
public static KeyedByTypeCollection<ISystem> systems = new KeyedByTypeCollection<ISystem>(); // Hold unique Systems
public static Guid CreateEntity() // Add a new GUID and return it
{
Guid entityId = Guid.NewGuid();
activeEntities.Add(entityId);
return entityId;
}
public static void DestroyEntity(Guid entityId) // Remove the entity from every component pool
{
activeEntities.Remove(entityId);
for (int i = 0; i < componentPools.Count; i++)
{
componentPools[i].Remove(entityId);
}
}
public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
return componentPools[typeof(TComponent)];
}
public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
componentPools.Add(new Dictionary<Guid, TComponent>());
}
public static void RemoveComponentPool<TComponent>() where TComponent : IComponent // remove a pool by its component type
{
componentPools.Remove(typeof(TComponent));
}
public static void AddComponentToEntity(Guid entityId, IComponent component) // add a component to an entity by the component type
{
componentPools[component.GetType()].Add(entityId, component);
}
public static void RemoveComponentFromEntity<TComponent>(Guid entityId) where TComponent : IComponent // remove a component from an entity by the component type
{
componentPools[typeof(TComponent)].Remove(entityId);
}
}
我创建了一个用于测试的小型移动系统:
internal class Movement : ISystem
{
public void Run()
{
for (int i = 0; i < EntityManager.activeEntities.Count; i++)
{
Guid entityId = EntityManager.activeEntities[i];
if (EntityManager.GetComponentPool<Position>().TryGetValue(entityId, out Position positionComponent)) // Get the position component
{
positionComponent.X++; // Move one to the right and one up ...
positionComponent.Y++;
}
}
}
}
由于字典查找,这应该没问题。我不确定我是否可以优化它,但我想我必须先遍历所有实体。
问题来了:
我无法使用 KeyedByTypeCollection<Dictionary<Guid, IComponent>>
因为我需要像
这样的东西
KeyedByTypeCollection<Dictionary<Guid, Type where Type : IComponent>>
我的 GetComponentPool
和 AddComponentPool
方法抛出错误。
GetComponentPool:无法将 IComponent 隐式转换为 TComponent
调用 GetComponentPool
时,我必须将字典值从 IComponent
转换为 TComponent
。
AddComponentPool:无法从 TComponent 转换为 IComponent
调用 AddComponentPool
时,我必须将 TComponent
转换为 IComponent
。
我不认为转换是一种选择,因为这似乎会降低性能。
有人介意帮我解决类型问题吗?
更新:
调试时我使用此代码测试整个 ECS
internal class Program
{
private static void Main(string[] args)
{
EntityManager.AddComponentPool<Position>();
EntityManager.AddComponentPool<MovementSpeed>();
EntityManager.Systems.Add(new Movement());
Guid playerId = EntityManager.CreateEntity();
EntityManager.AddComponentToEntity(playerId, new Position());
EntityManager.AddComponentToEntity(playerId, new MovementSpeed(1));
foreach (ISystem system in EntityManager.Systems)
{
system.Run();
}
}
}
我已经展示了Movement
系统,这是缺少的组件
internal class Position : IComponent
{
public Position(float x = 0, float y = 0)
{
X = x;
Y = y;
}
public float X { get; set; }
public float Y { get; set; }
}
internal class MovementSpeed : IComponent
{
public MovementSpeed(float value = 0)
{
Value = value;
}
public float Value { get; set; }
}
问题的核心是 Dictionary<Guid, TComponent>
和 Dictionary<Guid, IComponent>
是完全不相关的类型。它们不能相互转换。这是因为字典是可以读写的。如果可以将 List<Giraffe>
转换为 List<Animal>
,则可以存储 Tiger
,这会破坏类型安全。如果您可以转换为其他方式,您可以读取 Tiger
个实例并将它们视为 Giraffe
,这又会破坏类型安全。您可以在网络上搜索 .net covariance contravariance
以查找更多信息。
一个简单的解决方案是从
KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools
到
Dictionary<Type, object> componentPools
.
这使您能够在集合中存储一个 Dictionary<Guid, TComponent>
,这在以前是不可能的。缺点是你需要现在施法。但是您只需要转换相当便宜的字典实例。您不需要在 O(N) 时间内转换整个字典。
存取器:
public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
return (Dictionary<Guid, TComponent>)componentPools[typeof(TComponent)]; //cast inserted
}
public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
componentPools.Add(typeof(TComponent), (object)new Dictionary<Guid, TComponent>()); //unneeded cast inserted for clarity
}
也许您根本不喜欢我们需要演员表。我看不出有什么办法。实践经验表明,当您详细说明类型系统(泛型、变体)时,您会得到一个更糟糕的解决方案。所以不用担心。做有用的事。
我正在设置一个非常小的实体-组件-系统示例,但组件池存在一些问题。
目前我的实体只是 ID (GUID),这很好。
每个系统都必须实现 ISystem
接口
internal interface ISystem
{
void Run();
}
并存储在KeyedByTypeCollection
中。这个集合确保每个系统都是独一无二的。
每个组件都必须实现 IComponent
接口。
internal interface IComponent
{
}
通过这样做,我可以将所有不同的组件类型存储到它们匹配的组件池中。每个池是一个Dictionary<Guid, IComponent>
。键表示实体的 ID,值是该实体的组件。每个池都存储在 KeyedByTypeCollection
中以确保组件池是唯一的。
目前我的EntityManager
class包含核心逻辑。我不知道是否需要将其设为静态,但目前我的系统需要从中获取一些信息。
处理组件池的重要核心方法有:
internal static class EntityManager
{
public static List<Guid> activeEntities = new List<Guid>();
private static KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools = new KeyedByTypeCollection<Dictionary<Guid, IComponent>>();
public static KeyedByTypeCollection<ISystem> systems = new KeyedByTypeCollection<ISystem>(); // Hold unique Systems
public static Guid CreateEntity() // Add a new GUID and return it
{
Guid entityId = Guid.NewGuid();
activeEntities.Add(entityId);
return entityId;
}
public static void DestroyEntity(Guid entityId) // Remove the entity from every component pool
{
activeEntities.Remove(entityId);
for (int i = 0; i < componentPools.Count; i++)
{
componentPools[i].Remove(entityId);
}
}
public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
return componentPools[typeof(TComponent)];
}
public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
componentPools.Add(new Dictionary<Guid, TComponent>());
}
public static void RemoveComponentPool<TComponent>() where TComponent : IComponent // remove a pool by its component type
{
componentPools.Remove(typeof(TComponent));
}
public static void AddComponentToEntity(Guid entityId, IComponent component) // add a component to an entity by the component type
{
componentPools[component.GetType()].Add(entityId, component);
}
public static void RemoveComponentFromEntity<TComponent>(Guid entityId) where TComponent : IComponent // remove a component from an entity by the component type
{
componentPools[typeof(TComponent)].Remove(entityId);
}
}
我创建了一个用于测试的小型移动系统:
internal class Movement : ISystem
{
public void Run()
{
for (int i = 0; i < EntityManager.activeEntities.Count; i++)
{
Guid entityId = EntityManager.activeEntities[i];
if (EntityManager.GetComponentPool<Position>().TryGetValue(entityId, out Position positionComponent)) // Get the position component
{
positionComponent.X++; // Move one to the right and one up ...
positionComponent.Y++;
}
}
}
}
由于字典查找,这应该没问题。我不确定我是否可以优化它,但我想我必须先遍历所有实体。
问题来了:
我无法使用 KeyedByTypeCollection<Dictionary<Guid, IComponent>>
因为我需要像
KeyedByTypeCollection<Dictionary<Guid, Type where Type : IComponent>>
我的 GetComponentPool
和 AddComponentPool
方法抛出错误。
GetComponentPool:无法将 IComponent 隐式转换为 TComponent
调用 GetComponentPool
时,我必须将字典值从 IComponent
转换为 TComponent
。
AddComponentPool:无法从 TComponent 转换为 IComponent
调用 AddComponentPool
时,我必须将 TComponent
转换为 IComponent
。
我不认为转换是一种选择,因为这似乎会降低性能。
有人介意帮我解决类型问题吗?
更新:
调试时我使用此代码测试整个 ECS
internal class Program
{
private static void Main(string[] args)
{
EntityManager.AddComponentPool<Position>();
EntityManager.AddComponentPool<MovementSpeed>();
EntityManager.Systems.Add(new Movement());
Guid playerId = EntityManager.CreateEntity();
EntityManager.AddComponentToEntity(playerId, new Position());
EntityManager.AddComponentToEntity(playerId, new MovementSpeed(1));
foreach (ISystem system in EntityManager.Systems)
{
system.Run();
}
}
}
我已经展示了Movement
系统,这是缺少的组件
internal class Position : IComponent
{
public Position(float x = 0, float y = 0)
{
X = x;
Y = y;
}
public float X { get; set; }
public float Y { get; set; }
}
internal class MovementSpeed : IComponent
{
public MovementSpeed(float value = 0)
{
Value = value;
}
public float Value { get; set; }
}
问题的核心是 Dictionary<Guid, TComponent>
和 Dictionary<Guid, IComponent>
是完全不相关的类型。它们不能相互转换。这是因为字典是可以读写的。如果可以将 List<Giraffe>
转换为 List<Animal>
,则可以存储 Tiger
,这会破坏类型安全。如果您可以转换为其他方式,您可以读取 Tiger
个实例并将它们视为 Giraffe
,这又会破坏类型安全。您可以在网络上搜索 .net covariance contravariance
以查找更多信息。
一个简单的解决方案是从
KeyedByTypeCollection<Dictionary<Guid, IComponent>> componentPools
到
Dictionary<Type, object> componentPools
.
这使您能够在集合中存储一个 Dictionary<Guid, TComponent>
,这在以前是不可能的。缺点是你需要现在施法。但是您只需要转换相当便宜的字典实例。您不需要在 O(N) 时间内转换整个字典。
存取器:
public static Dictionary<Guid, TComponent> GetComponentPool<TComponent>() where TComponent : IComponent // get a component pool by its component type
{
return (Dictionary<Guid, TComponent>)componentPools[typeof(TComponent)]; //cast inserted
}
public static void AddComponentPool<TComponent>() where TComponent : IComponent // add a new pool by its component type
{
componentPools.Add(typeof(TComponent), (object)new Dictionary<Guid, TComponent>()); //unneeded cast inserted for clarity
}
也许您根本不喜欢我们需要演员表。我看不出有什么办法。实践经验表明,当您详细说明类型系统(泛型、变体)时,您会得到一个更糟糕的解决方案。所以不用担心。做有用的事。