Skip to content

Commit

Permalink
Merge branch 'group-event-optimizations'
Browse files Browse the repository at this point in the history
  • Loading branch information
grofit committed Jul 30, 2018
2 parents 5bfde74 + b436c8f commit a3ca085
Show file tree
Hide file tree
Showing 22 changed files with 217 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public IEnumerable<IGroup> CreateTestGroups(int cycles = 5)
/*
foreach (var component in _componenTypes)
{
yield return new Group(component);
yield return new LookupGroup(component);
}*/
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/EcsRx.Examples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Program
static void Main(string[] args)
{
var application = new GroupPerformanceApplication();
//var application = new OptimizedGroupPerformanceApplication();();
//var application = new OptimizedGroupPerformanceApplication();
//var application = new OptimizedEntityPerformanceApplication();
//var application = new EntityPerformanceApplication();
//var application = new ComputedGroupExampleApplication();
Expand Down
2 changes: 1 addition & 1 deletion src/EcsRx.PerformanceTests/GroupPerformanceScenario.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void GlobalSetup()
var entityFactory = new DefaultEntityFactory(new IdPool(), componentRepository);
var poolFactory = new DefaultEntityCollectionFactory(entityFactory);
var observableGroupFactory = new DefaultObservableObservableGroupFactory();
_entityCollectionManager = new EntityCollectionManager(poolFactory, observableGroupFactory);
_entityCollectionManager = new EntityCollectionManager(poolFactory, observableGroupFactory, componentLookup);

_availableComponents = _groupFactory.GetComponentTypes
.Select(x => Activator.CreateInstance(x) as IComponent)
Expand Down
2 changes: 1 addition & 1 deletion src/EcsRx.Systems/IReactToEntitySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace EcsRx.Systems
/// </summary>
/// <remarks>
/// If you do not need to react to each entity individually it is recommended you
/// use a React To Group system as they have less overhead as there is only one
/// use a React To LookupGroup system as they have less overhead as there is only one
/// subscription required rather than 1 per entity.
/// </remarks>
public interface IReactToEntitySystem : ISystem
Expand Down
2 changes: 1 addition & 1 deletion src/EcsRx.Systems/IReactToGroupSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace EcsRx.Systems
{
/// <summary>
/// React To Group systems are the more common ECS style system,
/// React To LookupGroup systems are the more common ECS style system,
/// as they batch handle all applicable entities at once. This means
/// you do not react to individual entities and instead react at the
/// group level, be it every frame or time period.
Expand Down
14 changes: 7 additions & 7 deletions src/EcsRx.Tests/Framework/EntityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public void should_raise_event_when_removing_component_that_exists()
var entity = new Entity(1, componentRepository);
var dummyComponent = Substitute.For<IComponent>();

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

var beforeWasCalled = false;
var afterWasCalled = false;
Expand Down Expand Up @@ -119,24 +120,23 @@ public void should_remove_all_components_when_disposing()

var componentRepository = Substitute.For<IComponentRepository>();
componentRepository.GetAll(fakeEntityId).Returns(fakeComponents);

componentRepository.Has(Arg.Any<int>(), Arg.Any<Type>()).Returns(true);
componentRepository.GetTypesFor(Arg.Any<Type[]>()).Returns(new []{1,2});
componentRepository.Has(Arg.Any<int>(), Arg.Any<int>()).Returns(true);

var entity = new Entity(fakeEntityId, componentRepository);

var beforeWasCalled = false;
var afterWasCalled = false;
var expectedRange = Enumerable.Range(1, 2);
entity.ComponentsRemoving.Subscribe(x =>
{
beforeWasCalled = true;
var fakeComponentTypes = fakeComponents.Select(y => y.GetType());
Assert.All(x, y => fakeComponentTypes.Contains(y));
Assert.All(x, y => expectedRange.Contains(y));
});
entity.ComponentsRemoved.Subscribe(x =>
{
afterWasCalled = true;
var fakeComponentTypes = fakeComponents.Select(y => y.GetType());
Assert.All(x, y => fakeComponentTypes.Contains(y));
Assert.All(x, y => expectedRange.Contains(y));
});

entity.Dispose();
Expand Down
42 changes: 21 additions & 21 deletions src/EcsRx.Tests/Framework/Observables/ObservableGroupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class ObservableGroupTests
public void should_include_entity_snapshot_on_creation()
{
var mockCollectionNotifier = Substitute.For<INotifyingEntityCollection>();
var accessorToken = new ObservableGroupToken(new Type[]{ typeof(TestComponentOne) }, new Type[0], "default");
var accessorToken = new ObservableGroupToken(new[]{1}, new int[0], "default");

var applicableEntity1 = Substitute.For<IEntity>();
var applicableEntity2 = Substitute.For<IEntity>();
Expand All @@ -32,9 +32,9 @@ public void should_include_entity_snapshot_on_creation()
applicableEntity2.Id.Returns(2);
notApplicableEntity1.Id.Returns(3);

applicableEntity1.HasComponent(Arg.Any<Type>()).Returns(true);
applicableEntity2.HasComponent(Arg.Any<Type>()).Returns(true);
notApplicableEntity1.HasComponent(Arg.Any<Type>()).Returns(false);
applicableEntity1.HasComponent(Arg.Any<int>()).Returns(true);
applicableEntity2.HasComponent(Arg.Any<int>()).Returns(true);
notApplicableEntity1.HasComponent(Arg.Any<int>()).Returns(false);

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

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

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

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

Expand Down Expand Up @@ -99,7 +99,7 @@ public void should_add_entity_and_raise_event_when_applicable_entity_added()
public void should_add_entity_and_raise_event_when_components_match_group()
{
var collectionName = "default";
var accessorToken = new ObservableGroupToken(new[] { typeof(TestComponentOne) }, new []{typeof(TestComponentTwo)}, collectionName);
var accessorToken = new ObservableGroupToken(new[] { 1 }, new []{ 2 }, collectionName);
var mockCollection = Substitute.For<IEntityCollection>();
mockCollection.Name.Returns(collectionName);

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

applicableEntity.HasAllComponents(accessorToken.Group.RequiredComponents).Returns(true);
applicableEntity.HasAnyComponents(accessorToken.Group.ExcludedComponents).Returns(false);
applicableEntity.HasAllComponents(accessorToken.LookupGroup.RequiredComponents).Returns(true);
applicableEntity.HasAnyComponents(accessorToken.LookupGroup.ExcludedComponents).Returns(false);
componentRemoved.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, null));

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

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

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

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

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

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

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

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

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

applicableEntity.HasAnyComponents(accessorToken.Group.RequiredComponents).Returns(false);
applicableEntity.HasAllComponents(accessorToken.Group.RequiredComponents).Returns(false);
componentRemoving.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, new[]{ typeof(TestComponentOne) }));
componentRemoved.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, new[]{ typeof(TestComponentOne) }));
applicableEntity.HasAnyComponents(accessorToken.LookupGroup.RequiredComponents).Returns(false);
applicableEntity.HasAllComponents(accessorToken.LookupGroup.RequiredComponents).Returns(false);
componentRemoving.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, new[]{ 1 }));
componentRemoved.OnNext(new ComponentsChangedEvent(mockCollection, applicableEntity, new[]{ 1 }));

