@skewedaspect/sage-vue 0.10.0-beta.1 → 0.10.0-beta.2

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.
@@ -0,0 +1,13 @@
1
+ import { Ref } from 'vue';
2
+ import { BehaviorClass } from '@skewedaspect/sage';
3
+ import { GameEntityView, ViewNarrowedTo } from "./useSageEntity.d.ts";
4
+ /**
5
+ * Answers with a view for each entity carrying the tag, whatever its class, in the order the simulation
6
+ * holds them. A view stays the same object for as long as its entity is in the collection, so a `v-for`
7
+ * keyed by `id` reuses its components (design/ui.md#gameentityview).
8
+ *
9
+ * @param tag - The tag the collection re-reads on every refresh.
10
+ * @param behaviorClass - Narrows the collection to the entities holding it, typed to its state and operations.
11
+ */
12
+ export declare function useSageEntities(tag: string): Ref<readonly GameEntityView[]>;
13
+ export declare function useSageEntities<Class extends BehaviorClass>(tag: string, behaviorClass: Class): Ref<readonly ViewNarrowedTo<Class>[]>;
@@ -0,0 +1,23 @@
1
+ import { BehaviorClass, DeepReadonly, GameEntityType, OpsFor, OpsOf, StateFor, StateSliceOf } from '@skewedaspect/sage';
2
+ /** What a UI observes an entity through (design/ui.md#gameentityview). */
3
+ export interface GameEntityView<Type extends GameEntityType = GameEntityType> {
4
+ readonly id: string;
5
+ readonly present: boolean;
6
+ readonly state: DeepReadonly<StateFor<Type['behaviors']>>;
7
+ readonly ops: OpsFor<Type['behaviors']>;
8
+ hasBehavior<Class extends BehaviorClass>(behaviorClass: Class): this is ViewNarrowedTo<Class>;
9
+ }
10
+ /** A view typed to one behavior's slice of state and operations (design/ui.md#gameentityview). */
11
+ export type ViewNarrowedTo<Class extends BehaviorClass> = GameEntityView & {
12
+ readonly state: DeepReadonly<StateSliceOf<Class>>;
13
+ readonly ops: OpsOf<Class>;
14
+ };
15
+ /**
16
+ * Answers with a view of one entity, resolved by class and ID against the simulation the engine currently
17
+ * holds. The view observes as it is created and refreshes once per rendered frame, pause included
18
+ * (design/ui.md#when-a-view-observes).
19
+ *
20
+ * @param entityClass - Types the view and checks it: another class leaves the view absent.
21
+ * @param id - The entity ID the view resolves on every refresh.
22
+ */
23
+ export declare function useSageEntity<Type extends GameEntityType>(entityClass: Type, id: string): GameEntityView<Type>;
@@ -0,0 +1,39 @@
1
+ import { BehaviorClass, GameEntity, GameEntityType, GameSimulationManager } from '@skewedaspect/sage';
2
+ type Bag = Record<string, unknown>;
3
+ /** Assigns what differs, adds what appeared, removes what vanished, and leaves everything else alone. */
4
+ export declare function syncBag(target: Bag, source: Readonly<Bag>): void;
5
+ /**
6
+ * Answers with the entity that ID names in the simulation the engine currently holds, when it is of the class
7
+ * asked for. A subclass is of that class (design/ui.md#gameentityview).
8
+ *
9
+ * @param simulation
10
+ * @param entityClass
11
+ * @param id
12
+ */
13
+ export declare function resolveEntity(simulation: GameSimulationManager | null | undefined, entityClass: GameEntityType, id: string): GameEntity | undefined;
14
+ /**
15
+ * Answers with the entities carrying the tag, whatever their class, in the order the simulation holds them.
16
+ * A behavior narrows that to the ones holding it (design/ui.md#gameentityview).
17
+ *
18
+ * @param simulation
19
+ * @param tag
20
+ * @param behaviorClass
21
+ */
22
+ export declare function resolveTagged(simulation: GameSimulationManager | null | undefined, tag: string, behaviorClass?: BehaviorClass): readonly GameEntity[];
23
+ /**
24
+ * Builds the narrowing guard a view exposes. It resolves the entity on every call, so it answers from the
25
+ * entity rather than from the view's copy of state, and a view whose entity has gone holds no behavior
26
+ * (design/ui.md#gameentityview).
27
+ *
28
+ * @param find
29
+ */
30
+ export declare function buildHasBehavior(find: () => GameEntity | undefined): (behaviorClass: BehaviorClass) => boolean;
31
+ /**
32
+ * Builds the operation surface a view exposes. Every call resolves the entity again, so a view whose entity
33
+ * has gone throws rather than reaching the one it used to hold (design/ui.md#absence).
34
+ *
35
+ * @param id
36
+ * @param find
37
+ */
38
+ export declare function buildOps(id: string, find: () => GameEntity | undefined): Record<string, unknown>;
39
+ export {};
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { default as SageCanvas } from './components/sageCanvas.vue';
2
2
  export { useSageEngine } from "./composables/useSageEngine.d.ts";
3
3
  export { useSageEvent } from "./composables/useSageEvent.d.ts";
4
4
  export { useSageAction } from "./composables/useSageAction.d.ts";
5
- export { useSageState } from "./composables/useSageState.d.ts";
6
5
  export { useSageTimer } from "./composables/useSageTimer.d.ts";
6
+ export { useSageEntity } from "./composables/useSageEntity.d.ts";
7
+ export type { GameEntityView, ViewNarrowedTo } from "./composables/useSageEntity.d.ts";
8
+ export { useSageEntities } from "./composables/useSageEntities.d.ts";
7
9
  export { SAGE_ENGINE_KEY } from "./symbols.d.ts";
package/dist/sage-vue.js CHANGED
@@ -1,7 +1,7 @@
1
- import { computed as e, createElementBlock as t, createElementVNode as n, defineComponent as r, inject as i, onMounted as a, onUnmounted as o, openBlock as s, provide as c, ref as l, renderSlot as u, shallowRef as d, useTemplateRef as f, watch as p } from "vue";
2
- import { createGameEngine as m } from "@skewedaspect/sage";
1
+ import { computed as e, createElementBlock as t, createElementVNode as n, defineComponent as r, inject as i, onMounted as a, onUnmounted as o, openBlock as s, provide as c, reactive as l, ref as u, renderSlot as d, shallowRef as f, useTemplateRef as p, watch as m } from "vue";
2
+ import { createGameEngine as h } from "@skewedaspect/sage";
3
3
  //#region src/symbols.ts
