docs: update skill guides for auto-tracking and batching changes

This commit is contained in:
2026-07-21 11:03:01 +08:00
parent 4b94f411bf
commit 52e1e8fdc9
2 changed files with 55 additions and 31 deletions
+38 -18
View File
@@ -97,12 +97,13 @@ target) are cleaned up automatically.
## Defining Systems
Systems implement `ISystem` (or `ITickedSystem` if they need delta time):
Systems implement `ISystem` (or `ITickedSystem` if they need delta time).
Implement `RunImpl` with the system logic:
```csharp
public class DealSystem : ISystem
{
public void Run(World world)
public void RunImpl(World world)
{
var state = world.ReadSingleton<GameState>();
if (state.Phase != GamePhase.Dealing)
@@ -117,15 +118,29 @@ The `ISystem` interface has no `Query` property. Systems read singletons,
build queries, and iterate on their own — this keeps the interface minimal
and gives systems full flexibility.
### Running Systems
Use the `Run` extension method to execute a system with automatic batching:
```csharp
var system = new MySystem();
system.Run(world); // wraps RunImpl in BeginBatching/EndBatching
```
`SystemGroup` handles this automatically — you just register systems and call
`RunLogical()` or `RunTimed()`.
### Iteration
Use `world.Select<T1..T6>()` with `foreach` for zero-allocation iteration with
`ref` access to components:
`ref` access to components. Use `RefN` for components you intend to mutate
(tracked for auto-dirty-marking) and `ValN` for read-only access (untracked):
```csharp
foreach (var it in world.Select<Position, Velocity>())
{
it.Item1.X += it.Item2.X * dt; // ref Position, ref Velocity
it.Ref1.X += it.Val2.X * dt; // Position tracked, Velocity not
it.Ref1.Y += it.Val2.Y * dt;
}
```
@@ -135,20 +150,20 @@ For queries with exclusion filters, create a `Query<T>` with `.Without<W>()`:
var query = new Query<Cell>().Without<Mark>();
foreach (var it in world.Select(query))
{
if (it.Item1.Row == row && it.Item1.Col == col) { ... }
if (it.Val1.Row == row && it.Val1.Col == col) { ... }
}
```
To find entities, use `FindEntity<T>()` or `FindEntities<T>()`:
To find a single entity, use `FindEntity<T>()`:
```csharp
var handEntity = world.FindEntity<PlayerHand>();
var allCards = world.FindEntities<Card>();
```
The `Select` ref struct enumerators expose:
- `it.Entity` — the current entity handle.
- `it.Item1`..`it.ItemN``ref` references to the matched components.
- `it.Ref1`..`it.RefN``ref` references to components (tracked for auto-dirty).
- `it.Val1`..`it.ValN``ref readonly` references to components (untracked).
Singleton entities are automatically excluded from query results.
@@ -165,8 +180,8 @@ group.Add(new PlayerBustCheckSystem());
group.Add(new DealerSystem());
```
`SystemGroup` automatically drains commands and posts changes after each system
and after the full tick.
`SystemGroup` automatically drains commands, flushes pending mutations, and
posts changes after each system and after the full tick.
## Defining Commands
@@ -191,7 +206,9 @@ public record struct PlaceBetCommand : ICommand
```
Enqueue commands via `world.Commands.Enqueue(...)`. They execute deferred
when the queue is drained (automatically by `SystemGroup`).
when the queue is drained (automatically by `SystemGroup`). Commands run
inside a batching scope, so `GetSingleton` and `GetComponent` calls are
auto-tracked for modification.
## Singletons
@@ -206,21 +223,24 @@ world.SetSingleton(new GameState { Phase = GamePhase.Betting, Chips = 100 });
// Read-only inspection:
var state = world.ReadSingleton<GameState>();
// Mutation:
// Mutation (auto-tracked during batching scopes):
ref var mutable = ref world.GetSingleton<GameState>();
mutable.Phase = GamePhase.RoundOver;
```
`GetSingleton` returns a `ref`. During system execution (inside `SystemGroup`),
mutations via `GetSingleton` are auto-marked. Outside of iteration, call
`MarkModified` after mutating so reactivity subscribers see the change.
`GetSingleton` returns a `ref`. During system execution, command drains, and
`foreach` iterations, mutations via `GetSingleton` are auto-marked for change
tracking. Outside a batching scope, call `MarkModified` after mutating.
`ReadSingleton` returns a copy and never auto-marks.
## Change Tracking
- Structural changes (entity create/destroy, component add/remove) are auto-marked.
- Value mutations (modifying a `ref T` component) must be manually marked via
`world.MarkModified<T>(entity)`.
- Structural changes (entity create/destroy, component add/remove) are always
auto-marked.
- Value mutations via `GetComponent<T>`, `GetSingleton<T>`, and `RefN` iterator
properties are auto-tracked during batching scopes (system runs, command
drains, `foreach` iterations).
- Outside batching scopes, call `world.MarkModified<T>(entity)` explicitly.
- Changes are posted after each system runs (automatic via `SystemGroup`).
## Serialization