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

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.
@@ -1,6 +1,6 @@
1
- import { GameEngine, GameEntityDefinition, SageOptions } from "@skewedaspect/sage";
1
+ import { GameEngine, GameEntityType, SageOptions } from '@skewedaspect/sage';
2
2
  type __VLS_Props = {
3
- entityDefinitions?: GameEntityDefinition[];
3
+ entityClasses?: Readonly<Record<string, GameEntityType>>;
4
4
  options?: SageOptions;
5
5
  engine?: GameEngine;
6
6
  };
@@ -27,8 +27,8 @@ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}
27
27
  "onEngine-ready"?: ((engine: GameEngine) => any) | undefined;
28
28
  "onEngine-error"?: ((error: unknown) => any) | undefined;
29
29
  }>, {
30
+ entityClasses: Readonly<Record<string, GameEntityType>>;
30
31
  options: SageOptions;
31
- entityDefinitions: GameEntityDefinition[];
32
32
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
33
33
  containerRef: HTMLDivElement;
34
34
  canvasRef: HTMLCanvasElement;
@@ -1,5 +1,5 @@
1
1
  import { ComputedRef, ShallowRef } from 'vue';
2
- import { GameEngine, GameEventBus } from "@skewedaspect/sage";
2
+ import { GameEngine, GameEventBus } from '@skewedaspect/sage';
3
3
  interface SageEngineContext {
4
4
  engine: ShallowRef<GameEngine | null>;
5
5
  eventBus: ComputedRef<GameEventBus | null>;
@@ -0,0 +1,4 @@
1
+ import { GameEvent, LibraryEventPayloadMap } from '@skewedaspect/sage';
2
+ type EventHandler = (event: GameEvent<string, LibraryEventPayloadMap>) => void | Promise<void>;
3
+ export declare function useSageEvent(pattern: string | RegExp, handler: EventHandler): void;
4
+ export {};
@@ -0,0 +1,14 @@
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>;
@@ -1,12 +1,13 @@
1
- import { CooldownHandle } from "@skewedaspect/sage";
1
+ import { CooldownHandle } from '@skewedaspect/sage';
2
2
  interface SageTimerComposable {
3
3
  delay: (ms: number, callback: () => void) => () => void;
4
4
  interval: (ms: number, callback: () => void) => () => void;
5
5
  cooldown: (ms: number) => CooldownHandle;
6
6
  }
7
7
  /**
8
- * Returns pause-aware timer functions (delay, interval, cooldown) from the game engine. All active timers are
9
- * automatically cancelled when the component unmounts.
8
+ * Returns pause-aware timer functions (delay, interval, cooldown) from the game engine. Timers requested before the
9
+ * engine has booted are queued and started as soon as it does, so a component can set them up in `setup()`. All active
10
+ * timers are cancelled when the component unmounts.
10
11
  */
11
12
  export declare function useSageTimer(): SageTimerComposable;
12
13
  export {};
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { default as SageCanvas } from './sageCanvas.vue';
2
- export { useSageEngine } from "./useSageEngine.d.ts";
3
- export { useSageEvent } from "./useSageEvent.d.ts";
4
- export { useSageAction } from "./useSageAction.d.ts";
5
- export { useSageState } from "./useSageState.d.ts";
6
- export { useSageTimer } from "./useSageTimer.d.ts";
1
+ export { default as SageCanvas } from './components/sageCanvas.vue';
2
+ export { useSageEngine } from "./composables/useSageEngine.d.ts";
3
+ export { useSageEvent } from "./composables/useSageEvent.d.ts";
4
+ export { useSageAction } from "./composables/useSageAction.d.ts";
5
+ export { useSageState } from "./composables/useSageState.d.ts";
6
+ export { useSageTimer } from "./composables/useSageTimer.d.ts";
7
7
  export { SAGE_ENGINE_KEY } from "./symbols.d.ts";
package/dist/sage-vue.css CHANGED
@@ -1,2 +1,2 @@
1
- .sage-container[data-v-7932fefe]{width:100%;height:100%;position:relative}.sage-canvas[data-v-7932fefe]{width:100%;height:100%;display:block}.sage-overlay[data-v-7932fefe]{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0}.sage-overlay[data-v-7932fefe] *{pointer-events:auto}
1
+ .sage-container[data-v-978219c2]{width:100%;height:100%;position:relative}.sage-canvas[data-v-978219c2]{width:100%;height:100%;display:block}.sage-overlay[data-v-978219c2]{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0}.sage-overlay[data-v-978219c2] *{pointer-events:auto}
2
2
  /*$vite$:1*/
package/dist/sage-vue.js CHANGED
@@ -1,14 +1,14 @@
1
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
2
  import { createGameEngine as m } from "@skewedaspect/sage";
3
3
  //#region src/symbols.ts
4
- var h = Symbol("sage-engine"), g = { class: "sage-overlay" }, _ = /* @__PURE__ */ ((e, t) => {
4
+ var h = Symbol("sage-engine"), g = { class: "sage-overlay" }, _ = /*#__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;
8
8
  })(/* @__PURE__ */ r({
9
9
  __name: "sageCanvas",
10
10
  props: {
11
- entityDefinitions: { default: () => [] },
11
+ entityClasses: { default: () => ({}) },
12
12
  options: { default: () => ({}) },
13
13
  engine: {}
14
14
  },
@@ -21,7 +21,10 @@ var h = Symbol("sage-engine"), g = { class: "sage-overlay" }, _ = /* @__PURE__ *
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.entityDefinitions, i.options), e) return;
24
+ if (i.engine ? (t = i.engine, S = !0) : t = await m(v.value, i.entityClasses, i.options), e) {
25
+ S || (await t.stop(), t.renderEngine.dispose());
26
+ return;
27
+ }
25
28
  y.value = t, _.value && (C = new ResizeObserver(() => {
26
29
  t.renderEngine.resize();
27
30
  }), C.observe(_.value)), p("engine-ready", t), b.value = !1;
@@ -45,9 +48,9 @@ var h = Symbol("sage-engine"), g = { class: "sage-overlay" }, _ = /* @__PURE__ *
45
48
  engine: y.value
46
49
  }, void 0, !0)])], 512));
47
50
  }
48
- }), [["__scopeId", "data-v-7932fefe"]]);
51
+ }), [["__scopeId", "data-v-978219c2"]]);
49
52
  //#endregion
50
- //#region src/useSageEngine.ts
53
+ //#region src/composables/useSageEngine.ts
51
54
  function v() {
52
55
  let t = i(h);
53
56
  if (!t) throw Error("useSageEngine() must be used inside a <SageCanvas> component.");
@@ -57,17 +60,20 @@ function v() {
57
60
  };
58
61
  }
59
62
  //#endregion
60
- //#region src/useSageEvent.ts
63
+ //#region src/composables/useSageEvent.ts
61
64
  function y(e, t) {
62
65
  let { engine: n } = v(), r = null;
63
66
  p(n, (n) => {
64
- r &&= (r(), null), n && (r = n.eventBus.subscribePattern(e, t));
65
- }, { immediate: !0 }), o(() => {
67
+ r &&= (r(), null), n && (r = n.eventBus.subscribe(e, t));
68
+ }, {
69
+ immediate: !0,
70
+ flush: "sync"
71
+ }), o(() => {
66
72
  r &&= (r(), null);
67
73
  });
68
74
  }
69
75
  //#endregion
70
- //#region src/useSageAction.ts
76
+ //#region src/composables/useSageAction.ts
71
77
  function b(e) {
72
78
  let t = l(0);
73
79
  return y(`action:${e}`, (e) => {
@@ -75,58 +81,73 @@ function b(e) {
75
81
  }), t;
76
82
  }
77
83
  //#endregion
78
- //#region src/useSageState.ts
84
+ //#region src/composables/useSageState.ts
79
85
  function x(e, t) {
80
86
  let { engine: n } = v(), r = () => {
81
- let r = n.value?.managers.entityManager.getEntity(e);
82
- if (r) return t ? r.state[t] : r.state;
83
- }, i = l(r());
87
+ let r = n.value?.simulation?.getEntity(e);
88
+ if (r) return t ? r.state[t] : { ...r.state };
89
+ }, i = d(r());
84
90
  return y("entity:state-changed", (t) => {
85
91
  t.payload.entityId === e && (i.value = r());
86
92
  }), i;
87
93
  }
88
94
  //#endregion
89
- //#region src/useSageTimer.ts
90
- var S = () => {}, C = {
91
- get ready() {
92
- return !1;
93
- },
94
- reset() {}
95
- };
96
- function w() {
97
- let { engine: e } = v(), t = [];
98
- o(() => {
95
+ //#region src/composables/useSageTimer.ts
96
+ function S() {
97
+ let { engine: e } = v(), t = [], n = /* @__PURE__ */ new Set();
98
+ p(e, (e) => {
99
+ if (!e) return;
100
+ let t = [...n];
101
+ n.clear();
102
+ for (let n of t) n(e);
103
+ }, {
104
+ immediate: !0,
105
+ flush: "sync"
106
+ }), o(() => {
107
+ n.clear();
99
108
  for (let e of t) e();
100
109
  t.length = 0;
101
110
  });
102
- function n(n, r) {
103
- if (!e.value) return S;
104
- let i = e.value.timer.delay(n, r);
105
- return t.push(i), () => {
106
- i();
107
- let e = t.indexOf(i);
108
- e >= 0 && t.splice(e, 1);
109
- };
111
+ function r(e) {
112
+ e();
113
+ let n = t.indexOf(e);
114
+ n >= 0 && t.splice(n, 1);
110
115
  }
111
- function r(n, r) {
112
- if (!e.value) return S;
113
- let i = e.value.timer.interval(n, r);
114
- return t.push(i), () => {
115
- i();
116
- let e = t.indexOf(i);
117
- e >= 0 && t.splice(e, 1);
116
+ function i(i) {
117
+ let a = null, o = (e) => {
118
+ a = i(e), t.push(a);
118
119
  };
120
+ return e.value ? o(e.value) : n.add(o), () => {
121
+ n.delete(o), a &&= (r(a), null);
122
+ };
123
+ }
124
+ function a(e, t) {
125
+ return i((n) => n.timer.delay(e, t));
119
126
  }
120
- function i(t) {
121
- return e.value ? e.value.timer.cooldown(t) : C;
127
+ function s(e, t) {
128
+ return i((n) => n.timer.interval(e, t));
129
+ }
130
+ function c(t) {
131
+ if (e.value) return e.value.timer.cooldown(t);
132
+ let r = null, i = !1;
133
+ return n.add((e) => {
134
+ r = e.timer.cooldown(t), i &&= (r.reset(), !1);
135
+ }), {
136
+ get ready() {
137
+ return r ? r.ready : !1;
138
+ },
139
+ reset() {
140
+ r ? r.reset() : i = !0;
141
+ }
142
+ };
122
143
  }
123
144
  return {
124
- delay: n,
125
- interval: r,
126
- cooldown: i
145
+ delay: a,
146
+ interval: s,
147
+ cooldown: c
127
148
  };
128
149
  }
129
150
  //#endregion
130
- export { h as SAGE_ENGINE_KEY, _ as SageCanvas, b as useSageAction, v as useSageEngine, y as useSageEvent, x as useSageState, w as useSageTimer };
151
+ export { h as SAGE_ENGINE_KEY, _ as SageCanvas, b as useSageAction, v as useSageEngine, y as useSageEvent, x as useSageState, S as useSageTimer };
131
152
 
132
153
  //# sourceMappingURL=sage-vue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sage-vue.js","names":[],"sources":["../src/symbols.ts","../src/sageCanvas.vue","../src/sageCanvas.vue","../src/useSageEngine.ts","../src/useSageEvent.ts","../src/useSageAction.ts","../src/useSageState.ts","../src/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\"></canvas>\n <div class=\"sage-overlay\">\n <slot :loading=\"loading\" :error=\"error\" :engine=\"engineRef\"></slot>\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 { ref, shallowRef, provide, onMounted, onUnmounted, useTemplateRef } from 'vue';\n import { createGameEngine } from '@skewedaspect/sage';\n import type { GameEngine, GameEntityDefinition, 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 entityDefinitions ?: GameEntityDefinition[];\n options ?: SageOptions;\n engine ?: GameEngine;\n }>(), {\n entityDefinitions: () => [],\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.entityDefinitions, props.options);\n }\n\n if(disposed) { return; }\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\"></canvas>\n <div class=\"sage-overlay\">\n <slot :loading=\"loading\" :error=\"error\" :engine=\"engineRef\"></slot>\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 { ref, shallowRef, provide, onMounted, onUnmounted, useTemplateRef } from 'vue';\n import { createGameEngine } from '@skewedaspect/sage';\n import type { GameEngine, GameEntityDefinition, 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 entityDefinitions ?: GameEntityDefinition[];\n options ?: SageOptions;\n engine ?: GameEngine;\n }>(), {\n entityDefinitions: () => [],\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.entityDefinitions, props.options);\n }\n\n if(disposed) { return; }\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, Unsubscribe, WildcardEventCallback, WildcardPattern } from '@skewedaspect/sage';\nimport { useSageEngine } from './useSageEngine.ts';\n\n//----------------------------------------------------------------------------------------------------------------------\n\ntype EventHandler = (event : GameEvent) => 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 watch(engine, (eng) =>\n {\n if(unsub)\n {\n unsub();\n unsub = null;\n }\n\n if(eng)\n {\n unsub = eng.eventBus.subscribePattern(\n pattern as WildcardPattern | RegExp,\n handler as WildcardEventCallback\n );\n }\n }, { immediate: true });\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, ref } 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 synced to an entity's state. If `key` is provided, tracks only that property; otherwise\n * tracks the entire state object.\n */\nexport function useSageState<T = unknown>(entityId : string, key ?: string) : Ref<T>\n{\n const { engine } = useSageEngine();\n\n // Read initial state from the entity (if it exists yet)\n const readState = () : T =>\n {\n const entity = engine.value?.managers.entityManager.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 = ref<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 } from 'vue';\nimport type { CooldownHandle } 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//----------------------------------------------------------------------------------------------------------------------\n\n// eslint-disable-next-line @typescript-eslint/no-empty-function\nconst NOOP = () : void => {};\n\nconst NOOP_COOLDOWN : CooldownHandle = {\n get ready() : boolean { return false; },\n reset() : void { /* no-op */ },\n};\n\n//----------------------------------------------------------------------------------------------------------------------\n\n/**\n * Returns pause-aware timer functions (delay, interval, cooldown) from the game engine. All active timers are\n * automatically cancelled when the component unmounts.\n */\nexport function useSageTimer() : SageTimerComposable\n{\n const { engine } = useSageEngine();\n const cancellers : (() => void)[] = [];\n\n onUnmounted(() =>\n {\n for(const cancel of cancellers)\n {\n cancel();\n }\n\n cancellers.length = 0;\n });\n\n function delay(ms : number, callback : () => void) : () => void\n {\n if(!engine.value)\n {\n return NOOP;\n }\n\n const cancel = engine.value.timer.delay(ms, callback);\n cancellers.push(cancel);\n\n return () =>\n {\n cancel();\n const idx = cancellers.indexOf(cancel);\n if(idx >= 0)\n {\n cancellers.splice(idx, 1);\n }\n };\n }\n\n function interval(ms : number, callback : () => void) : () => void\n {\n if(!engine.value)\n {\n return NOOP;\n }\n\n const cancel = engine.value.timer.interval(ms, callback);\n cancellers.push(cancel);\n\n return () =>\n {\n cancel();\n const idx = cancellers.indexOf(cancel);\n if(idx >= 0)\n {\n cancellers.splice(idx, 1);\n }\n };\n }\n\n function cooldown(ms : number) : CooldownHandle\n {\n if(!engine.value)\n {\n return NOOP_COOLDOWN;\n }\n\n const handle = engine.value.timer.cooldown(ms);\n\n // There's no cancel for cooldowns, but we don't need to track them — they're passive pollable objects\n return handle;\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/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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"}
package/dist/symbols.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { InjectionKey, ShallowRef } from 'vue';
2
- import { GameEngine } from "@skewedaspect/sage";
2
+ import { GameEngine } from '@skewedaspect/sage';
3
3
  export declare const SAGE_ENGINE_KEY: InjectionKey<ShallowRef<GameEngine | null>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skewedaspect/sage-vue",
3
- "version": "0.9.6",
3
+ "version": "0.10.0-beta.1",
4
4
  "type": "module",
5
5
  "main": "dist/sage-vue.js",
6
6
  "module": "dist/sage-vue.js",
@@ -23,7 +23,9 @@
23
23
  "license": "MIT",
24
24
  "description": "Vue 3 integration components for the SAGE game engine.",
25
25
  "scripts": {
26
- "build": "vite build"
26
+ "build": "vite build",
27
+ "test": "vitest run --config vitest.config.ts",
28
+ "lint:types": "vue-tsc --noEmit"
27
29
  },
28
30
  "files": [
29
31
  "dist"
@@ -33,10 +35,12 @@
33
35
  "vue": "^3.5.0"
34
36
  },
35
37
  "devDependencies": {
38
+ "@skewedaspect/sage": "*",
36
39
  "@vitejs/plugin-vue": "^6.0.3",
37
40
  "vite": "^8.0.3",
38
41
  "vite-plugin-dts": "^4.5.4",
39
42
  "typescript": "^6.0.2",
40
- "vue": "^3.5.27"
43
+ "vue": "^3.5.27",
44
+ "vue-tsc": "^3.3.11"
41
45
  }
42
46
  }
@@ -1,4 +0,0 @@
1
- import { GameEvent } from "@skewedaspect/sage";
2
- type EventHandler = (event: GameEvent) => void | Promise<void>;
3
- export declare function useSageEvent(pattern: string | RegExp, handler: EventHandler): void;
4
- export {};
@@ -1,6 +0,0 @@
1
- import { Ref } from 'vue';
2
- /**
3
- * Returns a reactive ref synced to an entity's state. If `key` is provided, tracks only that property; otherwise
4
- * tracks the entire state object.
5
- */
6
- export declare function useSageState<T = unknown>(entityId: string, key?: string): Ref<T>;