Assert.DoesNotContain(applicableEntity, observableGroup.CachedEntities.Values);
Assert.Equal(1, wasRemovingCalled);
Expand Down
2 changes: 1 addition & 1 deletion src/EcsRx.Tests/Framework/SanityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ private IEntityCollectionManager CreateCollectionManager()
var entityFactory = new DefaultEntityFactory(new IdPool(), componentRepository);
var collectionFactory = new DefaultEntityCollectionFactory(entityFactory);
var observableGroupFactory = new DefaultObservableObservableGroupFactory();
return new EntityCollectionManager(collectionFactory, observableGroupFactory);
return new EntityCollectionManager(collectionFactory, observableGroupFactory, componentLookupType);
}

private SystemExecutor CreateExecutor(IEntityCollectionManager entityCollectionManager)
Expand Down
35 changes: 29 additions & 6 deletions src/EcsRx/Collections/EntityCollectionManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EcsRx.Components;
using EcsRx.Entities;
using EcsRx.Events;
using EcsRx.Extensions;
Expand All @@ -21,23 +22,29 @@ public class EntityCollectionManager : IEntityCollectionManager, IDisposable
public IEnumerable<IEntityCollection> Collections => _collections.Values;
public IEntityCollectionFactory EntityCollectionFactory { get; }
public IObservableGroupFactory ObservableGroupFactory { get; }
public IComponentTypeLookup ComponentTypeLookup { get; }

public IObservable<CollectionEntityEvent> EntityAdded => _onEntityAdded;
public IObservable<CollectionEntityEvent> EntityRemoved => _onEntityRemoved;
public IObservable<ComponentsChangedEvent> EntityComponentsAdded => _onEntityComponentsAdded;
public IObservable<ComponentsChangedEvent> EntityComponentsRemoving => _onEntityComponentsRemoving;
public IObservable<ComponentsChangedEvent> EntityComponentsRemoved => _onEntityComponentsRemoved;
public IObservable<IEntityCollection> CollectionAdded => _onCollectionAdded;
public IObservable<IEntityCollection> CollectionRemoved => _onCollectionRemoved;

