Skip to content

Commit a3ca085

Browse files
committed
Merge branch 'group-event-optimizations'
2 parents 5bfde74 + b436c8f commit a3ca085

File tree

22 files changed

+217
-90
lines changed

22 files changed

+217
-90
lines changed

src/EcsRx.Examples/ExampleApps/Performance/Helper/RandomGroupFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public IEnumerable<IGroup> CreateTestGroups(int cycles = 5)
6464
/*
6565
foreach (var component in _componenTypes)
6666
{
67-
yield return new Group(component);
67+
yield return new LookupGroup(component);
6868
}*/
6969
}
7070
}

src/EcsRx.Examples/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Program
99
static void Main(string[] args)
1010
{
1111
var application = new GroupPerformanceApplication();
12-
//var application = new OptimizedGroupPerformanceApplication();();
12+
//var application = new OptimizedGroupPerformanceApplication();
1313
//var application = new OptimizedEntityPerformanceApplication();
1414
//var application = new EntityPerformanceApplication();
1515
//var application = new ComputedGroupExampleApplication();

src/EcsRx.PerformanceTests/GroupPerformanceScenario.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void GlobalSetup()
4646
var entityFactory = new DefaultEntityFactory(new IdPool(), componentRepository);
4747
var poolFactory = new DefaultEntityCollectionFactory(entityFactory);
4848
var observableGroupFactory = new DefaultObservableObservableGroupFactory();
49-
_entityCollectionManager = new EntityCollectionManager(poolFactory, observableGroupFactory);
49+
_entityCollectionManager = new EntityCollectionManager(poolFactory, observableGroupFactory, componentLookup);
5050

5151
_availableComponents = _groupFactory.GetComponentTypes
5252
.Select(x => Activator.CreateInstance(x) as IComponent)

src/EcsRx.Systems/IReactToEntitySystem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace EcsRx.Systems
1111
/// </summary>
1212
/// <remarks>
1313
/// If you do not need to react to each entity individually it is recommended you
14-
/// use a React To Group system as they have less overhead as there is only one
14+
/// use a React To LookupGroup system as they have less overhead as there is only one
1515
/// subscription required rather than 1 per entity.
1616
/// </remarks>
1717
public interface IReactToEntitySystem : ISystem

src/EcsRx.Systems/IReactToGroupSystem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace EcsRx.Systems
66
{
77
/// <summary>
8-
/// React To Group systems are the more common ECS style system,
8+
/// React To LookupGroup systems are the more common ECS style system,
99
/// as they batch handle all applicable entities at once. This means
1010
/// you do not react to individual entities and instead react at the
1111
/// group level, be it every frame or time period.

src/EcsRx.Tests/Framework/EntityTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public void should_raise_event_when_removing_component_that_exists()
3333
var entity = new Entity(1, componentRepository);
3434
var dummyComponent = Substitute.For<IComponent>();
3535

36-
componentRepository.Has(Arg.Any<int>(), dummyComponent.GetType()).Returns(true);
36+
componentRepository.Has(Arg.Any<int>(), 1).Returns(true);
37+
componentRepository.GetTypesFor(Arg.Any<Type>()).Returns(new []{1});
3738

3839
var beforeWasCalled = false;
3940
var afterWasCalled = false;
@@ -119,24 +120,23 @@ public void should_remove_all_components_when_disposing()
119120

120121
var componentRepository = Substitute.For<IComponentRepository>();
121122
componentRepository.GetAll(fakeEntityId).Returns(fakeComponents);
122-
123-
componentRepository.Has(Arg.Any<int>(), Arg.Any<Type>()).Returns(true);
123+
componentRepository.GetTypesFor(Arg.Any<Type[]>()).Returns(new []{1,2});
124+
componentRepository.Has(Arg.Any<int>(), Arg.Any<int>()).Returns(true);
124125

125126
var entity = new Entity(fakeEntityId, componentRepository);
126127

127128
var beforeWasCalled = false;
128129
var afterWasCalled = false;
130+
var expectedRange = Enumerable.Range(1, 2);
129131
entity.ComponentsRemoving.Subscribe(x =>
130132
{
131133
beforeWasCalled = true;
132-
var fakeComponentTypes = fakeComponents.Select(y => y.GetType());
133-
Assert.All(x, y => fakeComponentTypes.Contains(y));
134+
Assert.All(x, y => expectedRange.Contains(y));
134135
});
135136
entity.ComponentsRemoved.Subscribe(x =>
136137
{
137138
afterWasCalled = true;
138-
var fakeComponentTypes = fakeComponents.Select(y => y.GetType());
139-
Assert.All(x, y => fakeComponentTypes.Contains(y));
139+
Assert.All(x, y => expectedRange.Contains(y));
140140
});
141141

142142
entity.Dispose();

src/EcsRx.Tests/Framework/Observables/ObservableGroupTests.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class ObservableGroupTests
2222
public void should_include_entity_snapshot_on_creation()
2323
{
2424
var mockCollectionNotifier = Substitute.For<INotifyingEntityCollection>();
25-
var accessorToken = new ObservableGroupToken(new Type[]{ typeof(TestComponentOne) }, new Type[0], "default");
25+
var accessorToken = new ObservableGroupToken(new[]{1}, new int[0], "default");
2626

2727
var applicableEntity1 = Substitute.For<IEntity>();
2828
var applicableEntity2 = Substitute.For<IEntity>();
@@ -32,9 +32,9 @@ public void should_include_entity_snapshot_on_creation()
3232
applicableEntity2.Id.Returns(2);
3333
notApplicableEntity1.Id.Returns(3);
3434

35-
applicableEntity1.HasComponent(Arg.Any<Type>()).Returns(true);
36-
applicableEntity2.HasComponent(Arg.Any<Type>()).Returns(true);
37-
notApplicableEntity1.HasComponent(Arg.Any<Type>()).Returns(false);
35+
applicableEntity1.HasComponent(Arg.Any<int>()).Returns(true);
36+
applicableEntity2.HasComponent(Arg.Any<int>()).Returns(true);
37+
notApplicableEntity1.HasComponent(Arg.Any<int>()).Returns(false);
3838

3939
var dummyEntitySnapshot = new List<IEntity>
4040
{
@@ -60,17 +60,17 @@ public void should_include_entity_snapshot_on_creation()
6060
public void should_add_entity_and_raise_event_when_applicable_entity_added()
6161
{
6262
var collectionName = "default";
63-
var accessorToken = new ObservableGroupToken(new[] { typeof(TestComponentOne), typeof(TestComponentTwo) }, new Type[0], collectionName);
63+
var accessorToken = new ObservableGroupToken(new[] { 1,2 }, new int[0], collectionName);
6464
var mockCollection = Substitute.For<IEntityCollection>();
6565
mockCollection.Name.Returns(collectionName);
6666

6767
var applicableEntity = Substitute.For<IEntity>();
6868
applicableEntity.Id.Returns(1);
69-
applicableEntity.HasComponent(Arg.Is<Type>(x => accessorToken.Group.RequiredComponents.Contains(x))).Returns(true);
69+
applicableEntity.HasComponent(Arg.Is<int>(x => accessorToken.LookupGroup.RequiredComponents.Contains(x))).Returns(true);
7070

7171
var unapplicableEntity = Substitute.For<IEntity>();
7272
unapplicableEntity.Id.Returns(2);
73-
unapplicableEntity.HasComponent(Arg.Is<Type>(x => accessorToken.Group.RequiredComponents.Contains(x))).Returns(false);
73+
unapplicableEntity.HasComponent(Arg.Is<int>(x => accessorToken.LookupGroup.RequiredComponents.Contains(x))).Returns(false);
7474

7575
var mockCollectionNotifier = Substitute.For<INotifyingEntityCollection>();
7676

@@ -99,7 +99,7 @@ public void should_add_entity_and_raise_event_when_applicable_entity_added()
9999
public void should_add_entity_and_raise_event_when_components_match_group()
100100
{
101101
var collectionName = "default";
102-
var accessorToken = new ObservableGroupToken(new[] { typeof(TestComponentOne) }, new []{typeof(TestComponentTwo)}, collectionName);
102+
var accessorToken = new ObservableGroupToken(new[] { 1 }, new []{ 2 }, collectionName);
103103
var mockCollection = Substitute.For<IEntityCollection>();
104104
mockCollection.Name.Returns(collectionName);
105105

@@ -119,8 +119,8 @@ public void should_add_entity_and_raise_event_when_components_match_group()
119119
var wasCalled = 0;
120120
observableGroup.OnEntityAdded.Subscribe(x => wasCalled++);
121121

122-
applicableEntity.HasAllComponents(accessorToken.Group.RequiredComponents).Returns(true);
123-
applicableEntity.HasAnyComponents(accessorToken.Group.ExcludedComponents).Returns(false);
122+
applicableEntity.HasAllComponents(accessorToken.LookupGroup.RequiredComponents).Returns(true);
123+
applicableEntity.HasAnyComponents(accessorToken.LookupGroup.ExcludedComponents).Returns(false);
124124
componentRemoved.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, null));
125125

126126
Assert.Contains(applicableEntity, observableGroup.CachedEntities.Values);
@@ -131,14 +131,14 @@ public void should_add_entity_and_raise_event_when_components_match_group()
131131
public void should_remove_entity_and_raise_events_when_entity_removed_with_components()
132132
{
133133
var collectionName = "default";
134-
var accessorToken = new ObservableGroupToken(new[] { typeof(TestComponentOne), typeof(TestComponentTwo) }, new Type[0], collectionName);
134+
var accessorToken = new ObservableGroupToken(new[] { 1, 2 }, new int[0], collectionName);
135135
var mockCollection = Substitute.For<IEntityCollection>();
136136
mockCollection.Name.Returns(collectionName);
137137

138138
var applicableEntity = Substitute.For<IEntity>();
139139
applicableEntity.Id.Returns(1);
140-
applicableEntity.HasComponent(Arg.Is<Type>(x => accessorToken.Group.RequiredComponents.Contains(x))).Returns(true);
141-
applicableEntity.HasComponent(Arg.Is<Type>(x => accessorToken.Group.ExcludedComponents.Contains(x))).Returns(false);
140+
applicableEntity.HasComponent(Arg.Is<int>(x => accessorToken.LookupGroup.RequiredComponents.Contains(x))).Returns(true);
141+
applicableEntity.HasComponent(Arg.Is<int>(x => accessorToken.LookupGroup.ExcludedComponents.Contains(x))).Returns(false);
142142

143143
var mockCollectionNotifier = Substitute.For<INotifyingEntityCollection>();
144144

@@ -156,7 +156,7 @@ public void should_remove_entity_and_raise_events_when_entity_removed_with_compo
156156
var wasRemovedCalled = 0;
157157
observableGroup.OnEntityRemoved.Subscribe(x => wasRemovedCalled++);
158158

159-
componentRemoving.OnNext(new ComponentsChangedEvent(null, applicableEntity, new[]{typeof(TestComponentOne)}));
159+
componentRemoving.OnNext(new ComponentsChangedEvent(null, applicableEntity, new[]{1}));
160160

161161
Assert.Contains(applicableEntity, observableGroup.CachedEntities.Values);
162162
Assert.Equal(1, wasRemovingCalled);
@@ -174,14 +174,14 @@ public void should_remove_entity_and_raise_events_when_entity_removed_with_compo
174174
public void should_remove_entity_and_raise_event_when_no_longer_matches_group()
175175
{
176176
var collectionName = "default";
177-
var accessorToken = new ObservableGroupToken(new[] { typeof(TestComponentOne), typeof(TestComponentTwo) }, new Type[0], collectionName);
177+
var accessorToken = new ObservableGroupToken(new[] { 1,2 }, new int[0], collectionName);
178178
var mockCollection = Substitute.For<IEntityCollection>();
179179
mockCollection.Name.Returns(collectionName);
180180

181181
var applicableEntity = Substitute.For<IEntity>();
182182
applicableEntity.Id.Returns(1);
183-
applicableEntity.HasComponent(Arg.Is<Type>(x => accessorToken.Group.RequiredComponents.Contains(x))).Returns(true);
184-
applicableEntity.HasComponent(Arg.Is<Type>(x => accessorToken.Group.ExcludedComponents.Contains(x))).Returns(false);
183+
applicableEntity.HasComponent(Arg.Is<int>(x => accessorToken.LookupGroup.RequiredComponents.Contains(x))).Returns(true);
184+
applicableEntity.HasComponent(Arg.Is<int>(x => accessorToken.LookupGroup.ExcludedComponents.Contains(x))).Returns(false);
185185

186186
var mockCollectionNotifier = Substitute.For<INotifyingEntityCollection>();
187187

@@ -201,10 +201,10 @@ public void should_remove_entity_and_raise_event_when_no_longer_matches_group()
201201
var wasRemovedCalled = 0;
202202
observableGroup.OnEntityRemoved.Subscribe(x => wasRemovedCalled++);
203203

204-
applicableEntity.HasAnyComponents(accessorToken.Group.RequiredComponents).Returns(false);
205-
applicableEntity.HasAllComponents(accessorToken.Group.RequiredComponents).Returns(false);
206-
componentRemoving.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, new[]{ typeof(TestComponentOne) }));
207-
componentRemoved.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, new[]{ typeof(TestComponentOne) }));
204+
applicableEntity.HasAnyComponents(accessorToken.LookupGroup.RequiredComponents).Returns(false);
205+
applicableEntity.HasAllComponents(accessorToken.LookupGroup.RequiredComponents).Returns(false);
206+
componentRemoving.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, new[]{ 1 }));
207+
componentRemoved.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, new[]{ 1 }));
208208

