Skip to content

Commit b23d09b

Browse files
committed
Merge branch 'new-observable-approach'
2 parents fb84845 + 37a416e commit b23d09b

File tree

72 files changed

+1655
-336
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1655
-336
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# High Level Architecture
2+
3+
From a high level perspective EcsRx is built on top of SystemsRx, we make use of SystemRx's `ISystemsExecutor` and `ISystem` notions while adding our own custom handlers and ECS paradigms on top.
4+
5+
If we were to look at the highest level of what the MAIN architectural components were it would look something like this:
6+
7+
![](../diagrams/high-level-architecture.png)
8+
9+
## Systems Executor
10+
11+
As you can see we have the `Systems Executor` section which is where all systems end up within the framework, they are all setup internally by registered `System Handlers` which understand how to process certain kinds of system and wire up dependencies.
12+
13+
> In the real world most of the handlers are `IConventionalSystemHandler` implementations and you can make your own versions of these if you want
14+
15+
### Observable Groups
16+
17+
As systems use groups as their contracts we have `Observable Group` objects which deal with maintaining a list of applicable entities and notify any subscribers when they change in any way.
18+
19+
> There is a plugin which extends this further with the notion of `IComputedGroup` which wraps an observable group and filters/constrains it further.
20+
21+
### Observable Group Trackers
22+
23+
The last part of the `System Executor` section is the `Observable Group Trackers` which watch entity collections and notify when entities add/leave a group, this makes the `Observable Group` the storage of what entities are actually in the group, and the trackers just notify it when to change.
24+
25+
> There are various flavours of `ObservableGroupTracker` implementations, we use the Collection based one here but there are ones for individual entity tracking against groups or batched ones too.
26+
27+
## Entity Database
28+
29+
So the entity database contains all entity collections and acts as a sort of proxy to let you query into all collections if you need to, however in most cases you would deal with individual collections directly.
30+
31+
### Entity Collection
32+
33+
These objects just store entities and notify any listeners when entities are added/removed or components change, you can find out more [Here](entity-collections.md).
34+
35+
### Entities
36+
37+
Entities are the main thing developers interact with as they are the data containers that get bounced around the system, to find out more look in the [Entity Docs](../framework/entities.md).
38+
39+
## Component Database
40+
41+
The component database stores all the actual component data in large arrays, this allows us to more quickly access component data.
42+
43+
> Historically components were stored on entities but it causes the memory to become extremely fragmented so this gives a large performance benefit without anyone really needing to know whats going on.
44+
45+
### Component Pools
46+
47+
These are the large arrays (simply speaking) that contain all the actual component data.

docs/diagrams/diagrams.eddx

59.5 KB
Binary file not shown.

docs/diagrams/event-propagation.png

82.4 KB
Loading
70.3 KB
Loading

