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);
}
}