using System; using System.Collections; using System.Collections.Generic; using System.Linq; using EcsRx.Entities; using EcsRx.Extensions; using EcsRx.Groups.Observable; using EcsRx.Lookups; using EcsRx.MicroRx.Extensions; using EcsRx.MicroRx.Subjects; namespace EcsRx.Plugins.Computeds.Groups { public abstract class ComputedGroup : IObservableGroup, IDisposable { public readonly EntityLookup CachedEntities; public readonly IList Subscriptions; public ObservableGroupToken Token => InternalObservableGroup.Token; public IObservable OnEntityAdded => _onEntityAdded; public IObservable OnEntityRemoved => _onEntityRemoved; public IObservable OnEntityRemoving => _onEntityRemoving; private readonly Subject _onEntityAdded; private readonly Subject _onEntityRemoved; private readonly Subject _onEntityRemoving; public IObservableGroup InternalObservableGroup { get; } protected ComputedGroup(IObservableGroup internalObservableGroup) { InternalObservableGroup = internalObservableGroup; CachedEntities = new EntityLookup(); Subscriptions = new List(); _onEntityAdded = new Subject(); _onEntityRemoved = new Subject(); _onEntityRemoving = new Subject(); MonitorChanges(); RefreshEntities(); } public void MonitorChanges() { InternalObservableGroup.OnEntityAdded.Subscribe(OnEntityAddedToGroup).AddTo(Subscriptions); InternalObservableGroup.OnEntityRemoving.Subscribe(OnEntityRemovingFromGroup).AddTo(Subscriptions); RefreshWhen().Subscribe(x => RefreshEntities()).AddTo(Subscriptions); } public void OnEntityAddedToGroup(IEntity entity) { if (!IsEntityApplicable(entity)) { return; } CachedEntities.Add(entity); _onEntityAdded.OnNext(entity); } public void OnEntityRemovingFromGroup(IEntity entity) { if(!CachedEntities.Contains(entity.Id)) { return; } _onEntityRemoving.OnNext(entity); CachedEntities.Remove(entity.Id); _onEntityRemoved.OnNext(entity); } public void RefreshEntities() { var applicableEntities = InternalObservableGroup.Where(IsEntityApplicable).ToArray(); var entitiesToRemove = InternalObservableGroup.Where(x => applicableEntities.All(y => y.Id != x.Id)).ToArray(); var entitiesToAdd = applicableEntities.Where(x => !CachedEntities.Contains(x.Id)).ToArray(); for (var i = entitiesToAdd.Length - 1; i >= 0; i--) { CachedEntities.Add(entitiesToAdd[i]); _onEntityAdded.OnNext(entitiesToAdd[i]); } for (var i = entitiesToRemove.Length - 1; i >= 0; i--) { _onEntityRemoving.OnNext(entitiesToRemove[i]); CachedEntities.Remove(entitiesToRemove[i].Id); _onEntityRemoved.OnNext(entitiesToRemove[i]); } } public bool ContainsEntity(int id) { return CachedEntities.Contains(id); } public IEntity GetEntity(int id) { return CachedEntities[id]; } /// /// The method to indicate when the listings should be updated /// /// /// If there is no checking required outside of adding/removing this can /// return an empty observable, but common usages would be to refresh every update. /// The bool is throw away, but is a workaround for not having a Unit class /// /// An observable trigger that should trigger when the group should refresh public abstract IObservable RefreshWhen(); /// /// The method to check if the entity is applicable to this computed group /// /// The entity to check on /// true if it should be in the group, false if it should not public abstract bool IsEntityApplicable(IEntity entity); public virtual IEnumerable PostProcess(IEnumerable entities) { return entities; } public virtual IEnumerator GetEnumerator() { return PostProcess(CachedEntities).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public virtual void Dispose() { Subscriptions.DisposeAll(); _onEntityAdded.Dispose(); _onEntityRemoved.Dispose(); _onEntityRemoving.Dispose(); } public int Count => CachedEntities.Count; public IEntity this[int index] => CachedEntities.GetByIndex(index); } }