@forgeax/engine-app 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/engine-app",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -23,32 +23,32 @@
23
23
  "LICENSE"
24
24
  ],
25
25
  "dependencies": {
26
- "@forgeax/engine-animation": "0.1.6",
27
- "@forgeax/engine-assets-runtime": "0.1.6",
28
- "@forgeax/engine-audio": "0.1.6",
29
- "@forgeax/engine-audio-webaudio": "0.1.6",
30
- "@forgeax/engine-debug-draw": "0.1.6",
31
- "@forgeax/engine-ecs": "0.1.6",
32
- "@forgeax/engine-input": "0.1.6",
33
- "@forgeax/engine-physics": "0.1.6",
34
- "@forgeax/engine-physics-rapier2d": "0.1.6",
35
- "@forgeax/engine-physics-rapier3d": "0.1.6",
36
- "@forgeax/engine-plugin": "0.1.6",
37
- "@forgeax/engine-profiler": "0.1.6",
38
- "@forgeax/engine-render": "0.1.6",
39
- "@forgeax/engine-rhi": "0.1.6",
40
- "@forgeax/engine-runtime": "0.1.6",
41
- "@forgeax/engine-scene": "0.1.6",
42
- "@forgeax/engine-state": "0.1.6",
43
- "@forgeax/engine-tool-runtime": "0.1.6",
44
- "@forgeax/engine-types": "0.1.6"
26
+ "@forgeax/engine-animation": "0.1.7",
27
+ "@forgeax/engine-assets-runtime": "0.1.7",
28
+ "@forgeax/engine-audio": "0.1.7",
29
+ "@forgeax/engine-audio-webaudio": "0.1.7",
30
+ "@forgeax/engine-debug-draw": "0.1.7",
31
+ "@forgeax/engine-ecs": "0.1.7",
32
+ "@forgeax/engine-input": "0.1.7",
33
+ "@forgeax/engine-physics": "0.1.7",
34
+ "@forgeax/engine-physics-rapier2d": "0.1.7",
35
+ "@forgeax/engine-physics-rapier3d": "0.1.7",
36
+ "@forgeax/engine-plugin": "0.1.7",
37
+ "@forgeax/engine-profiler": "0.1.7",
38
+ "@forgeax/engine-render": "0.1.7",
39
+ "@forgeax/engine-rhi": "0.1.7",
40
+ "@forgeax/engine-runtime": "0.1.7",
41
+ "@forgeax/engine-scene": "0.1.7",
42
+ "@forgeax/engine-state": "0.1.7",
43
+ "@forgeax/engine-tool-runtime": "0.1.7",
44
+ "@forgeax/engine-types": "0.1.7"
45
45
  },