209209
Assert.DoesNotContain(applicableEntity, observableGroup.CachedEntities.Values);
210210
Assert.Equal(1, wasRemovingCalled);

src/EcsRx.Tests/Framework/SanityTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ private IEntityCollectionManager CreateCollectionManager()
3131
var entityFactory = new DefaultEntityFactory(new IdPool(), componentRepository);
3232
var collectionFactory = new DefaultEntityCollectionFactory(entityFactory);
3333
var observableGroupFactory = new DefaultObservableObservableGroupFactory();
34-
return new EntityCollectionManager(collectionFactory, observableGroupFactory);
34+
return new EntityCollectionManager(collectionFactory, observableGroupFactory, componentLookupType);
3535
}
3636

3737
private SystemExecutor CreateExecutor(IEntityCollectionManager entityCollectionManager)

src/EcsRx/Collections/EntityCollectionManager.cs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using EcsRx.Components;
45
using EcsRx.Entities;
56
using EcsRx.Events;
67
using EcsRx.Extensions;
@@ -21,23 +22,29 @@ public class EntityCollectionManager : IEntityCollectionManager, IDisposable
2122
public IEnumerable<IEntityCollection> Collections => _collections.Values;
2223
public IEntityCollectionFactory EntityCollectionFactory { get; }
2324
public IObservableGroupFactory ObservableGroupFactory { get; }
25+
public IComponentTypeLookup ComponentTypeLookup { get; }
2426

