@typeonce/ritmo 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +3 -2
  2. package/dist/Clock.d.ts +19 -4
  3. package/dist/Clock.d.ts.map +1 -1
  4. package/dist/Clock.js +18 -2
  5. package/dist/Clock.js.map +1 -1
  6. package/dist/Command.d.ts +101 -45
  7. package/dist/Command.d.ts.map +1 -1
  8. package/dist/Command.js +22 -4
  9. package/dist/Command.js.map +1 -1
  10. package/dist/Debug.d.ts +66 -8
  11. package/dist/Debug.d.ts.map +1 -1
  12. package/dist/Descriptor.d.ts +27 -9
  13. package/dist/Descriptor.d.ts.map +1 -1
  14. package/dist/Descriptor.js +13 -9
  15. package/dist/Descriptor.js.map +1 -1
  16. package/dist/Entity.d.ts +15 -8
  17. package/dist/Entity.d.ts.map +1 -1
  18. package/dist/Entity.js.map +1 -1
  19. package/dist/FixedLoop.d.ts +106 -0
  20. package/dist/FixedLoop.d.ts.map +1 -0
  21. package/dist/FixedLoop.js +97 -0
  22. package/dist/FixedLoop.js.map +1 -0
  23. package/dist/HostClock.d.ts +1 -1
  24. package/dist/HostClock.d.ts.map +1 -1
  25. package/dist/HostClock.js +2 -1
  26. package/dist/HostClock.js.map +1 -1
  27. package/dist/Machine.d.ts +4 -1
  28. package/dist/Machine.d.ts.map +1 -1
  29. package/dist/Machine.js +4 -1
  30. package/dist/Machine.js.map +1 -1
  31. package/dist/Query.d.ts +52 -10
  32. package/dist/Query.d.ts.map +1 -1
  33. package/dist/Query.js +2 -1
  34. package/dist/Query.js.map +1 -1
  35. package/dist/Relation.d.ts +5 -14
  36. package/dist/Relation.d.ts.map +1 -1
  37. package/dist/Relation.js +0 -32
  38. package/dist/Relation.js.map +1 -1
  39. package/dist/Runtime.d.ts +58 -15
  40. package/dist/Runtime.d.ts.map +1 -1
  41. package/dist/Runtime.js +288 -70
  42. package/dist/Runtime.js.map +1 -1
  43. package/dist/Schedule.d.ts +24 -15
  44. package/dist/Schedule.d.ts.map +1 -1
  45. package/dist/Schedule.js +2 -1
  46. package/dist/Schedule.js.map +1 -1
  47. package/dist/Schema.d.ts +98 -29
  48. package/dist/Schema.d.ts.map +1 -1
  49. package/dist/Schema.js +4 -4
  50. package/dist/Schema.js.map +1 -1
  51. package/dist/Snapshot.d.ts +76 -1
  52. package/dist/Snapshot.d.ts.map +1 -1
  53. package/dist/Snapshot.js +42 -1
  54. package/dist/Snapshot.js.map +1 -1
  55. package/dist/System.d.ts +68 -24
  56. package/dist/System.d.ts.map +1 -1
  57. package/dist/System.js.map +1 -1
  58. package/dist/index.d.ts +4 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +4 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/internal/cells.d.ts +2 -0
  63. package/dist/internal/cells.d.ts.map +1 -1
  64. package/dist/internal/cells.js +34 -8
  65. package/dist/internal/cells.js.map +1 -1
  66. package/dist/internal/debug.d.ts +3 -0
  67. package/dist/internal/debug.d.ts.map +1 -1
  68. package/dist/internal/debug.js +83 -15
  69. package/dist/internal/debug.js.map +1 -1
  70. package/dist/internal/fragments.d.ts +2 -6
  71. package/dist/internal/fragments.d.ts.map +1 -1
  72. package/dist/internal/fragments.js +25 -2
  73. package/dist/internal/fragments.js.map +1 -1
  74. package/dist/internal/game.d.ts +2 -0
  75. package/dist/internal/game.d.ts.map +1 -1
  76. package/dist/internal/game.js +16 -3
  77. package/dist/internal/game.js.map +1 -1
  78. package/dist/internal/queries.d.ts.map +1 -1
  79. package/dist/internal/queries.js +27 -18
  80. package/dist/internal/queries.js.map +1 -1
  81. package/dist/internal/relations.d.ts +10 -0
  82. package/dist/internal/relations.d.ts.map +1 -0
  83. package/dist/internal/relations.js +32 -0
  84. package/dist/internal/relations.js.map +1 -0
  85. package/dist/internal/streams.d.ts +2 -0
  86. package/dist/internal/streams.d.ts.map +1 -1
  87. package/dist/internal/streams.js +10 -0
  88. package/dist/internal/streams.js.map +1 -1
  89. package/dist/internal/world.d.ts +7 -1
  90. package/dist/internal/world.d.ts.map +1 -1
  91. package/dist/internal/world.js +53 -3
  92. package/dist/internal/world.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/Clock.ts +29 -4
  95. package/src/Command.ts +161 -67
  96. package/src/Debug.ts +61 -9
  97. package/src/Descriptor.ts +43 -17
  98. package/src/Entity.ts +16 -9
  99. package/src/FixedLoop.ts +218 -0
  100. package/src/HostClock.ts +2 -1
  101. package/src/Machine.ts +4 -1
  102. package/src/Query.ts +47 -10
  103. package/src/Relation.ts +5 -67
  104. package/src/Runtime.ts +345 -85
  105. package/src/Schedule.ts +31 -19
  106. package/src/Schema.ts +118 -35
  107. package/src/Snapshot.ts +90 -2
  108. package/src/System.ts +76 -23
  109. package/src/index.ts +4 -0
  110. package/src/internal/cells.ts +42 -10
  111. package/src/internal/debug.ts +84 -15
  112. package/src/internal/fragments.ts +23 -3
  113. package/src/internal/game.ts +19 -3
  114. package/src/internal/queries.ts +28 -2
  115. package/src/internal/relations.ts +52 -0
  116. package/src/internal/streams.ts +11 -0
  117. package/src/internal/world.ts +60 -4
