Last active
February 16, 2017 14:04
-
-
Save bddckr/4a1ffd481257929952f92d03129d12e2 to your computer and use it in GitHub Desktop.
A more detailed collector and reactive system for Entitas 0.37.0(+).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Entitas | |
{ | |
using System.Collections.Generic; | |
using System.Text; | |
using Entitas; | |
/// An Collector can observe one or more groups and collects | |
/// changed entities based on the specified groupEvent. | |
public sealed class DetailedCollector<TEntity> where TEntity : class, IEntity, new() | |
{ | |
/// Returns all collected updated entities with update info. | |
/// Call collector.ClearCollectedEntities() | |
/// once you processed all entities. | |
public readonly Dictionary<EntityUpdateKey<TEntity>, IComponent> collectedPreviousComponentsByEntityUpdateKey | |
= new Dictionary<EntityUpdateKey<TEntity>, IComponent>(EntityUpdateKeyEqualityComparer<TEntity>.comparer); | |
private readonly IGroup<TEntity>[] _groups; | |
private readonly GroupEvent[] _groupEvents; | |
private readonly GroupChanged<TEntity> _addEntityCache; | |
private readonly GroupChanged<TEntity> _removeEntityCache; | |
private readonly GroupUpdated<TEntity> _updateEntityCache; | |
private string _toStringCache; | |
private StringBuilder _toStringBuilder; | |
/// Creates an Collector and will collect changed entities | |
/// based on the specified groupEvent. | |
public DetailedCollector(IGroup<TEntity> group, GroupEvent groupEvent) | |
: this(new[] { group }, new[] { groupEvent }) | |
{ | |
} | |
/// Creates an Collector and will collect changed entities | |
/// based on the specified groupEvents. | |
public DetailedCollector(IGroup<TEntity>[] groups, GroupEvent[] groupEvents) | |
{ | |
if (groups.Length != groupEvents.Length) | |
{ | |
throw new CollectorException( | |
"Unbalanced count with groups (" + groups.Length + | |
") and group events (" + groupEvents.Length + ").", | |
"Group and group events count must be equal." | |
); | |
} | |
_groups = groups; | |
_groupEvents = groupEvents; | |
_addEntityCache = addEntity; | |
_removeEntityCache = removeEntity; | |
_updateEntityCache = updateEntity; | |
Activate(); | |
} | |
~DetailedCollector() | |
{ | |
Deactivate(); | |
} | |
/// Activates the Collector and will start collecting | |
/// changed entities. Collectors are activated by default. | |
public void Activate() | |
{ | |
for (var i = 0; i < _groups.Length; i++) | |
{ | |
var group = _groups[i]; | |
var groupEvent = _groupEvents[i]; | |
if (groupEvent == GroupEvent.Added || groupEvent == GroupEvent.AddedOrRemoved) | |
{ | |
group.OnEntityAdded -= _addEntityCache; | |
group.OnEntityAdded += _addEntityCache; | |
} | |
if (groupEvent == GroupEvent.Removed || groupEvent == GroupEvent.AddedOrRemoved) | |
{ | |
group.OnEntityRemoved -= _removeEntityCache; | |
group.OnEntityRemoved += _removeEntityCache; | |
} | |
if (groupEvent == GroupEvent.AddedOrRemoved) | |
{ | |
group.OnEntityUpdated -= _updateEntityCache; | |
group.OnEntityUpdated += _updateEntityCache; | |
} | |
} | |
} | |
/// Deactivates the Collector. | |
/// This will also clear all collected entities. | |
/// Collectors are activated by default. | |
public void Deactivate() | |
{ | |
foreach (var group in _groups) | |
{ | |
group.OnEntityAdded -= _addEntityCache; | |
group.OnEntityRemoved -= _removeEntityCache; | |
group.OnEntityUpdated -= _updateEntityCache; | |
} | |
ClearCollectedEntities(); | |
} | |
/// Clears all collected entities. | |
public void ClearCollectedEntities() | |
{ | |
foreach (var pair in collectedPreviousComponentsByEntityUpdateKey) | |
{ | |
var entityUpdateKey = pair.Key; | |
var entity = entityUpdateKey.entity; | |
var previousComponent = pair.Value; | |
if (previousComponent != null) | |
{ | |
entity.GetComponentPool(entityUpdateKey.componentIndex).Push(previousComponent); | |
} | |
#if ENTITAS_FAST_AND_UNSAFE | |
entity.Release(this); | |
#else | |
if (entity.owners.Contains(this)) | |
{ | |
entity.Release(this); | |
} | |
#endif | |
} | |
collectedPreviousComponentsByEntityUpdateKey.Clear(); | |
} | |
private void addEntity(IGroup<TEntity> group, | |
TEntity entity, | |
int index, | |
IComponent component) | |
{ | |
updateEntity(group, entity, index, null, component); | |
} | |
private void removeEntity(IGroup<TEntity> group, | |
TEntity entity, | |
int index, | |
IComponent component) | |
{ | |
updateEntity(group, entity, index, component, null); | |
} | |
private void updateEntity(IGroup<TEntity> group, | |
TEntity entity, | |
int index, | |
IComponent previousComponent, | |
IComponent newComponent) | |
{ | |
var entityUpdateKey = new EntityUpdateKey<TEntity>(group, entity, index); | |
if (collectedPreviousComponentsByEntityUpdateKey.ContainsKey(entityUpdateKey)) | |
{ | |
return; | |
} | |
IComponent clonedComponent; | |
if (previousComponent == null) | |
{ | |
clonedComponent = null; | |
} | |
else | |
{ | |
clonedComponent = entity.CreateComponent(index, previousComponent.GetType()); | |
previousComponent.CopyPublicMemberValues(clonedComponent); | |
} | |
collectedPreviousComponentsByEntityUpdateKey[entityUpdateKey] = clonedComponent; | |
#if ENTITAS_FAST_AND_UNSAFE | |
entity.Retain(this); | |
#else | |
if (!entity.owners.Contains(this)) | |
{ | |
entity.Retain(this); | |
} | |
#endif | |
} | |
public override string ToString() | |
{ | |
if (_toStringCache == null) | |
{ | |
if (_toStringBuilder == null) | |
{ | |
_toStringBuilder = new StringBuilder(); | |
} | |
_toStringBuilder.Length = 0; | |
_toStringBuilder.Append("DetailedCollector("); | |
const string separator = ", "; | |
var lastSeparator = _groups.Length - 1; | |
for (var i = 0; i < _groups.Length; i++) | |
{ | |
_toStringBuilder.Append(_groups[i]); | |
if (i < lastSeparator) | |
{ | |
_toStringBuilder.Append(separator); | |
} | |
} | |
_toStringBuilder.Append(")"); | |
_toStringCache = _toStringBuilder.ToString(); | |
} | |
return _toStringCache; | |
} | |
} | |
public sealed class EntityUpdateKey<TEntity> where TEntity : class, IEntity, new() | |
{ | |
public readonly IGroup<TEntity> group; | |
public readonly TEntity entity; | |
public readonly int componentIndex; | |
public EntityUpdateKey(IGroup<TEntity> group, TEntity entity, int componentIndex) | |
{ | |
this.group = group; | |
this.entity = entity; | |
this.componentIndex = componentIndex; | |
} | |
} | |
public sealed class EntityUpdateKeyEqualityComparer<TEntity> : IEqualityComparer<EntityUpdateKey<TEntity>> | |
where TEntity : class, IEntity, new() | |
{ | |
public static readonly EntityUpdateKeyEqualityComparer<TEntity> comparer | |
= new EntityUpdateKeyEqualityComparer<TEntity>(); | |
public bool Equals(EntityUpdateKey<TEntity> x, EntityUpdateKey<TEntity> y) | |
=> EntityEqualityComparer<TEntity>.comparer.Equals(x.entity, y.entity) | |
&& x.group == y.group | |
&& x.componentIndex == y.componentIndex; | |
public int GetHashCode(EntityUpdateKey<TEntity> obj) | |
=> EntityEqualityComparer<TEntity>.comparer.GetHashCode(obj.entity) | |
^ (obj.group.GetHashCode() << 2) | |
^ (obj.componentIndex.GetHashCode() >> 2); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Entitas | |
{ | |
using System.Collections.Generic; | |
using System.Linq; | |
using Entitas; | |
/// A DetailedReactiveSystem calls Execute() if there were changes based on | |
/// the specified Collector and will only pass in changed entities. | |
/// A common use-case is to react to changes, e.g. a change of the position | |
/// of an entity to update the gameObject.transform.position | |
/// of the related gameObject. | |
public abstract class DetailedReactiveSystem<TEntity> : IExecuteSystem | |
where TEntity : class, IEntity, new() | |
{ | |
private readonly DetailedCollector<TEntity> _collector; | |
private readonly Dictionary<EntityUpdateKey<TEntity>, IComponent> _buffer | |
= new Dictionary<EntityUpdateKey<TEntity>, IComponent>(); | |
private string _toStringCache; | |
protected DetailedReactiveSystem(IContext<TEntity> context) | |
{ | |
_collector = GetTrigger(context); | |
} | |
protected DetailedReactiveSystem(DetailedCollector<TEntity> collector) | |
{ | |
_collector = collector; | |
} | |
~DetailedReactiveSystem() | |
{ | |
Deactivate(); | |
} | |
/// Specify the collector that will trigger the DetailedReactiveSystem. | |
protected abstract DetailedCollector<TEntity> GetTrigger(IContext<TEntity> context); | |
/// This will exclude all entities which don't pass the filter. | |
protected abstract bool Filter(TEntity entity); | |
protected abstract void Execute(Dictionary<EntityUpdateKey<TEntity>, IComponent> collectedPreviousComponentsByEntityUpdateKey); | |
/// Activates the DetailedReactiveSystem and starts observing changes | |
/// based on the specified Collector. | |
/// DetailedReactiveSystem are activated by default. | |
public void Activate() | |
{ | |
_collector.Activate(); | |
} | |
/// Deactivates the DetailedReactiveSystem. | |
/// No changes will be tracked while deactivated. | |
/// This will also clear the DetailedReactiveSystem. | |
/// DetailedReactiveSystem are activated by default. | |
public void Deactivate() | |
{ | |
_collector.Deactivate(); | |
} | |
/// Clears all accumulated changes. | |
public void Clear() | |
{ | |
_collector.ClearCollectedEntities(); | |
} | |
/// Will call Execute() with added, removed and changed entities | |
/// if there are any. Otherwise it will not call Execute(). | |
public void Execute() | |
{ | |
var collectedPreviousComponentsByEntityUpdateKey = _collector.collectedPreviousComponentsByEntityUpdateKey; | |
if (collectedPreviousComponentsByEntityUpdateKey.Count == 0) | |
{ | |
return; | |
} | |
foreach (var pair in collectedPreviousComponentsByEntityUpdateKey) | |
{ | |
var key = pair.Key; | |
var entity = key.entity; | |
if (!Filter(entity)) | |
{ | |
continue; | |
} | |
#if ENTITAS_FAST_AND_UNSAFE | |
entity.Retain(this); | |
#else | |
if (!entity.owners.Contains(this)) | |
{ | |
entity.Retain(this); | |
} | |
#endif | |
_buffer[key] = pair.Value; | |
} | |
_collector.ClearCollectedEntities(); | |
if (_buffer.Count == 0) | |
{ | |
return; | |
} | |
Execute(_buffer); | |
foreach (var entity in _buffer.Select(pair => pair.Key.entity)) | |
{ | |
#if ENTITAS_FAST_AND_UNSAFE | |
entity.Release(this); | |
#else | |
if (entity.owners.Contains(this)) | |
{ | |
entity.Release(this); | |
} | |
#endif | |
} | |
_buffer.Clear(); | |
} | |
public override string ToString() | |
{ | |
if (_toStringCache == null) | |
{ | |
_toStringCache = "DetailedReactiveSystem(" + GetType().Name + ")"; | |
} | |
return _toStringCache; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace TestProject | |
{ | |
using System.Collections.Generic; | |
using System.Text; | |
using Entitas; | |
using EntitasHelpers; | |
using JetBrains.Annotations; | |
using UnityEngine; | |
public sealed class TestSystem : DetailedReactiveSystem<MapEntity> | |
{ | |
public TestSystem([NotNull] Contexts contexts) : base(contexts.map) | |
{ | |
} | |
protected override DetailedCollector<MapEntity> GetTrigger(IContext<MapEntity> context) => new DetailedCollector<MapEntity>( | |
new[] | |
{ | |
context.GetGroup(Matcher<MapEntity>.AllOf(MapMatcher.FloorY, MapMatcher.GridPosition)) | |
}, | |
new[] | |
{ | |
GroupEvent.AddedOrRemoved | |
} | |
); | |
protected override bool Filter(MapEntity entity) => true; | |
protected override void Execute(Dictionary<EntityUpdateKey<MapEntity>, IComponent> collectedPreviousComponentsByEntityUpdateKey) | |
{ | |
var stringBuilder = new StringBuilder(); | |
foreach (var pair in collectedPreviousComponentsByEntityUpdateKey) | |
{ | |
var key = pair.Key; | |
var entity = key.entity; | |
var componentIndex = key.componentIndex; | |
stringBuilder | |
.Append(entity) | |
.AppendFormat("({0}): ", key.group) | |
.Append(pair.Value?.ToString() ?? "null") | |
.Append(" --> ") | |
.Append(entity.HasComponent(componentIndex) ? entity.GetComponent(componentIndex).ToString() : "null"); | |
Debug.Log(stringBuilder); | |
stringBuilder.Length = 0; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TestSystem
logs things like:Deleting the
FloorY
component while changing theGridPosition
at the same time:Notice that the removal of
FloorY
doesn't remove the entity from the list that gets supplied toExecute
. To do that one would have to changeTestSystem.Filter
- the same when using the normalReactiveSystem
Entitas provides.