@chronon-sim/engine 0.1.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 (151) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +19 -0
  3. package/dist/components/autoscaler.d.ts +47 -0
  4. package/dist/components/autoscaler.d.ts.map +1 -0
  5. package/dist/components/autoscaler.js +116 -0
  6. package/dist/components/autoscaler.js.map +1 -0
  7. package/dist/components/generator.d.ts +32 -0
  8. package/dist/components/generator.d.ts.map +1 -0
  9. package/dist/components/generator.js +124 -0
  10. package/dist/components/generator.js.map +1 -0
  11. package/dist/components/index.d.ts +14 -0
  12. package/dist/components/index.d.ts.map +1 -0
  13. package/dist/components/index.js +11 -0
  14. package/dist/components/index.js.map +1 -0
  15. package/dist/components/message-queue.d.ts +45 -0
  16. package/dist/components/message-queue.d.ts.map +1 -0
  17. package/dist/components/message-queue.js +152 -0
  18. package/dist/components/message-queue.js.map +1 -0
  19. package/dist/components/pullable.d.ts +24 -0
  20. package/dist/components/pullable.d.ts.map +1 -0
  21. package/dist/components/pullable.js +2 -0
  22. package/dist/components/pullable.js.map +1 -0
  23. package/dist/components/queue.d.ts +32 -0
  24. package/dist/components/queue.d.ts.map +1 -0
  25. package/dist/components/queue.js +88 -0
  26. package/dist/components/queue.js.map +1 -0
  27. package/dist/components/rate-limiter.d.ts +24 -0
  28. package/dist/components/rate-limiter.d.ts.map +1 -0
  29. package/dist/components/rate-limiter.js +77 -0
  30. package/dist/components/rate-limiter.js.map +1 -0
  31. package/dist/components/registry.d.ts +19 -0
  32. package/dist/components/registry.d.ts.map +1 -0
  33. package/dist/components/registry.js +43 -0
  34. package/dist/components/registry.js.map +1 -0
  35. package/dist/components/retry-policy.d.ts +41 -0
  36. package/dist/components/retry-policy.d.ts.map +1 -0
  37. package/dist/components/retry-policy.js +122 -0
  38. package/dist/components/retry-policy.js.map +1 -0
  39. package/dist/components/server.d.ts +26 -0
  40. package/dist/components/server.d.ts.map +1 -0
  41. package/dist/components/server.js +82 -0
  42. package/dist/components/server.js.map +1 -0
  43. package/dist/components/sink.d.ts +12 -0
  44. package/dist/components/sink.d.ts.map +1 -0
  45. package/dist/components/sink.js +35 -0
  46. package/dist/components/sink.js.map +1 -0
  47. package/dist/components/worker-pool.d.ts +68 -0
  48. package/dist/components/worker-pool.d.ts.map +1 -0
  49. package/dist/components/worker-pool.js +224 -0
  50. package/dist/components/worker-pool.js.map +1 -0
  51. package/dist/format/definition.d.ts +72 -0
  52. package/dist/format/definition.d.ts.map +1 -0
  53. package/dist/format/definition.js +17 -0
  54. package/dist/format/definition.js.map +1 -0
  55. package/dist/format/index.d.ts +5 -0
  56. package/dist/format/index.d.ts.map +1 -0
  57. package/dist/format/index.js +3 -0
  58. package/dist/format/index.js.map +1 -0
  59. package/dist/format/loader.d.ts +19 -0
  60. package/dist/format/loader.d.ts.map +1 -0
  61. package/dist/format/loader.js +275 -0
  62. package/dist/format/loader.js.map +1 -0
  63. package/dist/index.d.ts +9 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +9 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/kernel/heap.d.ts +14 -0
  68. package/dist/kernel/heap.d.ts.map +1 -0
  69. package/dist/kernel/heap.js +55 -0
  70. package/dist/kernel/heap.js.map +1 -0
  71. package/dist/kernel/index.d.ts +3 -0
  72. package/dist/kernel/index.d.ts.map +1 -0
  73. package/dist/kernel/index.js +2 -0
  74. package/dist/kernel/index.js.map +1 -0
  75. package/dist/kernel/kernel.d.ts +104 -0
  76. package/dist/kernel/kernel.d.ts.map +1 -0
  77. package/dist/kernel/kernel.js +236 -0
  78. package/dist/kernel/kernel.js.map +1 -0
  79. package/dist/model/entity.d.ts +78 -0
  80. package/dist/model/entity.d.ts.map +1 -0
  81. package/dist/model/entity.js +108 -0
  82. package/dist/model/entity.js.map +1 -0
  83. package/dist/model/fifo.d.ts +9 -0
  84. package/dist/model/fifo.d.ts.map +1 -0
  85. package/dist/model/fifo.js +24 -0
  86. package/dist/model/fifo.js.map +1 -0
  87. package/dist/model/index.d.ts +4 -0
  88. package/dist/model/index.d.ts.map +1 -0
  89. package/dist/model/index.js +3 -0
  90. package/dist/model/index.js.map +1 -0
  91. package/dist/rng/distributions.d.ts +98 -0
  92. package/dist/rng/distributions.d.ts.map +1 -0
  93. package/dist/rng/distributions.js +224 -0
  94. package/dist/rng/distributions.js.map +1 -0
  95. package/dist/rng/index.d.ts +4 -0
  96. package/dist/rng/index.d.ts.map +1 -0
  97. package/dist/rng/index.js +3 -0
  98. package/dist/rng/index.js.map +1 -0
  99. package/dist/rng/rng.d.ts +26 -0
  100. package/dist/rng/rng.d.ts.map +1 -0
  101. package/dist/rng/rng.js +101 -0
  102. package/dist/rng/rng.js.map +1 -0
  103. package/dist/run/assertions.d.ts +9 -0
  104. package/dist/run/assertions.d.ts.map +1 -0
  105. package/dist/run/assertions.js +57 -0
  106. package/dist/run/assertions.js.map +1 -0
  107. package/dist/run/index.d.ts +6 -0
  108. package/dist/run/index.d.ts.map +1 -0
  109. package/dist/run/index.js +4 -0
  110. package/dist/run/index.js.map +1 -0
  111. package/dist/run/results.d.ts +72 -0
  112. package/dist/run/results.d.ts.map +1 -0
  113. package/dist/run/results.js +2 -0
  114. package/dist/run/results.js.map +1 -0
  115. package/dist/run/run.d.ts +29 -0
  116. package/dist/run/run.d.ts.map +1 -0
  117. package/dist/run/run.js +80 -0
  118. package/dist/run/run.js.map +1 -0
  119. package/dist/run/simulation.d.ts +34 -0
  120. package/dist/run/simulation.d.ts.map +1 -0
  121. package/dist/run/simulation.js +104 -0
  122. package/dist/run/simulation.js.map +1 -0
  123. package/dist/schema/index.d.ts +5 -0
  124. package/dist/schema/index.d.ts.map +1 -0
  125. package/dist/schema/index.js +3 -0
  126. package/dist/schema/index.js.map +1 -0
  127. package/dist/schema/types.d.ts +83 -0
  128. package/dist/schema/types.d.ts.map +1 -0
  129. package/dist/schema/types.js +19 -0
  130. package/dist/schema/types.js.map +1 -0
  131. package/dist/schema/validate.d.ts +19 -0
  132. package/dist/schema/validate.d.ts.map +1 -0
  133. package/dist/schema/validate.js +120 -0
  134. package/dist/schema/validate.js.map +1 -0
  135. package/dist/stats/index.d.ts +6 -0
  136. package/dist/stats/index.d.ts.map +1 -0
  137. package/dist/stats/index.js +4 -0
  138. package/dist/stats/index.js.map +1 -0
  139. package/dist/stats/percentile.d.ts +38 -0
  140. package/dist/stats/percentile.d.ts.map +1 -0
  141. package/dist/stats/percentile.js +62 -0
  142. package/dist/stats/percentile.js.map +1 -0
  143. package/dist/stats/summary.d.ts +20 -0
  144. package/dist/stats/summary.d.ts.map +1 -0
  145. package/dist/stats/summary.js +38 -0
  146. package/dist/stats/summary.js.map +1 -0
  147. package/dist/stats/time-weighted.d.ts +31 -0
  148. package/dist/stats/time-weighted.d.ts.map +1 -0
  149. package/dist/stats/time-weighted.js +69 -0
  150. package/dist/stats/time-weighted.js.map +1 -0
  151. package/package.json +41 -0
