2026-07-18 19:42:59 +08:00
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
|
|
|
|
|
namespace OECS;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Maintains a reverse index for relationship components, mapping
|
|
|
|
|
/// target entities to the set of source entities that point to them.
|
|
|
|
|
///
|
|
|
|
|
/// Updated automatically by <see cref="World"/> when relationship
|
|
|
|
|
/// components are added, removed, or when entities are destroyed.
|
2026-07-21 22:39:35 +08:00
|
|
|
///
|
|
|
|
|
/// Source sets are insertion-ordered — the order in which relationships
|
|
|
|
|
/// are added is preserved during iteration.
|
2026-07-18 19:42:59 +08:00
|
|
|
/// </summary>
|
|
|
|
|
internal class RelationshipIndex
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Per relationship type: target entity → set of source entities.
|
|
|
|
|
/// </summary>
|
2026-07-21 22:39:35 +08:00
|
|
|
private readonly Dictionary<Type, Dictionary<Entity, OrderedEntitySet>> _index = new();
|
2026-07-18 19:42:59 +08:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Registers that a source entity now has a relationship pointing to a target.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void OnAdded(Type relationshipType, Entity source, Entity target)
|
|
|
|
|
{
|
|
|
|
|
if (!_index.TryGetValue(relationshipType, out var targetMap))
|
|
|
|
|
{
|
2026-07-21 22:39:35 +08:00
|
|
|
targetMap = new Dictionary<Entity, OrderedEntitySet>();
|
2026-07-18 19:42:59 +08:00
|
|
|
_index[relationshipType] = targetMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!targetMap.TryGetValue(target, out var sources))
|
|
|
|
|
{
|
2026-07-21 22:39:35 +08:00
|
|
|
sources = new OrderedEntitySet();
|
2026-07-18 19:42:59 +08:00
|
|
|
targetMap[target] = sources;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sources.Add(source);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Unregisters a relationship from a source entity pointing to a target.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void OnRemoved(Type relationshipType, Entity source, Entity target)
|
|
|
|
|
{
|
|
|
|
|
if (!_index.TryGetValue(relationshipType, out var targetMap))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!targetMap.TryGetValue(target, out var sources))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
sources.Remove(source);
|
|
|
|
|
|
|
|
|
|
if (sources.Count == 0)
|
|
|
|
|
targetMap.Remove(target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns all source entities that have a relationship of the given type
|
2026-07-21 22:39:35 +08:00
|
|
|
/// pointing to the specified target entity, in insertion order.
|
2026-07-18 19:42:59 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public IReadOnlyCollection<Entity> GetSources<T>(Entity target)
|
|
|
|
|
where T : struct, IRelationship
|
|
|
|
|
{
|
|
|
|
|
var type = typeof(T);
|
|
|
|
|
if (!_index.TryGetValue(type, out var targetMap))
|
|
|
|
|
return Array.Empty<Entity>();
|
|
|
|
|
|
|
|
|
|
if (!targetMap.TryGetValue(target, out var sources))
|
|
|
|
|
return Array.Empty<Entity>();
|
|
|
|
|
|
|
|
|
|
return sources;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes all index entries where the given entity appears as a source.
|
|
|
|
|
/// Called before the entity's components are removed during destruction.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void RemoveAllSourcesForEntity(Entity entity)
|
|
|
|
|
{
|
|
|
|
|
foreach (var (_, targetMap) in _index)
|
|
|
|
|
{
|
|
|
|
|
foreach (var (target, sources) in targetMap)
|
|
|
|
|
{
|
|
|
|
|
sources.Remove(entity);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean up empty target entries.
|
|
|
|
|
foreach (var (_, targetMap) in _index)
|
|
|
|
|
{
|
|
|
|
|
var emptyTargets = new List<Entity>();
|
|
|
|
|
foreach (var (target, sources) in targetMap)
|
|
|
|
|
{
|
|
|
|
|
if (sources.Count == 0)
|
|
|
|
|
emptyTargets.Add(target);
|
|
|
|
|
}
|
|
|
|
|
foreach (var target in emptyTargets)
|
|
|
|
|
{
|
|
|
|
|
targetMap.Remove(target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns all relationship types for which the given entity is a target,
|
|
|
|
|
/// along with the set of source entities pointing to it.
|
|
|
|
|
/// Used during entity destruction to cascade-remove incoming relationships.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public IEnumerable<(Type RelationshipType, IReadOnlyCollection<Entity> Sources)> GetIncomingRelationships(Entity target)
|
|
|
|
|
{
|
|
|
|
|
foreach (var (relType, targetMap) in _index)
|
|
|
|
|
{
|
|
|
|
|
if (targetMap.TryGetValue(target, out var sources) && sources.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
yield return (relType, sources);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns true if the index has any entries for the given relationship type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool HasType(Type relationshipType)
|
|
|
|
|
{
|
|
|
|
|
return _index.ContainsKey(relationshipType);
|
|
|
|
|
}
|
2026-07-21 22:39:35 +08:00
|
|
|
}
|