2527
public IObservable<CollectionEntityEvent> EntityAdded => _onEntityAdded;
2628
public IObservable<CollectionEntityEvent> EntityRemoved => _onEntityRemoved;
2729
public IObservable<ComponentsChangedEvent> EntityComponentsAdded => _onEntityComponentsAdded;
2830
public IObservable<ComponentsChangedEvent> EntityComponentsRemoving => _onEntityComponentsRemoving;
2931
public IObservable<ComponentsChangedEvent> EntityComponentsRemoved => _onEntityComponentsRemoved;
32+
public IObservable<IEntityCollection> CollectionAdded => _onCollectionAdded;
33+
public IObservable<IEntityCollection> CollectionRemoved => _onCollectionRemoved;
3034

3135
private readonly Subject<CollectionEntityEvent> _onEntityAdded;
3236
private readonly Subject<CollectionEntityEvent> _onEntityRemoved;
3337
private readonly Subject<ComponentsChangedEvent> _onEntityComponentsAdded;
3438
private readonly Subject<ComponentsChangedEvent> _onEntityComponentsRemoving;
3539
private readonly Subject<ComponentsChangedEvent> _onEntityComponentsRemoved;
40+
private readonly Subject<IEntityCollection> _onCollectionAdded;
41+
private readonly Subject<IEntityCollection> _onCollectionRemoved;
3642