@@ -0,0 +1,104 @@
1
+ /** Conventional event priorities. Lower numbers run first among events at the same tick. */
2
+ export declare const Priority: {
3
+ readonly HIGHEST: 0;
4
+ readonly HIGH: 2;
5
+ readonly DEFAULT: 5;
6
+ readonly LOW: 8;
7
+ readonly LOWEST: 10;
8
+ };
9
+ /** Default simulation resolution: microseconds. */
10
+ export declare const DEFAULT_TICKS_PER_SECOND = 1000000;
11
+ /**
12
+ * How many events may run in a row without the clock advancing before the run is declared stuck.
13
+ * A legitimate burst (thousands of simultaneous arrivals) is far below this; a zero-delay loop hits it in seconds.
14
+ */
15
+ export declare const DEFAULT_MAX_EVENTS_PER_TICK = 10000000;
16
+ /** Safety limits for a kernel. Both are optional. */
17
+ export interface KernelLimits {
18
+ /** Largest number of events allowed at one instant (default {@link DEFAULT_MAX_EVENTS_PER_TICK}). */
19
+ maxEventsPerTick?: number;
20
+ /** Largest total number of events in a run (default unlimited). */
21
+ maxEvents?: number;
22
+ }
23
+ /** Thrown when a simulation hits a safety limit, such as a zero-delay loop that would never end. */
24
+ export declare class SimulationLimitError extends Error {
25
+ constructor(message: string);
26
+ }
27
+ /** A handle to a scheduled event or registered condition. */
28
+ export interface EventHandle {
29
+ /** Cancel the event. No effect if it already fired or was cancelled. */
30
+ cancel(): void;
31
+ /** True while the event is waiting to fire (not fired, not cancelled). */
32
+ isScheduled(): boolean;
33
+ }
34
+ /** Options for {@link Kernel.schedule}. */
35
+ export interface ScheduleOptions {
36
+ /** Insert ahead of other events with the same tick and priority (default: FIFO, i.e. behind them). */
37
+ lifo?: boolean;
38
+ }
39
+ /**
40
+ * The discrete event kernel. Time is an integer number of ticks (a JS number, safe to 2^53).
41
+ *
42
+ * Events are ordered by (tick, priority, insertion sequence), which makes execution fully
43
+ * deterministic. Conditional events ({@link Kernel.waitUntil}) are evaluated only when the next
44
+ * event would advance the clock, never mid-tick.
45
+ */
46
+ export declare class Kernel {
47
+ /** Ticks per simulated second. */
48
+ readonly ticksPerSecond: number;
49
+ private tick;
50
+ private readonly heap;
51
+ private nextSeq;
52
+ private nextLifoSeq;
53
+ private pendingEvents;
54
+ private conditions;
55
+ private processed;
56
+ /** How many events in a row have run without the clock moving. */
57
+ private sameTickStreak;
58
+ private readonly maxEventsPerTick;
59
+ private readonly maxEvents;
60
+ constructor(ticksPerSecond?: number, limits?: KernelLimits);
61
+ /** The current simulation time in ticks. */
62
+ get currentTick(): number;
63
+ /** The current simulation time in seconds. */
64
+ get currentSeconds(): number;
65
+ /** Number of events waiting to fire (excludes cancelled events and unsatisfied conditions). */
66
+ get pendingCount(): number;
67
+ /** Total number of events executed so far. */
68
+ get eventsProcessed(): number;
69
+ /**
70
+ * Convert seconds to the nearest whole tick. Results are capped at the largest exactly representable
71
+ * tick count: a delay that long (about 285 years at the default resolution) means "never within this
72
+ * run", and an uncapped absurd value would overflow to Infinity and be rejected by {@link Kernel.schedule}.
73
+ */
74
+ secondsToTicks(seconds: number): number;
75
+ /** Convert ticks to seconds. */
76
+ ticksToSeconds(ticks: number): number;
77
+ /**
78
+ * Schedule `callback` to run `delayTicks` ticks from now.
79
+ * @param delayTicks non-negative integer
80
+ * @param priority lower runs first among events at the same tick (see {@link Priority})
81
+ */
82
+ schedule(delayTicks: number, priority: number, callback: () => void, options?: ScheduleOptions): EventHandle;
83
+ /**
84
+ * Register a condition. When `predicate()` first returns true — checked only when the clock is
85
+ * about to advance — `callback` is scheduled at the current tick with the given priority.
86
+ */
87
+ waitUntil(predicate: () => boolean, callback: () => void, priority?: number): EventHandle;
88
+ /** Execute the next event (advancing the clock if needed). Returns false if nothing is left to run. */
89
+ step(): boolean;
90
+ /** Execute all events with tick <= `tick`, then set the clock to `tick`. */
91
+ runUntil(tick: number): void;
92
+ /** Run for `durationSeconds` of simulated time from now. */
93
+ run(durationSeconds: number): void;
94
+ private peekLive;
95
+ private stepUpTo;
96
+ /**
97
+ * Stop a model that can never finish. Events that keep scheduling more events at the same instant
98
+ * (a zero-delay loop) would otherwise run forever without the clock moving, so the run neither ends
99
+ * nor reports anything. The limits turn that into a clear error.
100
+ */
101
+ private guardAgainstRunaway;
102
+ private fireReadyConditions;
103
+ }
104
+ //# sourceMappingURL=kernel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernel.d.ts","sourceRoot":"","sources":["../../src/kernel/kernel.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,eAAO,MAAM,QAAQ;;;;;;CAMX,CAAC;AAEX,mDAAmD;AACnD,eAAO,MAAM,wBAAwB,UAAY,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,WAAa,CAAC;AAEtD,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,qGAAqG;IACrG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,oGAAoG;AACpG,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,6DAA6D;AAC7D,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,MAAM,IAAI,IAAI,CAAC;IACf,0EAA0E;IAC1E,WAAW,IAAI,OAAO,CAAC;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAC9B,sGAAsG;IACtG,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAgDD;;;;;;GAMG;AACH,qBAAa,MAAM;IACjB,kCAAkC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiD;IACtE,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,SAAS,CAAK;IACtB,kEAAkE;IAClE,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,cAAc,GAAE,MAAiC,EAAE,MAAM,GAAE,YAAiB;IASxF,4CAA4C;IAC5C,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,8CAA8C;IAC9C,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,+FAA+F;IAC/F,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,8CAA8C;IAC9C,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED;;;;OAIG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIvC,gCAAgC;IAChC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIrC;;;;OAIG;IACH,QAAQ,CACN,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,IAAI,EACpB,OAAO,GAAE,eAAoB,GAC5B,WAAW;IAad;;;OAGG;IACH,SAAS,CACP,SAAS,EAAE,MAAM,OAAO,EACxB,QAAQ,EAAE,MAAM,IAAI,EACpB,QAAQ,GAAE,MAAyB,GAClC,WAAW;IAMd,uGAAuG;IACvG,IAAI,IAAI,OAAO;IAIf,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAU5B,4DAA4D;IAC5D,GAAG,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI;IAIlC,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,QAAQ;IAiBhB;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,mBAAmB;CAmB5B"}
@@ -0,0 +1,236 @@
1
+ import { BinaryHeap } from "./heap.js";
2
+ /** Conventional event priorities. Lower numbers run first among events at the same tick. */
3
+ export const Priority = {
4
+ HIGHEST: 0,
5
+ HIGH: 2,
6
+ DEFAULT: 5,
7
+ LOW: 8,
8
+ LOWEST: 10,
9
+ };
10
+ /** Default simulation resolution: microseconds. */
11
+ export const DEFAULT_TICKS_PER_SECOND = 1_000_000;
12
+ /**
13
+ * How many events may run in a row without the clock advancing before the run is declared stuck.
14
+ * A legitimate burst (thousands of simultaneous arrivals) is far below this; a zero-delay loop hits it in seconds.
15
+ */
16
+ export const DEFAULT_MAX_EVENTS_PER_TICK = 10_000_000;
17
+ /** Thrown when a simulation hits a safety limit, such as a zero-delay loop that would never end. */
18
+ export class SimulationLimitError extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = "SimulationLimitError";
22
+ }
23
+ }
24
+ const PENDING = 0;
25
+ const FIRED = 1;
26
+ const CANCELLED = 2;
27
+ class ScheduledEvent {
28
+ tick;
29
+ priority;
30
+ seq;
31
+ callback;
32
+ onCancel;
33
+ state = PENDING;
34
+ constructor(tick, priority, seq, callback, onCancel) {
35
+ this.tick = tick;
36
+ this.priority = priority;
37
+ this.seq = seq;
38
+ this.callback = callback;
39
+ this.onCancel = onCancel;
40
+ }
41
+ cancel() {
42
+ if (this.state !== PENDING)
43
+ return;
44
+ this.state = CANCELLED;
45
+ this.onCancel();
46
+ }
47
+ isScheduled() {
48
+ return this.state === PENDING;
49
+ }
50
+ }
51
+ class Condition {
52
+ predicate;
53
+ callback;
54
+ priority;
55
+ state = PENDING;
56
+ constructor(predicate, callback, priority) {
57
+ this.predicate = predicate;
58
+ this.callback = callback;
59
+ this.priority = priority;
60
+ }
61
+ cancel() {
62
+ if (this.state === PENDING)
63
+ this.state = CANCELLED;
64
+ }
65
+ isScheduled() {
66
+ return this.state === PENDING;
67
+ }
68
+ }
69
+ function compareEvents(a, b) {
70
+ return a.tick - b.tick || a.priority - b.priority || a.seq - b.seq;
71
+ }
72
+ /**
73
+ * The discrete event kernel. Time is an integer number of ticks (a JS number, safe to 2^53).
74
+ *
75
+ * Events are ordered by (tick, priority, insertion sequence), which makes execution fully
76
+ * deterministic. Conditional events ({@link Kernel.waitUntil}) are evaluated only when the next
77
+ * event would advance the clock, never mid-tick.
78
+ */
79
+ export class Kernel {
80
+ /** Ticks per simulated second. */
81
+ ticksPerSecond;
82
+ tick = 0;
83
+ heap = new BinaryHeap(compareEvents);
84
+ nextSeq = 0;
85
+ nextLifoSeq = -1;
86
+ pendingEvents = 0;
87
+ conditions = [];
88
+ processed = 0;
89
+ /** How many events in a row have run without the clock moving. */
90
+ sameTickStreak = 0;
91
+ maxEventsPerTick;
92
+ maxEvents;
93
+ constructor(ticksPerSecond = DEFAULT_TICKS_PER_SECOND, limits = {}) {
94
+ if (!Number.isInteger(ticksPerSecond) || ticksPerSecond < 1) {
95
+ throw new RangeError(`ticksPerSecond must be a positive integer, got ${ticksPerSecond}`);
96
+ }
97
+ this.ticksPerSecond = ticksPerSecond;
98
+ this.maxEventsPerTick = limits.maxEventsPerTick ?? DEFAULT_MAX_EVENTS_PER_TICK;
99
+ this.maxEvents = limits.maxEvents ?? Infinity;
100
+ }
101
+ /** The current simulation time in ticks. */
102
+ get currentTick() {
103
+ return this.tick;
104
+ }
105
+ /** The current simulation time in seconds. */
106
+ get currentSeconds() {
107
+ return this.tick / this.ticksPerSecond;
108
+ }
109
+ /** Number of events waiting to fire (excludes cancelled events and unsatisfied conditions). */
110
+ get pendingCount() {
111
+ return this.pendingEvents;
112
+ }
113
+ /** Total number of events executed so far. */
114
+ get eventsProcessed() {
115
+ return this.processed;
116
+ }
117
+ /**
118
+ * Convert seconds to the nearest whole tick. Results are capped at the largest exactly representable
119
+ * tick count: a delay that long (about 285 years at the default resolution) means "never within this
120
+ * run", and an uncapped absurd value would overflow to Infinity and be rejected by {@link Kernel.schedule}.
121
+ */
122
+ secondsToTicks(seconds) {
123
+ return Math.min(Number.MAX_SAFE_INTEGER, Math.round(seconds * this.ticksPerSecond));
124
+ }
125
+ /** Convert ticks to seconds. */
126
+ ticksToSeconds(ticks) {
127
+ return ticks / this.ticksPerSecond;
128
+ }
129
+ /**
130
+ * Schedule `callback` to run `delayTicks` ticks from now.
131
+ * @param delayTicks non-negative integer
132
+ * @param priority lower runs first among events at the same tick (see {@link Priority})
133
+ */
134
+ schedule(delayTicks, priority, callback, options = {}) {
135
+ if (!Number.isInteger(delayTicks) || delayTicks < 0) {
136
+ throw new RangeError(`delayTicks must be a non-negative integer, got ${delayTicks}`);
137
+ }
138
+ const seq = options.lifo ? this.nextLifoSeq-- : this.nextSeq++;
139
+ const event = new ScheduledEvent(this.tick + delayTicks, priority, seq, callback, () => {
140
+ this.pendingEvents--;
141
+ });
142
+ this.pendingEvents++;
143
+ this.heap.push(event);
144
+ return event;
145
+ }
146
+ /**
147
+ * Register a condition. When `predicate()` first returns true — checked only when the clock is
148
+ * about to advance — `callback` is scheduled at the current tick with the given priority.
149
+ */
150
+ waitUntil(predicate, callback, priority = Priority.DEFAULT) {
151
+ const condition = new Condition(predicate, callback, priority);
152
+ this.conditions.push(condition);
153
+ return condition;
154
+ }
155
+ /** Execute the next event (advancing the clock if needed). Returns false if nothing is left to run. */
156
+ step() {
157
+ return this.stepUpTo(Infinity);
158
+ }
159
+ /** Execute all events with tick <= `tick`, then set the clock to `tick`. */
160
+ runUntil(tick) {
161
+ if (!Number.isFinite(tick) || tick < this.tick) {
162
+ throw new RangeError(`runUntil(${tick}) is before the current tick ${this.tick}`);
163
+ }
164
+ while (this.stepUpTo(tick)) {
165
+ /* keep going */
166
+ }
167
+ this.tick = tick;
168
+ }
169
+ /** Run for `durationSeconds` of simulated time from now. */
170
+ run(durationSeconds) {
171
+ this.runUntil(this.tick + this.secondsToTicks(durationSeconds));
172
+ }
173
+ peekLive() {
174
+ for (;;) {
175
+ const top = this.heap.peek();
176
+ if (top === undefined || top.state === PENDING)
177
+ return top;
178
+ this.heap.pop(); // discard cancelled
179
+ }
180
+ }
181
+ stepUpTo(limit) {
182
+ let next = this.peekLive();
183
+ if (next === undefined || next.tick > this.tick) {
184
+ // The clock is about to advance (or the queue is empty): evaluate conditions now.
185
+ if (this.conditions.length > 0 && this.fireReadyConditions())
186
+ next = this.peekLive();
187
+ }
188
+ if (next === undefined || next.tick > limit)
189
+ return false;
190
+ this.guardAgainstRunaway(next.tick);
191
+ this.heap.pop();
192
+ this.tick = next.tick;
193
+ next.state = FIRED;
194
+ this.pendingEvents--;
195
+ this.processed++;
196
+ next.callback();
197
+ return true;
198
+ }
199
+ /**
200
+ * Stop a model that can never finish. Events that keep scheduling more events at the same instant
201
+ * (a zero-delay loop) would otherwise run forever without the clock moving, so the run neither ends
202
+ * nor reports anything. The limits turn that into a clear error.
203
+ */
204
+ guardAgainstRunaway(nextTick) {
205
+ this.sameTickStreak = nextTick === this.tick ? this.sameTickStreak + 1 : 1;
206
+ if (this.sameTickStreak > this.maxEventsPerTick) {
207
+ throw new SimulationLimitError(`the simulation appears stuck: more than ${this.maxEventsPerTick.toLocaleString("en-US")} events ran at t=${this.ticksToSeconds(this.tick)} s without the clock advancing. ` +
208
+ "This usually means a loop with no delay in it, for example an interArrivalTime or retry baseDelay of 0 with nothing to stop it (add maxNumber or a delay).");
209
+ }
210
+ if (this.processed >= this.maxEvents) {
211
+ throw new SimulationLimitError(`the simulation reached its limit of ${this.maxEvents.toLocaleString("en-US")} events at t=${this.ticksToSeconds(this.tick)} s`);
212
+ }
213
+ }
214
+ fireReadyConditions() {
215
+ const current = this.conditions;
216
+ this.conditions = [];
217
+ const waiting = [];
218
+ let fired = false;
219
+ for (const c of current) {
220
+ if (c.state !== PENDING)
221
+ continue;
222
+ if (c.predicate()) {
223
+ c.state = FIRED;
224
+ this.schedule(0, c.priority, c.callback);
225
+ fired = true;
226
+ }
227
+ else {
228
+ waiting.push(c);
229
+ }
230
+ }
231
+ // Conditions registered by predicates during evaluation were pushed to the fresh array.
232
+ this.conditions = waiting.concat(this.conditions);
233
+ return fired;
234
+ }
235
+ }
236
+ //# sourceMappingURL=kernel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kernel.js","sourceRoot":"","sources":["../../src/kernel/kernel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import type { Kernel } from "../kernel/index.js";
2
+ import { Rng, type SampleProvider, type SamplerSpec } from "../rng/index.js";
3
+ /** A lightweight moving entity (a request or message) flowing through the model. */
4
+ export interface MovingEntity {
5
+ /** Unique within a run. */
6
+ readonly id: number;
7
+ /** Tick at which the entity entered the model. */
8
+ readonly createdAt: number;
9
+ /** Free-form attributes for later phases. */
10
+ attributes: Record<string, unknown>;
11
+ }
12
+ /** Services the model provides to every component. */
13
+ export interface SimContext {
14
+ readonly kernel: Kernel;
15
+ /** Create an RNG for the given stream id, derived from the run's seed. */
16
+ rng(streamId: string): Rng;
17
+ /** Create a new moving entity stamped with the current tick. */
18
+ createEntity(): MovingEntity;
19
+ }
20
+ /** What the model hands a component constructor. */
21
+ export interface ComponentInit {
22
+ name: string;
23
+ /** Base RNG stream id (defaults to the component name). */
24
+ stream: string;
25
+ /** Validated inputs with defaults applied. */
26
+ inputs: Record<string, unknown>;
27
+ }
28
+ /** Base class: a model object with a unique name, created by the model. */
29
+ export declare class Entity {
30
+ protected readonly ctx: SimContext;
31
+ readonly name: string;
32
+ constructor(ctx: SimContext, name: string);
33
+ }
34
+ /** An entity with a current state that tracks total time in each state since the last statistics reset. */
35
+ export declare class StateEntity extends Entity {
36
+ private currentState;
37
+ private stateSince;
38
+ private readonly timeInState;
39
+ constructor(ctx: SimContext, name: string, initialState: string);
40
+ get state(): string;
41
+ protected setState(state: string): void;
42
+ /** Total seconds spent in `state` since the last statistics reset (including the ongoing stretch). */
43
+ secondsInState(state: string): number;
44
+ /** Clear accumulated statistics (called at the end of warm-up). */
45
+ resetStatistics(): void;
46
+ }
47
+ /** A component that receives entities and (usually) passes them on via `next`. */
48
+ export declare class LinkedComponent extends StateEntity {
49
+ /** Downstream component, if linked. */
50
+ next: LinkedComponent | null;
51
+ protected readonly stream: string;
52
+ private added;
53
+ private completed;
54
+ private inProgress;
55
+ constructor(ctx: SimContext, init: ComponentInit, initialState: string);
56
+ /** Entities accepted since the last statistics reset. */
57
+ get numberAdded(): number;
58
+ /** Entities finished with (passed on, consumed or recorded) since the last statistics reset. */
59
+ get numberCompleted(): number;
60
+ /** Entities currently held by this component. */
61
+ get numberInProgress(): number;
62
+ /** Receive an entity. Components that do not accept pushed entities keep this default. */
63
+ addEntity(_entity: MovingEntity): void;
64
+ /** Wire a named link (see the component schema's `links`). Default handles `next`. */
65
+ setLink(key: string, target: LinkedComponent): void;
66
+ /** Called once after all links are wired, before the run starts. Schedule initial events here. */
67
+ start(): void;
68
+ protected noteAdded(): void;
69
+ protected noteCompleted(): void;
70
+ /** Mark `entity` completed here and hand it to `next` (it leaves the model if there is none). */
71
+ protected sendToNext(entity: MovingEntity): void;
72
+ /** A sampler on its own RNG stream, `<stream>/<purpose>`, so samplers never share numbers. */
73
+ protected makeSampler(spec: SamplerSpec, purpose: string): SampleProvider;
74
+ /** Convert a sampled time in seconds to whole ticks (negative samples clamp to 0). */
75
+ protected secondsToTicks(seconds: number): number;
76
+ resetStatistics(): void;
77
+ }
78
+ //# sourceMappingURL=entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../src/model/entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ import { createSampler } from "../rng/index.js";
2
+ /** Base class: a model object with a unique name, created by the model. */
3
+ export class Entity {
4
+ ctx;
5
+ name;
6
+ constructor(ctx, name) {
7
+ this.ctx = ctx;
8
+ this.name = name;
9
+ }
10
+ }
11
+ /** An entity with a current state that tracks total time in each state since the last statistics reset. */
12
+ export class StateEntity extends Entity {
13
+ currentState;
14
+ stateSince;
15
+ timeInState = new Map();
16
+ constructor(ctx, name, initialState) {
17
+ super(ctx, name);
18
+ this.currentState = initialState;
19
+ this.stateSince = ctx.kernel.currentTick;
20
+ }
21
+ get state() {
22
+ return this.currentState;
23
+ }
24
+ setState(state) {
25
+ if (state === this.currentState)
26
+ return;
27
+ const now = this.ctx.kernel.currentTick;
28
+ this.timeInState.set(this.currentState, (this.timeInState.get(this.currentState) ?? 0) + (now - this.stateSince));
29
+ this.currentState = state;
30
+ this.stateSince = now;
31
+ }
32
+ /** Total seconds spent in `state` since the last statistics reset (including the ongoing stretch). */
33
+ secondsInState(state) {
34
+ const now = this.ctx.kernel.currentTick;
35
+ let ticks = this.timeInState.get(state) ?? 0;
36
+ if (state === this.currentState)
37
+ ticks += now - this.stateSince;
38
+ return this.ctx.kernel.ticksToSeconds(ticks);
39
+ }
40
+ /** Clear accumulated statistics (called at the end of warm-up). */
41
+ resetStatistics() {
42
+ this.timeInState.clear();
43
+ this.stateSince = this.ctx.kernel.currentTick;
44
+ }
45
+ }
46
+ /** A component that receives entities and (usually) passes them on via `next`. */
47
+ export class LinkedComponent extends StateEntity {
48
+ /** Downstream component, if linked. */
49
+ next = null;
50
+ stream;
51
+ added = 0;
52
+ completed = 0;
53
+ inProgress = 0;
54
+ constructor(ctx, init, initialState) {
55
+ super(ctx, init.name, initialState);
56
+ this.stream = init.stream;
57
+ }
58
+ /** Entities accepted since the last statistics reset. */
59
+ get numberAdded() {
60
+ return this.added;
61
+ }
62
+ /** Entities finished with (passed on, consumed or recorded) since the last statistics reset. */
63
+ get numberCompleted() {
64
+ return this.completed;
65
+ }
66
+ /** Entities currently held by this component. */
67
+ get numberInProgress() {
68
+ return this.inProgress;
69
+ }
70
+ /** Receive an entity. Components that do not accept pushed entities keep this default. */
71
+ addEntity(_entity) {
72
+ throw new Error(`${this.name} does not accept entities directly`);
73
+ }
74
+ /** Wire a named link (see the component schema's `links`). Default handles `next`. */
75
+ setLink(key, target) {
76
+ if (key === "next")
77
+ this.next = target;
78
+ }
79
+ /** Called once after all links are wired, before the run starts. Schedule initial events here. */
80
+ start() { }
81
+ noteAdded() {
82
+ this.added++;
83
+ this.inProgress++;
84
+ }
85
+ noteCompleted() {
86
+ this.completed++;
87
+ this.inProgress--;
88
+ }
89
+ /** Mark `entity` completed here and hand it to `next` (it leaves the model if there is none). */
90
+ sendToNext(entity) {
91
+ this.noteCompleted();
92
+ this.next?.addEntity(entity);
93
+ }
94
+ /** A sampler on its own RNG stream, `<stream>/<purpose>`, so samplers never share numbers. */
95
+ makeSampler(spec, purpose) {
96
+ return createSampler(spec, this.ctx.rng(`${this.stream}/${purpose}`));
97
+ }
98
+ /** Convert a sampled time in seconds to whole ticks (negative samples clamp to 0). */
99
+ secondsToTicks(seconds) {
100
+ return Math.max(0, this.ctx.kernel.secondsToTicks(seconds));
101
+ }
102
+ resetStatistics() {
103
+ super.resetStatistics();
104
+ this.added = 0;
105
+ this.completed = 0;
106
+ }
107
+ }
108
+ //# sourceMappingURL=entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.js","sourceRoot":"","sources":["../../src/model/entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /** O(1) amortised FIFO buffer. @internal */
2
+ export declare class FifoBuffer<T> {
3
+ private items;
4
+ private head;
5
+ get length(): number;
6
+ push(item: T): void;
7
+ shift(): T | undefined;
8
+ }
9
+ //# sourceMappingURL=fifo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fifo.d.ts","sourceRoot":"","sources":["../../src/model/fifo.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,qBAAa,UAAU,CAAC,CAAC;IACvB,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,IAAI,CAAK;IAEjB,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;IAInB,KAAK,IAAI,CAAC,GAAG,SAAS;CAWvB"}
@@ -0,0 +1,24 @@
1
+ /** O(1) amortised FIFO buffer. @internal */
2
+ export class FifoBuffer {
3
+ items = [];
4
+ head = 0;
5
+ get length() {
6
+ return this.items.length - this.head;
7
+ }
8
+ push(item) {
9
+ this.items.push(item);
10
+ }
11
+ shift() {
12
+ if (this.head >= this.items.length)
13
+ return undefined;
14
+ const item = this.items[this.head];
15
+ this.items[this.head] = undefined;
16
+ this.head++;
17
+ if (this.head > 1024 && this.head * 2 > this.items.length) {
18
+ this.items = this.items.slice(this.head);
19
+ this.head = 0;
20
+ }
21
+ return item;
22
+ }
23
+ }
24
+ //# sourceMappingURL=fifo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fifo.js","sourceRoot":"","sources":["../../src/model/fifo.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,MAAM,OAAO,UAAU;IACb,KAAK,GAAQ,EAAE,CAAC;IAChB,IAAI,GAAG,CAAC,CAAC;IAEjB,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC;IACvC,CAAC;IAED,IAAI,CAAC,IAAO;QACV,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,SAAc,CAAC;QACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC1D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ export { Entity, StateEntity, LinkedComponent } from "./entity.js";
2
+ export type { MovingEntity, SimContext, ComponentInit } from "./entity.js";
3
+ export { FifoBuffer } from "./fifo.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { Entity, StateEntity, LinkedComponent } from "./entity.js";
2
+ export { FifoBuffer } from "./fifo.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}