Skip to content

Commit

Permalink
a smarter groupof strategy to well use memory (cache)
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonXuDeveloper committed Jan 20, 2025
1 parent 04d823b commit 69fd1f6
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 20 deletions.
298 changes: 295 additions & 3 deletions EasyEcs.Core/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class Context : IAsyncDisposable

private readonly ConcurrentDictionary<long, List<Entity>> _groupsCache = new();
private static readonly ConcurrentQueue<List<Entity>> Pool = new();

/// <summary>
/// Called when an error occurs.
/// </summary>
Expand Down Expand Up @@ -185,7 +185,7 @@ public async ValueTask Update(bool parallel = true)
{
_executeTasks.Add(system.Update(this));
}

try
{
await Task.WhenAll(_executeTasks);
Expand All @@ -205,6 +205,298 @@ public async ValueTask Update(bool parallel = true)
_removeList.Clear();
}

#region Generic GroupOf

/*
* I miss C++ templates.
*/

/// <summary>
/// Get all entities that have the specified components.
/// <br/>
/// Note: remember to dispose the returned enumerable. (i.e. using)
/// <code>
/// using var entities = context.GroupOf<![CDATA[<Component>]]>();
/// </code>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public PooledCollection<List<(Entity, T)>, (Entity, T)> GroupOf<T>() where T : class, IComponent, new()
{
// request a pooled enumerable
var ret = PooledCollection<List<(Entity, T)>, (Entity, T)>.Create();
var lst = ret.Collection;
lst.Clear();

// iterate over all entities and check if they have the components, then add it
using var allEntities = AllEntities;
foreach (var entity in allEntities)
{
if (entity.TryGetComponent(out T component))
{
lst.Add((entity, component));
}
}

return ret;
}

/// <summary>
/// Get all entities that have the specified components.
/// <br/>
/// Note: remember to dispose the returned enumerable. (i.e. using)
/// <code>
/// using var entities = context.GroupOf<![CDATA[<Component1, ...>]]>();
/// </code>
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <returns></returns>
public PooledCollection<List<(Entity, T1, T2)>, (Entity, T1, T2)> GroupOf<T1, T2>()
where T1 : class, IComponent, new()
where T2 : class, IComponent, new()
{
// request a pooled enumerable
var ret = PooledCollection<List<(Entity, T1, T2)>, (Entity, T1, T2)>.Create();
var lst = ret.Collection;
lst.Clear();

// iterate over all entities and check if they have the components, then add it
using var allEntities = AllEntities;
foreach (var entity in allEntities)
{
if (entity.TryGetComponent(out T1 component1) &&
entity.TryGetComponent(out T2 component2))
{
lst.Add((entity, component1, component2));
}
}

return ret;
}

/// <summary>
/// Get all entities that have the specified components.
/// <br/>
/// Note: remember to dispose the returned enumerable. (i.e. using)
/// <code>
/// using var entities = context.GroupOf<![CDATA[<Component1, ...>]]>();
/// </code>
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <returns></returns>
public PooledCollection<List<(Entity, T1, T2, T3)>, (Entity, T1, T2, T3)> GroupOf<T1, T2, T3>()
where T1 : class, IComponent, new()
where T2 : class, IComponent, new()
where T3 : class, IComponent, new()
{
// request a pooled enumerable
var ret = PooledCollection<List<(Entity, T1, T2, T3)>, (Entity, T1, T2, T3)>.Create();
var lst = ret.Collection;
lst.Clear();

// iterate over all entities and check if they have the components, then add it
using var allEntities = AllEntities;
foreach (var entity in allEntities)
{
if (entity.TryGetComponent(out T1 component1) &&
entity.TryGetComponent(out T2 component2) &&
entity.TryGetComponent(out T3 component3))
{
lst.Add((entity, component1, component2, component3));
}
}

return ret;
}