docs/framework/observable-groups.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ IComputedGroup <- This acts as another layer of filtration on an IObse
1515
i.e Top 5 entities with PlayerComponent sorted by Score
1616
```
1717

18+
Here is a diagram which shows at a high level how they all hook together.
19+
20+
![](../diagrams/event-propagation.png)
21+
1822
## IObservableGroupManager
1923

2024
The observable group manager maintains a collection of `IObservableGroup` so if you have 5 systems which all use the same group, there will only actually be 1 instance of the `IObservableGroup` that is shared between them all.
@@ -23,10 +27,14 @@ The observable group manager maintains a collection of `IObservableGroup` so if
2327

2428
The observable group is created within and maintained by an `IEntityCollectionManager` and exposes all entities which match the associated group. It also exposes observables to represent when an entity has been added or removed from the underlying group.
2529

26-
Under the hood, the observable group watches entities to see when they are added/removed from pools as well as when their components change. When this occurs it will check to see if it effects the current group and if so it updates it internal list accordingly.
30+
Under the hood, the observable group watches entities (via `IObservableGroupTrackers`) to see when they are added/removed from pools as well as when their components change. When this occurs it will check to see if it effects the current group and if so it updates it internal list accordingly.
2731

2832
This has a huge performance benefit as it will stop you needing to evaluate linq chains into the underlying pools and just give you a cached list of all entities currently matching, given also that these observable groups are shared between any systems that require the same underlying components it can save a lot of resources.
2933

34+
## IObservableGroupTracker
35+
36+
This acts as the notifying mechanism for Observable groups to know when changes happen to the underlying entity data.
37+
3038
## IComputedGroup
3139

3240
The computed group is created manually and requires an `IObservableGroup` for it to use for the basis of its queries. It is provided to allow you to filter past the group level and get more specific data sets without having to hard code the logic for the lookup in various systems.
@@ -45,7 +53,7 @@ This implementation has caching built in so it will try to keep a pre-evaluted l
4553

4654
> This now needs the `EcsRx.Plugins.Computeds` nuget package, as the computed functionality has been moved there.
4755
48-
## Querys
56+
## Query Object
4957

5058
So up to this point we have discussed the general filtration process, however there are some extension methods which let you do more ad-hoc queries on data, these are not cached in any way but allow you to drill down into a subset of data in a pre-defined way, this overlaps a bit with the `IComputedGroup` but lets you query directly at the pool level or accessor level.
5159

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using BenchmarkDotNet.Attributes;
5+
using EcsRx.Components;
6+
using EcsRx.Entities;
7+
using EcsRx.Examples.ExampleApps.Performance.Components.Specific;
8+
using EcsRx.Examples.ExampleApps.Performance.Helper;
9+
using EcsRx.Extensions;
10+
using SystemsRx.Extensions;
11+
12+
namespace EcsRx.Benchmarks.Benchmarks
13+
{
14+
[BenchmarkCategory("Entities")]
15+
public class EntityAddComponentsBenchmark : EcsRxBenchmark
16+
{
17+
private Type[] _availableComponentTypes;
18+
private List<IEntity> _entities = new List<IEntity>();
19+
private IComponent[] _components;
20+
private readonly RandomGroupFactory _groupFactory = new RandomGroupFactory();
21+
22+
[Params(1000)]
23+
public int EntityCount;
24+
25+
[Params(1, 25, 50)]
26+
public int ComponentCount;
27+
28+
public EntityAddComponentsBenchmark()
29+
{
30+
var componentNamespace = typeof(Component1).Namespace;
31+
_availableComponentTypes = _groupFactory.GetComponentTypes
32+
.Where(x => x.Namespace == componentNamespace)
33+
.ToArray();
34+
}
35+
36+
public override void Setup()
37+
{
38+
for (var i = 0; i < EntityCount; i++)
39+
{
40+
var entity = new Entity(i, ComponentDatabase, ComponentTypeLookup);
41+
_entities.Add(entity);
42+
}
43+
44+
_components = _availableComponentTypes
45+
.Take(ComponentCount)
46+
.Select(x => Activator.CreateInstance(x) as IComponent)
47+
.ToArray();
48+
}
49+
50+
public override void Cleanup()
51+
{
52+
_entities.ForEach(x => x.RemoveAllComponents());
53+
_entities.Clear();
54+
}
55+
56+
[Benchmark]
57+
public void EntitiesBatchAddComponents()
58+
{
59+
_entities.ForEach(x => x.AddComponents(_components));
60+
}
61+
62+
[Benchmark]
63+
public void EntitiesAddIndividualComponents()
64+
{
65+
_entities.ForEach(x => _components.ForEachRun(y => x.AddComponents(y)));
66+
}
67+
}
68+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using BenchmarkDotNet.Attributes;
5+
using EcsRx.Components;
6+
using EcsRx.Entities;
7+
using EcsRx.Examples.ExampleApps.Performance.Components.Specific;
8+
using EcsRx.Examples.ExampleApps.Performance.Helper;
9+
using EcsRx.Extensions;
10+
using EcsRx.Groups;
11+
12+
namespace EcsRx.Benchmarks.Benchmarks
13+
{
14+
[BenchmarkCategory("Groups", "Entities")]
15+
public class EntityGroupMatchingBenchmark : EcsRxBenchmark
16+
{
17+
private LookupGroup _lookupGroup;
18+
private List<IEntity> _entities = new List<IEntity>();
19+
private Type[] _availableComponentTypes;
20+
21+
private IComponent[] _components;
22+
23+
private readonly RandomGroupFactory _groupFactory = new RandomGroupFactory();
24+
25+
[Params(1000, 10000)]
26+
public int EntityCount;
27+
28+
[Params(1, 25, 50)]
29+
public int ComponentCount;
30+
31+
32+
public EntityGroupMatchingBenchmark() : base()
33+
{
34+
var componentNamespace = typeof(Component1).Namespace;
35+
_availableComponentTypes = _groupFactory.GetComponentTypes
36+
.Where(x => x.Namespace == componentNamespace)
37+
.ToArray();
38+
39+
_lookupGroup = new LookupGroup(_availableComponentTypes
40+
.Select(ComponentTypeLookup.GetComponentTypeId)
41+
.ToArray(), Array.Empty<int>());
42+
}
43+
44+
public override void Setup()
45+
{
46+
_components = _availableComponentTypes
47+
.Take(ComponentCount)
48+
.Select(x => Activator.CreateInstance(x) as IComponent)
49+
.ToArray();
50+
51+
for (var i = 0; i < EntityCount; i++)
52+
{
53+
var entity = new Entity(i, ComponentDatabase, ComponentTypeLookup);
54+
entity.AddComponents(_components);
55+
_entities.Add(entity);
56+
}
57+
}
58+
59+
public override void Cleanup()
60+
{
61+
_entities.ForEach(x => x.RemoveAllComponents());
62+
_entities.Clear();
63+
}
64+
65+
[Benchmark]
66+
public void EntitiesMatchGroup()
67+
{ _entities.ForEach(x => _lookupGroup.Matches(x)); }
68+
}
69+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using BenchmarkDotNet.Attributes;
5+
using EcsRx.Components;
6+
using EcsRx.Entities;
7+
using EcsRx.Examples.ExampleApps.Performance.Components.Specific;
8+
using EcsRx.Examples.ExampleApps.Performance.Helper;
9+
using EcsRx.Extensions;
10+
11+
namespace EcsRx.Benchmarks.Benchmarks
12+
{
13+
[BenchmarkCategory("Entities")]
14+
public class EntityRetrievalBenchmark : EcsRxBenchmark
15+
{
16+
private IComponent[] _availableComponents;
17+
private Type[] _availableComponentTypes;
18+
private List<IEntity> _entities = new List<IEntity>();
19+
private readonly RandomGroupFactory _groupFactory = new RandomGroupFactory();
20+
21+
[Params(1000, 10000, 100000)]
22+
public int EntityCount;
23+
24+
public EntityRetrievalBenchmark() : base()
25+
{
26+
var componentNamespace = typeof(Component1).Namespace;
27+
_availableComponentTypes = _groupFactory.GetComponentTypes
28+
.Where(x => x.Namespace == componentNamespace)
29+
.ToArray();
30+
31+
_availableComponents = _availableComponentTypes
32+
.Select(x => Activator.CreateInstance(x) as IComponent)
33+
.ToArray();
34+
}
35+
36+
public void ProcessEntity(IEntity entity)
37+
{
38+
entity.GetComponent<Component1>();
39+
entity.GetComponent<Component2>();
40+
entity.GetComponent<Component3>();
41+
entity.GetComponent<Component4>();
42+
entity.GetComponent<Component5>();
43+
entity.GetComponent<Component6>();
44+
entity.GetComponent<Component7>();
45+
entity.GetComponent<Component8>();
46+
entity.GetComponent<Component9>();
47+
entity.GetComponent<Component10>();
48+
entity.GetComponent<Component11>();
49+
entity.GetComponent<Component12>();
50+
entity.GetComponent<Component13>();
51+
entity.GetComponent<Component14>();
52+
entity.GetComponent<Component15>();
53+
entity.GetComponent<Component16>();
54+
entity.GetComponent<Component17>();
55+
entity.GetComponent<Component18>();
56+
entity.GetComponent<Component19>();
57+
entity.GetComponent<Component20>();
58+
}
59+
60+
public override void Setup()
61+
{
62+
for (var i = 0; i < EntityCount; i++)
63+
{
64+
var entity = new Entity(i, ComponentDatabase, ComponentTypeLookup);
65+
entity.AddComponents(_availableComponents);
66+
_entities.Add(entity);
67+
}
68+
}
69+
70+
public override void Cleanup()
71+
{
72+
_entities.ForEach(x => x.RemoveAllComponents());
73+
_entities.Clear();
74+
}
75+
76+
[Benchmark]
77+
public void EntitiesGetComponents()
78+
{
79+
for (var i = 0; i < EntityCount; i++)
80+
{ ProcessEntity(_entities[i]); }
81+
}
82+
83+
[Benchmark]
84+
public bool EntitiesHasAllComponents()
85+
{
86+
var ignore = false;
87+
for (var i = 0; i < EntityCount; i++)
88+
{ ignore = _entities[i].HasAllComponents(_availableComponentTypes); }
89+
return ignore;
90+
}
91+
92+
[Benchmark]
93+
public bool EntitiesHasAnyComponents()
94+
{
95+
var ignore = false;
96+
for (var i = 0; i < EntityCount; i++)
97+
{ ignore = _entities[i].HasAnyComponents(_availableComponentTypes); }
98+
return ignore;
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)