Im Play spiegelt World.syncToModel (src/runtime/World.ts:307) nach jedem Frame jede Entity in die Laufzeit-Kopie des Projekts. PlaySession.tick ruft es mit Absicht jeden Frame auf: Eine Bearbeitung während des Laufs landet auf dem Modell, und das darf kein alter Stand sein.
Pro Objekt wird dabei neu aufgelöst, was das Modell für das Objekt sagt (resolveSceneObjectProps, bagWithoutItemTypes), und Schlüssel für Schlüssel mit der Entity verglichen. Das passiert auch für Objekte, an denen sich in diesem Frame nichts geändert hat, und auch für ausgeschaltete.
Der Bereich „Editor“ im Performance-Panel wächst deshalb mit der Zahl der Entities. Gemessen in #284 am Projekt cbbf160bb7e8 (222 Entities, auf stein2): 0,98 ms pro Frame mit den 162 Entities des geschlossenen Inventar-Fensters, 0,22 ms ohne sie. Gezeichnet werden diese Entities nicht.
Die lineare Suche pro Objekt ist seit #284 weg (eine Map pro Durchlauf). Übrig ist das Auflösen und Vergleichen selbst.
Zu bauen (Vorschlag)
Was das Modell für ein Objekt auflöst, ändert sich nur, wenn jemand das Modell schreibt: syncToModel selbst, ein Kommando während des Laufs oder ein Node, der ins Projekt schreibt. Dazwischen kann das Ergebnis pro Objekt stehen bleiben.
Dann kostet ein Frame pro Objekt nur noch den Vergleich der Entity mit dem gemerkten Stand, und nur was abweicht, wird neu aufgelöst und geschrieben.
Vorher messen, wo die Zeit genau liegt (Auflösen, Kopieren, Vergleichen), und nachher mit derselben Sonde.
Tests (Vorschlag)
Unit: Ein Lauf über viele Frames ohne Änderung löst kein Objekt neu auf. Eine Änderung an einer Entity schreibt genau dieses Objekt. Eine Bearbeitung am Modell während des Laufs (Override, Asset-Wert, Platzierung) kommt weiter an und wird nicht mit einem alten Stand überschrieben.
Die bestehenden Tests in tests/core/world-sync.test.ts bleiben unverändert grün.
Messung am Referenzprojekt vorher und nachher, Zahlen in den Kommentar.
Aus #284 (dort gemessen, nicht behoben).
## Befund
- Im Play spiegelt `World.syncToModel` (`src/runtime/World.ts:307`) nach jedem Frame jede Entity in die Laufzeit-Kopie des Projekts. `PlaySession.tick` ruft es mit Absicht jeden Frame auf: Eine Bearbeitung während des Laufs landet auf dem Modell, und das darf kein alter Stand sein.
- Pro Objekt wird dabei neu aufgelöst, was das Modell für das Objekt sagt (`resolveSceneObjectProps`, `bagWithoutItemTypes`), und Schlüssel für Schlüssel mit der Entity verglichen. Das passiert auch für Objekte, an denen sich in diesem Frame nichts geändert hat, und auch für ausgeschaltete.
- Der Bereich „Editor“ im Performance-Panel wächst deshalb mit der Zahl der Entities. Gemessen in #284 am Projekt `cbbf160bb7e8` (222 Entities, auf stein2): 0,98 ms pro Frame mit den 162 Entities des geschlossenen Inventar-Fensters, 0,22 ms ohne sie. Gezeichnet werden diese Entities nicht.
- Die lineare Suche pro Objekt ist seit #284 weg (eine Map pro Durchlauf). Übrig ist das Auflösen und Vergleichen selbst.
## Zu bauen (Vorschlag)
- Was das Modell für ein Objekt auflöst, ändert sich nur, wenn jemand das Modell schreibt: `syncToModel` selbst, ein Kommando während des Laufs oder ein Node, der ins Projekt schreibt. Dazwischen kann das Ergebnis pro Objekt stehen bleiben.
- Dann kostet ein Frame pro Objekt nur noch den Vergleich der Entity mit dem gemerkten Stand, und nur was abweicht, wird neu aufgelöst und geschrieben.
- Vorher messen, wo die Zeit genau liegt (Auflösen, Kopieren, Vergleichen), und nachher mit derselben Sonde.
## Tests (Vorschlag)
- Unit: Ein Lauf über viele Frames ohne Änderung löst kein Objekt neu auf. Eine Änderung an einer Entity schreibt genau dieses Objekt. Eine Bearbeitung am Modell während des Laufs (Override, Asset-Wert, Platzierung) kommt weiter an und wird nicht mit einem alten Stand überschrieben.
- Die bestehenden Tests in `tests/core/world-sync.test.ts` bleiben unverändert grün.
- Messung am Referenzprojekt vorher und nachher, Zahlen in den Kommentar.
The world keeps per entity what the model said when it last read the object (syncFromModel, spawn) or wrote it. A frame compares the entity with that and resolves nothing while both agree.
A place that differs is written as three numbers, without resolving anything.
A value that differs is written as an override, and only that one object is resolved again. An override can bring rows of its own (a sprite's shader).
The objects of entities that left the run are dropped only in a frame in which one left (World.remove notes that).
Editing during a run works as before. The editor has the model read again after every command, and that renews what the world keeps.
One difference to before
A write to the model that goes past the editor and does not have it read again now stays. The next frame used to overwrite it with the state of the entity. No code does that: during a run only the world writes.
Measured (stein2, reference project cbbf160bb7e8, 222 entities, ms per frame)
before
after
syncToModel alone (Node, 600 frames)
0.92
0.12
Editor in the performance panel (browser, three runs each)
1.14 / 1.20 / 1.29
0.25 / 0.25 / 0.27
Physics (about 0.9 ms) and Drawing (about 2.3 ms) are unchanged.
The full suite ran this time, because syncToModel runs in every frame of Play and of the editor run and CI is switched off. That measures the 1613 checks, which were only computed since cbda6d6.
Four new unit tests, no new e2e check:
60 frames without a change resolve no object anew.
An edit during the run (override, asset value, place, new object) arrives and is not overwritten.
An override that brings rows of its own is seen with them from the next frame on.
Content of a container in an editor run is written into the instance edits once, not every frame.
The existing tests in tests/core/world-sync.test.ts are unchanged and green.
Seven counter-checks, all work: 1 to 4 tests fail each, clean it is 37 of 37 again.
CI is still switched off, so there is no CI run for 26cc632.
Done in `26cc632`, wiki `9d9e73e` (*Architecture*).
**What changes**
- The world keeps per entity what the model said when it last read the object (`syncFromModel`, `spawn`) or wrote it. A frame compares the entity with that and resolves nothing while both agree.
- A place that differs is written as three numbers, without resolving anything.
- A value that differs is written as an override, and only that one object is resolved again. An override can bring rows of its own (a sprite's shader).
- The objects of entities that left the run are dropped only in a frame in which one left (`World.remove` notes that).
- **Editing during a run works as before.** The editor has the model read again after every command, and that renews what the world keeps.
**One difference to before**
A write to the model that goes past the editor and does not have it read again now stays. The next frame used to overwrite it with the state of the entity. No code does that: during a run only the world writes.
**Measured** (stein2, reference project `cbbf160bb7e8`, 222 entities, ms per frame)
| | before | after |
|---|---|---|
| `syncToModel` alone (Node, 600 frames) | 0.92 | 0.12 |
| *Editor* in the performance panel (browser, three runs each) | 1.14 / 1.20 / 1.29 | 0.25 / 0.25 / 0.27 |
Physics (about 0.9 ms) and Drawing (about 2.3 ms) are unchanged.
**Tests** (on `melektaus-atx`)
```
gate 198 files, 2891 tests, CHECK EXIT 0 (2887 + 4)
e2e (full) ALL PASSED - 1613 checks, E2E EXIT 0
smokes 24 / 19 / 21, SMOKES EXIT 0
```
- The full suite ran this time, because `syncToModel` runs in every frame of Play and of the editor run and CI is switched off. That measures the 1613 checks, which were only computed since `cbda6d6`.
- Four new unit tests, no new e2e check:
- 60 frames without a change resolve no object anew.
- An edit during the run (override, asset value, place, new object) arrives and is not overwritten.
- An override that brings rows of its own is seen with them from the next frame on.
- Content of a container in an editor run is written into the instance edits once, not every frame.
- The existing tests in `tests/core/world-sync.test.ts` are unchanged and green.
- Seven counter-checks, all work: 1 to 4 tests fail each, clean it is 37 of 37 again.
- CI is still switched off, so there is no CI run for `26cc632`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Aus #284 (dort gemessen, nicht behoben).
Befund
World.syncToModel(src/runtime/World.ts:307) nach jedem Frame jede Entity in die Laufzeit-Kopie des Projekts.PlaySession.tickruft es mit Absicht jeden Frame auf: Eine Bearbeitung während des Laufs landet auf dem Modell, und das darf kein alter Stand sein.resolveSceneObjectProps,bagWithoutItemTypes), und Schlüssel für Schlüssel mit der Entity verglichen. Das passiert auch für Objekte, an denen sich in diesem Frame nichts geändert hat, und auch für ausgeschaltete.cbbf160bb7e8(222 Entities, auf stein2): 0,98 ms pro Frame mit den 162 Entities des geschlossenen Inventar-Fensters, 0,22 ms ohne sie. Gezeichnet werden diese Entities nicht.Zu bauen (Vorschlag)
syncToModelselbst, ein Kommando während des Laufs oder ein Node, der ins Projekt schreibt. Dazwischen kann das Ergebnis pro Objekt stehen bleiben.Tests (Vorschlag)
tests/core/world-sync.test.tsbleiben unverändert grün.Done in
26cc632, wiki9d9e73e(Architecture).What changes
syncFromModel,spawn) or wrote it. A frame compares the entity with that and resolves nothing while both agree.World.removenotes that).One difference to before
A write to the model that goes past the editor and does not have it read again now stays. The next frame used to overwrite it with the state of the entity. No code does that: during a run only the world writes.
Measured (stein2, reference project
cbbf160bb7e8, 222 entities, ms per frame)syncToModelalone (Node, 600 frames)Physics (about 0.9 ms) and Drawing (about 2.3 ms) are unchanged.
Tests (on
melektaus-atx)syncToModelruns in every frame of Play and of the editor run and CI is switched off. That measures the 1613 checks, which were only computed sincecbda6d6.tests/core/world-sync.test.tsare unchanged and green.26cc632.