/// <summary>
/// Get all entities that have the specified components.
/// <br/>
/// Note: remember to dispose the returned enumerable. (i.e. using)
/// <code>
/// using var entities = context.GroupOf<![CDATA[<Component1, ...>]]>();
/// </code>
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <typeparam name="T4"></typeparam>
/// <returns></returns>
public PooledCollection<List<(Entity, T1, T2, T3, T4)>, (Entity, T1, T2, T3, T4)> GroupOf<T1, T2, T3, T4>()
where T1 : class, IComponent, new()
where T2 : class, IComponent, new()
where T3 : class, IComponent, new()
where T4 : class, IComponent, new()
{
// request a pooled enumerable
var ret = PooledCollection<List<(Entity, T1, T2, T3, T4)>, (Entity, T1, T2, T3, T4)>.Create();
var lst = ret.Collection;
lst.Clear();

// iterate over all entities and check if they have the components, then add it
using var allEntities = AllEntities;
foreach (var entity in allEntities)
{
if (entity.TryGetComponent(out T1 component1) &&
entity.TryGetComponent(out T2 component2) &&
entity.TryGetComponent(out T3 component3) &&
entity.TryGetComponent(out T4 component4))
{
lst.Add((entity, component1, component2, component3, component4));
}
}

return ret;
}

/// <summary>
/// Get all entities that have the specified components.
/// <br/>
/// Note: remember to dispose the returned enumerable. (i.e. using)
/// <code>
/// using var entities = context.GroupOf<![CDATA[<Component1, ...>]]>();
/// </code>
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <typeparam name="T4"></typeparam>
/// <typeparam name="T5"></typeparam>
/// <returns></returns>
public PooledCollection<List<(Entity, T1, T2, T3, T4, T5)>, (Entity, T1, T2, T3, T4, T5)> GroupOf<T1, T2, T3, T4,
T5>()
where T1 : class, IComponent, new()
where T2 : class, IComponent, new()
where T3 : class, IComponent, new()
where T4 : class, IComponent, new()
where T5 : class, IComponent, new()
{
// request a pooled enumerable
var ret = PooledCollection<List<(Entity, T1, T2, T3, T4, T5)>, (Entity, T1, T2, T3, T4, T5)>.Create();
var lst = ret.Collection;
lst.Clear();

// iterate over all entities and check if they have the components, then add it
using var allEntities = AllEntities;
foreach (var entity in allEntities)
{
if (entity.TryGetComponent(out T1 component1) &&
entity.TryGetComponent(out T2 component2) &&
entity.TryGetComponent(out T3 component3) &&
entity.TryGetComponent(out T4 component4) &&
entity.TryGetComponent(out T5 component5))
{
lst.Add((entity, component1, component2, component3, component4, component5));
}
}

return ret;
}

/// <summary>
/// Get all entities that have the specified components.
/// <br/>
/// Note: remember to dispose the returned enumerable. (i.e. using)
/// <code>
/// using var entities = context.GroupOf<![CDATA[<Component1, ...>]]>();
/// </code>
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <typeparam name="T4"></typeparam>
/// <typeparam name="T5"></typeparam>
/// <typeparam name="T6"></typeparam>
/// <returns></returns>
public PooledCollection<List<(Entity, T1, T2, T3, T4, T5, T6)>,
(Entity, T1, T2, T3, T4, T5, T6)> GroupOf<T1, T2, T3, T4, T5, T6>()
where T1 : class, IComponent, new()
where T2 : class, IComponent, new()
where T3 : class, IComponent, new()
where T4 : class, IComponent, new()
where T5 : class, IComponent, new()
where T6 : class, IComponent, new()
{
// request a pooled enumerable
var ret = PooledCollection<List<(Entity, T1, T2, T3, T4, T5, T6)>,
(Entity, T1, T2, T3, T4, T5, T6)>.Create();
var lst = ret.Collection;
lst.Clear();

// iterate over all entities and check if they have the components, then add it
using var allEntities = AllEntities;
foreach (var entity in allEntities)
{
if (entity.TryGetComponent(out T1 component1) &&
entity.TryGetComponent(out T2 component2) &&
entity.TryGetComponent(out T3 component3) &&
entity.TryGetComponent(out T4 component4) &&
entity.TryGetComponent(out T5 component5) &&
entity.TryGetComponent(out T6 component6))
{
lst.Add((entity, component1, component2, component3, component4, component5, component6));
}
}

return ret;
}

