@forgeax/engine-state 0.0.0-dev.8d955ade1c79
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.
- package/LICENSE +202 -0
- package/README.md +87 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/cli-state.d.ts +10 -0
- package/dist/cli-state.d.ts.map +1 -0
- package/dist/cli-state.mjs +273 -0
- package/dist/cli-state.mjs.map +1 -0
- package/dist/conditions.d.ts +31 -0
- package/dist/conditions.d.ts.map +1 -0
- package/dist/define-state.d.ts +71 -0
- package/dist/define-state.d.ts.map +1 -0
- package/dist/errors.d.ts +70 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +402 -0
- package/dist/index.mjs.map +1 -0
- package/dist/on-enter-on-exit.d.ts +65 -0
- package/dist/on-enter-on-exit.d.ts.map +1 -0
- package/dist/plugin-factory.d.ts +9 -0
- package/dist/plugin-factory.d.ts.map +1 -0
- package/dist/register-plugin.d.ts +27 -0
- package/dist/register-plugin.d.ts.map +1 -0
- package/dist/resources.d.ts +23 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/scoped-component.d.ts +38 -0
- package/dist/scoped-component.d.ts.map +1 -0
- package/dist/set-next-state.d.ts +25 -0
- package/dist/set-next-state.d.ts.map +1 -0
- package/dist/transition-system.d.ts +3 -0
- package/dist/transition-system.d.ts.map +1 -0
- package/package.json +62 -0
- package/src/cli-state.ts +232 -0
- package/src/conditions.ts +60 -0
- package/src/define-state.ts +158 -0
- package/src/errors.ts +147 -0
- package/src/index.ts +42 -0
- package/src/on-enter-on-exit.ts +156 -0
- package/src/plugin-factory.ts +19 -0
- package/src/register-plugin.ts +114 -0
- package/src/resources.ts +45 -0
- package/src/scoped-component.ts +159 -0
- package/src/set-next-state.ts +101 -0
- package/src/transition-system.ts +153 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { defineSystemSet, defineSystem, Update, defineComponent } from '@forgeax/engine-ecs';
|
|
2
|
+
import { worldDespawnScene } from '@forgeax/engine-scene';
|
|
3
|
+
import { err, ok } from '@forgeax/engine-types';
|
|
4
|
+
|
|
5
|
+
// src/resources.ts
|
|
6
|
+
var STATE_PREFIX = "__state__";
|
|
7
|
+
var NEXT_STATE_PREFIX = "__nextState__";
|
|
8
|
+
var PREVIOUS_STATE_PREFIX = "__previousState__";
|
|
9
|
+
function stateResourceKey(token) {
|
|
10
|
+
return `${STATE_PREFIX}${token.name}`;
|
|
11
|
+
}
|
|
12
|
+
function nextStateResourceKey(token) {
|
|
13
|
+
return `${NEXT_STATE_PREFIX}${token.name}`;
|
|
14
|
+
}
|
|
15
|
+
function previousStateResourceKey(token) {
|
|
16
|
+
return `${PREVIOUS_STATE_PREFIX}${token.name}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/conditions.ts
|
|
20
|
+
function inState(token, variant) {
|
|
21
|
+
const key = stateResourceKey(token);
|
|
22
|
+
const expectedIdx = token.nameToIdx.get(variant);
|
|
23
|
+
if (expectedIdx === void 0) {
|
|
24
|
+
return (_world) => false;
|
|
25
|
+
}
|
|
26
|
+
return (world) => {
|
|
27
|
+
if (!world.hasResource(key)) return false;
|
|
28
|
+
const currentIdx = world.getResource(key);
|
|
29
|
+
return currentIdx === expectedIdx;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
function makeError(code, expected, hint, detail) {
|
|
35
|
+
const error = {
|
|
36
|
+
code,
|
|
37
|
+
expected,
|
|
38
|
+
hint,
|
|
39
|
+
detail,
|
|
40
|
+
get message() {
|
|
41
|
+
return `[${code}] ${hint}`;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return error;
|
|
45
|
+
}
|
|
46
|
+
function throwStateError(code, expected, hint, detail) {
|
|
47
|
+
throw makeError(code, expected, hint, detail);
|
|
48
|
+
}
|
|
49
|
+
function stateNotRegistered(name) {
|
|
50
|
+
return makeError(
|
|
51
|
+
"state-not-registered",
|
|
52
|
+
"registerStatesPlugin(world) must be called before using setNextState / getState",
|
|
53
|
+
`State "${name}" has not been registered via registerStatesPlugin. createApp auto-registers the plugin in both canvas and assemble forms.`,
|
|
54
|
+
{ code: "state-not-registered", name }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function invalidVariant(name, got, valid) {
|
|
58
|
+
const validSnapshot = [...valid];
|
|
59
|
+
return makeError(
|
|
60
|
+
"invalid-variant",
|
|
61
|
+
`Variant must be one of: ${validSnapshot.join(", ")}`,
|
|
62
|
+
`"${got}" is not a valid variant for state "${name}". Did you mean one of: ${validSnapshot.join(", ")}? Check for typos.`,
|
|
63
|
+
{ code: "invalid-variant", name, got, valid: validSnapshot }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/define-state.ts
|
|
68
|
+
var STATE_REGISTRY = /* @__PURE__ */ new Map();
|
|
69
|
+
var STATE_DEFINED_LISTENERS = /* @__PURE__ */ new Set();
|
|
70
|
+
function getRegisteredTokens() {
|
|
71
|
+
return STATE_REGISTRY;
|
|
72
|
+
}
|
|
73
|
+
function onStateDefined(listener) {
|
|
74
|
+
STATE_DEFINED_LISTENERS.add(listener);
|
|
75
|
+
return () => STATE_DEFINED_LISTENERS.delete(listener);
|
|
76
|
+
}
|
|
77
|
+
function defineState(name, variants) {
|
|
78
|
+
if (STATE_REGISTRY.has(name)) {
|
|
79
|
+
throwStateError(
|
|
80
|
+
"state-already-defined",
|
|
81
|
+
"Each StateToken name must be registered exactly once at module level",
|
|
82
|
+
`State "${name}" is already defined. Use the existing token.`,
|
|
83
|
+
{ code: "state-already-defined", name, firstDefinedAt: void 0 }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (variants.length === 0) {
|
|
87
|
+
throwStateError(
|
|
88
|
+
"state-default-required",
|
|
89
|
+
"defineState requires at least one variant (non-empty array)",
|
|
90
|
+
`State "${name}" was defined with an empty variants array. Provide at least one variant, e.g. defineState("${name}", ["default"] as const).`,
|
|
91
|
+
{ code: "state-default-required", name }
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const seen = /* @__PURE__ */ new Set();
|
|
95
|
+
for (const v of variants) {
|
|
96
|
+
if (seen.has(v)) {
|
|
97
|
+
throwStateError(
|
|
98
|
+
"state-default-required",
|
|
99
|
+
"Variants must be unique within a state token",
|
|
100
|
+
`State "${name}" has duplicate variant "${v}". Each variant must appear exactly once.`,
|
|
101
|
+
{ code: "state-default-required", name }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
seen.add(v);
|
|
105
|
+
}
|
|
106
|
+
const nameToIdx = /* @__PURE__ */ new Map();
|
|
107
|
+
for (let i = 0; i < variants.length; i++) {
|
|
108
|
+
nameToIdx.set(variants[i], i);
|
|
109
|
+
}
|
|
110
|
+
const token = {
|
|
111
|
+
__forgeaxState: void 0,
|
|
112
|
+
name,
|
|
113
|
+
variants,
|
|
114
|
+
nameToIdx,
|
|
115
|
+
defaultValue: variants[0]
|
|
116
|
+
};
|
|
117
|
+
STATE_REGISTRY.set(name, token);
|
|
118
|
+
for (const listener of STATE_DEFINED_LISTENERS) listener(token);
|
|
119
|
+
return token;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/on-enter-on-exit.ts
|
|
123
|
+
var ON_ENTER_LABEL_PREFIX = "__OnEnter__";
|
|
124
|
+
var ON_EXIT_LABEL_PREFIX = "__OnExit__";
|
|
125
|
+
var _registry = /* @__PURE__ */ new Map();
|
|
126
|
+
function makeLabel(tokenName, prefix, variant) {
|
|
127
|
+
return `${tokenName}${prefix}${variant}`;
|
|
128
|
+
}
|
|
129
|
+
function OnEnter(token, variant) {
|
|
130
|
+
return makeLabel(token.name, ON_ENTER_LABEL_PREFIX, variant);
|
|
131
|
+
}
|
|
132
|
+
function OnExit(token, variant) {
|
|
133
|
+
return makeLabel(token.name, ON_EXIT_LABEL_PREFIX, variant);
|
|
134
|
+
}
|
|
135
|
+
function addOnEnter(token, variant, fn) {
|
|
136
|
+
const label = OnEnter(token, variant);
|
|
137
|
+
return _add(label, fn);
|
|
138
|
+
}
|
|
139
|
+
function addOnExit(token, variant, fn) {
|
|
140
|
+
const label = OnExit(token, variant);
|
|
141
|
+
return _add(label, fn);
|
|
142
|
+
}
|
|
143
|
+
function _add(label, fn) {
|
|
144
|
+
let entries = _registry.get(label);
|
|
145
|
+
if (!entries) {
|
|
146
|
+
entries = [];
|
|
147
|
+
_registry.set(label, entries);
|
|
148
|
+
}
|
|
149
|
+
const id = /* @__PURE__ */ Symbol();
|
|
150
|
+
entries.push({ id, fn });
|
|
151
|
+
return () => {
|
|
152
|
+
const list = _registry.get(label);
|
|
153
|
+
if (!list) return;
|
|
154
|
+
const idx = list.findIndex((e) => e.id === id);
|
|
155
|
+
if (idx !== -1) {
|
|
156
|
+
list.splice(idx, 1);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function getCallbacks(label) {
|
|
161
|
+
const entries = _registry.get(label);
|
|
162
|
+
if (!entries) return [];
|
|
163
|
+
return entries.map((e) => e.fn);
|
|
164
|
+
}
|
|
165
|
+
var SCOPED_COMPONENTS = /* @__PURE__ */ new Map();
|
|
166
|
+
var SCOPED_MODE_VALUE = { exit: 0, enter: 1 };
|
|
167
|
+
function getOrCreateScopedComponent(token) {
|
|
168
|
+
const existing = SCOPED_COMPONENTS.get(token.name);
|
|
169
|
+
if (existing) return existing;
|
|
170
|
+
const valueLabels = {};
|
|
171
|
+
token.variants.forEach((v, i) => {
|
|
172
|
+
valueLabels[v] = i;
|
|
173
|
+
});
|
|
174
|
+
const comp = defineComponent(`__scopedTo__${token.name}`, {
|
|
175
|
+
value: { type: "enum", default: 0, labels: valueLabels },
|
|
176
|
+
mode: { type: "enum", default: SCOPED_MODE_VALUE.exit, labels: SCOPED_MODE_VALUE }
|
|
177
|
+
});
|
|
178
|
+
SCOPED_COMPONENTS.set(token.name, comp);
|
|
179
|
+
return comp;
|
|
180
|
+
}
|
|
181
|
+
function getScopedComponent(token) {
|
|
182
|
+
return getOrCreateScopedComponent(token);
|
|
183
|
+
}
|
|
184
|
+
function resolveVariantIndex(token, variant) {
|
|
185
|
+
const idx = token.nameToIdx.get(variant);
|
|
186
|
+
if (idx === void 0) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Invalid variant "${variant}" for state "${token.name}". Valid: ${token.variants.join(", ")}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return idx;
|
|
192
|
+
}
|
|
193
|
+
function addScopedComponent(world, entity, scoped, value, mode) {
|
|
194
|
+
return world.addComponent(entity, {
|
|
195
|
+
component: scoped,
|
|
196
|
+
// generic ComponentSchema loses the concrete {value, mode} enum-field
|
|
197
|
+
// types; data is u32 at runtime.
|
|
198
|
+
data: { value, mode }
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function despawnOnExit(world, entity, token, variant) {
|
|
202
|
+
const idx = resolveVariantIndex(token, variant);
|
|
203
|
+
const scoped = getOrCreateScopedComponent(token);
|
|
204
|
+
const result = addScopedComponent(world, entity, scoped, idx, SCOPED_MODE_VALUE.exit);
|
|
205
|
+
if (!result.ok) {
|
|
206
|
+
throw result.error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function despawnOnEnter(world, entity, token, variant) {
|
|
210
|
+
const idx = resolveVariantIndex(token, variant);
|
|
211
|
+
const scoped = getOrCreateScopedComponent(token);
|
|
212
|
+
const result = addScopedComponent(world, entity, scoped, idx, SCOPED_MODE_VALUE.enter);
|
|
213
|
+
if (!result.ok) {
|
|
214
|
+
throw result.error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function registerScopedComponents() {
|
|
218
|
+
for (const token of getRegisteredTokens().values()) {
|
|
219
|
+
getOrCreateScopedComponent(token);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function scopeDespawn(world, scopedComponent, mode, value) {
|
|
223
|
+
const query = world.query({ read: [scopedComponent] }).unwrap();
|
|
224
|
+
const despawns = [];
|
|
225
|
+
for (const row of query) {
|
|
226
|
+
const scoped = row.get(scopedComponent);
|
|
227
|
+
if (scoped.mode === mode && scoped.value === value) despawns.push(row.entity);
|
|
228
|
+
}
|
|
229
|
+
const sceneInstance = world.components.resolve("SceneInstance");
|
|
230
|
+
if (sceneInstance !== void 0) {
|
|
231
|
+
for (const e of despawns) {
|
|
232
|
+
if (world.get(e, sceneInstance).ok) worldDespawnScene(world, e);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
for (const e of despawns) {
|
|
236
|
+
world.despawn(e);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function transitionStatesSystem(world) {
|
|
240
|
+
for (const token of getRegisteredTokens().values()) {
|
|
241
|
+
const nsKey = nextStateResourceKey(token);
|
|
242
|
+
if (!world.hasResource(nsKey)) continue;
|
|
243
|
+
const ns = world.getResource(nsKey);
|
|
244
|
+
if (ns === void 0) continue;
|
|
245
|
+
const sKey = stateResourceKey(token);
|
|
246
|
+
const prevIdx = world.getResource(sKey);
|
|
247
|
+
const nextIdx = ns.value;
|
|
248
|
+
const force = ns.force;
|
|
249
|
+
if (prevIdx === nextIdx && !force) {
|
|
250
|
+
world.insertResource(nsKey, void 0);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const psKey = previousStateResourceKey(token);
|
|
254
|
+
world.insertResource(psKey, prevIdx);
|
|
255
|
+
world.insertResource(sKey, nextIdx);
|
|
256
|
+
const scopedComponent = world.components.resolve(`__scopedTo__${token.name}`);
|
|
257
|
+
if (scopedComponent) {
|
|
258
|
+
scopeDespawn(world, scopedComponent, SCOPED_MODE_VALUE.exit, prevIdx);
|
|
259
|
+
const prevVariant = token.variants[prevIdx];
|
|
260
|
+
if (prevVariant !== void 0) {
|
|
261
|
+
const exitLabel = OnExit(token, prevVariant);
|
|
262
|
+
for (const fn of getCallbacks(exitLabel)) {
|
|
263
|
+
fn(world);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
scopeDespawn(world, scopedComponent, SCOPED_MODE_VALUE.enter, nextIdx);
|
|
267
|
+
const nextVariant = token.variants[nextIdx];
|
|
268
|
+
if (nextVariant !== void 0) {
|
|
269
|
+
const enterLabel = OnEnter(token, nextVariant);
|
|
270
|
+
for (const fn of getCallbacks(enterLabel)) {
|
|
271
|
+
fn(world);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const nsAfterCallbacks = world.getResource(nsKey);
|
|
276
|
+
if (nsAfterCallbacks !== void 0 && nsAfterCallbacks.value === ns.value && nsAfterCallbacks.force === ns.force) {
|
|
277
|
+
world.insertResource(nsKey, void 0);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/register-plugin.ts
|
|
283
|
+
var FRAME_START_SCAN_SYSTEM_NAME = "input-frame-start-scan";
|
|
284
|
+
var PROPAGATE_TRANSFORMS_SYSTEM = "propagateTransforms";
|
|
285
|
+
var TRANSITION_STATES_SYSTEM_NAME = "transitionStates";
|
|
286
|
+
var StateSet = defineSystemSet({ name: "state" });
|
|
287
|
+
var ACTIVE_STATE_RUNTIMES = /* @__PURE__ */ new WeakSet();
|
|
288
|
+
var TransitionStates = defineSystem({
|
|
289
|
+
name: TRANSITION_STATES_SYSTEM_NAME,
|
|
290
|
+
queries: [],
|
|
291
|
+
after: [FRAME_START_SCAN_SYSTEM_NAME],
|
|
292
|
+
before: [PROPAGATE_TRANSFORMS_SYSTEM],
|
|
293
|
+
fn: transitionStatesSystem
|
|
294
|
+
});
|
|
295
|
+
function registerStatesPlugin(world) {
|
|
296
|
+
if (ACTIVE_STATE_RUNTIMES.has(world)) return () => {
|
|
297
|
+
};
|
|
298
|
+
const resourceKeys = /* @__PURE__ */ new Set();
|
|
299
|
+
const componentLeases = /* @__PURE__ */ new Map();
|
|
300
|
+
const registerToken = (token) => {
|
|
301
|
+
registerScopedComponents();
|
|
302
|
+
const scopedComponent = getScopedComponent(token);
|
|
303
|
+
if (!componentLeases.has(token.name)) {
|
|
304
|
+
const lease = world.components.register(scopedComponent);
|
|
305
|
+
if (!lease.ok) throw lease.error;
|
|
306
|
+
componentLeases.set(token.name, lease.value);
|
|
307
|
+
}
|
|
308
|
+
const defaultValueIdx = token.nameToIdx.get(token.defaultValue);
|
|
309
|
+
if (defaultValueIdx === void 0) return;
|
|
310
|
+
const stateKey = stateResourceKey(token);
|
|
311
|
+
const nextKey = nextStateResourceKey(token);
|
|
312
|
+
const previousKey = previousStateResourceKey(token);
|
|
313
|
+
if (world.hasResource(stateKey)) return;
|
|
314
|
+
resourceKeys.add(stateKey);
|
|
315
|
+
resourceKeys.add(nextKey);
|
|
316
|
+
resourceKeys.add(previousKey);
|
|
317
|
+
world.insertResource(stateKey, defaultValueIdx);
|
|
318
|
+
world.insertResource(nextKey, void 0);
|
|
319
|
+
world.insertResource(previousKey, defaultValueIdx);
|
|
320
|
+
};
|
|
321
|
+
for (const token of getRegisteredTokens().values()) registerToken(token);
|
|
322
|
+
const installed = world.addSystems(Update, StateSet, [TransitionStates]);
|
|
323
|
+
if (!installed.ok) {
|
|
324
|
+
for (const key of resourceKeys) {
|
|
325
|
+
world.removeResource(key);
|
|
326
|
+
}
|
|
327
|
+
throw installed.error;
|
|
328
|
+
}
|
|
329
|
+
const unsubscribe = onStateDefined(registerToken);
|
|
330
|
+
ACTIVE_STATE_RUNTIMES.add(world);
|
|
331
|
+
let disposed = false;
|
|
332
|
+
return () => {
|
|
333
|
+
if (disposed) return;
|
|
334
|
+
disposed = true;
|
|
335
|
+
unsubscribe();
|
|
336
|
+
ACTIVE_STATE_RUNTIMES.delete(world);
|
|
337
|
+
world.removeSystem(Update, TRANSITION_STATES_SYSTEM_NAME);
|
|
338
|
+
for (const key of resourceKeys) {
|
|
339
|
+
world.removeResource(key);
|
|
340
|
+
}
|
|
341
|
+
for (const lease of componentLeases.values()) {
|
|
342
|
+
lease.dispose();
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/plugin-factory.ts
|
|
348
|
+
function statePlugin() {
|
|
349
|
+
return {
|
|
350
|
+
name: "state",
|
|
351
|
+
inject: ["world"],
|
|
352
|
+
apply(ctx) {
|
|
353
|
+
ctx.effect(() => registerStatesPlugin(ctx.world), "state/systems");
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function setNextState(world, token, variant) {
|
|
358
|
+
return _runCheckAndWrite(world, token, variant, false);
|
|
359
|
+
}
|
|
360
|
+
function setNextStateForce(world, token, variant) {
|
|
361
|
+
return _runCheckAndWrite(world, token, variant, true);
|
|
362
|
+
}
|
|
363
|
+
function _runCheckAndWrite(world, token, variant, force) {
|
|
364
|
+
const nsKey = nextStateResourceKey(token);
|
|
365
|
+
if (!world.hasResource(nsKey)) {
|
|
366
|
+
return err(stateNotRegistered(token.name));
|
|
367
|
+
}
|
|
368
|
+
const idx = token.nameToIdx.get(variant);
|
|
369
|
+
if (idx === void 0) {
|
|
370
|
+
return err(invalidVariant(token.name, variant, token.variants));
|
|
371
|
+
}
|
|
372
|
+
world.insertResource(nsKey, { value: idx, force });
|
|
373
|
+
return ok(void 0);
|
|
374
|
+
}
|
|
375
|
+
function getState(world, token) {
|
|
376
|
+
const key = stateResourceKey(token);
|
|
377
|
+
if (!world.hasResource(key)) {
|
|
378
|
+
return err(stateNotRegistered(token.name));
|
|
379
|
+
}
|
|
380
|
+
const idx = world.getResource(key);
|
|
381
|
+
const variant = token.variants[idx];
|
|
382
|
+
if (variant === void 0) {
|
|
383
|
+
return err(invalidVariant(token.name, String(idx), token.variants));
|
|
384
|
+
}
|
|
385
|
+
return ok(variant);
|
|
386
|
+
}
|
|
387
|
+
function getPreviousState(world, token) {
|
|
388
|
+
const key = previousStateResourceKey(token);
|
|
389
|
+
if (!world.hasResource(key)) {
|
|
390
|
+
return err(stateNotRegistered(token.name));
|
|
391
|
+
}
|
|
392
|
+
const idx = world.getResource(key);
|
|
393
|
+
const variant = token.variants[idx];
|
|
394
|
+
if (variant === void 0) {
|
|
395
|
+
return err(invalidVariant(token.name, String(idx), token.variants));
|
|
396
|
+
}
|
|
397
|
+
return ok(variant);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export { OnEnter, OnExit, StateSet, addOnEnter, addOnExit, defineState, despawnOnEnter, despawnOnExit, getPreviousState, getState, inState, registerStatesPlugin, setNextState, setNextStateForce, statePlugin };
|
|
401
|
+
//# sourceMappingURL=index.mjs.map
|
|
402
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resources.ts","../src/conditions.ts","../src/errors.ts","../src/define-state.ts","../src/on-enter-on-exit.ts","../src/scoped-component.ts","../src/transition-system.ts","../src/register-plugin.ts","../src/plugin-factory.ts","../src/set-next-state.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @forgeax/engine-state -- Resource key constructors (feat-20260616 M1 / m1w2)\n//\n// Three Resource keys per StateToken: State (current value), NextState (pending\n// transition request), PreviousState (cached last-frame value for OnExit / getPreviousState).\n//\n// Decision anchors:\n// - plan-strategy D-1: Resources are per-token, keyed by __state__ prefix\n// - plan-strategy D-4: keys are pure string constructors; Resource CRUD is in registerStatesPlugin M2\n// - requirements F-4/F-5/F-6: State / NextState / PreviousState as Resources\n\nimport type { StateToken } from './define-state';\n\nconst STATE_PREFIX = '__state__';\nconst NEXT_STATE_PREFIX = '__nextState__';\nconst PREVIOUS_STATE_PREFIX = '__previousState__';\n\n/**\n * Resource key for the current state value of a token.\n *\n * The {@link StateTokenVariant} is stored; `getState(world, token)` reads\n * this Resource and decodes the index to a variant string.\n */\nexport function stateResourceKey(token: StateToken): string {\n return `${STATE_PREFIX}${token.name}`;\n}\n\n/**\n * Resource key for the pending next-state transition request.\n *\n * Written by `setNextState` / `setNextStateForce` (M2); consumed by\n * `transitionStatesSystem` (M3).\n */\nexport function nextStateResourceKey(token: StateToken): string {\n return `${NEXT_STATE_PREFIX}${token.name}`;\n}\n\n/**\n * Resource key for the previous-frame state value.\n *\n * Written by `transitionStatesSystem` before flipping `State`; read by\n * `getPreviousState(world, token)` (M2).\n */\nexport function previousStateResourceKey(token: StateToken): string {\n return `${PREVIOUS_STATE_PREFIX}${token.name}`;\n}\n","// @forgeax/engine-state -- run conditions (feat-20260618 M3 / w25)\n//\n// inState(token, variant) returns a (world: World) => boolean predicate\n// suitable for SystemDescriptor.runIf. The predicate reads the state token's\n// current value from the World resource store.\n//\n// Decision anchors:\n// - requirements section \"inState factory\" -- single factory, no and/or/not\n// combiners (OOS-7); returns (world) => boolean\n// - research Finding 3 -- reuse stateResourceKey + hasResource + getResource<number>\n// - plan-strategy D-8 -- runIf receives World, inState returns the predicate\n\nimport type { World } from '@forgeax/engine-ecs';\nimport type { StateToken } from './define-state';\nimport { stateResourceKey } from './resources';\n\n/**\n * Create a run-condition predicate that passes when the state machine\n * identified by `token` is in the given `variant`.\n *\n * The predicate reads the current state index from the World resource store\n * (keyed by {@link stateResourceKey}) and compares it against the variant's\n * index in `token.nameToIdx`.\n *\n * If the state resource has not been inserted yet (state not activated), the\n * predicate returns `false` -- the system is skipped silently.\n *\n * @example\n * ```ts\n * const GameState = defineState('GameState', ['Menu', 'Playing', 'Paused'] as const);\n *\n * const S = defineSystem({\n * name: 'gameplay',\n * queries: [{ with: [Transform] }],\n * runIf: inState(GameState, 'Playing'),\n * fn: (world, queryResults, commands) => { ... },\n * });\n * ```\n *\n * @param token - The state token returned by {@link defineState}.\n * @param variant - The variant name to check against (must be a member of token.variants).\n * @returns A predicate `(world: World) => boolean` suitable for `SystemDescriptor.runIf`.\n */\nexport function inState(token: StateToken, variant: string): (world: World) => boolean {\n const key = stateResourceKey(token);\n const expectedIdx = token.nameToIdx.get(variant);\n // If the variant string is not in the token's vocabulary, the predicate\n // always returns false -- the system will never run. This is a programmer\n // error (typo in variant name) but we don't throw at definition time\n // because the predicate is a closure evaluated each frame; a throw here\n // would kill the entire schedule.\n if (expectedIdx === undefined) {\n return (_world: World) => false;\n }\n return (world: World) => {\n if (!world.hasResource(key)) return false;\n const currentIdx = world.getResource<number>(key);\n return currentIdx === expectedIdx;\n };\n}\n","// @forgeax/engine-state -- error model SSOT (feat-20260616-engine-state-and-state-scoped-entities M1 / m1w4)\n//\n// Closed union StateErrorCode (4 members), discriminated detail union,\n// and structured StateError carrying .code / .expected / .hint / .detail.\n//\n// Decision anchors:\n// - plan-strategy D-4: 4-code order-locked closed union + discriminated detail\n// - requirements sec 2.7: error code union, defineState throws (programmer error),\n// setNextState / getState return Result.err (runtime AI user calls)\n// - AGENTS.md Error model: structured errors with .expected / .hint / .detail,\n// never throw for runtime paths; exhaustive switch without default\n\n/** {@link state-already-defined} payload: carries the conflicting name and optional first-definition site. */\nexport interface StateAlreadyDefinedDetail {\n readonly code: 'state-already-defined';\n readonly name: string;\n readonly firstDefinedAt: string | undefined;\n}\n\n/** {@link state-not-registered} payload: carries the token name that has no plugin registration. */\nexport interface StateNotRegisteredDetail {\n readonly code: 'state-not-registered';\n readonly name: string;\n}\n\n/** {@link invalid-variant} payload: carries the token name, the invalid variant string, and the valid variants list. */\nexport interface InvalidVariantDetail {\n readonly code: 'invalid-variant';\n readonly name: string;\n readonly got: string;\n readonly valid: readonly string[];\n}\n\n/** {@link state-default-required} payload: carries the token name whose variants array was empty. */\nexport interface StateDefaultRequiredDetail {\n readonly code: 'state-default-required';\n readonly name: string;\n}\n\ninterface StateErrorDetailByCode {\n 'state-already-defined': StateAlreadyDefinedDetail;\n 'state-not-registered': StateNotRegisteredDetail;\n 'invalid-variant': InvalidVariantDetail;\n 'state-default-required': StateDefaultRequiredDetail;\n}\n\n/**\n * Closed {@link StateErrorCode} union -- 4 members, order-locked.\n * Exhaustive `switch (err.code)` needs no default fallback.\n *\n * | code | trigger |\n * |:--|:--|\n * | `'state-already-defined'` | `defineState()` called with a name already registered |\n * | `'state-not-registered'` | `setNextState()` / `getState()` called before `registerStatesPlugin()` |\n * | `'invalid-variant'` | `setNextState()` called with a variant string not in the token's variants tuple |\n * | `'state-default-required'` | `defineState()` called with empty variants array |\n */\nexport type StateErrorCode = keyof StateErrorDetailByCode;\n\n/**\n * Discriminated detail union for {@link StateError}, narrowed per\n * `StateError.code`. AI users obtain the concrete shape via\n * `switch (err.code)` without a fallback `as` cast.\n */\nexport type StateErrorDetail = StateErrorDetailByCode[StateErrorCode];\n\n/**\n * Structured state-machine error -- four-field surface\n * (`.code` / `.expected` / `.hint` / `.detail`).\n *\n * AI users consume the structured triple by fields, not by parsing `.message`.\n */\ntype StateErrorVariant<C extends StateErrorCode> = {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: StateErrorDetailByCode[C];\n};\n\nexport type StateError = {\n [C in StateErrorCode]: StateErrorVariant<C>;\n}[StateErrorCode];\n\nfunction makeError<C extends StateErrorCode>(\n code: C,\n expected: string,\n hint: string,\n detail: StateErrorDetailByCode[C],\n): StateErrorVariant<C> {\n const error = {\n code,\n expected,\n hint,\n detail,\n get message(): string {\n return `[${code}] ${hint}`;\n },\n };\n return error;\n}\n\n/** Convenience throw wrapper for programmer errors (defineState constructor phase). */\nexport function throwStateError<C extends StateErrorCode>(\n code: C,\n expected: string,\n hint: string,\n detail: StateErrorDetailByCode[C],\n): never {\n throw makeError(code, expected, hint, detail);\n}\n\nexport function stateAlreadyDefined(name: string, firstDefinedAt?: string): StateError {\n return makeError(\n 'state-already-defined',\n 'Each StateToken name must be registered exactly once at module level',\n `State \"${name}\" is already defined${firstDefinedAt ? ` (first defined at ${firstDefinedAt})` : ''}. Use the existing token.`,\n { code: 'state-already-defined', name, firstDefinedAt },\n );\n}\n\nexport function stateNotRegistered(name: string): StateError {\n return makeError(\n 'state-not-registered',\n 'registerStatesPlugin(world) must be called before using setNextState / getState',\n `State \"${name}\" has not been registered via registerStatesPlugin. createApp auto-registers the plugin in both canvas and assemble forms.`,\n { code: 'state-not-registered', name },\n );\n}\n\nexport function invalidVariant(name: string, got: string, valid: readonly string[]): StateError {\n const validSnapshot = [...valid];\n return makeError(\n 'invalid-variant',\n `Variant must be one of: ${validSnapshot.join(', ')}`,\n `\"${got}\" is not a valid variant for state \"${name}\". Did you mean one of: ${validSnapshot.join(', ')}? Check for typos.`,\n { code: 'invalid-variant', name, got, valid: validSnapshot },\n );\n}\n\nexport function stateDefaultRequired(name: string): StateError {\n return makeError(\n 'state-default-required',\n 'defineState requires at least one variant (non-empty array)',\n `State \"${name}\" was defined with an empty variants array. Provide at least one variant, e.g. defineState(\"${name}\", [\"default\"] as const).`,\n { code: 'state-default-required', name },\n );\n}\n","// @forgeax/engine-state -- defineState + StateToken SSOT (feat-20260616 M1 / m1w2)\n//\n// Module-level state registration: defineState(name, variants as const) returns a\n// branded StateToken. The token holds the variants vocabulary (name <-> idx lookup)\n// and is used both as a compile-time type witness and as a runtime key for Resource\n// registration.\n//\n// Decision anchors:\n// - plan-strategy D-1: variants vocabulary lives in StateToken, not in ECS types\n// - plan-strategy D-4: defineState throws on programmer errors (duplicate name / empty variants)\n// - plan-strategy sec 8.1: as const + readonly tuple for compile-time variant narrowing\n// - defineState registers schemas at module level; active state plugins project them into Worlds\n\nimport { throwStateError } from './errors';\n\n/**\n * Opaque branded type for type-level state-machine tokens.\n *\n * Use {@link defineState} to create a token; never construct manually.\n * The {@link __forgeaxState} brand prevents plain-object assignment and\n * enables TypeScript narrowing of variant literal types.\n *\n * @typeParam Name - The string literal name of the state machine.\n * @typeParam V - The union of variant string literals derived from the const tuple.\n */\nexport interface StateToken<Name extends string = string, V extends string = string> {\n /** Brand -- prevents structural compatibility with plain objects. */\n readonly __forgeaxState: typeof FORGEAX_STATE_BRAND;\n /** The user-supplied state-machine name. */\n readonly name: Name;\n /** The ordered, read-only variants tuple. */\n readonly variants: readonly V[];\n /** Fast lookup: variant string -> its zero-based index in `variants`. */\n readonly nameToIdx: ReadonlyMap<V, number>;\n /** Convenience: `variants[0]`, the default / initial state value. */\n readonly defaultValue: V;\n}\n\n/**\n * Brand symbol for {@link StateToken}. Declared (not runtime-initialised) so\n * the token interface carries nominal identity without a runtime allocation.\n */\ndeclare const FORGEAX_STATE_BRAND: unique symbol;\n\n/**\n * Extract the variant union from a {@link StateToken}.\n *\n * ```ts\n * const L = defineState('LevelId', ['menu', 'game'] as const);\n * type LevelVariant = StateTokenVariant<typeof L>;\n * // ^? 'menu' | 'game'\n * ```\n */\nexport type StateTokenVariant<T extends StateToken> =\n T extends StateToken<infer _Name, infer V> ? V : never;\n\n/**\n * Extract the name literal from a {@link StateToken}.\n */\nexport type StateTokenName<T extends StateToken> =\n T extends StateToken<infer Name, infer _V> ? Name : never;\n\n/**\n * Global registry of all state tokens, keyed by token name.\n *\n * `defineState` writes here; active state plugins project it into Worlds;\n * cli-state reflection also reads it.\n */\nconst STATE_REGISTRY = new Map<string, StateToken>();\nconst STATE_DEFINED_LISTENERS = new Set<(token: StateToken) => void>();\n\n/**\n * Internal: get the read-only snapshot of all registered tokens.\n * Exported for M2 registerStatesPlugin and M6 cli-state.\n */\nexport function getRegisteredTokens(): ReadonlyMap<string, StateToken> {\n return STATE_REGISTRY;\n}\n\n/** @internal Subscribe one active World adapter to future module-level tokens. */\nexport function onStateDefined(listener: (token: StateToken) => void): () => void {\n STATE_DEFINED_LISTENERS.add(listener);\n return () => STATE_DEFINED_LISTENERS.delete(listener);\n}\n\n/**\n * Define a typed state machine.\n *\n * Must be called at module level. The `as const` assertion on the variants\n * array enables TypeScript to infer the exact literal tuple type, giving\n * compile-time narrowing on variant parameters (e.g. `setNextState(world,\n * token, 'misspell')` is a type error).\n *\n * @param name - Unique state-machine identifier (e.g. `'LevelId'`).\n * @param variants - Readonly tuple of variant string literals. Must be non-empty.\n * @returns A branded {@link StateToken} for use with `setNextState`, `getState`, etc.\n * @throws StateError if `name` is already registered or `variants` is empty.\n *\n * ```ts\n * export const LevelId = defineState('LevelId', ['main-menu', 'tutorial', 'street-a'] as const);\n * // LevelId.variants -> readonly ['main-menu', 'tutorial', 'street-a']\n * // LevelId.nameToIdx.get('tutorial') -> 1\n * // LevelId.defaultValue -> 'main-menu'\n * ```\n */\nexport function defineState<Name extends string, const Variants extends readonly string[]>(\n name: Name,\n variants: Variants,\n): StateToken<Name, Variants[number]> {\n if (STATE_REGISTRY.has(name)) {\n throwStateError(\n 'state-already-defined',\n 'Each StateToken name must be registered exactly once at module level',\n `State \"${name}\" is already defined. Use the existing token.`,\n { code: 'state-already-defined', name, firstDefinedAt: undefined },\n );\n }\n\n if (variants.length === 0) {\n throwStateError(\n 'state-default-required',\n 'defineState requires at least one variant (non-empty array)',\n `State \"${name}\" was defined with an empty variants array. Provide at least one variant, e.g. defineState(\"${name}\", [\"default\"] as const).`,\n { code: 'state-default-required', name },\n );\n }\n\n // Check for duplicate variants within the array\n const seen = new Set<string>();\n for (const v of variants) {\n if (seen.has(v)) {\n throwStateError(\n 'state-default-required',\n 'Variants must be unique within a state token',\n `State \"${name}\" has duplicate variant \"${v}\". Each variant must appear exactly once.`,\n { code: 'state-default-required', name },\n );\n }\n seen.add(v);\n }\n\n const nameToIdx = new Map<Variants[number], number>();\n for (let i = 0; i < variants.length; i++) {\n nameToIdx.set(variants[i] as Variants[number], i);\n }\n\n const token = {\n __forgeaxState: undefined as unknown as typeof FORGEAX_STATE_BRAND,\n name,\n variants,\n nameToIdx,\n defaultValue: variants[0] as Variants[number],\n } as StateToken<Name, Variants[number]>;\n\n STATE_REGISTRY.set(name, token as unknown as StateToken);\n for (const listener of STATE_DEFINED_LISTENERS) listener(token as unknown as StateToken);\n return token;\n}\n","// @forgeax/engine-state -- OnEnter / OnExit callback registry (M4 / m4w2)\n//\n// OnEnter(token, value) and OnExit(token, value) return branded schedule-label\n// strings (pattern: `${name}__OnEnter__${value}` / `${name}__OnExit__${value}`).\n//\n// addOnEnter(token, variant, fn) / addOnExit(token, variant, fn) push fn into a\n// per-label callback registry and return an unsubscribe handle. The registry is\n// consumed by transitionStatesSystem (m4w4) which dispatches callbacks during\n// state transitions.\n//\n// These labels are NOT ECS schedule labels — they are state-package-internal\n// dispatchers. There is no ECS sub-schedule (research F-5); all dispatch happens\n// inside transitionStatesSystem itself (plan-strategy D-5).\n//\n// Decision anchors:\n// - plan-strategy D-5: fn[] registry + transition body dispatch, zero ECS change\n// - research F-5: ECS has no schedule label / sub-schedule API\n// - requirements F-11: OnEnter/OnExit return schedule labels for user-facing API\n\nimport type { World } from '@forgeax/engine-ecs';\nimport type { StateToken, StateTokenVariant } from './define-state';\n\nconst ON_ENTER_LABEL_PREFIX = '__OnEnter__';\nconst ON_EXIT_LABEL_PREFIX = '__OnExit__';\n\n/**\n * Callback type for OnEnter / OnExit hooks.\n *\n * Receives the {@link World} for ECS interaction (e.g. spawning entities,\n * reading Resources). Callbacks fire synchronously inside transitionStatesSystem.\n */\nexport type StateCallback = (world: World) => void;\n\n/**\n * Unsubscribe handle returned by {@link addOnEnter} / {@link addOnExit}.\n * Call to remove the callback from future dispatch.\n */\nexport type UnsubscribeHandle = () => void;\n\n/**\n * Internal callback entry: pairs a function with its identity for removal.\n * The `id` is a unique symbol used as the remove key.\n */\ninterface CallbackEntry {\n id: symbol;\n fn: StateCallback;\n}\n\n/**\n * Module-private callback registry.\n *\n * Key: `${tokenName}__OnEnter__${variant}` or `${tokenName}__OnExit__${variant}`\n * Value: ordered array of callback entries (fired in registration order).\n */\nconst _registry = new Map<string, CallbackEntry[]>();\n\nfunction makeLabel(tokenName: string, prefix: string, variant: string): string {\n return `${tokenName}${prefix}${variant}`;\n}\n\n/**\n * Returns a branded schedule-label string for an OnEnter hook.\n *\n * The label is NOT an ECS schedule label. It is consumed internally by\n * {@link addOnEnter} and dispatched by transitionStatesSystem during state\n * transition (plan-strategy D-5).\n *\n * @param token - The StateToken defining the state machine.\n * @param variant - The variant whose entry triggers the callback.\n * @returns A unique label string usable with {@link addOnEnter}.\n */\nexport function OnEnter<T extends StateToken>(token: T, variant: StateTokenVariant<T>): string {\n return makeLabel(token.name, ON_ENTER_LABEL_PREFIX, variant);\n}\n\n/**\n * Returns a branded schedule-label string for an OnExit hook.\n *\n * @param token - The StateToken defining the state machine.\n * @param variant - The variant whose exit triggers the callback.\n * @returns A unique label string usable with {@link addOnExit}.\n */\nexport function OnExit<T extends StateToken>(token: T, variant: StateTokenVariant<T>): string {\n return makeLabel(token.name, ON_EXIT_LABEL_PREFIX, variant);\n}\n\n/**\n * Register a callback to fire when `token` transitions into `variant`.\n *\n * @param token - The state machine token.\n * @param variant - The target variant that triggers this callback.\n * @param fn - The callback to invoke (receives World parameter).\n * @returns An unsubscribe handle. Call it to remove the callback from future dispatch.\n */\nexport function addOnEnter<T extends StateToken>(\n token: T,\n variant: StateTokenVariant<T>,\n fn: StateCallback,\n): UnsubscribeHandle {\n const label = OnEnter(token, variant);\n return _add(label, fn);\n}\n\n/**\n * Register a callback to fire when `token` transitions away from `variant`.\n *\n * @param token - The state machine token.\n * @param variant - The variant whose exit triggers this callback.\n * @param fn - The callback to invoke (receives World parameter).\n * @returns An unsubscribe handle. Call it to remove the callback from future dispatch.\n */\nexport function addOnExit<T extends StateToken>(\n token: T,\n variant: StateTokenVariant<T>,\n fn: StateCallback,\n): UnsubscribeHandle {\n const label = OnExit(token, variant);\n return _add(label, fn);\n}\n\nfunction _add(label: string, fn: StateCallback): UnsubscribeHandle {\n let entries = _registry.get(label);\n if (!entries) {\n entries = [];\n _registry.set(label, entries);\n }\n\n const id = Symbol();\n entries.push({ id, fn });\n\n return () => {\n const list = _registry.get(label);\n if (!list) return;\n const idx = list.findIndex((e) => e.id === id);\n if (idx !== -1) {\n list.splice(idx, 1);\n }\n };\n}\n\n/**\n * Get all registered callbacks for a given label, in registration order.\n *\n * Returns the callbacks as an array of functions. An empty array means no\n * callbacks are registered for this label. Consumed by transitionStatesSystem\n * during dispatch (m4w4).\n *\n * @param label - An OnEnter or OnExit label string.\n * @returns Array of callback functions (may be empty).\n * @internal\n */\nexport function getCallbacks(label: string): StateCallback[] {\n const entries = _registry.get(label);\n if (!entries) return [];\n return entries.map((e) => e.fn);\n}\n","// @forgeax/engine-state -- ScopedTo components + despawnOnExit/Enter (M3 / m3w2)\n//\n// Per-token __scopedTo__<name> components: defineComponent with two enum fields\n// (value = u32 variant index, mode = exit/enter enum). Components are lazily\n// created on first use via getOrCreateScopedComponent.\n//\n// despawnOnExit / despawnOnEnter are free functions that add the corresponding\n// ScopedTo component to an entity. On duplicate add the ECS returns\n// ComponentAlreadyPresentError, which we throw.\n//\n// Decision anchors:\n// - plan-strategy D-1: value field uses 'enum' (u32 index into token.variants)\n// - requirements F-7/F-8: despawnOnExit/despawnOnEnter free functions\n// - requirements AC-11: duplicate add fail-fast via ECS default exclusive=false\n// - research F-2: 'enum' is already a ScalarFieldType\n\nimport { defineComponent, type EntityHandle, type World } from '@forgeax/engine-ecs';\nimport type { StateToken, StateTokenVariant } from './define-state';\nimport { getRegisteredTokens } from './define-state';\n\nconst SCOPED_COMPONENTS = new Map<string, ReturnType<typeof defineComponent>>();\n\n/** Fixed label-to-value map for the ScopedTo `mode` enum field. */\nexport const SCOPED_MODE_VALUE = { exit: 0, enter: 1 } as const;\n\nfunction getOrCreateScopedComponent(token: StateToken): ReturnType<typeof defineComponent> {\n const existing = SCOPED_COMPONENTS.get(token.name);\n if (existing) return existing;\n\n // `value` is the token's variant index — derive its label→index map from\n // `token.variants` so reflection (describeComponent) can name each variant;\n // `mode` uses the fixed exit/enter map. Both attach labels to the enum field\n // (Derive, don't Duplicate: the variant order IS the label source).\n const valueLabels: Record<string, number> = {};\n token.variants.forEach((v, i) => {\n valueLabels[v] = i;\n });\n\n const comp = defineComponent(`__scopedTo__${token.name}`, {\n value: { type: 'enum', default: 0, labels: valueLabels },\n mode: { type: 'enum', default: SCOPED_MODE_VALUE.exit, labels: SCOPED_MODE_VALUE },\n });\n SCOPED_COMPONENTS.set(token.name, comp);\n return comp;\n}\n\n/** Resolve the world-local ScopedTo token for a state. */\nexport function getScopedComponent(token: StateToken): ReturnType<typeof defineComponent> {\n return getOrCreateScopedComponent(token);\n}\n\nfunction resolveVariantIndex(token: StateToken, variant: string): number {\n const idx = token.nameToIdx.get(variant as never);\n if (idx === undefined) {\n throw new Error(\n `Invalid variant \"${variant}\" for state \"${token.name}\". Valid: ${token.variants.join(', ')}`,\n );\n }\n return idx;\n}\n\n/**\n * Mark `entity` to be despawned when `token` leaves `variant`.\n *\n * Adds a `__scopedTo__<token.name>` component with mode=exit\n * and value=<variant index>. When transitionStatesSystem detects\n * the token transitions away from `variant`, it despawns the entity.\n *\n * Throws if `entity` already carries this token's ScopedTo component\n * (ECS default exclusive=false fail-fast).\n */\nfunction addScopedComponent(\n world: World,\n entity: EntityHandle,\n scoped: ReturnType<typeof defineComponent>,\n value: number,\n mode: number,\n): ReturnType<typeof world.addComponent> {\n return world.addComponent(entity, {\n component: scoped,\n // generic ComponentSchema loses the concrete {value, mode} enum-field\n // types; data is u32 at runtime.\n data: { value, mode } as Record<string, unknown> as Parameters<\n typeof world.addComponent\n >[1]['data'],\n });\n}\n\nexport function despawnOnExit<T extends StateToken>(\n world: World,\n entity: EntityHandle,\n token: T,\n variant: StateTokenVariant<T>,\n): void {\n const idx = resolveVariantIndex(token, variant);\n const scoped = getOrCreateScopedComponent(token);\n const result = addScopedComponent(world, entity, scoped, idx, SCOPED_MODE_VALUE.exit);\n if (!result.ok) {\n throw result.error;\n }\n}\n\n/**\n * Mark `entity` to be despawned when `token` enters `variant`.\n *\n * Adds a `__scopedTo__<token.name>` component with mode=enter\n * and value=<variant index>. When transitionStatesSystem detects\n * the token transitions into `variant`, it despawns the entity.\n *\n * Throws if `entity` already carries this token's ScopedTo component\n * (ECS default exclusive=false fail-fast).\n */\nexport function despawnOnEnter<T extends StateToken>(\n world: World,\n entity: EntityHandle,\n token: T,\n variant: StateTokenVariant<T>,\n): void {\n const idx = resolveVariantIndex(token, variant);\n const scoped = getOrCreateScopedComponent(token);\n const result = addScopedComponent(world, entity, scoped, idx, SCOPED_MODE_VALUE.enter);\n if (!result.ok) {\n throw result.error;\n }\n}\n\n/**\n * Pre-register scoped components for all state tokens in the global registry.\n * Called by registerStatesPlugin during boot; idempotent.\n *\n * @internal\n */\nexport function registerScopedComponents(): void {\n for (const token of getRegisteredTokens().values()) {\n getOrCreateScopedComponent(token);\n }\n}\n\n/**\n * Count entities carrying `token`'s ScopedTo component, grouped by the variant\n * index they are scoped to (irrespective of exit/enter mode). Returns an array\n * aligned to `token.variants` (index i = count for `token.variants[i]`).\n *\n * Used by the `state get <name>` CLI inspector to report per-variant scoped\n * entity counts (requirements AC-15). Reflection only — does not mutate World.\n */\nexport function countScopedEntitiesByVariant(world: World, token: StateToken): number[] {\n const counts = new Array<number>(token.variants.length).fill(0);\n const scoped = getOrCreateScopedComponent(token);\n const query = world.query({ read: [scoped] }).unwrap();\n for (const row of query) {\n const idx = row.get(scoped).value;\n if (typeof idx !== 'number') continue;\n if (idx >= 0 && idx < counts.length) counts[idx] = (counts[idx] ?? 0) + 1;\n }\n return counts;\n}\n\n// resolveScopedComponent removed — transitionStatesSystem uses the World-local catalog.\n","// @forgeax/engine-state -- transitionStatesSystem (M3 / m3w4)\n//\n// 8-step per-token transition logic executed every frame by the\n// 'transitionStates' system registered in registerStatesPlugin.\n//\n// Per token:\n// 1. Read NextState Resource; if undefined -> continue (zero-cost skip)\n// 2. Read State Resource; if prev===next && !force -> clear NextState, continue (same-state no-op)\n// 3. Write PreviousState = prev, flip State = next\n// 4. Collect exit-scoped entities -> world.despawn each\n// 5. OnExit placeholder (M4)\n// 6. Collect enter-scoped entities -> world.despawn each\n// 7. OnEnter placeholder (M4)\n// 8. Clear NextState = undefined\n//\n// Decision anchors:\n// - plan-strategy sec 3.2: 8-step flowchart + OnEnter/OnExit dispatch between flip and despawn\n// - plan-strategy D-2: unified world.despawn via linkedSpawn cascade\n// - plan-strategy D-5: fn[] registry + transition body dispatch, zero ECS change\n// - research F-6: row iteration + world.despawn is sufficient\n// - requirements sec 7: despawn tolerance (entity already dead = no error)\n\nimport type { Component, EntityHandle, World } from '@forgeax/engine-ecs';\nimport { worldDespawnScene } from '@forgeax/engine-scene';\nimport { getRegisteredTokens } from './define-state';\nimport { getCallbacks, OnEnter, OnExit } from './on-enter-on-exit';\nimport { nextStateResourceKey, previousStateResourceKey, stateResourceKey } from './resources';\nimport { SCOPED_MODE_VALUE } from './scoped-component';\n\ninterface NextStatePayload {\n value: number;\n force: boolean;\n}\n\n/**\n * Collect entities whose ScopedTo component matches a given mode and value,\n * then despawn all of them. Single despawn fault (already-dead entity) does\n * not abort the batch — despawn tolerance per requirements sec 7.\n *\n * A scoped entity that is a SceneInstance root is torn down with `despawnScene`\n * (cascade over its instantiated members), NOT plain `world.despawn`. Plain\n * despawn does not cascade through `ChildOf` (which ships `linkedSpawn=false`),\n * so a scoped scene root would orphan every member entity it instantiated. On a\n * state replay (e.g. Title->Play->Title->Play) those orphans linger, their index\n * slots are reused at a new generation, and a surviving member's stale\n * `ChildOf -> (oldRoot, oldGen)` makes `propagateTransforms` throw\n * `hierarchy-broken` every frame. Cascading via `despawnScene` removes the whole\n * instantiated subtree so nothing is left pointing at the dead root.\n */\nfunction scopeDespawn(world: World, scopedComponent: Component, mode: number, value: number): void {\n const query = world.query({ read: [scopedComponent] }).unwrap();\n const despawns: EntityHandle[] = [];\n for (const row of query) {\n const scoped = row.get(scopedComponent);\n if (scoped.mode === mode && scoped.value === value) despawns.push(row.entity);\n }\n // SceneInstance is resolved by name through the global registry so the state\n // package stays free of a runtime dependency (layering: state -> ecs only).\n const sceneInstance = world.components.resolve('SceneInstance');\n // Tear down SceneInstance roots FIRST, via despawnScene (cascade over their\n // instantiated members). This must precede the plain despawns: a scoped scene\n // root is often ChildOf a scoped non-scene entity (e.g. a character rig parented\n // under a KCC body), and despawning that parent first invalidates the root\n // handle before we can cascade it -- leaving the scene's members orphaned with a\n // stale ChildOf -> dead-root ref. Doing the cascades up front guarantees each\n // SceneInstance subtree is fully removed while its root is still live.\n if (sceneInstance !== undefined) {\n for (const e of despawns) {\n if (world.get(e, sceneInstance).ok) worldDespawnScene(world, e);\n }\n }\n for (const e of despawns) {\n // Scene roots already torn down above are now dead -> world.despawn is a\n // tolerated no-op (requirements sec 7); every other scoped entity despawns here.\n world.despawn(e);\n }\n}\n\nexport function transitionStatesSystem(world: World): void {\n for (const token of getRegisteredTokens().values()) {\n const nsKey = nextStateResourceKey(token);\n\n // (0) Skip tokens with no Resources yet. getRegisteredTokens() returns every\n // token ever defined, but registerStatesPlugin only inserts Resources for\n // tokens known at plugin time. A token defined after the plugin ran has no\n // NextState Resource; world.getResource would throw ResourceNotFoundError.\n // hasResource guard mirrors setNextState / getState in this package.\n if (!world.hasResource(nsKey)) continue;\n const ns = world.getResource<NextStatePayload | undefined>(nsKey);\n\n // (1) No pending transition — zero-cost continue\n if (ns === undefined) continue;\n\n const sKey = stateResourceKey(token);\n const prevIdx = world.getResource<number>(sKey);\n const nextIdx = ns.value;\n const force = ns.force;\n\n // (2) Same-state no-op (unless force flag overrides)\n if (prevIdx === nextIdx && !force) {\n world.insertResource<NextStatePayload | undefined>(nsKey, undefined);\n continue;\n }\n\n // (3) Write PreviousState = prev, flip State = next\n const psKey = previousStateResourceKey(token);\n world.insertResource(psKey, prevIdx);\n world.insertResource(sKey, nextIdx);\n\n // Resolve the per-token ScopedTo component from the global ECS registry\n const scopedComponent = world.components.resolve(`__scopedTo__${token.name}`);\n if (scopedComponent) {\n // (4) Despawn exit-scoped entities (value=prev)\n scopeDespawn(world, scopedComponent, SCOPED_MODE_VALUE.exit, prevIdx);\n\n // (5) OnExit dispatch: fire registered callbacks for prev variant.\n // Errors bubble to the transitionStatesSystem call stack per req §7.\n const prevVariant = token.variants[prevIdx];\n if (prevVariant !== undefined) {\n const exitLabel = OnExit(token, prevVariant);\n for (const fn of getCallbacks(exitLabel)) {\n fn(world);\n }\n }\n\n // (6) Despawn enter-scoped entities (value=next)\n scopeDespawn(world, scopedComponent, SCOPED_MODE_VALUE.enter, nextIdx);\n\n // (7) OnEnter dispatch: fire registered callbacks for next variant.\n // Errors bubble to the transitionStatesSystem call stack per req §7.\n const nextVariant = token.variants[nextIdx];\n if (nextVariant !== undefined) {\n const enterLabel = OnEnter(token, nextVariant);\n for (const fn of getCallbacks(enterLabel)) {\n fn(world);\n }\n }\n }\n\n // (8) Clear NextState — but only if OnEnter callbacks did not already\n // write a new NextState payload (e.g. nested setNextState). If the\n // payload differs from the original `ns`, leave it for the next frame.\n const nsAfterCallbacks = world.getResource<NextStatePayload | undefined>(nsKey);\n if (\n nsAfterCallbacks !== undefined &&\n nsAfterCallbacks.value === ns.value &&\n nsAfterCallbacks.force === ns.force\n ) {\n world.insertResource<NextStatePayload | undefined>(nsKey, undefined);\n }\n // else: callbacks wrote a new NextState — survive for next frame\n }\n}\n","import { Update } from '@forgeax/engine-ecs';\n// @forgeax/engine-state -- registerStatesPlugin (M2 / m2w2, M3 / m3w4)\n//\n// Idempotent plugin that inserts per-token Resources (State / NextState /\n// PreviousState), pre-registers ScopedTo components, and registers the\n// transitionStatesSystem in the ECS schedule. Called automatically by\n// createApp in both canvas and assemble forms.\n//\n// M3 / m3w4: stub replaced with transitionStatesSystem from transition-system.ts.\n//\n// Decision anchors:\n// - requirements F-9: idempotent, canvas + assemble dual-form auto-wire\n// - plan-strategy D-6: schedule anchors 'input-frame-start-scan' -> 'transitionStates' -> 'propagateTransforms'\n// - plan-strategy D-4: insertResource initial values from token.defaultValue\n\nimport { defineSystem, defineSystemSet, type SystemHandle, type World } from '@forgeax/engine-ecs';\nimport { getRegisteredTokens, onStateDefined, type StateToken } from './define-state';\nimport { nextStateResourceKey, previousStateResourceKey, stateResourceKey } from './resources';\nimport { getScopedComponent, registerScopedComponents } from './scoped-component';\nimport { transitionStatesSystem } from './transition-system';\n\n/** Schedule anchor: system name for the input frame-start scan (registered by {@link @forgeax/engine-input}). */\nconst FRAME_START_SCAN_SYSTEM_NAME = 'input-frame-start-scan' as const;\n\n/** Schedule anchor: system name for the transform-propagation system (registered by {@link @forgeax/engine-runtime}). */\nconst PROPAGATE_TRANSFORMS_SYSTEM = 'propagateTransforms' as const;\n\nconst TRANSITION_STATES_SYSTEM_NAME = 'transitionStates';\nexport const StateSet = defineSystemSet({ name: 'state' });\nconst ACTIVE_STATE_RUNTIMES = new WeakSet<World>();\n\n/**\n * The `transitionStates` system token (M2 — full resource-ification, D-4).\n *\n * Module-level `defineSystem` with the real fn body — no closure, no\n * placeholder. The fn reads `world` from its first parameter (the M1\n * world-first signature) and delegates to {@link transitionStatesSystem}.\n * Anchored `after: ['input-frame-start-scan']`, `before: ['propagateTransforms']`\n * and labelled `'state'` (spec §6.2 label-anchor map).\n */\nexport const TransitionStates: SystemHandle<readonly []> = defineSystem({\n name: TRANSITION_STATES_SYSTEM_NAME,\n queries: [],\n after: [FRAME_START_SCAN_SYSTEM_NAME],\n before: [PROPAGATE_TRANSFORMS_SYSTEM],\n fn: transitionStatesSystem,\n});\n\n/**\n * Register the state-machine plugin on a {@link World}.\n *\n * Side effects:\n * 1. Pre-registers `__scopedTo__<name>` components for all known tokens.\n * 2. For each globally registered {@link StateToken}: inserts three Resources\n * ({@link State} = defaultValue index, {@link NextState} = undefined,\n * {@link PreviousState} = defaultValue index).\n * 3. Registers the {@link TransitionStates} system in the schedule.\n *\n * Tokens defined after registration are projected immediately. Repeated calls\n * on the same World are no-ops; the first owner receives the sole disposer.\n */\nexport function registerStatesPlugin(world: World): () => void {\n if (ACTIVE_STATE_RUNTIMES.has(world)) return () => {};\n\n const resourceKeys = new Set<string>();\n const componentLeases = new Map<string, { dispose(): unknown }>();\n const registerToken = (token: StateToken): void => {\n registerScopedComponents();\n const scopedComponent = getScopedComponent(token);\n if (!componentLeases.has(token.name)) {\n const lease = world.components.register(scopedComponent);\n if (!lease.ok) throw lease.error;\n componentLeases.set(token.name, lease.value);\n }\n const defaultValueIdx = token.nameToIdx.get(token.defaultValue);\n if (defaultValueIdx === undefined) return;\n\n const stateKey = stateResourceKey(token);\n const nextKey = nextStateResourceKey(token);\n const previousKey = previousStateResourceKey(token);\n if (world.hasResource(stateKey)) return;\n resourceKeys.add(stateKey);\n resourceKeys.add(nextKey);\n resourceKeys.add(previousKey);\n world.insertResource(stateKey, defaultValueIdx);\n world.insertResource(nextKey, undefined as { value: number; force: boolean } | undefined);\n world.insertResource(previousKey, defaultValueIdx);\n };\n\n for (const token of getRegisteredTokens().values()) registerToken(token);\n const installed = world.addSystems(Update, StateSet, [TransitionStates]);\n if (!installed.ok) {\n for (const key of resourceKeys) {\n world.removeResource(key);\n }\n throw installed.error;\n }\n const unsubscribe = onStateDefined(registerToken);\n ACTIVE_STATE_RUNTIMES.add(world);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n unsubscribe();\n ACTIVE_STATE_RUNTIMES.delete(world);\n world.removeSystem(Update, TRANSITION_STATES_SYSTEM_NAME);\n for (const key of resourceKeys) {\n world.removeResource(key);\n }\n for (const lease of componentLeases.values()) {\n lease.dispose();\n }\n };\n}\n","import type { Plugin } from '@forgeax/engine-plugin';\n\nimport { registerStatesPlugin } from './register-plugin';\n\n/**\n * statePlugin -- registers the state-machine systems + per-token Resources.\n *\n * Installs the transition system and projects both current and later\n * module-defined StateTokens into the injected World.\n */\nexport function statePlugin(): Plugin {\n return {\n name: 'state',\n inject: ['world'],\n apply(ctx) {\n ctx.effect(() => registerStatesPlugin(ctx.world), 'state/systems');\n },\n };\n}\n","// @forgeax/engine-state -- setNextState / getState / getPreviousState (M2 / m2w4)\n//\n// Free functions that read and write per-token Resource slots. All return\n// Result<T, StateError> (never throw for AI-user call sites per AGENTS.md\n// Error model).\n//\n// Decision anchors:\n// - requirements C-3: setNextState returns Result.err, not throw\n// - requirements C-4: free functions, not world.x methods\n// - requirements F-5/F-6: State / NextState / PreviousState as Resources\n// - plan-strategy D-4: State stores variant index (u32), decoded via token.variants\n\nimport type { World } from '@forgeax/engine-ecs';\nimport { err, ok, type Result } from '@forgeax/engine-types';\nimport type { StateToken, StateTokenVariant } from './define-state';\nimport type { StateError } from './errors';\nimport { invalidVariant, stateNotRegistered } from './errors';\nimport { nextStateResourceKey, previousStateResourceKey, stateResourceKey } from './resources';\n\ninterface NextStatePayload {\n value: number;\n force: boolean;\n}\n\n/**\n * Request a state transition for `token` to `variant` at the next frame.\n *\n * `variant` is narrowed to the token's variant union: a misspelled variant is\n * a compile-time error (`StateTokenVariant<T>`), not just a runtime\n * `invalid-variant` Result.\n */\nexport function setNextState<T extends StateToken>(\n world: World,\n token: T,\n variant: StateTokenVariant<T>,\n): Result<void, StateError> {\n return _runCheckAndWrite(world, token, variant, false);\n}\n\n/**\n * Like {@link setNextState} but with `force=true`.\n */\nexport function setNextStateForce<T extends StateToken>(\n world: World,\n token: T,\n variant: StateTokenVariant<T>,\n): Result<void, StateError> {\n return _runCheckAndWrite(world, token, variant, true);\n}\n\nfunction _runCheckAndWrite(\n world: World,\n token: StateToken,\n variant: string,\n force: boolean,\n): Result<void, StateError> {\n const nsKey = nextStateResourceKey(token);\n if (!world.hasResource(nsKey)) {\n return err(stateNotRegistered(token.name));\n }\n\n const idx = token.nameToIdx.get(variant as never);\n if (idx === undefined) {\n return err(invalidVariant(token.name, variant, token.variants));\n }\n\n world.insertResource<NextStatePayload>(nsKey, { value: idx, force });\n return ok(undefined);\n}\n\n/**\n * Read the current state value for `token`.\n */\nexport function getState(world: World, token: StateToken): Result<string, StateError> {\n const key = stateResourceKey(token);\n if (!world.hasResource(key)) {\n return err(stateNotRegistered(token.name));\n }\n const idx = world.getResource<number>(key);\n const variant = token.variants[idx];\n if (variant === undefined) {\n return err(invalidVariant(token.name, String(idx), token.variants));\n }\n return ok(variant);\n}\n\n/**\n * Read the previous-frame state value for `token`.\n */\nexport function getPreviousState(world: World, token: StateToken): Result<string, StateError> {\n const key = previousStateResourceKey(token);\n if (!world.hasResource(key)) {\n return err(stateNotRegistered(token.name));\n }\n const idx = world.getResource<number>(key);\n const variant = token.variants[idx];\n if (variant === undefined) {\n return err(invalidVariant(token.name, String(idx), token.variants));\n }\n return ok(variant);\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { World } from '@forgeax/engine-ecs';
|
|
2
|
+
import type { StateToken, StateTokenVariant } from './define-state';
|
|
3
|
+
/**
|
|
4
|
+
* Callback type for OnEnter / OnExit hooks.
|
|
5
|
+
*
|
|
6
|
+
* Receives the {@link World} for ECS interaction (e.g. spawning entities,
|
|
7
|
+
* reading Resources). Callbacks fire synchronously inside transitionStatesSystem.
|
|
8
|
+
*/
|
|
9
|
+
export type StateCallback = (world: World) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Unsubscribe handle returned by {@link addOnEnter} / {@link addOnExit}.
|
|
12
|
+
* Call to remove the callback from future dispatch.
|
|
13
|
+
*/
|
|
14
|
+
export type UnsubscribeHandle = () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Returns a branded schedule-label string for an OnEnter hook.
|
|
17
|
+
*
|
|
18
|
+
* The label is NOT an ECS schedule label. It is consumed internally by
|
|
19
|
+
* {@link addOnEnter} and dispatched by transitionStatesSystem during state
|
|
20
|
+
* transition (plan-strategy D-5).
|
|
21
|
+
*
|
|
22
|
+
* @param token - The StateToken defining the state machine.
|
|
23
|
+
* @param variant - The variant whose entry triggers the callback.
|
|
24
|
+
* @returns A unique label string usable with {@link addOnEnter}.
|
|
25
|
+
*/
|
|
26
|
+
export declare function OnEnter<T extends StateToken>(token: T, variant: StateTokenVariant<T>): string;
|
|
27
|
+
/**
|
|
28
|
+
* Returns a branded schedule-label string for an OnExit hook.
|
|
29
|
+
*
|
|
30
|
+
* @param token - The StateToken defining the state machine.
|
|
31
|
+
* @param variant - The variant whose exit triggers the callback.
|
|
32
|
+
* @returns A unique label string usable with {@link addOnExit}.
|
|
33
|
+
*/
|
|
34
|
+
export declare function OnExit<T extends StateToken>(token: T, variant: StateTokenVariant<T>): string;
|
|
35
|
+
/**
|
|
36
|
+
* Register a callback to fire when `token` transitions into `variant`.
|
|
37
|
+
*
|
|
38
|
+
* @param token - The state machine token.
|
|
39
|
+
* @param variant - The target variant that triggers this callback.
|
|
40
|
+
* @param fn - The callback to invoke (receives World parameter).
|
|
41
|
+
* @returns An unsubscribe handle. Call it to remove the callback from future dispatch.
|
|
42
|
+
*/
|
|
43
|
+
export declare function addOnEnter<T extends StateToken>(token: T, variant: StateTokenVariant<T>, fn: StateCallback): UnsubscribeHandle;
|
|
44
|
+
/**
|
|
45
|
+
* Register a callback to fire when `token` transitions away from `variant`.
|
|
46
|
+
*
|
|
47
|
+
* @param token - The state machine token.
|
|
48
|
+
* @param variant - The variant whose exit triggers this callback.
|
|
49
|
+
* @param fn - The callback to invoke (receives World parameter).
|
|
50
|
+
* @returns An unsubscribe handle. Call it to remove the callback from future dispatch.
|
|
51
|
+
*/
|
|
52
|
+
export declare function addOnExit<T extends StateToken>(token: T, variant: StateTokenVariant<T>, fn: StateCallback): UnsubscribeHandle;
|
|
53
|
+
/**
|
|
54
|
+
* Get all registered callbacks for a given label, in registration order.
|
|
55
|
+
*
|
|
56
|
+
* Returns the callbacks as an array of functions. An empty array means no
|
|
57
|
+
* callbacks are registered for this label. Consumed by transitionStatesSystem
|
|
58
|
+
* during dispatch (m4w4).
|
|
59
|
+
*
|
|
60
|
+
* @param label - An OnEnter or OnExit label string.
|
|
61
|
+
* @returns Array of callback functions (may be empty).
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export declare function getCallbacks(label: string): StateCallback[];
|
|
65
|
+
//# sourceMappingURL=on-enter-on-exit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-enter-on-exit.d.ts","sourceRoot":"","sources":["../src/on-enter-on-exit.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAKpE;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC;AAuB3C;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,CAE7F;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,CAE5F;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,EAC7C,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC7B,EAAE,EAAE,aAAa,GAChB,iBAAiB,CAGnB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,UAAU,EAC5C,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC7B,EAAE,EAAE,aAAa,GAChB,iBAAiB,CAGnB;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAI3D"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Plugin } from '@forgeax/engine-plugin';
|
|
2
|
+
/**
|
|
3
|
+
* statePlugin -- registers the state-machine systems + per-token Resources.
|
|
4
|
+
*
|
|
5
|
+
* Installs the transition system and projects both current and later
|
|
6
|
+
* module-defined StateTokens into the injected World.
|
|
7
|
+
*/
|
|
8
|
+
export declare function statePlugin(): Plugin;
|
|
9
|
+
//# sourceMappingURL=plugin-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-factory.d.ts","sourceRoot":"","sources":["../src/plugin-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAIrD;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAQpC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type SystemHandle, type World } from '@forgeax/engine-ecs';
|
|
2
|
+
export declare const StateSet: import("@forgeax/engine-ecs").SystemSet;
|
|
3
|
+
/**
|
|
4
|
+
* The `transitionStates` system token (M2 — full resource-ification, D-4).
|
|
5
|
+
*
|
|
6
|
+
* Module-level `defineSystem` with the real fn body — no closure, no
|
|
7
|
+
* placeholder. The fn reads `world` from its first parameter (the M1
|
|
8
|
+
* world-first signature) and delegates to {@link transitionStatesSystem}.
|
|
9
|
+
* Anchored `after: ['input-frame-start-scan']`, `before: ['propagateTransforms']`
|
|
10
|
+
* and labelled `'state'` (spec §6.2 label-anchor map).
|
|
11
|
+
*/
|
|
12
|
+
export declare const TransitionStates: SystemHandle<readonly []>;
|
|
13
|
+
/**
|
|
14
|
+
* Register the state-machine plugin on a {@link World}.
|
|
15
|
+
*
|
|
16
|
+
* Side effects:
|
|
17
|
+
* 1. Pre-registers `__scopedTo__<name>` components for all known tokens.
|
|
18
|
+
* 2. For each globally registered {@link StateToken}: inserts three Resources
|
|
19
|
+
* ({@link State} = defaultValue index, {@link NextState} = undefined,
|
|
20
|
+
* {@link PreviousState} = defaultValue index).
|
|
21
|
+
* 3. Registers the {@link TransitionStates} system in the schedule.
|
|
22
|
+
*
|
|
23
|
+
* Tokens defined after registration are projected immediately. Repeated calls
|
|
24
|
+
* on the same World are no-ops; the first owner receives the sole disposer.
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerStatesPlugin(world: World): () => void;
|
|
27
|
+
//# sourceMappingURL=register-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-plugin.d.ts","sourceRoot":"","sources":["../src/register-plugin.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiC,KAAK,YAAY,EAAE,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAanG,eAAO,MAAM,QAAQ,yCAAqC,CAAC;AAG3D;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAAY,CAAC,SAAS,EAAE,CAMrD,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,IAAI,CAoD7D"}
|