@almadar/ui 5.101.0 → 5.103.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.
- package/dist/avl/index.cjs +238 -40
- package/dist/avl/index.js +240 -42
- package/dist/components/index.cjs +89 -19
- package/dist/components/index.js +74 -8
- package/dist/hooks/index.cjs +71 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +69 -2
- package/dist/hooks/useSharedEntityStore.d.ts +53 -0
- package/dist/hooks/useTraitStateMachine.d.ts +32 -1
- package/dist/runtime/index.cjs +242 -44
- package/dist/runtime/index.js +244 -46
- package/package.json +2 -2
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @packageDocumentation
|
|
18
18
|
*/
|
|
19
|
-
import
|
|
19
|
+
import { type MutableRefObject } from 'react';
|
|
20
|
+
import { type SharedEntityWriter } from './index';
|
|
21
|
+
import type { ResolvedTraitTick, EventPayload, SExpr, ServiceParams, ResolvedTrait } from '@almadar/core';
|
|
20
22
|
import { type TraitState } from '@almadar/runtime';
|
|
21
23
|
import type { ResolvedTraitBinding } from '../types/runtime-types';
|
|
22
24
|
import type { useUISlots } from '../providers/UISlotContext';
|
|
@@ -31,6 +33,35 @@ export interface TraitStateMachineResult {
|
|
|
31
33
|
/** Check if a trait can handle an event from its current state */
|
|
32
34
|
canHandleEvent: (traitName: string, eventKey: string) => boolean;
|
|
33
35
|
}
|
|
36
|
+
/** Effect head-operator that writes a field onto a shared entity. */
|
|
37
|
+
export declare const SHARED_ENTITY_WRITE_OPS: ReadonlySet<string>;
|
|
38
|
+
/** Effect head-operators that paint a shared entity's merged state. */
|
|
39
|
+
export declare const SHARED_ENTITY_RENDER_OPS: ReadonlySet<string>;
|
|
40
|
+
/** Every tick + transition effect a trait declares, flattened for a structural scan. */
|
|
41
|
+
export declare function collectAllTraitEffects(trait: ResolvedTrait): SExpr[];
|
|
42
|
+
/**
|
|
43
|
+
* Does ANY effect in this list call one of `ops` anywhere in its (possibly
|
|
44
|
+
* `do`/`if`/`when`/`let`-nested) tree? Reuses `@almadar/core`'s canonical
|
|
45
|
+
* `walkSExpr` rather than a bespoke recursive walk, and looks only at the
|
|
46
|
+
* effect's head operator — a structural presence check, no name-matching.
|
|
47
|
+
*/
|
|
48
|
+
export declare function effectsCallOp(effects: SExpr[], ops: ReadonlySet<string>): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Build one writer trait's tick into a synchronous `SharedEntityWriter`
|
|
51
|
+
* (`(scratch) => writes[]`) so `runTickFrame` can fold several writer ticks
|
|
52
|
+
* into one merged commit, in binding order.
|
|
53
|
+
*
|
|
54
|
+
* `runTickFrame`'s writer contract is synchronous, but `@almadar/runtime`'s
|
|
55
|
+
* `EffectExecutor` is inherently async (`executeAll`'s `for...of await`
|
|
56
|
+
* sequencing only guarantees the FIRST top-level effect lands before a
|
|
57
|
+
* fire-and-forget caller reads the result back — everything after the first
|
|
58
|
+
* `await` boundary would be silently dropped). `@almadar/evaluator`'s
|
|
59
|
+
* `executeEffects` is fully synchronous end-to-end (its `set`/`do`/`if`/
|
|
60
|
+
* `when`/`let` dispatch never awaits), so it — not `EffectExecutor` — is the
|
|
61
|
+
* correct tool here; `ctx.mutateEntity` captures each write instead of
|
|
62
|
+
* applying it through a handler.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createSharedEntityWriter(binding: ResolvedTraitBinding, tick: ResolvedTraitTick, traitStatesRef: MutableRefObject<Map<string, TraitState>>, emit: (event: string, payload?: EventPayload) => void): SharedEntityWriter;
|
|
34
65
|
export interface UseTraitStateMachineOptions {
|
|
35
66
|
/**
|
|
36
67
|
* Callback invoked after each event is processed (for server forwarding).
|
package/dist/runtime/index.cjs
CHANGED
|
@@ -7730,7 +7730,7 @@ function recordTransition(trace) {
|
|
|
7730
7730
|
...trace,
|
|
7731
7731
|
id: `t-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
7732
7732
|
};
|
|
7733
|
-
|
|
7733
|
+
log4.debug("transition:recorded", { trait: trace.traitName, from: trace.from, to: trace.to, event: trace.event, effectCount: trace.effects.length });
|
|
7734
7734
|
getState().transitions.push(entry);
|
|
7735
7735
|
if (getState().transitions.length > MAX_TRANSITIONS) {
|
|
7736
7736
|
getState().transitions.shift();
|
|
@@ -7832,7 +7832,7 @@ function getTraitSnapshots() {
|
|
|
7832
7832
|
try {
|
|
7833
7833
|
snapshots.push(getter());
|
|
7834
7834
|
} catch (err) {
|
|
7835
|
-
|
|
7835
|
+
log4.error("traitSnapshot getter failed", { trait: traitName, err: String(err) });
|
|
7836
7836
|
}
|
|
7837
7837
|
}
|
|
7838
7838
|
return snapshots;
|
|
@@ -7931,10 +7931,10 @@ function updateAssetStatus(url, status) {
|
|
|
7931
7931
|
window.__orbitalVerification.assetStatus[url] = status;
|
|
7932
7932
|
}
|
|
7933
7933
|
}
|
|
7934
|
-
var
|
|
7934
|
+
var log4, MAX_TRANSITIONS;
|
|
7935
7935
|
var init_verificationRegistry = __esm({
|
|
7936
7936
|
"lib/verificationRegistry.ts"() {
|
|
7937
|
-
|
|
7937
|
+
log4 = logger.createLogger("almadar:bridge");
|
|
7938
7938
|
MAX_TRANSITIONS = 500;
|
|
7939
7939
|
exposeOnWindow();
|
|
7940
7940
|
}
|
|
@@ -17542,7 +17542,7 @@ var init_avl_elk_layout = __esm({
|
|
|
17542
17542
|
elk = new ELK__default.default();
|
|
17543
17543
|
}
|
|
17544
17544
|
});
|
|
17545
|
-
var
|
|
17545
|
+
var log5, SWIM_GUTTER, CENTER_W, BehaviorView;
|
|
17546
17546
|
var init_BehaviorView = __esm({
|
|
17547
17547
|
"components/avl/molecules/BehaviorView.tsx"() {
|
|
17548
17548
|
"use client";
|
|
@@ -17551,7 +17551,7 @@ var init_BehaviorView = __esm({
|
|
|
17551
17551
|
init_AvlSwimLane();
|
|
17552
17552
|
init_avl_atom_types();
|
|
17553
17553
|
init_avl_elk_layout();
|
|
17554
|
-
|
|
17554
|
+
log5 = logger.createLogger("almadar:ui:avl:behavior-view");
|
|
17555
17555
|
SWIM_GUTTER = 120;
|
|
17556
17556
|
CENTER_W = 360;
|
|
17557
17557
|
BehaviorView = ({ data }) => {
|
|
@@ -17562,7 +17562,7 @@ var init_BehaviorView = __esm({
|
|
|
17562
17562
|
const dataKey = React96.useMemo(() => JSON.stringify(traitData), [traitData]);
|
|
17563
17563
|
React96.useEffect(() => {
|
|
17564
17564
|
if (!traitData) return;
|
|
17565
|
-
computeTraitLayout(traitData).then(setLayout).catch((err) =>
|
|
17565
|
+
computeTraitLayout(traitData).then(setLayout).catch((err) => log5.error("compute-trait-layout-failed", { error: err instanceof Error ? err : String(err) }));
|
|
17566
17566
|
}, [dataKey]);
|
|
17567
17567
|
if (!traitData) {
|
|
17568
17568
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] p-4 text-center text-[var(--color-muted-foreground)] text-sm", children: t("avl.noTraitData") });
|
|
@@ -18114,7 +18114,7 @@ function generateDiff(oldVal, newVal) {
|
|
|
18114
18114
|
}
|
|
18115
18115
|
return diff;
|
|
18116
18116
|
}
|
|
18117
|
-
var orbStyleOverrides, orbStyle, loloStyleOverrides, loloStyle,
|
|
18117
|
+
var orbStyleOverrides, orbStyle, loloStyleOverrides, loloStyle, log6, CODE_LANGUAGES, CODE_LANGUAGE_SET, DIFF_STYLES, DIFF_STYLE_FALLBACK, LINE_PROPS_FN, HIDDEN_LINE_NUMBERS, CodeBlock;
|
|
18118
18118
|
var init_CodeBlock = __esm({
|
|
18119
18119
|
"components/core/molecules/markdown/CodeBlock.tsx"() {
|
|
18120
18120
|
init_cn();
|
|
@@ -18197,7 +18197,7 @@ var init_CodeBlock = __esm({
|
|
|
18197
18197
|
"lolo-op-async": { color: syntax.ORB_COLORS.dark.async }
|
|
18198
18198
|
};
|
|
18199
18199
|
loloStyle = { ...dark__default.default, ...loloStyleOverrides };
|
|
18200
|
-
|
|
18200
|
+
log6 = logger.createLogger("almadar:ui:markdown-code");
|
|
18201
18201
|
CODE_LANGUAGES = [
|
|
18202
18202
|
"text",
|
|
18203
18203
|
"json",
|
|
@@ -18465,7 +18465,7 @@ var init_CodeBlock = __esm({
|
|
|
18465
18465
|
eventBus.emit("UI:COPY_CODE", { language: activeLanguage, success: true });
|
|
18466
18466
|
setTimeout(() => setCopied(false), 2e3);
|
|
18467
18467
|
} catch (err) {
|
|
18468
|
-
|
|
18468
|
+
log6.error("Failed to copy code", { error: err instanceof Error ? err : String(err) });
|
|
18469
18469
|
eventBus.emit("UI:COPY_CODE", { language: activeLanguage, success: false });
|
|
18470
18470
|
}
|
|
18471
18471
|
};
|
|
@@ -27330,14 +27330,14 @@ function useSafeEventBus3() {
|
|
|
27330
27330
|
} };
|
|
27331
27331
|
}
|
|
27332
27332
|
}
|
|
27333
|
-
var
|
|
27333
|
+
var log7, lookStyles5, ButtonGroup;
|
|
27334
27334
|
var init_ButtonGroup = __esm({
|
|
27335
27335
|
"components/core/molecules/ButtonGroup.tsx"() {
|
|
27336
27336
|
"use client";
|
|
27337
27337
|
init_cn();
|
|
27338
27338
|
init_atoms();
|
|
27339
27339
|
init_useEventBus();
|
|
27340
|
-
|
|
27340
|
+
log7 = logger.createLogger("almadar:ui:button-group");
|
|
27341
27341
|
lookStyles5 = {
|
|
27342
27342
|
"right-aligned-buttons": "",
|
|
27343
27343
|
"floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
|
|
@@ -27418,7 +27418,7 @@ var init_ButtonGroup = __esm({
|
|
|
27418
27418
|
{
|
|
27419
27419
|
variant: "ghost",
|
|
27420
27420
|
onClick: () => {
|
|
27421
|
-
|
|
27421
|
+
log7.debug("Filter clicked", { field: filter.field });
|
|
27422
27422
|
},
|
|
27423
27423
|
children: filter.label
|
|
27424
27424
|
},
|
|
@@ -28327,9 +28327,9 @@ function debug(...args) {
|
|
|
28327
28327
|
const [first, ...rest] = args;
|
|
28328
28328
|
const message = typeof first === "string" ? first : "<debug>";
|
|
28329
28329
|
if (rest.length === 0 && typeof first === "string") {
|
|
28330
|
-
|
|
28330
|
+
log8.debug(message);
|
|
28331
28331
|
} else {
|
|
28332
|
-
|
|
28332
|
+
log8.debug(message, { args: rest.length > 0 ? formatArgs(rest) : formatArgs([first]) });
|
|
28333
28333
|
}
|
|
28334
28334
|
}
|
|
28335
28335
|
function debugGroup(label) {
|
|
@@ -28357,11 +28357,11 @@ function toLogMetaValue(v) {
|
|
|
28357
28357
|
}
|
|
28358
28358
|
return String(v);
|
|
28359
28359
|
}
|
|
28360
|
-
var NAMESPACE,
|
|
28360
|
+
var NAMESPACE, log8;
|
|
28361
28361
|
var init_debug = __esm({
|
|
28362
28362
|
"lib/debug.ts"() {
|
|
28363
28363
|
NAMESPACE = "almadar:ui:debug";
|
|
28364
|
-
|
|
28364
|
+
log8 = logger.createLogger(NAMESPACE);
|
|
28365
28365
|
logger.createLogger("almadar:ui:debug:input");
|
|
28366
28366
|
logger.createLogger("almadar:ui:debug:collision");
|
|
28367
28367
|
logger.createLogger("almadar:ui:debug:physics");
|
|
@@ -30519,12 +30519,12 @@ var init_MapView = __esm({
|
|
|
30519
30519
|
shadowSize: [41, 41]
|
|
30520
30520
|
});
|
|
30521
30521
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
30522
|
-
const { useEffect: useEffect67, useRef:
|
|
30522
|
+
const { useEffect: useEffect67, useRef: useRef66, useCallback: useCallback98, useState: useState97 } = React96__namespace.default;
|
|
30523
30523
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
30524
30524
|
const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
30525
30525
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
30526
30526
|
const map = useMap();
|
|
30527
|
-
const prevRef =
|
|
30527
|
+
const prevRef = useRef66({ centerLat, centerLng, zoom });
|
|
30528
30528
|
useEffect67(() => {
|
|
30529
30529
|
const prev = prevRef.current;
|
|
30530
30530
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
@@ -43786,7 +43786,7 @@ function getAllEvents(traits2) {
|
|
|
43786
43786
|
function EventDispatcherTab({ traits: traits2, schema }) {
|
|
43787
43787
|
const eventBus = useEventBus();
|
|
43788
43788
|
const { t } = hooks.useTranslate();
|
|
43789
|
-
const [
|
|
43789
|
+
const [log10, setLog] = React96__namespace.useState([]);
|
|
43790
43790
|
const prevStatesRef = React96__namespace.useRef(/* @__PURE__ */ new Map());
|
|
43791
43791
|
React96__namespace.useEffect(() => {
|
|
43792
43792
|
for (const trait of traits2) {
|
|
@@ -43850,9 +43850,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
|
|
|
43850
43850
|
/* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
|
|
43851
43851
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: unavailableEvents.map((event) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", className: "opacity-50", children: event }, event)) })
|
|
43852
43852
|
] }),
|
|
43853
|
-
|
|
43853
|
+
log10.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
43854
43854
|
/* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
|
|
43855
|
-
/* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children:
|
|
43855
|
+
/* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log10.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
|
|
43856
43856
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-purple-400", children: entry.traitName }),
|
|
43857
43857
|
" ",
|
|
43858
43858
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500", children: entry.from }),
|
|
@@ -47562,7 +47562,67 @@ var init_UISlotRenderer = __esm({
|
|
|
47562
47562
|
|
|
47563
47563
|
// hooks/index.ts
|
|
47564
47564
|
init_useEventBus();
|
|
47565
|
-
var log2 = logger.createLogger("almadar:ui:
|
|
47565
|
+
var log2 = logger.createLogger("almadar:ui:shared-entity-store");
|
|
47566
|
+
var EMPTY_ENTITY_STATE = {};
|
|
47567
|
+
function createSharedEntityStore() {
|
|
47568
|
+
const states = /* @__PURE__ */ new Map();
|
|
47569
|
+
const subscribers2 = /* @__PURE__ */ new Map();
|
|
47570
|
+
const getSnapshot3 = (entityId) => states.get(entityId) ?? EMPTY_ENTITY_STATE;
|
|
47571
|
+
const subscribe2 = (entityId, callback) => {
|
|
47572
|
+
let set = subscribers2.get(entityId);
|
|
47573
|
+
if (!set) {
|
|
47574
|
+
set = /* @__PURE__ */ new Set();
|
|
47575
|
+
subscribers2.set(entityId, set);
|
|
47576
|
+
}
|
|
47577
|
+
set.add(callback);
|
|
47578
|
+
return () => {
|
|
47579
|
+
const current = subscribers2.get(entityId);
|
|
47580
|
+
if (!current) return;
|
|
47581
|
+
current.delete(callback);
|
|
47582
|
+
if (current.size === 0) {
|
|
47583
|
+
subscribers2.delete(entityId);
|
|
47584
|
+
}
|
|
47585
|
+
};
|
|
47586
|
+
};
|
|
47587
|
+
const commit = (entityId, nextState) => {
|
|
47588
|
+
states.set(entityId, nextState);
|
|
47589
|
+
const set = subscribers2.get(entityId);
|
|
47590
|
+
if (!set) return;
|
|
47591
|
+
set.forEach((callback) => {
|
|
47592
|
+
try {
|
|
47593
|
+
callback();
|
|
47594
|
+
} catch (error) {
|
|
47595
|
+
log2.error("Shared entity subscriber error", {
|
|
47596
|
+
entityId,
|
|
47597
|
+
error: error instanceof Error ? error : String(error)
|
|
47598
|
+
});
|
|
47599
|
+
}
|
|
47600
|
+
});
|
|
47601
|
+
};
|
|
47602
|
+
const seed = (entityId, initialState) => {
|
|
47603
|
+
if (!states.has(entityId)) {
|
|
47604
|
+
states.set(entityId, initialState);
|
|
47605
|
+
}
|
|
47606
|
+
};
|
|
47607
|
+
return { getSnapshot: getSnapshot3, subscribe: subscribe2, commit, seed };
|
|
47608
|
+
}
|
|
47609
|
+
function useSharedEntityStore() {
|
|
47610
|
+
const storeRef = React96.useRef(null);
|
|
47611
|
+
if (storeRef.current === null) {
|
|
47612
|
+
storeRef.current = createSharedEntityStore();
|
|
47613
|
+
}
|
|
47614
|
+
return storeRef.current;
|
|
47615
|
+
}
|
|
47616
|
+
function runTickFrame(entityId, orderedWriters, store) {
|
|
47617
|
+
let scratch = store.getSnapshot(entityId);
|
|
47618
|
+
for (const writer of orderedWriters) {
|
|
47619
|
+
const writes = writer(scratch);
|
|
47620
|
+
scratch = core.mergeEntityFrame(scratch, writes);
|
|
47621
|
+
}
|
|
47622
|
+
store.commit(entityId, scratch);
|
|
47623
|
+
return scratch;
|
|
47624
|
+
}
|
|
47625
|
+
var log3 = logger.createLogger("almadar:ui:effects:client-handlers");
|
|
47566
47626
|
function createClientEffectHandlers(options) {
|
|
47567
47627
|
const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
|
|
47568
47628
|
return {
|
|
@@ -47571,12 +47631,12 @@ function createClientEffectHandlers(options) {
|
|
|
47571
47631
|
eventBus.emit(prefixedEvent, payload);
|
|
47572
47632
|
},
|
|
47573
47633
|
persist: async () => {
|
|
47574
|
-
|
|
47634
|
+
log3.warn("persist is server-side only, ignored on client");
|
|
47575
47635
|
},
|
|
47576
47636
|
// @almadar/runtime EffectHandlers.set types value:unknown — should be FieldValue (upstream fix queued)
|
|
47577
47637
|
set: ((_entityId, field, value) => {
|
|
47578
47638
|
if (!liveEntity) {
|
|
47579
|
-
|
|
47639
|
+
log3.warn("set is server-side only, ignored on client (no live entity)");
|
|
47580
47640
|
return;
|
|
47581
47641
|
}
|
|
47582
47642
|
liveEntity[field] = value;
|
|
@@ -47608,10 +47668,10 @@ function createClientEffectHandlers(options) {
|
|
|
47608
47668
|
slotSetter.addPattern(slot, pattern, props);
|
|
47609
47669
|
},
|
|
47610
47670
|
navigate: navigate ?? ((path) => {
|
|
47611
|
-
|
|
47671
|
+
log3.warn("No navigate handler, ignoring", { path });
|
|
47612
47672
|
}),
|
|
47613
47673
|
notify: notify ?? ((msg, type) => {
|
|
47614
|
-
|
|
47674
|
+
log3.debug("notify", { type, message: msg });
|
|
47615
47675
|
})
|
|
47616
47676
|
};
|
|
47617
47677
|
}
|
|
@@ -47782,11 +47842,101 @@ function toTraitDefinition(binding) {
|
|
|
47782
47842
|
function normalizeEventKey(eventKey) {
|
|
47783
47843
|
return eventKey.startsWith("UI:") ? eventKey.slice(3) : eventKey;
|
|
47784
47844
|
}
|
|
47845
|
+
var SHARED_ENTITY_WRITE_OPS = /* @__PURE__ */ new Set(["set"]);
|
|
47846
|
+
var SHARED_ENTITY_RENDER_OPS = /* @__PURE__ */ new Set(["render-ui", "render"]);
|
|
47847
|
+
function collectAllTraitEffects(trait) {
|
|
47848
|
+
return [
|
|
47849
|
+
...trait.ticks.flatMap((t) => t.effects),
|
|
47850
|
+
...trait.transitions.flatMap((t) => t.effects)
|
|
47851
|
+
];
|
|
47852
|
+
}
|
|
47853
|
+
function effectsCallOp(effects, ops) {
|
|
47854
|
+
for (const effect of effects) {
|
|
47855
|
+
let found = false;
|
|
47856
|
+
core.walkSExpr(effect, (node) => {
|
|
47857
|
+
if (!found && Array.isArray(node) && typeof node[0] === "string" && ops.has(node[0])) {
|
|
47858
|
+
found = true;
|
|
47859
|
+
}
|
|
47860
|
+
});
|
|
47861
|
+
if (found) return true;
|
|
47862
|
+
}
|
|
47863
|
+
return false;
|
|
47864
|
+
}
|
|
47865
|
+
function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
|
|
47866
|
+
return (scratch) => {
|
|
47867
|
+
const traitName = binding.trait.name;
|
|
47868
|
+
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
47869
|
+
if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return [];
|
|
47870
|
+
const scratchEntity = { ...scratch };
|
|
47871
|
+
const writes = [];
|
|
47872
|
+
const ctx = evaluator.createMinimalContext(scratchEntity, {}, currentState);
|
|
47873
|
+
const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
|
|
47874
|
+
const callSiteConfig = binding.config;
|
|
47875
|
+
if (declaredDefaults || callSiteConfig) {
|
|
47876
|
+
ctx.config = { ...declaredDefaults ?? {}, ...callSiteConfig ?? {} };
|
|
47877
|
+
}
|
|
47878
|
+
ctx.mutateEntity = (changes) => {
|
|
47879
|
+
for (const [field, value] of Object.entries(changes)) {
|
|
47880
|
+
const fieldValue = value;
|
|
47881
|
+
scratchEntity[field] = fieldValue;
|
|
47882
|
+
writes.push({ field, value: fieldValue });
|
|
47883
|
+
}
|
|
47884
|
+
};
|
|
47885
|
+
ctx.emit = (event, payload) => {
|
|
47886
|
+
emit(event, payload);
|
|
47887
|
+
};
|
|
47888
|
+
if (tick.guard !== void 0 && !evaluator.evaluateGuard(tick.guard, ctx)) {
|
|
47889
|
+
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
47890
|
+
return [];
|
|
47891
|
+
}
|
|
47892
|
+
evaluator.executeEffects(tick.effects, ctx);
|
|
47893
|
+
return writes;
|
|
47894
|
+
};
|
|
47895
|
+
}
|
|
47785
47896
|
function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
47786
47897
|
const eventBus = useEventBus();
|
|
47787
47898
|
const { entities } = providers.useEntitySchema();
|
|
47788
47899
|
const traitConfigsByName = options?.traitConfigsByName;
|
|
47789
47900
|
const orbitalsByTrait = options?.orbitalsByTrait;
|
|
47901
|
+
const sharedEntityStore = useSharedEntityStore();
|
|
47902
|
+
const sharedGroups = React96.useMemo(() => {
|
|
47903
|
+
const groups = /* @__PURE__ */ new Map();
|
|
47904
|
+
for (const binding of traitBindings) {
|
|
47905
|
+
const linkedEntityName = binding.linkedEntity ?? binding.trait.linkedEntity;
|
|
47906
|
+
if (!linkedEntityName) continue;
|
|
47907
|
+
const entityDef = entities.get(linkedEntityName);
|
|
47908
|
+
if (!entityDef?.shared) continue;
|
|
47909
|
+
const orbitalName = orbitalsByTrait?.[binding.trait.name] ?? "";
|
|
47910
|
+
const storeKey = `${orbitalName}::${linkedEntityName}`;
|
|
47911
|
+
let group = groups.get(storeKey);
|
|
47912
|
+
if (!group) {
|
|
47913
|
+
let defaults;
|
|
47914
|
+
for (const field of entityDef.fields) {
|
|
47915
|
+
if (field.default !== void 0) {
|
|
47916
|
+
(defaults ?? (defaults = {}))[field.name] = field.default;
|
|
47917
|
+
}
|
|
47918
|
+
}
|
|
47919
|
+
group = { storeKey, entityName: linkedEntityName, writerBindings: [], renderBindings: [], defaults };
|
|
47920
|
+
groups.set(storeKey, group);
|
|
47921
|
+
}
|
|
47922
|
+
const allEffects = collectAllTraitEffects(binding.trait);
|
|
47923
|
+
if (effectsCallOp(allEffects, SHARED_ENTITY_WRITE_OPS)) group.writerBindings.push(binding);
|
|
47924
|
+
if (effectsCallOp(allEffects, SHARED_ENTITY_RENDER_OPS)) group.renderBindings.push(binding);
|
|
47925
|
+
}
|
|
47926
|
+
return groups;
|
|
47927
|
+
}, [traitBindings, entities, orbitalsByTrait]);
|
|
47928
|
+
for (const group of sharedGroups.values()) {
|
|
47929
|
+
if (group.defaults) sharedEntityStore.seed(group.storeKey, group.defaults);
|
|
47930
|
+
}
|
|
47931
|
+
const sharedKeyByTraitNameRef = React96.useRef(/* @__PURE__ */ new Map());
|
|
47932
|
+
React96.useEffect(() => {
|
|
47933
|
+
const map = /* @__PURE__ */ new Map();
|
|
47934
|
+
for (const group of sharedGroups.values()) {
|
|
47935
|
+
for (const binding of group.writerBindings) map.set(binding.trait.name, group.storeKey);
|
|
47936
|
+
for (const binding of group.renderBindings) map.set(binding.trait.name, group.storeKey);
|
|
47937
|
+
}
|
|
47938
|
+
sharedKeyByTraitNameRef.current = map;
|
|
47939
|
+
}, [sharedGroups]);
|
|
47790
47940
|
const manager = React96.useMemo(() => {
|
|
47791
47941
|
const traitDefs = traitBindings.map(toTraitDefinition);
|
|
47792
47942
|
const m = new runtime.StateMachineManager(traitDefs);
|
|
@@ -47969,14 +48119,21 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
47969
48119
|
};
|
|
47970
48120
|
}, [traitBindings]);
|
|
47971
48121
|
const executeTransitionEffects = React96.useCallback(async (params) => {
|
|
47972
|
-
const { binding, previousState, newState, payload, flushEvent, syncOnly, log:
|
|
48122
|
+
const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log10 } = params;
|
|
47973
48123
|
const traitName = binding.trait.name;
|
|
47974
48124
|
const linkedEntity = binding.linkedEntity || "";
|
|
47975
48125
|
const entityId = payload?.entityId;
|
|
47976
|
-
|
|
47977
|
-
|
|
47978
|
-
|
|
47979
|
-
|
|
48126
|
+
const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
|
|
48127
|
+
let liveEntity;
|
|
48128
|
+
if (sharedKey !== void 0) {
|
|
48129
|
+
liveEntity = { ...sharedEntityStore.getSnapshot(sharedKey) };
|
|
48130
|
+
} else {
|
|
48131
|
+
let existing = traitFieldStatesRef.current.get(traitName);
|
|
48132
|
+
if (!existing) {
|
|
48133
|
+
existing = {};
|
|
48134
|
+
traitFieldStatesRef.current.set(traitName, existing);
|
|
48135
|
+
}
|
|
48136
|
+
liveEntity = existing;
|
|
47980
48137
|
}
|
|
47981
48138
|
const effects = syncOnly ? params.effects.filter(
|
|
47982
48139
|
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
@@ -48058,7 +48215,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
48058
48215
|
...handlers,
|
|
48059
48216
|
set: async (targetId, field, value) => {
|
|
48060
48217
|
if (baseSet) await baseSet(targetId, field, value);
|
|
48061
|
-
|
|
48218
|
+
log10.debug("set:write", {
|
|
48062
48219
|
traitName,
|
|
48063
48220
|
field,
|
|
48064
48221
|
value: JSON.stringify(value),
|
|
@@ -48098,17 +48255,20 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
48098
48255
|
const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
48099
48256
|
try {
|
|
48100
48257
|
await executor.executeAll(effects);
|
|
48101
|
-
|
|
48258
|
+
if (sharedKey !== void 0) {
|
|
48259
|
+
sharedEntityStore.commit(sharedKey, liveEntity);
|
|
48260
|
+
}
|
|
48261
|
+
log10.debug("effects:executed", () => ({
|
|
48102
48262
|
traitName,
|
|
48103
48263
|
transition: `${previousState}->${newState}`,
|
|
48104
48264
|
event: flushEvent,
|
|
48105
48265
|
effectCount: effects.length,
|
|
48106
48266
|
emitted: emittedDuringExec.join(","),
|
|
48107
|
-
entityAfter: JSON.stringify(
|
|
48267
|
+
entityAfter: JSON.stringify(liveEntity ?? {}),
|
|
48108
48268
|
slotsTouched: Array.from(pendingSlots.keys()).join(",")
|
|
48109
48269
|
}));
|
|
48110
48270
|
for (const [slot, patterns] of pendingSlots) {
|
|
48111
|
-
|
|
48271
|
+
log10.debug("flush:slot", {
|
|
48112
48272
|
traitName,
|
|
48113
48273
|
slot,
|
|
48114
48274
|
patternCount: patterns.length,
|
|
@@ -48123,7 +48283,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
48123
48283
|
});
|
|
48124
48284
|
}
|
|
48125
48285
|
} catch (error) {
|
|
48126
|
-
|
|
48286
|
+
log10.error("effects:error", {
|
|
48127
48287
|
traitName,
|
|
48128
48288
|
transition: `${previousState}->${newState}`,
|
|
48129
48289
|
event: flushEvent,
|
|
@@ -48132,14 +48292,16 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
48132
48292
|
});
|
|
48133
48293
|
}
|
|
48134
48294
|
return emittedDuringExec;
|
|
48135
|
-
}, [eventBus, flushSlot]);
|
|
48295
|
+
}, [eventBus, flushSlot, sharedEntityStore]);
|
|
48136
48296
|
const runTickEffects = React96.useCallback((tick, binding) => {
|
|
48137
48297
|
const traitName = binding.trait.name;
|
|
48138
48298
|
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
48139
48299
|
if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return;
|
|
48140
48300
|
if (tick.guard !== void 0) {
|
|
48301
|
+
const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
|
|
48302
|
+
const entity = sharedKey !== void 0 ? sharedEntityStore.getSnapshot(sharedKey) : traitFieldStatesRef.current.get(traitName) ?? {};
|
|
48141
48303
|
const guardCtx = {
|
|
48142
|
-
entity
|
|
48304
|
+
entity,
|
|
48143
48305
|
payload: {},
|
|
48144
48306
|
state: currentState
|
|
48145
48307
|
};
|
|
@@ -48164,10 +48326,46 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
48164
48326
|
syncOnly: true,
|
|
48165
48327
|
log: tickLog
|
|
48166
48328
|
});
|
|
48167
|
-
}, [executeTransitionEffects]);
|
|
48329
|
+
}, [executeTransitionEffects, sharedEntityStore]);
|
|
48330
|
+
const emitFromSharedWriter = React96.useCallback((event, payload) => {
|
|
48331
|
+
const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
|
|
48332
|
+
eventBus.emit(prefixedEvent, payload);
|
|
48333
|
+
}, [eventBus]);
|
|
48168
48334
|
React96.useEffect(() => {
|
|
48169
48335
|
const scheduler = runtime.createTickScheduler();
|
|
48336
|
+
const writerTraitNames = /* @__PURE__ */ new Set();
|
|
48337
|
+
for (const group of sharedGroups.values()) {
|
|
48338
|
+
const ticksByInterval = /* @__PURE__ */ new Map();
|
|
48339
|
+
for (const binding of group.writerBindings) {
|
|
48340
|
+
writerTraitNames.add(binding.trait.name);
|
|
48341
|
+
for (const tick of binding.trait.ticks ?? []) {
|
|
48342
|
+
const intervalKey = String(tick.interval);
|
|
48343
|
+
const entries = ticksByInterval.get(intervalKey) ?? [];
|
|
48344
|
+
entries.push({ binding, tick });
|
|
48345
|
+
ticksByInterval.set(intervalKey, entries);
|
|
48346
|
+
}
|
|
48347
|
+
}
|
|
48348
|
+
for (const entries of ticksByInterval.values()) {
|
|
48349
|
+
const interval = entries[0].tick.interval;
|
|
48350
|
+
const onDue = () => {
|
|
48351
|
+
const writers = entries.map(
|
|
48352
|
+
({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
|
|
48353
|
+
);
|
|
48354
|
+
runTickFrame(group.storeKey, writers, sharedEntityStore);
|
|
48355
|
+
};
|
|
48356
|
+
if (interval === "frame") {
|
|
48357
|
+
scheduler.add(0, onDue);
|
|
48358
|
+
} else if (typeof interval === "number") {
|
|
48359
|
+
scheduler.add(interval, onDue);
|
|
48360
|
+
} else if (runtime.isValidCronExpression(interval)) {
|
|
48361
|
+
scheduler.addCron(interval, onDue);
|
|
48362
|
+
} else {
|
|
48363
|
+
scheduler.add(runtime.parseDurationString(interval), onDue);
|
|
48364
|
+
}
|
|
48365
|
+
}
|
|
48366
|
+
}
|
|
48170
48367
|
for (const binding of traitBindings) {
|
|
48368
|
+
if (writerTraitNames.has(binding.trait.name)) continue;
|
|
48171
48369
|
for (const tick of binding.trait.ticks ?? []) {
|
|
48172
48370
|
if (tick.interval === "frame") {
|
|
48173
48371
|
scheduler.add(0, () => runTickEffects(tick, binding));
|
|
@@ -48181,7 +48379,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
48181
48379
|
}
|
|
48182
48380
|
}
|
|
48183
48381
|
return () => scheduler.stopAll();
|
|
48184
|
-
}, [traitBindings, runTickEffects]);
|
|
48382
|
+
}, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
|
|
48185
48383
|
const processEventQueued = React96.useCallback(async (eventKey, payload) => {
|
|
48186
48384
|
const normalizedEvent = normalizeEventKey(eventKey);
|
|
48187
48385
|
const bindings = traitBindingsRef.current;
|
|
@@ -48701,7 +48899,7 @@ function buildOrbitalsByTrait(schema, resolvedPages = []) {
|
|
|
48701
48899
|
// runtime/OrbPreview.tsx
|
|
48702
48900
|
init_verificationRegistry();
|
|
48703
48901
|
var PERF_NAMESPACE = "almadar:perf:canvas";
|
|
48704
|
-
var
|
|
48902
|
+
var log9 = logger.createLogger(PERF_NAMESPACE);
|
|
48705
48903
|
var RING_SIZE = 50;
|
|
48706
48904
|
var ring = [];
|
|
48707
48905
|
var writeIdx = 0;
|
|
@@ -48756,7 +48954,7 @@ function perfEnd(name, startToken, detail) {
|
|
|
48756
48954
|
}
|
|
48757
48955
|
}
|
|
48758
48956
|
push({ name, durationMs, ts: endTs, detail });
|
|
48759
|
-
|
|
48957
|
+
log9.debug(name, () => ({ durationMs, ...detail ?? {} }));
|
|
48760
48958
|
}
|
|
48761
48959
|
function perfTime(name, fn, detail) {
|
|
48762
48960
|
const t = perfStart(name);
|
|
@@ -48774,7 +48972,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
|
|
|
48774
48972
|
ts: commitTime,
|
|
48775
48973
|
detail: { baseDuration }
|
|
48776
48974
|
});
|
|
48777
|
-
|
|
48975
|
+
log9.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
|
|
48778
48976
|
};
|
|
48779
48977
|
function getPerfSnapshot() {
|
|
48780
48978
|
if (ring.length < RING_SIZE) return ring.slice();
|