/// <summary>
/// Get all entities that have the specified components.
/// <br/>
/// Note: remember to dispose the returned enumerable. (i.e. using)
/// <code>
/// using var entities = context.GroupOf<![CDATA[<Component1, ...>]]>();
/// </code>
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="T3"></typeparam>
/// <typeparam name="T4"></typeparam>
/// <typeparam name="T5"></typeparam>
/// <typeparam name="T6"></typeparam>
/// <typeparam name="T7"></typeparam>
/// <returns></returns>
public PooledCollection<List<(Entity, T1, T2, T3, T4, T5, T6, T7)>,
(Entity, T1, T2, T3, T4, T5, T6, T7)> GroupOf<T1, T2, T3, T4, T5, T6, T7>()
where T1 : class, IComponent, new()
where T2 : class, IComponent, new()
where T3 : class, IComponent, new()
where T4 : class, IComponent, new()
where T5 : class, IComponent, new()
where T6 : class, IComponent, new()
where T7 : class, IComponent, new()
{
// request a pooled enumerable
var ret = PooledCollection<List<(Entity, T1, T2, T3, T4, T5, T6, T7)>,
(Entity, T1, T2, T3, T4, T5, T6, T7)>.Create();
var lst = ret.Collection;
lst.Clear();

// iterate over all entities and check if they have the components, then add it
using var allEntities = AllEntities;
foreach (var entity in allEntities)
{
if (entity.TryGetComponent(out T1 component1) &&
entity.TryGetComponent(out T2 component2) &&
entity.TryGetComponent(out T3 component3) &&
entity.TryGetComponent(out T4 component4) &&
entity.TryGetComponent(out T5 component5) &&
entity.TryGetComponent(out T6 component6) &&
entity.TryGetComponent(out T7 component7))
{
lst.Add((entity, component1, component2, component3, component4, component5, component6, component7));
}
}

return ret;
}

#endregion

/// <summary>
/// Get all entities that have the specified components.
/// <br/>
Expand All @@ -228,7 +520,7 @@ public PooledCollection<List<Entity>, Entity> GroupOf(params Type[] components)
var ret = PooledCollection<List<Entity>, Entity>.Create();
var lst = ret.Collection;
lst.Clear();

// if we have already looked up this group at this update, we simply copy the results
if (_groupsCache.TryGetValue(group, out var entities))
{
Expand Down
21 changes: 21 additions & 0 deletions EasyEcs.Core/Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,27 @@ public Entity(Context context, int id)
throw new InvalidOperationException($"Component {typeof(T)} not found.");
}

/// <summary>
/// Try to get a component from the entity.
/// </summary>
/// <param name="component"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public bool TryGetComponent<T>([NotNullWhen(true)] out T component) where T : class, IComponent, new()
{
foreach (var c in _components)
{
if (c is T comp)
{
component = comp;
return true;
}
}

component = null;
return false;
}

/// <summary>
/// Does the entity have a component?
/// </summary>
Expand Down
10 changes: 4 additions & 6 deletions EasyEcs.UnitTest/Systems/ModificationSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace EasyEcs.UnitTest.Systems;

public class ModificationSystem: SystemBase, IExecuteSystem, IEndSystem
public class ModificationSystem : SystemBase, IExecuteSystem, IEndSystem
{
public int ExecuteFrequency => 5;
public override int Priority => 1;
Expand Down Expand Up @@ -35,16 +35,14 @@ public Task OnExecute(Context context)
/// <returns></returns>
public Task OnEnd(Context context)
{
using var candidates = context.GroupOf(
typeof(SizeComponent));
using var candidates = context.GroupOf<SizeComponent>();

foreach (var entity in candidates)
foreach (var (_, sizeComponent) in candidates)
{
var sizeComponent = entity.GetComponent<SizeComponent>();
sizeComponent.Width = 0;
sizeComponent.Height = 0;
}

return Task.CompletedTask;
}
}
Loading

0 comments on commit 69fd1f6

Please sign in to comment.