46
46
  "devDependencies": {
47
- "@forgeax/engine-remote": "0.1.6",
48
- "@forgeax/engine-rhi-debug": "0.1.6",
49
- "@forgeax/engine-rhi-null": "0.1.6",
50
- "@forgeax/engine-rhi-webgpu": "0.1.6",
51
- "@forgeax/engine-rhi-wgpu": "0.1.6",
47
+ "@forgeax/engine-remote": "0.1.7",
48
+ "@forgeax/engine-rhi-debug": "0.1.7",
49
+ "@forgeax/engine-rhi-null": "0.1.7",
50
+ "@forgeax/engine-rhi-webgpu": "0.1.7",
51
+ "@forgeax/engine-rhi-wgpu": "0.1.7",
52
52
  "@types/ws": "^8.5.10",
53
53
  "@webgpu/types": "^0.1.71",
54
54
  "ws": "^8.20.0"
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Browser-visible render progress owned by the App frame loop.
3
+ *
4
+ * The Renderer already emits `frame-submitted` after its sole queue-submit
5
+ * boundary. This adapter projects that event onto the document without
6
+ * exposing a renderer handle or creating a second readiness registry. DevKit
7
+ * capture waits on the same projection, then verifies compositor pixels.
8
+ */
9
+
10
+ export const FORGEAX_FRAME_SUBMITTED_DATASET = 'forgeaxFrameSubmitted';
11
+ export const FORGEAX_FRAME_SUBMITTED_EVENT = 'forgeax:frame-submitted';
12
+
13
+ export interface BrowserFrameSubmitted {
14
+ readonly frameId: number;
15
+ readonly deviceGeneration: number;
16
+ }
17
+
18
+ export function resetBrowserFrameSubmitted(canvas: HTMLCanvasElement): void {
19
+ const documentElement = canvas.ownerDocument?.documentElement;
20
+ if (documentElement !== undefined) {
21
+ delete documentElement.dataset[FORGEAX_FRAME_SUBMITTED_DATASET];
22
+ }
23
+ }
24
+
25
+ export function publishBrowserFrameSubmitted(
26
+ canvas: HTMLCanvasElement,
27
+ event: BrowserFrameSubmitted,
28
+ ): void {
29
+ const documentElement = canvas.ownerDocument?.documentElement;
30
+ if (documentElement !== undefined) {
31
+ documentElement.dataset[FORGEAX_FRAME_SUBMITTED_DATASET] = String(event.frameId);
32
+ }
33
+ // Native/Web test canvases may be structural objects without DOM event
34
+ // methods. The dataset projection above remains optional as well, so the
35
+ // signal must stay a no-op outside a browser host instead of poisoning the
36
+ // frame loop with a listener exception.
37
+ if (typeof CustomEvent === 'function' && typeof canvas.dispatchEvent === 'function') {
38
+ canvas.dispatchEvent(
39
+ new CustomEvent(FORGEAX_FRAME_SUBMITTED_EVENT, {
40
+ detail: Object.freeze({ ...event }),
41
+ }),
42
+ );
43
+ }
44
+ }
package/src/create-app.ts CHANGED
@@ -47,6 +47,7 @@ import {
47
47
  import { err, ok, type Result } from '@forgeax/engine-types';
48
48
 
49
49
  import { createAnimationPayloadLookup } from './animation-asset-lookup';
50
+ import { publishBrowserFrameSubmitted, resetBrowserFrameSubmitted } from './browser-frame-signal';
50
51
  import type { AppErrorCode, AppErrorDetailFor } from './errors';
51
52
  import { APP_ERROR_HINTS, APP_EXPECTED, AppError } from './errors';
52
53
  import {
@@ -564,6 +565,7 @@ async function createAppFromCanvas(
564
565
  world,
565
566
  pluginContext,
566
567
  executionControl,
568
+ onFrameSubmitted: (event) => publishBrowserFrameSubmitted(canvas, event),
567
569
  ...(inputBackend !== undefined ? { inputBackend } : {}),
568
570
  ...(inputHandle === undefined
569
571
  ? {}
@@ -600,6 +602,7 @@ async function createAppFromCanvas(
600
602
 
601
603
  // The aspect-sync sidecar belongs to the canvas path because it owns the DOM
602
604
  // canvas. It runs as an Update system so it shares World scheduling semantics.
605
+ resetBrowserFrameSubmitted(canvas);
603
606
  const built = await buildApp(buildArgs);
604
607
  if (!built.ok) {
605
608
  await pluginContext.fiber.dispose();
@@ -994,6 +997,11 @@ interface BuildAppArgs {
994
997
  | undefined;
995
998
  readonly profiler?: import('@forgeax/engine-profiler').Profiler;
996
999
  readonly executionControl?: import('./execution/control').LocalExecutionControl;
1000
+ /** Canvas-form projection of the Renderer frame-submitted event. */
1001
+ readonly onFrameSubmitted?: (event: {
1002
+ readonly frameId: number;
1003
+ readonly deviceGeneration: number;
1004
+ }) => void;
997
1005
  }
998
1006
 
999
1007
  /**
@@ -1118,6 +1126,10 @@ async function buildApp(args: BuildAppArgs): Promise<Result<App, AppError | RhiE
1118
1126
  return;
1119
1127
  }
1120
1128
  rendererUnsubscribe = renderer.subscribe((event) => {
1129
+ if (event.kind === 'frame-submitted') {
1130
+ args.onFrameSubmitted?.(event);
1131
+ return;
1132
+ }
1121
1133
  if (event.kind !== 'error') return;
1122
1134
  const e: RenderError = event.error;
1123
1135
  dispatch(e);
package/src/index.ts CHANGED
@@ -39,6 +39,13 @@ export {
39
39
  createFullscreenRenderFeature,
40
40
  type FullscreenRenderFeatureOptions,
41
41
  } from '@forgeax/engine-render/authoring';
42
+ export {
43
+ type BrowserFrameSubmitted,
44
+ FORGEAX_FRAME_SUBMITTED_DATASET,
45
+ FORGEAX_FRAME_SUBMITTED_EVENT,
46
+ publishBrowserFrameSubmitted,
47
+ resetBrowserFrameSubmitted,
48
+ } from './browser-frame-signal';
42
49
  export { createApp, measureCanvasDrawingBuffer, syncCanvasDrawingBuffer } from './create-app';
43
50
  export type {
44
51
  AppDetailCanvasDetached,