37-
public EntityCollectionManager(IEntityCollectionFactory entityCollectionFactory, IObservableGroupFactory observableGroupFactory)
43+
public EntityCollectionManager(IEntityCollectionFactory entityCollectionFactory, IObservableGroupFactory observableGroupFactory, IComponentTypeLookup componentTypeLookup)
3844
{
3945
EntityCollectionFactory = entityCollectionFactory;
4046
ObservableGroupFactory = observableGroupFactory;
47+
ComponentTypeLookup = componentTypeLookup;
4148

4249
_observableGroups = new Dictionary<ObservableGroupToken, IObservableGroup>();
4350
_collections = new Dictionary<string, IEntityCollection>();
@@ -48,6 +55,8 @@ public EntityCollectionManager(IEntityCollectionFactory entityCollectionFactory,
4855
_onEntityComponentsAdded = new Subject<ComponentsChangedEvent>();
4956
_onEntityComponentsRemoving = new Subject<ComponentsChangedEvent>();
5057
_onEntityComponentsRemoved = new Subject<ComponentsChangedEvent>();
58+
_onCollectionAdded = new Subject<IEntityCollection>();
59+
_onCollectionRemoved = new Subject<IEntityCollection>();
5160

5261
CreateCollection(DefaultPoolName);
5362
}
@@ -74,8 +83,8 @@ public IEntityCollection CreateCollection(string name)
7483
_collections.Add(name, collection);
7584
SubscribeToCollection(collection);
7685

77-
//EventSystem.Publish(new CollectionAddedEvent(collection));
78-
86+
_onCollectionAdded.OnNext(collection);
87+
7988
return collection;
8089
}
8190