private readonly Subject<CollectionEntityEvent> _onEntityAdded;
private readonly Subject<CollectionEntityEvent> _onEntityRemoved;
private readonly Subject<ComponentsChangedEvent> _onEntityComponentsAdded;
private readonly Subject<ComponentsChangedEvent> _onEntityComponentsRemoving;
private readonly Subject<ComponentsChangedEvent> _onEntityComponentsRemoved;
private readonly Subject<IEntityCollection> _onCollectionAdded;
private readonly Subject<IEntityCollection> _onCollectionRemoved;

public EntityCollectionManager(IEntityCollectionFactory entityCollectionFactory, IObservableGroupFactory observableGroupFactory)
public EntityCollectionManager(IEntityCollectionFactory entityCollectionFactory, IObservableGroupFactory observableGroupFactory, IComponentTypeLookup componentTypeLookup)
{
EntityCollectionFactory = entityCollectionFactory;
ObservableGroupFactory = observableGroupFactory;
ComponentTypeLookup = componentTypeLookup;

_observableGroups = new Dictionary<ObservableGroupToken, IObservableGroup>();
_collections = new Dictionary<string, IEntityCollection>();
Expand All @@ -48,6 +55,8 @@ public EntityCollectionManager(IEntityCollectionFactory entityCollectionFactory,
_onEntityComponentsAdded = new Subject<ComponentsChangedEvent>();
_onEntityComponentsRemoving = new Subject<ComponentsChangedEvent>();
_onEntityComponentsRemoved = new Subject<ComponentsChangedEvent>();
_onCollectionAdded = new Subject<IEntityCollection>();
_onCollectionRemoved = new Subject<IEntityCollection>();

CreateCollection(DefaultPoolName);
}
Expand All @@ -74,8 +83,8 @@ public IEntityCollection CreateCollection(string name)
_collections.Add(name, collection);
SubscribeToCollection(collection);

//EventSystem.Publish(new CollectionAddedEvent(collection));

_onCollectionAdded.OnNext(collection);
return collection;
}

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

UnsubscribeFromCollection(name);

//EventSystem.Publish(new CollectionRemovedEvent(collection));
_onCollectionRemoved.OnNext(collection);
}

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

return Collections.GetAllEntities().MatchingGroup(group);
}

public IEnumerable<IEntity> GetEntitiesFor(ILookupGroup lookupGroup, string collectionName = null)
{
if(lookupGroup.RequiredComponents.Length == 0 && lookupGroup.ExcludedComponents.Length == 0)
{ return new IEntity[0]; }

if (collectionName != null)
{ return _collections[collectionName].MatchingGroup(lookupGroup); }

return Collections.GetAllEntities().MatchingGroup(lookupGroup);
}

public IObservableGroup GetObservableGroup(IGroup group, string collectionName = null)
{
var observableGroupToken = new ObservableGroupToken(group, collectionName);
var requiredComponents = ComponentTypeLookup.GetComponentTypes(group.RequiredComponents);
var excludedComponents = ComponentTypeLookup.GetComponentTypes(group.ExcludedComponents);
var lookupGroup = new LookupGroup(requiredComponents, excludedComponents);
var observableGroupToken = new ObservableGroupToken(lookupGroup, collectionName);
if (_observableGroups.ContainsKey(observableGroupToken)) { return _observableGroups[observableGroupToken]; }

var entityMatches = GetEntitiesFor(group, collectionName);
var entityMatches = GetEntitiesFor(lookupGroup, collectionName);
var configuration = new ObservableGroupConfiguration
{
ObservableGroupToken = observableGroupToken,
Expand Down
15 changes: 14 additions & 1 deletion src/EcsRx/Collections/IEntityCollectionManager.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using EcsRx.Entities;
using EcsRx.Events;
using EcsRx.Groups;
using EcsRx.Groups.Observable;

Expand All @@ -15,6 +18,16 @@ public interface IEntityCollectionManager : INotifyingEntityCollection
/// All the entity collections that the manager contains
/// </summary>
IEnumerable<IEntityCollection> Collections { get; }

/// <summary>
/// Fired when a collection has been added
/// </summary>
IObservable<IEntityCollection> CollectionAdded { get; }

/// <summary>
/// Fired when a collection has been removed
/// </summary>
IObservable<IEntityCollection> CollectionRemoved { get; }

/// <summary>
/// Gets an enumerable collection of entities for you to iterate through,
Expand Down
Loading

0 comments on commit a3ca085

Please sign in to comment.