perf: cache hash code in QueryDescriptor

fix: notify change buffer when removing components in World

docs: add caution about structural changes during ForEach

fix: prevent multiple disposals in World
This commit is contained in:
hypercross 2026-07-18 20:13:25 +08:00
parent 6f9efc050b
commit dddddbdbd6
2 changed files with 18 additions and 1 deletions

View File

@ -17,10 +17,13 @@ public class QueryDescriptor
/// </summary> /// </summary>
public IReadOnlySet<Type> Without { get; } public IReadOnlySet<Type> Without { get; }
private readonly int _hashCode;
internal QueryDescriptor(HashSet<Type> with, HashSet<Type> without) internal QueryDescriptor(HashSet<Type> with, HashSet<Type> without)
{ {
With = with; With = with;
Without = without; Without = without;
_hashCode = ComputeHashCode();
} }
/// <summary> /// <summary>
@ -36,7 +39,9 @@ public class QueryDescriptor
return With.SetEquals(other.With) && Without.SetEquals(other.Without); return With.SetEquals(other.With) && Without.SetEquals(other.Without);
} }
public override int GetHashCode() public override int GetHashCode() => _hashCode;
private int ComputeHashCode()
{ {
var hash = new HashCode(); var hash = new HashCode();
foreach (var t in With.OrderBy(t => t.GUID.ToString())) foreach (var t in With.OrderBy(t => t.GUID.ToString()))

View File

@ -22,6 +22,7 @@ public class World : IDisposable
private readonly RelationshipIndex _relationships; private readonly RelationshipIndex _relationships;
private readonly ChangeBuffer _changes; private readonly ChangeBuffer _changes;
private bool _singletonCreated; private bool _singletonCreated;
private bool _disposed;
#if DEBUG #if DEBUG
// Tracks entities whose components were accessed via GetComponent<T> // Tracks entities whose components were accessed via GetComponent<T>
@ -82,6 +83,7 @@ public class World : IDisposable
{ {
_relationships.OnRemoved(relType, source, entity); _relationships.OnRemoved(relType, source, entity);
_components.Remove(source, relType); _components.Remove(source, relType);
_changes.Pending.MarkComponentRemoved(source, relType);
} }
} }
@ -358,6 +360,13 @@ public class World : IDisposable
/// <summary> /// <summary>
/// Iterates all entities matching the query, providing ref access to /// Iterates all entities matching the query, providing ref access to
/// one component type. /// one component type.
///
/// <b>Caution:</b> Adding or removing components (including destroying
/// entities) during iteration may cause entities to be skipped or
/// processed twice. This is a consequence of the sparse set's
/// swap-remove strategy. If you need to make structural changes,
/// collect the affected entities first, then apply changes after
/// the iteration.
/// </summary> /// </summary>
public void ForEach<T1>( public void ForEach<T1>(
QueryDescriptor query, QueryDescriptor query,
@ -486,6 +495,9 @@ public class World : IDisposable
public void Dispose() public void Dispose()
{ {
if (_disposed)
return;
_disposed = true;
_changes.Dispose(); _changes.Dispose();
} }