@@ -91,7 +100,7 @@ public void RemoveCollection(string name, bool disposeEntities = true)
91100

92101
UnsubscribeFromCollection(name);
93102

94-
//EventSystem.Publish(new CollectionRemovedEvent(collection));
103+
_onCollectionRemoved.OnNext(collection);
95104
}
96105

97106
public IEnumerable<IEntity> GetEntitiesFor(IGroup group, string collectionName = null)
@@ -104,13 +113,27 @@ public IEnumerable<IEntity> GetEntitiesFor(IGroup group, string collectionName =
104113

105114
return Collections.GetAllEntities().MatchingGroup(group);
106115
}
116+
117+
public IEnumerable<IEntity> GetEntitiesFor(ILookupGroup lookupGroup, string collectionName = null)
118+
{
119+
if(lookupGroup.RequiredComponents.Length == 0 && lookupGroup.ExcludedComponents.Length == 0)
120+
{ return new IEntity[0]; }
121+
122+
if (collectionName != null)
123+
{ return _collections[collectionName].MatchingGroup(lookupGroup); }
124+
125+
return Collections.GetAllEntities().MatchingGroup(lookupGroup);
126+
}
107127

108128
public IObservableGroup GetObservableGroup(IGroup group, string collectionName = null)
109129
{
110-
var observableGroupToken = new ObservableGroupToken(group, collectionName);
130+
var requiredComponents = ComponentTypeLookup.GetComponentTypes(group.RequiredComponents);
131+
var excludedComponents = ComponentTypeLookup.GetComponentTypes(group.ExcludedComponents);
132+
var lookupGroup = new LookupGroup(requiredComponents, excludedComponents);
133+
var observableGroupToken = new ObservableGroupToken(lookupGroup, collectionName);
111134
if (_observableGroups.ContainsKey(observableGroupToken)) { return _observableGroups[observableGroupToken]; }
112135

113-
var entityMatches = GetEntitiesFor(group, collectionName);
136+
var entityMatches = GetEntitiesFor(lookupGroup, collectionName);
114137
var configuration = new ObservableGroupConfiguration
115138
{
116139
ObservableGroupToken = observableGroupToken,

src/EcsRx/Collections/IEntityCollectionManager.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
24
using EcsRx.Entities;
5+
using EcsRx.Events;
36
using EcsRx.Groups;
47
using EcsRx.Groups.Observable;
58

@@ -15,6 +18,16 @@ public interface IEntityCollectionManager : INotifyingEntityCollection
1518
/// All the entity collections that the manager contains
1619
/// </summary>
1720
IEnumerable<IEntityCollection> Collections { get; }
21+
22+
/// <summary>
23+
/// Fired when a collection has been added
24+
/// </summary>
25+
IObservable<IEntityCollection> CollectionAdded { get; }
26+
27+
/// <summary>
28+
/// Fired when a collection has been removed
29+
/// </summary>
30+
IObservable<IEntityCollection> CollectionRemoved { get; }
1831

1932
/// <summary>
2033
/// Gets an enumerable collection of entities for you to iterate through,

0 commit comments

Comments
 (0)