using R3; namespace OECS; /// /// Buffers changes during system execution and posts them to R3 subjects /// when is called. /// /// Subscribers receive batched changes: all changes accumulated since the /// last call are pushed in a single burst. /// internal class ChangeBuffer { private readonly ChangeSet _pending = new(); private readonly Subject _entitySubject = new(); private readonly Dictionary> _componentSubjects = new(); private readonly Dictionary> _querySubjects = new(); /// /// The change set currently accumulating. Cleared after each . /// public ChangeSet Pending => _pending; /// /// Pushes all pending changes to R3 subjects, then clears the pending set. /// public void Post() { if (_pending.Count == 0) return; var changes = _pending.Changes; foreach (var change in changes) { // Push to the global entity subject. _entitySubject.OnNext(change); // Push to component-specific subjects. if (change.ComponentType != null) { if (_componentSubjects.TryGetValue(change.ComponentType, out var compSubject)) { compSubject.OnNext(change); } } // Push to matching query subjects. foreach (var (query, subject) in _querySubjects) { if (ChangeMatchesQuery(change, query)) { subject.OnNext(change); } } } _pending.Clear(); } /// /// Returns an observable that emits all entity changes. /// public Observable ObserveEntityChanges() { return _entitySubject; } /// /// Returns an observable that emits changes for a specific component type. /// public Observable ObserveComponentChanges(Type componentType) { if (!_componentSubjects.TryGetValue(componentType, out var subject)) { subject = new Subject(); _componentSubjects[componentType] = subject; } return subject; } /// /// Returns an observable that emits changes matching the given query. /// public Observable ObserveQuery(QueryDescriptor query) { if (!_querySubjects.TryGetValue(query, out var subject)) { subject = new Subject(); _querySubjects[query] = subject; } return subject; } /// /// Disposes all R3 subjects. /// public void Dispose() { _entitySubject.Dispose(); foreach (var subject in _componentSubjects.Values) { subject.Dispose(); } _componentSubjects.Clear(); foreach (var subject in _querySubjects.Values) { subject.Dispose(); } _querySubjects.Clear(); } private static bool ChangeMatchesQuery(EntityChange change, QueryDescriptor query) { // Entity-level changes: match if the entity was added/removed and // the query has any "with" types (we can't know component state for // removed entities, so we only match added entities that would satisfy // the query — but we don't have component data here). // For simplicity, we only match component-level changes against queries. if (change.ComponentType == null) return false; // A component change matches a query if the component type is in the // query's With set and not in the Without set. if (query.Without.Contains(change.ComponentType)) return false; return query.With.Contains(change.ComponentType); } }