@@ -59,6 +59,8 @@ export interface Streams<T> {
59
59
  * reader has read, then enforces the capacity.
60
60
  */
61
61
  trim(windowBoundary: number): void
62
+ /** Drops every batch stamped after `tick` (what a rolled-back phase published). */
63
+ retract(tick: number): void
62
64
  /** Clears simulation messages; unread host messages survive world restore. */
63
65
  clear(): void
64
66
  /** Every key with retained entries or registered readers. */
@@ -151,6 +153,15 @@ export const make = <T>(capacity: number): Streams<T> => {
151
153
  dropFront(log, overflow)
152
154
  }
153
155
  },
156
+ retract(tick) {
157
+ for (const log of logs.values()) {
158
+ const start = firstAfter(log.ticks, tick)
159
+ for (let index = start; index < log.batches.length; index++) log.size -= log.batches[index]!.length
160
+ log.ticks.length = start
161
+ log.batches.length = start
162
+ log.surviveRestore.length = start
163
+ }
164
+ },
154
165
  clear() {
155
166
  for (const [key, log] of logs) {
156
167
  const ticks: Array<number> = []
@@ -48,6 +48,8 @@ export interface EntityRecord {
48
48
  * Cached query matches by query ordinal, reused while the entity matches.
49
49
  */
50
50
  readonly matches: Array<unknown>
51
+ /** The name of the component at an ordinal, for error messages (shared by the world's records). */
52
+ readonly nameAt: (ordinal: number) => string
51
53
  alive: boolean
52
54
  }
53
55
 
@@ -227,6 +229,16 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
227
229
  const journalRecords: Array<EntityRecord> = []
228
230
  const journalOrdinals: Array<number> = []
229
231
  const journalValues: Array<unknown> = []
232
+ /**
233
+ * Journal of a phase: several system transactions undone together (a
234
+ * transition's exit schedules). Each committed write keeps its value and
235
+ * change tick from before it, so rollback restores both, newest first.
236
+ */
237
+ let inPhase = false
238
+ const phaseRecords: Array<EntityRecord> = []
239
+ const phaseOrdinals: Array<number> = []
240
+ const phaseValues: Array<unknown> = []
241
+ const phaseChanged: Array<number> = []
230
242
 
231
243
  /**
232
244
  * Entity ownership used by scene and level lifetime scopes.
@@ -238,6 +250,8 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
238
250
  /** Suppresses per-component and relation hooks inside spawns and despawns. */
239
251
  let hookDepth = 0
240
252
 
253
+ const nameAt = (ordinal: number): string => descriptors[ordinal]?.name ?? `component #${ordinal}`
254
+
241
255
  const ordinalOf = (descriptor: ComponentDescriptor): number => {
242
256
  const known = ordinals.get(descriptor.key)
243
257
  if (known !== undefined) {
@@ -337,6 +351,7 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
337
351
  values,
338
352
  marks,
339
353
  matches: [],
354
+ nameAt,
340
355
  alive: true
341
356
  }
342
357
  records.set(record.id, record)
@@ -363,14 +378,19 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
363
378
  /**
364
379
  * Writes an existing component value in place and records the change.
365
380
  */
366
- const setComponentValue = (record: EntityRecord, ordinal: number, value: unknown): void => {
367
- if (!record.alive) {
368
- return
381
+ /**
382
+ * Writes a component the entity holds. Returns `false`, writing nothing,
383
+ * when it does not (despawned records hold nothing): only a stale query
384
+ * cell gets here, and it reports the misuse.
385
+ */
386
+ const setComponentValue = (record: EntityRecord, ordinal: number, value: unknown): boolean => {
387
+ if (record.values[ordinal] === ABSENT) {
388
+ return false
369
389
  }
370
390
  if (!inTransaction) {
371
391
  record.values[ordinal] = value
372
392
  markChanged(record, ordinal)
373
- return
393
+ return true
374
394
  }
375
395
  const slot = ordinal * MARK_STRIDE + JOURNALED
376
396
  if (record.marks[slot] !== transaction) {
@@ -380,6 +400,7 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
380
400
  journalValues.push(record.values[ordinal])
381
401
  }
382
402
  record.values[ordinal] = value
403
+ return true
383
404
  }
384
405
 
385
406
  const clearJournal = (): void => {
@@ -403,6 +424,13 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
403
424
  inTransaction = false
404
425
  for (let index = 0; index < journalRecords.length; index++) {
405
426
  const record = journalRecords[index]!
427
+ if (inPhase) {
428
+ const ordinal = journalOrdinals[index]!
429
+ phaseRecords.push(record)
430
+ phaseOrdinals.push(ordinal)
431
+ phaseValues.push(journalValues[index])
432
+ phaseChanged.push(record.marks[ordinal * MARK_STRIDE + CHANGED]!)
433
+ }
406
434
  if (record.alive) {
407
435
  markChanged(record, journalOrdinals[index]!)
408
436
  }
@@ -421,6 +449,30 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
421
449
  clearJournal()
422
450
  }
423
451
 
452
+ const clearPhase = (): void => {
453
+ inPhase = false
454
+ phaseRecords.length = 0
455
+ phaseOrdinals.length = 0
456
+ phaseValues.length = 0
457
+ phaseChanged.length = 0
458
+ }
459
+
460
+ /** Starts a phase: the transactions committed until it ends can be undone together. */
461
+ const beginPhase = (): void => {
462
+ inPhase = true
463
+ }
464
+
465
+ /** Restores every write committed in the phase, with its change tick. */
466
+ const rollbackPhase = (): void => {
467
+ for (let index = phaseRecords.length - 1; index >= 0; index--) {
468
+ const record = phaseRecords[index]!
469
+ const ordinal = phaseOrdinals[index]!
470
+ record.values[ordinal] = phaseValues[index]
471
+ record.marks[ordinal * MARK_STRIDE + CHANGED] = phaseChanged[index]!
472
+ }
473
+ clearPhase()
474
+ }
475
+
424
476
  const clearEntityScope = (id: number): void => {
425
477
  const scopeKey = scopeByEntity.get(id)
426
478
  if (scopeKey === undefined) {
@@ -779,6 +831,7 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
779
831
  records,
780
832
  relationDefinitions,
781
833
  ordinalOf,
834
+ nameAt,
782
835
  has,
783
836
  entityIdOf,
784
837
  allocateEntity,
@@ -792,6 +845,9 @@ export const makeWorld = <S extends Schema.Any>(schema: S, lifecycleCapacity: nu
792
845
  beginTransaction,
793
846
  commitTransaction,
794
847
  rollbackTransaction,
848
+ beginPhase,
849
+ commitPhase: clearPhase,
850
+ rollbackPhase,
795
851
  tryRelate,
796
852
  unrelate,
797
853
  reorderChildren,