4
- var h = Symbol("sage-engine"), g = { class: "sage-overlay" }, _ = /*#__PURE__*/ ((e, t) => {
4
+ var g = Symbol("sage-engine"), _ = { class: "sage-overlay" }, v = /*#__PURE__*/ ((e, t) => {
5
5
  let n = e.__vccOpts || e;
6
6
  for (let [e, r] of t) n[e] = r;
7
7
  return n;
@@ -14,35 +14,35 @@ var h = Symbol("sage-engine"), g = { class: "sage-overlay" }, _ = /*#__PURE__*/
14
14
  },
15
15
  emits: ["engine-ready", "engine-error"],
16
16
  setup(e, { emit: r }) {
17
- let i = e, p = r, _ = f("containerRef"), v = f("canvasRef"), y = d(null), b = l(!0), x = l(null), S = !1, C = null;
18
- return c(h, y), a(async () => {
17
+ let i = e, l = r, m = p("containerRef"), v = p("canvasRef"), y = f(null), b = u(!0), x = u(null), S = !1, C = null;
18
+ return c(g, y), a(async () => {
19
19
  let e = !1;
20
20
  if (o(() => {
21
21
  e = !0;
22
22
  }), v.value) try {
23
23
  let t;
24
- if (i.engine ? (t = i.engine, S = !0) : t = await m(v.value, i.entityClasses, i.options), e) {
24
+ if (i.engine ? (t = i.engine, S = !0) : t = await h(v.value, i.entityClasses, i.options), e) {
25
25
  S || (await t.stop(), t.renderEngine.dispose());
26
26
  return;
27
27
  }
28
- y.value = t, _.value && (C = new ResizeObserver(() => {
28
+ y.value = t, m.value && (C = new ResizeObserver(() => {
29
29
  t.renderEngine.resize();
30
- }), C.observe(_.value)), p("engine-ready", t), b.value = !1;
30
+ }), C.observe(m.value)), l("engine-ready", t), b.value = !1;
31
31
  } catch (t) {
32
32
  if (e) return;
33
- x.value = t, p("engine-error", t), b.value = !1;
33
+ x.value = t, l("engine-error", t), b.value = !1;
34
34
  }
35
35
  }), o(async () => {
36
36
  C &&= (C.disconnect(), null), !S && y.value && (await y.value.stop(), y.value.renderEngine.dispose());
37
37
  }), (e, r) => (s(), t("div", {
38
38
  ref_key: "containerRef",
39
- ref: _,
39
+ ref: m,
40
40
  class: "sage-container"
41
41
  }, [n("canvas", {
42
42
  ref_key: "canvasRef",
43
43
  ref: v,
44
44
  class: "sage-canvas"
45
- }, null, 512), n("div", g, [u(e.$slots, "default", {
45
+ }, null, 512), n("div", _, [d(e.$slots, "default", {
46
46
  loading: b.value,
47
47
  error: x.value,
48
48
  engine: y.value
@@ -51,8 +51,8 @@ var h = Symbol("sage-engine"), g = { class: "sage-overlay" }, _ = /*#__PURE__*/
51
51
  }), [["__scopeId", "data-v-978219c2"]]);
52
52
  //#endregion
53
53
  //#region src/composables/useSageEngine.ts
54
- function v() {
55
- let t = i(h);
54
+ function y() {
55
+ let t = i(g);
56
56
  if (!t) throw Error("useSageEngine() must be used inside a <SageCanvas> component.");
57
57
  return {
58
58
  engine: t,
@@ -61,9 +61,9 @@ function v() {
61
61
  }
62
62
  //#endregion
63
63
  //#region src/composables/useSageEvent.ts
64
- function y(e, t) {
65
- let { engine: n } = v(), r = null;
66
- p(n, (n) => {
64
+ function b(e, t) {
65
+ let { engine: n } = y(), r = null;
66
+ m(n, (n) => {
67
67
  r &&= (r(), null), n && (r = n.eventBus.subscribe(e, t));
68
68
  }, {
69
69
  immediate: !0,
@@ -74,28 +74,17 @@ function y(e, t) {
74
74
  }
75
75
  //#endregion
76
76
  //#region src/composables/useSageAction.ts
77
- function b(e) {
78
- let t = l(0);
79
- return y(`action:${e}`, (e) => {
77
+ function x(e) {
78
+ let t = u(0);
79
+ return b(`action:${e}`, (e) => {
80
80
  t.value = e.payload.value;
81
81
  }), t;
82
82
  }
83
83
  //#endregion
84
- //#region src/composables/useSageState.ts
85
- function x(e, t) {
86
- let { engine: n } = v(), r = () => {
87
- let r = n.value?.simulation?.getEntity(e);
88
- if (r) return t ? r.state[t] : { ...r.state };
89
- }, i = d(r());
90
- return y("entity:state-changed", (t) => {
91
- t.payload.entityId === e && (i.value = r());
92
- }), i;
93
- }
94
- //#endregion
95
84
  //#region src/composables/useSageTimer.ts
96
85
  function S() {
97
- let { engine: e } = v(), t = [], n = /* @__PURE__ */ new Set();
98
- p(e, (e) => {
86
+ let { engine: e } = y(), t = [], n = /* @__PURE__ */ new Set();
87
+ m(e, (e) => {
99
88
  if (!e) return;
100
89
  let t = [...n];
101
90
  n.clear();
@@ -148,6 +137,128 @@ function S() {
148
137
  };
149
138
  }
150
139
  //#endregion
151
- export { h as SAGE_ENGINE_KEY, _ as SageCanvas, b as useSageAction, v as useSageEngine, y as useSageEvent, x as useSageState, S as useSageTimer };
140
+ //#region src/composables/viewSync.ts
141
+ function C(e) {
142
+ if (Array.isArray(e)) return e.map((e) => C(e));
143
+ if (typeof e == "object" && e) {
144
+ let t = {};
145
+ for (let [n, r] of Object.entries(e)) t[n] = C(r);
146
+ return t;
147
+ }
148
+ return e;
149
+ }
150
+ function w(e, t) {
151
+ if (Array.isArray(t)) {
152
+ if (!Array.isArray(e)) return C(t);
153
+ e.length !== t.length && (e.length = t.length);
154
+ for (let [n, r] of t.entries()) e[n] = w(e[n], r);
155
+ return e;
156
+ }
157
+ if (typeof t == "object" && t) {
158
+ if (typeof e != "object" || !e || Array.isArray(e)) return C(t);
159
+ let n = e, r = t;
160
+ for (let e of Object.keys(n)) e in r || Reflect.deleteProperty(n, e);
161
+ for (let [e, t] of Object.entries(r)) {
162
+ let r = w(n[e], t);
163
+ r !== n[e] && (n[e] = r);
164
+ }
165
+ return e;
166
+ }
167
+ return t;
168
+ }
169
+ function T(e, t) {
170
+ w(e, t);
171
+ }
172
+ function E(e, t, n) {
173
+ let r = e?.getEntity(n);
174
+ if (r !== void 0 && r instanceof t) return r;
175
+ }
176
+ function D(e, t, n) {
177
+ if (e == null) return [];
178
+ let r = e.getEntitiesByTag(t);
179
+ return n === void 0 ? r : r.filter((e) => e.hasBehavior(n));
180
+ }
181
+ function O(e) {
182
+ return (t) => e()?.hasBehavior(t) ?? !1;
183
+ }
184
+ function k(e, t) {
185
+ return new Proxy({}, { get(n, r) {
186
+ return (...n) => {
187
+ let i = t();
188
+ if (i === void 0) throw Error(`No entity "${e}" to call "${String(r)}" on.`);
189
+ return i.ops[String(r)](...n);
190
+ };
191
+ } });
192
+ }
193
+ //#endregion
194
+ //#region src/composables/useSageEntity.ts
195
+ function A(e, t) {
196
+ let { engine: n } = y(), r = l({}), i = u(!1), a = () => E(n.value?.simulation, e, t), s = () => {
197
+ let e = a();
198
+ if (e === void 0) {
199
+ i.value = !1;
200
+ return;
201
+ }
202
+ i.value = !0, T(r, e.state);
203
+ }, c = k(t, a), d = O(a), f = null;
204
+ return m(n, (e) => {
205
+ f?.(), f = null, s(), e !== null && (f = e.managers.gameManager.registerFrameCallback(s, { suspendedByPause: !1 }));
206
+ }, { immediate: !0 }), o(() => {
207
+ f?.(), f = null;
208
+ }), {
209
+ id: t,
210
+ get present() {
211
+ return i.value;
212
+ },
213
+ state: r,
214
+ ops: c,
215
+ hasBehavior(e) {
216
+ return d(e);
217
+ }
218
+ };
219
+ }
220
+ //#endregion
221
+ //#region src/composables/useSageEntities.ts
222
+ function j(e, t) {
223
+ if (e.length !== t.length) return !0;
224
+ for (let n = 0; n < t.length; n++) if (e[n] !== t[n]) return !0;
225
+ return !1;
226
+ }
227
+ function M(e, t) {
228
+ let { engine: n } = y(), r = f([]), i = /* @__PURE__ */ new Map(), a = (e) => n.value?.simulation?.getEntity(e), s = (e) => {
229
+ let t = i.get(e);
230
+ if (t !== void 0) return t;
231
+ let n = l({}), r = u(!0), o = () => a(e), s = k(e, o), c = O(o), d = {
232
+ state: n,
233
+ view: {
234
+ id: e,
235
+ get present() {
236
+ return r.value;
237
+ },
238
+ state: n,
239
+ ops: s,
240
+ hasBehavior(e) {
241
+ return c(e);
242
+ }
243
+ }
244
+ };
245
+ return i.set(e, d), d;
246
+ }, c = () => {
247
+ let a = D(n.value?.simulation, e, t), o = [], c = /* @__PURE__ */ new Set();
248
+ for (let e of a) {
249
+ let t = s(e.id);
250
+ T(t.state, e.state), o.push(t.view), c.add(e.id);
251
+ }
252
+ for (let e of [...i.keys()]) c.has(e) || i.delete(e);
253
+ j(r.value, o) && (r.value = o);
254
+ }, d = null;
255
+ return m(n, (e) => {
256
+ d?.(), d = null, c(), e !== null && (d = e.managers.gameManager.registerFrameCallback(c, { suspendedByPause: !1 }));
257
+ }, { immediate: !0 }), o(() => {
258
+ d?.(), d = null;
259
+ }), r;
260
+ }
261
+ //#endregion
262
+ export { g as SAGE_ENGINE_KEY, v as SageCanvas, x as useSageAction, y as useSageEngine, M as useSageEntities, A as useSageEntity, b as useSageEvent, S as useSageTimer };
152
263
 
153
264
  //# sourceMappingURL=sage-vue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sage-vue.js","names":[],"sources":["../src/symbols.ts","../src/components/sageCanvas.vue","../src/components/sageCanvas.vue","../src/composables/useSageEngine.ts","../src/composables/useSageEvent.ts","../src/composables/useSageAction.ts","../src/composables/useSageState.ts","../src/composables/useSageTimer.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Injection Key Symbols\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { InjectionKey, ShallowRef } from 'vue';\nimport type { GameEngine } from '@skewedaspect/sage';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport const SAGE_ENGINE_KEY : InjectionKey<ShallowRef<GameEngine | null>> = Symbol('sage-engine');\n\n//----------------------------------------------------------------------------------------------------------------------\n","<!----------------------------------------------------------------------------------------------------------------------\n - SageCanvas - Vue wrapper component for the SAGE game engine\n ------------------------------------------------------------------------------------------------------------------->\n\n<template>\n <div ref=\"containerRef\" class=\"sage-container\">\n <canvas ref=\"canvasRef\" class=\"sage-canvas\" />\n <div class=\"sage-overlay\">\n <slot :loading=\"loading\" :error=\"error\" :engine=\"engineRef\" />\n </div>\n </div>\n</template>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n\n<style scoped>\n .sage-container {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n .sage-canvas {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .sage-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n\n .sage-overlay :deep(*) {\n pointer-events: auto;\n }\n</style>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n\n<script setup lang=\"ts\">\n import { onMounted, onUnmounted, provide, ref, shallowRef, useTemplateRef } from 'vue';\n import { createGameEngine } from '@skewedaspect/sage';\n import type { GameEngine, GameEntityType, SageOptions } from '@skewedaspect/sage';\n import { SAGE_ENGINE_KEY } from '../symbols.ts';\n\n //------------------------------------------------------------------------------------------------------------------\n // Props & Emits\n //------------------------------------------------------------------------------------------------------------------\n\n const props = withDefaults(defineProps<{\n entityClasses ?: Readonly<Record<string, GameEntityType>>;\n options ?: SageOptions;\n engine ?: GameEngine;\n }>(), {\n entityClasses: () => ({}),\n options: () => ({}),\n });\n\n const emit = defineEmits<{\n 'engine-ready' : [engine : GameEngine];\n 'engine-error' : [error : unknown];\n }>();\n\n //------------------------------------------------------------------------------------------------------------------\n // State\n //------------------------------------------------------------------------------------------------------------------\n\n const containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\n const engineRef = shallowRef<GameEngine | null>(null);\n const loading = ref(true);\n const error = ref<unknown>(null);\n let controlled = false;\n let resizeObserver : ResizeObserver | null = null;\n\n // Provide the engine ref synchronously so child composables can inject it\n provide(SAGE_ENGINE_KEY, engineRef);\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n onMounted(async() =>\n {\n let disposed = false;\n\n onUnmounted(() =>\n {\n disposed = true;\n });\n\n if(!canvasRef.value)\n {\n return;\n }\n\n try\n {\n let engine : GameEngine;\n\n if(props.engine)\n {\n engine = props.engine;\n controlled = true;\n }\n else\n {\n engine = await createGameEngine(canvasRef.value, props.entityClasses, props.options);\n }\n\n // The component went away while the engine was still booting, so nothing else will ever hold a\n // reference to it. Tear it down here or it keeps rendering forever.\n if(disposed)\n {\n if(!controlled)\n {\n await engine.stop();\n engine.renderEngine.dispose();\n }\n\n return;\n }\n\n engineRef.value = engine;\n\n // Set up resize observer on the container\n if(containerRef.value)\n {\n resizeObserver = new ResizeObserver(() =>\n {\n engine.renderEngine.resize();\n });\n resizeObserver.observe(containerRef.value);\n }\n\n emit('engine-ready', engine);\n loading.value = false;\n }\n catch(err)\n {\n if(disposed) { return; }\n\n error.value = err;\n emit('engine-error', err);\n loading.value = false;\n }\n });\n\n onUnmounted(async() =>\n {\n if(resizeObserver)\n {\n resizeObserver.disconnect();\n resizeObserver = null;\n }\n\n if(!controlled && engineRef.value)\n {\n await engineRef.value.stop();\n engineRef.value.renderEngine.dispose();\n }\n });\n</script>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n","<!----------------------------------------------------------------------------------------------------------------------\n - SageCanvas - Vue wrapper component for the SAGE game engine\n ------------------------------------------------------------------------------------------------------------------->\n\n<template>\n <div ref=\"containerRef\" class=\"sage-container\">\n <canvas ref=\"canvasRef\" class=\"sage-canvas\" />\n <div class=\"sage-overlay\">\n <slot :loading=\"loading\" :error=\"error\" :engine=\"engineRef\" />\n </div>\n </div>\n</template>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n\n<style scoped>\n .sage-container {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n .sage-canvas {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .sage-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n\n .sage-overlay :deep(*) {\n pointer-events: auto;\n }\n</style>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n\n<script setup lang=\"ts\">\n import { onMounted, onUnmounted, provide, ref, shallowRef, useTemplateRef } from 'vue';\n import { createGameEngine } from '@skewedaspect/sage';\n import type { GameEngine, GameEntityType, SageOptions } from '@skewedaspect/sage';\n import { SAGE_ENGINE_KEY } from '../symbols.ts';\n\n //------------------------------------------------------------------------------------------------------------------\n // Props & Emits\n //------------------------------------------------------------------------------------------------------------------\n\n const props = withDefaults(defineProps<{\n entityClasses ?: Readonly<Record<string, GameEntityType>>;\n options ?: SageOptions;\n engine ?: GameEngine;\n }>(), {\n entityClasses: () => ({}),\n options: () => ({}),\n });\n\n const emit = defineEmits<{\n 'engine-ready' : [engine : GameEngine];\n 'engine-error' : [error : unknown];\n }>();\n\n //------------------------------------------------------------------------------------------------------------------\n // State\n //------------------------------------------------------------------------------------------------------------------\n\n const containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\n const engineRef = shallowRef<GameEngine | null>(null);\n const loading = ref(true);\n const error = ref<unknown>(null);\n let controlled = false;\n let resizeObserver : ResizeObserver | null = null;\n\n // Provide the engine ref synchronously so child composables can inject it\n provide(SAGE_ENGINE_KEY, engineRef);\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n onMounted(async() =>\n {\n let disposed = false;\n\n onUnmounted(() =>\n {\n disposed = true;\n });\n\n if(!canvasRef.value)\n {\n return;\n }\n\n try\n {\n let engine : GameEngine;\n\n if(props.engine)\n {\n engine = props.engine;\n controlled = true;\n }\n else\n {\n engine = await createGameEngine(canvasRef.value, props.entityClasses, props.options);\n }\n\n // The component went away while the engine was still booting, so nothing else will ever hold a\n // reference to it. Tear it down here or it keeps rendering forever.\n if(disposed)\n {\n if(!controlled)\n {\n await engine.stop();\n engine.renderEngine.dispose();\n }\n\n return;\n }\n\n engineRef.value = engine;\n\n // Set up resize observer on the container\n if(containerRef.value)\n {\n resizeObserver = new ResizeObserver(() =>\n {\n engine.renderEngine.resize();\n });\n resizeObserver.observe(containerRef.value);\n }\n\n emit('engine-ready', engine);\n loading.value = false;\n }\n catch(err)\n {\n if(disposed) { return; }\n\n error.value = err;\n emit('engine-error', err);\n loading.value = false;\n }\n });\n\n onUnmounted(async() =>\n {\n if(resizeObserver)\n {\n resizeObserver.disconnect();\n resizeObserver = null;\n }\n\n if(!controlled && engineRef.value)\n {\n await engineRef.value.stop();\n engineRef.value.renderEngine.dispose();\n }\n });\n</script>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageEngine Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type ComputedRef, type ShallowRef, computed, inject } from 'vue';\nimport type { GameEngine, GameEventBus } from '@skewedaspect/sage';\nimport { SAGE_ENGINE_KEY } from '../symbols.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface SageEngineContext\n{\n engine : ShallowRef<GameEngine | null>;\n eventBus : ComputedRef<GameEventBus | null>;\n}\n\nexport function useSageEngine() : SageEngineContext\n{\n const engine = inject(SAGE_ENGINE_KEY);\n if(!engine)\n {\n throw new Error('useSageEngine() must be used inside a <SageCanvas> component.');\n }\n\n return {\n engine,\n eventBus: computed(() => engine.value?.eventBus ?? null),\n };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageEvent Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { onUnmounted, watch } from 'vue';\nimport type { GameEvent, LibraryEventPayloadMap, Unsubscribe } from '@skewedaspect/sage';\nimport { useSageEngine } from './useSageEngine.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype EventHandler = (event : GameEvent<string, LibraryEventPayloadMap>) => void | Promise<void>;\n\nexport function useSageEvent(pattern : string | RegExp, handler : EventHandler) : void\n{\n const { engine } = useSageEngine();\n let unsub : Unsubscribe | null = null;\n\n // Sync flush, not the default 'pre': SageCanvas assigns the engine and emits engine-ready in one turn, so a\n // deferred watcher would miss anything published in that window.\n watch(engine, (eng) =>\n {\n if(unsub)\n {\n unsub();\n unsub = null;\n }\n\n if(eng)\n {\n // subscribe() sends an exact type to the bus's direct map and anything wildcard-ish to the pattern list.\n unsub = eng.eventBus.subscribe(pattern, handler);\n }\n }, { immediate: true, flush: 'sync' });\n\n onUnmounted(() =>\n {\n if(unsub)\n {\n unsub();\n unsub = null;\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageAction Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Ref, ref } from 'vue';\nimport { useSageEvent } from './useSageEvent.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function useSageAction(actionName : string) : Ref<boolean | number>\n{\n const value = ref<boolean | number>(0);\n\n useSageEvent(`action:${ actionName }`, (event) =>\n {\n value.value = (event.payload as { value : boolean | number }).value;\n });\n\n return value;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageState Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Ref, shallowRef } from 'vue';\nimport type { EntityStateChangedPayload } from '@skewedaspect/sage';\nimport { useSageEngine } from './useSageEngine.ts';\nimport { useSageEvent } from './useSageEvent.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Returns a reactive ref holding a copy of an entity's state, refreshed whenever the entity announces a change. If\n * `key` is provided, tracks only that property; otherwise tracks a shallow copy of the whole state object.\n *\n * The ref is a copy, not a window onto the entity: writing through it never reaches the entity, and edits made to the\n * entity only show up once it publishes `entity:state-changed`. Nested objects are shared with the entity, so reach\n * into them at your own risk.\n *\n * No 0.10 entity or behavior emits `entity:state-changed` -- the design states plainly that writing a state field\n * notifies nobody. The initial read below still works; the refresh on that event does not fire until something\n * in a game emits it itself.\n */\nexport function useSageState<T = unknown>(entityID : string, key ?: string) : Ref<T>\n{\n const { engine } = useSageEngine();\n\n // Snapshot the entity's state (if it exists yet)\n const readState = () : T =>\n {\n const entity = engine.value?.simulation?.getEntity(entityID);\n if(!entity)\n {\n return undefined as T;\n }\n\n if(key)\n {\n return (entity.state as Record<string, unknown>)[key] as T;\n }\n\n return { ...entity.state } as T;\n };\n\n const state = shallowRef<T>(readState()) as Ref<T>;\n\n useSageEvent('entity:state-changed', (event) =>\n {\n const payload = event.payload as EntityStateChangedPayload;\n if(payload.entityId !== entityID)\n {\n return;\n }\n\n // Re-read from the entity to get the authoritative value\n state.value = readState();\n });\n\n return state;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageTimer Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { onUnmounted, watch } from 'vue';\nimport type { CooldownHandle, GameEngine } from '@skewedaspect/sage';\nimport { useSageEngine } from './useSageEngine.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface SageTimerComposable\n{\n delay : (ms : number, callback : () => void) => () => void;\n interval : (ms : number, callback : () => void) => () => void;\n cooldown : (ms : number) => CooldownHandle;\n}\n\n/** A registration waiting for the engine to boot. */\ntype PendingTimer = (engine : GameEngine) => void;\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Returns pause-aware timer functions (delay, interval, cooldown) from the game engine. Timers requested before the\n * engine has booted are queued and started as soon as it does, so a component can set them up in `setup()`. All active\n * timers are cancelled when the component unmounts.\n */\nexport function useSageTimer() : SageTimerComposable\n{\n const { engine } = useSageEngine();\n const cancellers : (() => void)[] = [];\n const pending = new Set<PendingTimer>();\n\n // Sync flush so queued timers start the moment the engine lands, rather than a scheduler tick later.\n watch(engine, (eng) =>\n {\n if(!eng)\n {\n return;\n }\n\n const queued = [ ...pending ];\n pending.clear();\n\n for(const start of queued)\n {\n start(eng);\n }\n }, { immediate: true, flush: 'sync' });\n\n onUnmounted(() =>\n {\n pending.clear();\n\n for(const cancel of cancellers)\n {\n cancel();\n }\n\n cancellers.length = 0;\n });\n\n function release(cancel : () => void) : void\n {\n cancel();\n\n const idx = cancellers.indexOf(cancel);\n if(idx >= 0)\n {\n cancellers.splice(idx, 1);\n }\n }\n\n /**\n * Runs `start` against the engine now, or queues it until the engine boots. The returned canceller covers both\n * cases: called while still queued, it drops the registration so it never starts at all.\n */\n function schedule(start : (eng : GameEngine) => () => void) : () => void\n {\n let cancel : (() => void) | null = null;\n\n const begin = (eng : GameEngine) : void =>\n {\n cancel = start(eng);\n cancellers.push(cancel);\n };\n\n if(engine.value)\n {\n begin(engine.value);\n }\n else\n {\n pending.add(begin);\n }\n\n return () =>\n {\n pending.delete(begin);\n\n if(cancel)\n {\n release(cancel);\n cancel = null;\n }\n };\n }\n\n function delay(ms : number, callback : () => void) : () => void\n {\n return schedule((eng) => eng.timer.delay(ms, callback));\n }\n\n function interval(ms : number, callback : () => void) : () => void\n {\n return schedule((eng) => eng.timer.interval(ms, callback));\n }\n\n /**\n * Cooldowns are passive pollable objects with nothing to cancel, so they aren't tracked for unmount. Before the\n * engine boots the caller gets a stand-in that forwards to the real handle once there is one; a `reset()` landing\n * in the gap is replayed against it.\n */\n function cooldown(ms : number) : CooldownHandle\n {\n if(engine.value)\n {\n return engine.value.timer.cooldown(ms);\n }\n\n let handle : CooldownHandle | null = null;\n let resetWhileWaiting = false;\n\n pending.add((eng) =>\n {\n handle = eng.timer.cooldown(ms);\n\n if(resetWhileWaiting)\n {\n handle.reset();\n resetWhileWaiting = false;\n }\n });\n\n return {\n get ready() : boolean\n {\n return handle ? handle.ready : false;\n },\n reset() : void\n {\n if(handle)\n {\n handle.reset();\n }\n else\n {\n resetWhileWaiting = true;\n }\n },\n };\n }\n\n return { delay, interval, cooldown };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n"],"mappings":"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"}
1
+ {"version":3,"file":"sage-vue.js","names":[],"sources":["../src/symbols.ts","../src/components/sageCanvas.vue","../src/components/sageCanvas.vue","../src/composables/useSageEngine.ts","../src/composables/useSageEvent.ts","../src/composables/useSageAction.ts","../src/composables/useSageTimer.ts","../src/composables/viewSync.ts","../src/composables/useSageEntity.ts","../src/composables/useSageEntities.ts"],"sourcesContent":["//----------------------------------------------------------------------------------------------------------------------\n// Injection Key Symbols\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type { InjectionKey, ShallowRef } from 'vue';\nimport type { GameEngine } from '@skewedaspect/sage';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport const SAGE_ENGINE_KEY : InjectionKey<ShallowRef<GameEngine | null>> = Symbol('sage-engine');\n\n//----------------------------------------------------------------------------------------------------------------------\n","<!----------------------------------------------------------------------------------------------------------------------\n - SageCanvas - Vue wrapper component for the SAGE game engine\n ------------------------------------------------------------------------------------------------------------------->\n\n<template>\n <div ref=\"containerRef\" class=\"sage-container\">\n <canvas ref=\"canvasRef\" class=\"sage-canvas\" />\n <div class=\"sage-overlay\">\n <slot :loading=\"loading\" :error=\"error\" :engine=\"engineRef\" />\n </div>\n </div>\n</template>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n\n<style scoped>\n .sage-container {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n .sage-canvas {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .sage-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n\n .sage-overlay :deep(*) {\n pointer-events: auto;\n }\n</style>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n\n<script setup lang=\"ts\">\n import { onMounted, onUnmounted, provide, ref, shallowRef, useTemplateRef } from 'vue';\n import { createGameEngine } from '@skewedaspect/sage';\n import type { GameEngine, GameEntityType, SageOptions } from '@skewedaspect/sage';\n import { SAGE_ENGINE_KEY } from '../symbols.ts';\n\n //------------------------------------------------------------------------------------------------------------------\n // Props & Emits\n //------------------------------------------------------------------------------------------------------------------\n\n const props = withDefaults(defineProps<{\n entityClasses ?: Readonly<Record<string, GameEntityType>>;\n options ?: SageOptions;\n engine ?: GameEngine;\n }>(), {\n entityClasses: () => ({}),\n options: () => ({}),\n });\n\n const emit = defineEmits<{\n 'engine-ready' : [engine : GameEngine];\n 'engine-error' : [error : unknown];\n }>();\n\n //------------------------------------------------------------------------------------------------------------------\n // State\n //------------------------------------------------------------------------------------------------------------------\n\n const containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\n const engineRef = shallowRef<GameEngine | null>(null);\n const loading = ref(true);\n const error = ref<unknown>(null);\n let controlled = false;\n let resizeObserver : ResizeObserver | null = null;\n\n // Provide the engine ref synchronously so child composables can inject it\n provide(SAGE_ENGINE_KEY, engineRef);\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n onMounted(async() =>\n {\n let disposed = false;\n\n onUnmounted(() =>\n {\n disposed = true;\n });\n\n if(!canvasRef.value)\n {\n return;\n }\n\n try\n {\n let engine : GameEngine;\n\n if(props.engine)\n {\n engine = props.engine;\n controlled = true;\n }\n else\n {\n engine = await createGameEngine(canvasRef.value, props.entityClasses, props.options);\n }\n\n // The component went away while the engine was still booting, so nothing else will ever hold a\n // reference to it. Tear it down here or it keeps rendering forever.\n if(disposed)\n {\n if(!controlled)\n {\n await engine.stop();\n engine.renderEngine.dispose();\n }\n\n return;\n }\n\n engineRef.value = engine;\n\n // Set up resize observer on the container\n if(containerRef.value)\n {\n resizeObserver = new ResizeObserver(() =>\n {\n engine.renderEngine.resize();\n });\n resizeObserver.observe(containerRef.value);\n }\n\n emit('engine-ready', engine);\n loading.value = false;\n }\n catch(err)\n {\n if(disposed) { return; }\n\n error.value = err;\n emit('engine-error', err);\n loading.value = false;\n }\n });\n\n onUnmounted(async() =>\n {\n if(resizeObserver)\n {\n resizeObserver.disconnect();\n resizeObserver = null;\n }\n\n if(!controlled && engineRef.value)\n {\n await engineRef.value.stop();\n engineRef.value.renderEngine.dispose();\n }\n });\n</script>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n","<!----------------------------------------------------------------------------------------------------------------------\n - SageCanvas - Vue wrapper component for the SAGE game engine\n ------------------------------------------------------------------------------------------------------------------->\n\n<template>\n <div ref=\"containerRef\" class=\"sage-container\">\n <canvas ref=\"canvasRef\" class=\"sage-canvas\" />\n <div class=\"sage-overlay\">\n <slot :loading=\"loading\" :error=\"error\" :engine=\"engineRef\" />\n </div>\n </div>\n</template>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n\n<style scoped>\n .sage-container {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n .sage-canvas {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .sage-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n }\n\n .sage-overlay :deep(*) {\n pointer-events: auto;\n }\n</style>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n\n<script setup lang=\"ts\">\n import { onMounted, onUnmounted, provide, ref, shallowRef, useTemplateRef } from 'vue';\n import { createGameEngine } from '@skewedaspect/sage';\n import type { GameEngine, GameEntityType, SageOptions } from '@skewedaspect/sage';\n import { SAGE_ENGINE_KEY } from '../symbols.ts';\n\n //------------------------------------------------------------------------------------------------------------------\n // Props & Emits\n //------------------------------------------------------------------------------------------------------------------\n\n const props = withDefaults(defineProps<{\n entityClasses ?: Readonly<Record<string, GameEntityType>>;\n options ?: SageOptions;\n engine ?: GameEngine;\n }>(), {\n entityClasses: () => ({}),\n options: () => ({}),\n });\n\n const emit = defineEmits<{\n 'engine-ready' : [engine : GameEngine];\n 'engine-error' : [error : unknown];\n }>();\n\n //------------------------------------------------------------------------------------------------------------------\n // State\n //------------------------------------------------------------------------------------------------------------------\n\n const containerRef = useTemplateRef<HTMLDivElement>('containerRef');\n const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');\n const engineRef = shallowRef<GameEngine | null>(null);\n const loading = ref(true);\n const error = ref<unknown>(null);\n let controlled = false;\n let resizeObserver : ResizeObserver | null = null;\n\n // Provide the engine ref synchronously so child composables can inject it\n provide(SAGE_ENGINE_KEY, engineRef);\n\n //------------------------------------------------------------------------------------------------------------------\n // Lifecycle\n //------------------------------------------------------------------------------------------------------------------\n\n onMounted(async() =>\n {\n let disposed = false;\n\n onUnmounted(() =>\n {\n disposed = true;\n });\n\n if(!canvasRef.value)\n {\n return;\n }\n\n try\n {\n let engine : GameEngine;\n\n if(props.engine)\n {\n engine = props.engine;\n controlled = true;\n }\n else\n {\n engine = await createGameEngine(canvasRef.value, props.entityClasses, props.options);\n }\n\n // The component went away while the engine was still booting, so nothing else will ever hold a\n // reference to it. Tear it down here or it keeps rendering forever.\n if(disposed)\n {\n if(!controlled)\n {\n await engine.stop();\n engine.renderEngine.dispose();\n }\n\n return;\n }\n\n engineRef.value = engine;\n\n // Set up resize observer on the container\n if(containerRef.value)\n {\n resizeObserver = new ResizeObserver(() =>\n {\n engine.renderEngine.resize();\n });\n resizeObserver.observe(containerRef.value);\n }\n\n emit('engine-ready', engine);\n loading.value = false;\n }\n catch(err)\n {\n if(disposed) { return; }\n\n error.value = err;\n emit('engine-error', err);\n loading.value = false;\n }\n });\n\n onUnmounted(async() =>\n {\n if(resizeObserver)\n {\n resizeObserver.disconnect();\n resizeObserver = null;\n }\n\n if(!controlled && engineRef.value)\n {\n await engineRef.value.stop();\n engineRef.value.renderEngine.dispose();\n }\n });\n</script>\n\n<!--------------------------------------------------------------------------------------------------------------------->\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageEngine Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type ComputedRef, type ShallowRef, computed, inject } from 'vue';\nimport type { GameEngine, GameEventBus } from '@skewedaspect/sage';\nimport { SAGE_ENGINE_KEY } from '../symbols.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface SageEngineContext\n{\n engine : ShallowRef<GameEngine | null>;\n eventBus : ComputedRef<GameEventBus | null>;\n}\n\nexport function useSageEngine() : SageEngineContext\n{\n const engine = inject(SAGE_ENGINE_KEY);\n if(!engine)\n {\n throw new Error('useSageEngine() must be used inside a <SageCanvas> component.');\n }\n\n return {\n engine,\n eventBus: computed(() => engine.value?.eventBus ?? null),\n };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageEvent Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { onUnmounted, watch } from 'vue';\nimport type { GameEvent, LibraryEventPayloadMap, Unsubscribe } from '@skewedaspect/sage';\nimport { useSageEngine } from './useSageEngine.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype EventHandler = (event : GameEvent<string, LibraryEventPayloadMap>) => void | Promise<void>;\n\nexport function useSageEvent(pattern : string | RegExp, handler : EventHandler) : void\n{\n const { engine } = useSageEngine();\n let unsub : Unsubscribe | null = null;\n\n // Sync flush, not the default 'pre': SageCanvas assigns the engine and emits engine-ready in one turn, so a\n // deferred watcher would miss anything published in that window.\n watch(engine, (eng) =>\n {\n if(unsub)\n {\n unsub();\n unsub = null;\n }\n\n if(eng)\n {\n // subscribe() sends an exact type to the bus's direct map and anything wildcard-ish to the pattern list.\n unsub = eng.eventBus.subscribe(pattern, handler);\n }\n }, { immediate: true, flush: 'sync' });\n\n onUnmounted(() =>\n {\n if(unsub)\n {\n unsub();\n unsub = null;\n }\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageAction Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Ref, ref } from 'vue';\nimport { useSageEvent } from './useSageEvent.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\nexport function useSageAction(actionName : string) : Ref<boolean | number>\n{\n const value = ref<boolean | number>(0);\n\n useSageEvent(`action:${ actionName }`, (event) =>\n {\n value.value = (event.payload as { value : boolean | number }).value;\n });\n\n return value;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageTimer Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { onUnmounted, watch } from 'vue';\nimport type { CooldownHandle, GameEngine } from '@skewedaspect/sage';\nimport { useSageEngine } from './useSageEngine.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface SageTimerComposable\n{\n delay : (ms : number, callback : () => void) => () => void;\n interval : (ms : number, callback : () => void) => () => void;\n cooldown : (ms : number) => CooldownHandle;\n}\n\n/** A registration waiting for the engine to boot. */\ntype PendingTimer = (engine : GameEngine) => void;\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Returns pause-aware timer functions (delay, interval, cooldown) from the game engine. Timers requested before the\n * engine has booted are queued and started as soon as it does, so a component can set them up in `setup()`. All active\n * timers are cancelled when the component unmounts.\n */\nexport function useSageTimer() : SageTimerComposable\n{\n const { engine } = useSageEngine();\n const cancellers : (() => void)[] = [];\n const pending = new Set<PendingTimer>();\n\n // Sync flush so queued timers start the moment the engine lands, rather than a scheduler tick later.\n watch(engine, (eng) =>\n {\n if(!eng)\n {\n return;\n }\n\n const queued = [ ...pending ];\n pending.clear();\n\n for(const start of queued)\n {\n start(eng);\n }\n }, { immediate: true, flush: 'sync' });\n\n onUnmounted(() =>\n {\n pending.clear();\n\n for(const cancel of cancellers)\n {\n cancel();\n }\n\n cancellers.length = 0;\n });\n\n function release(cancel : () => void) : void\n {\n cancel();\n\n const idx = cancellers.indexOf(cancel);\n if(idx >= 0)\n {\n cancellers.splice(idx, 1);\n }\n }\n\n /**\n * Runs `start` against the engine now, or queues it until the engine boots. The returned canceller covers both\n * cases: called while still queued, it drops the registration so it never starts at all.\n */\n function schedule(start : (eng : GameEngine) => () => void) : () => void\n {\n let cancel : (() => void) | null = null;\n\n const begin = (eng : GameEngine) : void =>\n {\n cancel = start(eng);\n cancellers.push(cancel);\n };\n\n if(engine.value)\n {\n begin(engine.value);\n }\n else\n {\n pending.add(begin);\n }\n\n return () =>\n {\n pending.delete(begin);\n\n if(cancel)\n {\n release(cancel);\n cancel = null;\n }\n };\n }\n\n function delay(ms : number, callback : () => void) : () => void\n {\n return schedule((eng) => eng.timer.delay(ms, callback));\n }\n\n function interval(ms : number, callback : () => void) : () => void\n {\n return schedule((eng) => eng.timer.interval(ms, callback));\n }\n\n /**\n * Cooldowns are passive pollable objects with nothing to cancel, so they aren't tracked for unmount. Before the\n * engine boots the caller gets a stand-in that forwards to the real handle once there is one; a `reset()` landing\n * in the gap is replayed against it.\n */\n function cooldown(ms : number) : CooldownHandle\n {\n if(engine.value)\n {\n return engine.value.timer.cooldown(ms);\n }\n\n let handle : CooldownHandle | null = null;\n let resetWhileWaiting = false;\n\n pending.add((eng) =>\n {\n handle = eng.timer.cooldown(ms);\n\n if(resetWhileWaiting)\n {\n handle.reset();\n resetWhileWaiting = false;\n }\n });\n\n return {\n get ready() : boolean\n {\n return handle ? handle.ready : false;\n },\n reset() : void\n {\n if(handle)\n {\n handle.reset();\n }\n else\n {\n resetWhileWaiting = true;\n }\n },\n };\n }\n\n return { delay, interval, cooldown };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// View Synchronisation\n//\n// The walk a view refreshes by, and the resolution and operation surface both composables share\n// (design/ui.md#how-a-view-notices-a-change).\n//----------------------------------------------------------------------------------------------------------------------\n\nimport type {\n BehaviorClass, GameEntity, GameEntityType, GameSimulationManager,\n} from '@skewedaspect/sage';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype Bag = Record<string, unknown>;\n\n/** State is finite JSON-compatible data, so a copy needs nothing exotic (design/entities.md#state). */\nfunction copyValue(value : unknown) : unknown\n{\n if(Array.isArray(value))\n {\n return value.map((item) => copyValue(item));\n }\n\n if(value !== null && typeof value === 'object')\n {\n const copy : Bag = {};\n\n for(const [ key, field ] of Object.entries(value as Bag))\n {\n copy[key] = copyValue(field);\n }\n\n return copy;\n }\n\n return value;\n}\n\n/**\n * Answers with what the view should hold for one value: the target itself once it has been brought into\n * line, or a copy when the target cannot be reused. A view never holds an object entity state also holds, so\n * a later change inside that object is one the walk can still see.\n */\nfunction syncValue(current : unknown, next : unknown) : unknown\n{\n if(Array.isArray(next))\n {\n if(!Array.isArray(current))\n {\n return copyValue(next);\n }\n\n if(current.length !== next.length)\n {\n current.length = next.length;\n }\n\n for(const [ index, item ] of next.entries())\n {\n current[index] = syncValue(current[index], item);\n }\n\n return current;\n }\n\n if(next !== null && typeof next === 'object')\n {\n if(current === null || typeof current !== 'object' || Array.isArray(current))\n {\n return copyValue(next);\n }\n\n const target = current as Bag;\n const source = next as Bag;\n\n for(const key of Object.keys(target))\n {\n if(!(key in source))\n {\n Reflect.deleteProperty(target, key);\n }\n }\n\n for(const [ key, field ] of Object.entries(source))\n {\n const settled = syncValue(target[key], field);\n\n if(settled !== target[key])\n {\n target[key] = settled;\n }\n }\n\n return current;\n }\n\n return next;\n}\n\n/** Assigns what differs, adds what appeared, removes what vanished, and leaves everything else alone. */\nexport function syncBag(target : Bag, source : Readonly<Bag>) : void\n{\n syncValue(target, source);\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype EntityConstructor = abstract new (...args : never[]) => GameEntity;\n\n/**\n * Answers with the entity that ID names in the simulation the engine currently holds, when it is of the class\n * asked for. A subclass is of that class (design/ui.md#gameentityview).\n *\n * @param simulation\n * @param entityClass\n * @param id\n */\nexport function resolveEntity(\n simulation : GameSimulationManager | null | undefined,\n entityClass : GameEntityType,\n id : string\n) : GameEntity | undefined\n{\n const entity = simulation?.getEntity(id);\n\n if(entity === undefined)\n {\n return undefined;\n }\n\n if(!(entity instanceof (entityClass as unknown as EntityConstructor)))\n {\n return undefined;\n }\n\n return entity;\n}\n\n/**\n * Answers with the entities carrying the tag, whatever their class, in the order the simulation holds them.\n * A behavior narrows that to the ones holding it (design/ui.md#gameentityview).\n *\n * @param simulation\n * @param tag\n * @param behaviorClass\n */\nexport function resolveTagged(\n simulation : GameSimulationManager | null | undefined,\n tag : string,\n behaviorClass ?: BehaviorClass\n) : readonly GameEntity[]\n{\n if(simulation === null || simulation === undefined)\n {\n return [];\n }\n\n const tagged = simulation.getEntitiesByTag(tag);\n\n if(behaviorClass === undefined)\n {\n return tagged;\n }\n\n return tagged.filter((entity) => entity.hasBehavior(behaviorClass));\n}\n\n/**\n * Builds the narrowing guard a view exposes. It resolves the entity on every call, so it answers from the\n * entity rather than from the view's copy of state, and a view whose entity has gone holds no behavior\n * (design/ui.md#gameentityview).\n *\n * @param find\n */\nexport function buildHasBehavior(\n find : () => GameEntity | undefined\n) : (behaviorClass : BehaviorClass) => boolean\n{\n return (behaviorClass) =>\n {\n return find()?.hasBehavior(behaviorClass) ?? false;\n };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Builds the operation surface a view exposes. Every call resolves the entity again, so a view whose entity\n * has gone throws rather than reaching the one it used to hold (design/ui.md#absence).\n *\n * @param id\n * @param find\n */\nexport function buildOps(id : string, find : () => GameEntity | undefined) : Record<string, unknown>\n{\n return new Proxy({}, {\n get(_target : object, name : string | symbol) : unknown\n {\n return (...args : unknown[]) : unknown =>\n {\n const entity = find();\n\n if(entity === undefined)\n {\n throw new Error(`No entity \"${ id }\" to call \"${ String(name) }\" on.`);\n }\n\n const ops = entity.ops as Record<string, (...called : unknown[]) => unknown>;\n\n return ops[String(name)](...args);\n };\n },\n });\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageEntity Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { onUnmounted, reactive, ref, watch } from 'vue';\nimport type {\n BehaviorClass, DeepReadonly, GameEntityType, OpsFor, OpsOf, StateFor, StateSliceOf,\n} from '@skewedaspect/sage';\n\nimport { useSageEngine } from './useSageEngine.ts';\nimport { buildHasBehavior, buildOps, resolveEntity, syncBag } from './viewSync.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/** What a UI observes an entity through (design/ui.md#gameentityview). */\nexport interface GameEntityView<Type extends GameEntityType = GameEntityType>\n{\n readonly id : string;\n readonly present : boolean;\n readonly state : DeepReadonly<StateFor<Type['behaviors']>>;\n readonly ops : OpsFor<Type['behaviors']>;\n\n hasBehavior<Class extends BehaviorClass>(behaviorClass : Class) : this is ViewNarrowedTo<Class>;\n}\n\n/** A view typed to one behavior's slice of state and operations (design/ui.md#gameentityview). */\nexport type ViewNarrowedTo<Class extends BehaviorClass> = GameEntityView & {\n readonly state : DeepReadonly<StateSliceOf<Class>>;\n readonly ops : OpsOf<Class>;\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Answers with a view of one entity, resolved by class and ID against the simulation the engine currently\n * holds. The view observes as it is created and refreshes once per rendered frame, pause included\n * (design/ui.md#when-a-view-observes).\n *\n * @param entityClass - Types the view and checks it: another class leaves the view absent.\n * @param id - The entity ID the view resolves on every refresh.\n */\nexport function useSageEntity<Type extends GameEntityType>(\n entityClass : Type,\n id : string\n) : GameEntityView<Type>\n{\n const { engine } = useSageEngine();\n\n const state = reactive({}) as Record<string, unknown>;\n const present = ref(false);\n\n const find = () : ReturnType<typeof resolveEntity> =>\n {\n return resolveEntity(engine.value?.simulation, entityClass, id);\n };\n\n const refresh = () : void =>\n {\n const entity = find();\n\n if(entity === undefined)\n {\n // The values already read stay, so a template reads a field without guarding each one\n // (design/ui.md#absence).\n present.value = false;\n return;\n }\n\n present.value = true;\n syncBag(state, entity.state as Record<string, unknown>);\n };\n\n const ops = buildOps(id, find);\n const hasBehavior = buildHasBehavior(find);\n\n let unregister : (() => void) | null = null;\n\n watch(engine, (current) =>\n {\n unregister?.();\n unregister = null;\n\n refresh();\n\n if(current !== null)\n {\n unregister = current.managers.gameManager.registerFrameCallback(refresh, { suspendedByPause: false });\n }\n }, { immediate: true });\n\n onUnmounted(() =>\n {\n unregister?.();\n unregister = null;\n });\n\n return {\n id,\n get present() : boolean { return present.value; },\n state: state as DeepReadonly<StateFor<Type['behaviors']>>,\n ops: ops as OpsFor<Type['behaviors']>,\n hasBehavior<Class extends BehaviorClass>(behaviorClass : Class) : this is ViewNarrowedTo<Class>\n {\n return hasBehavior(behaviorClass);\n },\n };\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n","//----------------------------------------------------------------------------------------------------------------------\n// useSageEntities Composable\n//----------------------------------------------------------------------------------------------------------------------\n\nimport { type Ref, onUnmounted, reactive, ref, shallowRef, watch } from 'vue';\nimport type {\n BehaviorClass, DeepReadonly, GameEntity, GameEntityType, OpsFor, StateFor,\n} from '@skewedaspect/sage';\n\nimport { useSageEngine } from './useSageEngine.ts';\nimport type { GameEntityView, ViewNarrowedTo } from './useSageEntity.ts';\nimport { buildHasBehavior, buildOps, resolveTagged, syncBag } from './viewSync.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ninterface Held\n{\n view : GameEntityView;\n state : Record<string, unknown>;\n}\n\n// The array is replaced only when the collection's membership or its order changed, so a UI reading the\n// collection re-renders for a join, a departure, or a reorder and not once a frame\n// (design/ui.md#how-a-view-notices-a-change).\nfunction membershipChanged(current : readonly GameEntityView[], next : readonly GameEntityView[]) : boolean\n{\n if(current.length !== next.length)\n {\n return true;\n }\n\n for(let index = 0; index < next.length; index++)\n {\n if(current[index] !== next[index])\n {\n return true;\n }\n }\n\n return false;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Answers with a view for each entity carrying the tag, whatever its class, in the order the simulation\n * holds them. A view stays the same object for as long as its entity is in the collection, so a `v-for`\n * keyed by `id` reuses its components (design/ui.md#gameentityview).\n *\n * @param tag - The tag the collection re-reads on every refresh.\n * @param behaviorClass - Narrows the collection to the entities holding it, typed to its state and operations.\n */\nexport function useSageEntities(tag : string) : Ref<readonly GameEntityView[]>;\nexport function useSageEntities<Class extends BehaviorClass>(\n tag : string,\n behaviorClass : Class\n) : Ref<readonly ViewNarrowedTo<Class>[]>;\nexport function useSageEntities(\n tag : string,\n behaviorClass ?: BehaviorClass\n) : Ref<readonly GameEntityView[]>\n{\n const { engine } = useSageEngine();\n\n const views = shallowRef<readonly GameEntityView[]>([]);\n const held = new Map<string, Held>();\n\n const findEntity = (id : string) : GameEntity | undefined =>\n {\n return engine.value?.simulation?.getEntity(id);\n };\n\n const viewFor = (id : string) : Held =>\n {\n const existing = held.get(id);\n\n if(existing !== undefined)\n {\n return existing;\n }\n\n const state = reactive({}) as Record<string, unknown>;\n const present = ref(true);\n const find = () : GameEntity | undefined => findEntity(id);\n const ops = buildOps(id, find);\n const hasBehavior = buildHasBehavior(find);\n\n const record : Held = {\n state,\n view: {\n id,\n get present() : boolean { return present.value; },\n state: state as DeepReadonly<StateFor<GameEntityType['behaviors']>>,\n ops: ops as OpsFor<GameEntityType['behaviors']>,\n hasBehavior<Class extends BehaviorClass>(named : Class) : this is ViewNarrowedTo<Class>\n {\n return hasBehavior(named);\n },\n },\n };\n\n held.set(id, record);\n\n return record;\n };\n\n const refresh = () : void =>\n {\n const matched = resolveTagged(engine.value?.simulation, tag, behaviorClass);\n const next : GameEntityView[] = [];\n const seen = new Set<string>();\n\n for(const entity of matched)\n {\n const record = viewFor(entity.id);\n\n syncBag(record.state, entity.state as Record<string, unknown>);\n next.push(record.view);\n seen.add(entity.id);\n }\n\n for(const id of [ ...held.keys() ])\n {\n if(!seen.has(id))\n {\n held.delete(id);\n }\n }\n\n if(membershipChanged(views.value, next))\n {\n views.value = next;\n }\n };\n\n let unregister : (() => void) | null = null;\n\n watch(engine, (current) =>\n {\n unregister?.();\n unregister = null;\n\n refresh();\n\n if(current !== null)\n {\n unregister = current.managers.gameManager.registerFrameCallback(refresh, { suspendedByPause: false });\n }\n }, { immediate: true });\n\n onUnmounted(() =>\n {\n unregister?.();\n unregister = null;\n });\n\n return views;\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skewedaspect/sage-vue",
3
- "version": "0.10.0-beta.1",
3
+ "version": "0.10.0-beta.2",
4
4
  "type": "module",
5
5
  "main": "dist/sage-vue.js",
6
6
  "module": "dist/sage-vue.js",
@@ -1,14 +0,0 @@
1
- import { Ref } from 'vue';
2
- /**
3
- * Returns a reactive ref holding a copy of an entity's state, refreshed whenever the entity announces a change. If
4
- * `key` is provided, tracks only that property; otherwise tracks a shallow copy of the whole state object.
5
- *
6
- * The ref is a copy, not a window onto the entity: writing through it never reaches the entity, and edits made to the
7
- * entity only show up once it publishes `entity:state-changed`. Nested objects are shared with the entity, so reach
8
- * into them at your own risk.
9
- *
10
- * No 0.10 entity or behavior emits `entity:state-changed` -- the design states plainly that writing a state field
11
- * notifies nobody. The initial read below still works; the refresh on that event does not fire until something
12
- * in a game emits it itself.
13
- */
14
- export declare function useSageState<T = unknown>(entityID: string, key?: string): Ref<T>;