Created
February 8, 2017 13:10
-
-
Save bddckr/77754804018eaf0a951d1ef1bb671ad4 to your computer and use it in GitHub Desktop.
A more detailed collector and reactive system for Entitas.
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 Serialization; | |
/// An Collector can observe one or more groups and collects | |
/// changed entities based on the specified groupEvent. | |
public sealed class DetailedCollector | |
{ | |
/// Returns all collected updated entities with update info. | |
/// Call collector.ClearCollectedEntities() | |
/// once you processed all entities. | |
public readonly Dictionary<EntityUpdateKey, IComponent> collectedPreviousComponentsByEntityUpdateKey | |
= new Dictionary<EntityUpdateKey, IComponent>(EntityUpdateKeyEqualityComparer.comparer); | |
private readonly Group[] _groups; | |
private readonly GroupEvent[] _groupEvents; | |
private readonly Group.GroupChanged _addEntityCache; | |
private readonly Group.GroupChanged _removeEntityCache; | |
private readonly Group.GroupUpdated _updateEntityCache; | |
private string _toStringCache; | |
private StringBuilder _toStringBuilder; | |
/// Creates an Collector and will collect changed entities | |
/// based on the specified groupEvent. | |
public DetailedCollector(Group group, GroupEvent groupEvent) | |
: this(new[] { group }, new[] { groupEvent }) | |
{ | |
} | |
/// Creates an Collector and will collect changed entities | |
/// based on the specified groupEvents. | |
public DetailedCollector(Group[] 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(Group group, | |
Entity entity, | |
int index, | |
IComponent component) | |
{ | |
updateEntity(group, entity, index, null, component); | |
} | |
private void removeEntity(Group group, | |
Entity entity, | |
int index, | |
IComponent component) | |
{ | |
updateEntity(group, entity, index, component, null); | |
} | |
private void updateEntity(Group group, | |
Entity entity, | |
int index, | |
IComponent previousComponent, | |
IComponent newComponent) | |
{ | |
var entityUpdateKey = new EntityUpdateKey(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 | |
{ | |
public readonly Group group; | |
public readonly Entity entity; | |
public readonly int componentIndex; | |
public EntityUpdateKey(Group group, Entity entity, int componentIndex) | |
{ | |
this.group = group; | |
this.entity = entity; | |
this.componentIndex = componentIndex; | |
} | |
} | |
public sealed class EntityUpdateKeyEqualityComparer : IEqualityComparer<EntityUpdateKey> | |
{ | |
public static readonly EntityUpdateKeyEqualityComparer comparer = new EntityUpdateKeyEqualityComparer(); | |
public bool Equals(EntityUpdateKey x, EntityUpdateKey y) | |
=> EntityEqualityComparer.comparer.Equals(x.entity, y.entity) | |
&& x.group == y.group | |
&& x.componentIndex == y.componentIndex; | |
public int GetHashCode(EntityUpdateKey obj) | |
=> EntityEqualityComparer.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; | |
/// A ReactiveSystem 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 : IExecuteSystem | |
{ | |
private readonly DetailedCollector _collector; | |
private readonly Dictionary<EntityUpdateKey, IComponent> _buffer = new Dictionary<EntityUpdateKey, IComponent>(); | |
private string _toStringCache; | |
protected DetailedReactiveSystem(Context context) | |
{ | |
_collector = GetTrigger(context); | |
} | |
protected DetailedReactiveSystem(DetailedCollector collector) | |
{ | |
_collector = collector; | |
} | |
~DetailedReactiveSystem() | |
{ | |
Deactivate(); | |
} | |
/// Specify the collector that will trigger the ReactiveSystem. | |
protected abstract DetailedCollector GetTrigger(Context context); | |
/// This will exclude all entities which don't pass the filter. | |
protected abstract bool Filter(Entity entity); | |
protected abstract void Execute(Dictionary<EntityUpdateKey, IComponent> collectedPreviousComponentsByEntityUpdateKey); | |
/// Activates the ReactiveSystem and starts observing changes | |
/// based on the specified Collector. | |
/// ReactiveSystem are activated by default. | |
public void Activate() | |
{ | |
_collector.Activate(); | |
} | |
/// Deactivates the ReactiveSystem. | |
/// No changes will be tracked while deactivated. | |
/// This will also clear the ReactiveSystem. | |
/// ReactiveSystem 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 JetBrains.Annotations; | |
using UnityEngine; | |
public sealed class TestSystem : DetailedReactiveSystem | |
{ | |
public TestSystem([NotNull] Contexts contexts) : base(contexts.mapContext) | |
{ | |
} | |
protected override DetailedCollector GetTrigger(Context context) => new DetailedCollector( | |
new[] | |
{ | |
context.GetGroup(Matcher.AllOf(MapContextMatcher.FloorY, MapContextMatcher.GridPosition)) | |
}, | |
new[] | |
{ | |
GroupEvent.AddedOrRemoved | |
} | |
); | |
protected override bool Filter(Entity entity) => true; | |
protected override void Execute(Dictionary<EntityUpdateKey, 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
For Entitas 0.37.0(+) see this gist.