Skip to content

Instantly share code, notes, and snippets.

@bddckr
Created February 8, 2017 13:10
Show Gist options
  • Save bddckr/77754804018eaf0a951d1ef1bb671ad4 to your computer and use it in GitHub Desktop.
Save bddckr/77754804018eaf0a951d1ef1bb671ad4 to your computer and use it in GitHub Desktop.
A more detailed collector and reactive system for Entitas.
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);
}
}
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;
}
}
}
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;
}
}
}
}
@bddckr
Copy link
Author

bddckr commented Feb 16, 2017

For Entitas 0.37.0(+) see this gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment