@effindomv2/create-fui-as-app 0.1.32 → 0.1.35

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,2 +1,2 @@
1
- export declare const FUI_AS_VERSION = "0.1.26";
2
- export declare const RUNTIME_VERSION = "0.1.10";
1
+ export declare const FUI_AS_VERSION = "0.1.29";
2
+ export declare const RUNTIME_VERSION = "0.1.12";
@@ -1,2 +1,2 @@
1
- export const FUI_AS_VERSION = "0.1.26";
2
- export const RUNTIME_VERSION = "0.1.10";
1
+ export const FUI_AS_VERSION = "0.1.29";
2
+ export const RUNTIME_VERSION = "0.1.12";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effindomv2/create-fui-as-app",
3
- "version": "0.1.32",
3
+ "version": "0.1.35",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Scaffold a minimal EffinDom v2 FUI-AS app",
@@ -8,6 +8,32 @@ This template is the smallest FUI-AS app shape:
8
8
  - `src/host/host-services.ts` and `src/host/host-events.ts` define app-owned JS bridge contracts.
9
9
  - `src/host/generated/*` is generated from those host definition files.
10
10
 
11
+ ## Staging project assets
12
+
13
+ `stage-assets.json` is a convention-over-configuration manifest for copying project-owned files into `public/runtime/` during `npm run build:assets`. You should never need to edit `scripts/prepare-runtime.ts` — just drop files into your project and declare them here.
14
+
15
+ ```json
16
+ {
17
+ "stage": {
18
+ "fonts/*.ttf": "fonts",
19
+ "images/**": "images"
20
+ }
21
+ }
22
+ ```
23
+
24
+ - **Keys** are **glob patterns** relative to the project root. Supported patterns:
25
+ - `"*.ext"` — all files with a given extension in the project root.
26
+ - `"dir/*.ext"` — all files matching the extension inside `dir/` (non-recursive).
27
+ - `"dir/**"` — copy the entire `dir/` tree recursively.
28
+ - **Values** are the **destination subdirectory** under `public/runtime/`. The value can be a nested path like `"fonts/custom"` and directories are created automatically.
29
+ - The config is optional — if `stage-assets.json` is missing, the build script skips staging with no error.
30
+
31
+ For example, to ship custom fonts alongside the bundled Noto set:
32
+
33
+ 1. Place your `.ttf` files in a `fonts/` directory at the project root.
34
+ 2. Add `"fonts/*.ttf": "fonts"` to `stage-assets.json`.
35
+ 3. Reference them in app code as `/runtime/fonts/YourFont.ttf`.
36
+
11
37
  ## Typical workflow
12
38
 
13
39
  1. Build UI from `src/App.ts` with controls/nodes from `./fui/Fui`.
@@ -19,6 +19,6 @@
19
19
  }
20
20
  },
21
21
  "options": {
22
- "runtime": "stub"
22
+ "runtime": "minimal"
23
23
  }
24
24
  }
@@ -1,12 +1,79 @@
1
- import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { basename, join } from "node:path";
2
3
 
3
4
  const outputDir = "public";
4
5
 
6
+ interface StageConfig {
7
+ stage: Record<string, string>;
8
+ }
9
+
10
+ function resolveGlob(pattern: string): string[] {
11
+ const slash = pattern.lastIndexOf("/");
12
+ if (slash === -1) {
13
+ const suffix = pattern.startsWith("*.") ? pattern.slice(1) : "";
14
+ if (suffix.length === 0) return [];
15
+ return readdirSync(".").filter((f) => f.endsWith(suffix));
16
+ }
17
+
18
+ const dir = pattern.slice(0, slash);
19
+ const filePart = pattern.slice(slash + 1);
20
+
21
+ if (filePart === "**") {
22
+ return [dir];
23
+ }
24
+
25
+ if (filePart.startsWith("*.")) {
26
+ const suffix = filePart.slice(1);
27
+ try {
28
+ return readdirSync(dir)
29
+ .filter((f) => f.endsWith(suffix))
30
+ .map((f) => join(dir, f));
31
+ } catch {
32
+ return [];
33
+ }
34
+ }
35
+
36
+ if (existsSync(pattern)) {
37
+ return [pattern];
38
+ }
39
+
40
+ return [];
41
+ }
42
+
43
+ function stageProjectAssets(outputDir: string): void {
44
+ const configPath = "stage-assets.json";
45
+ if (!existsSync(configPath)) {
46
+ return;
47
+ }
48
+
49
+ const raw = readFileSync(configPath, "utf8");
50
+ const config: StageConfig = JSON.parse(raw);
51
+
52
+ for (const [pattern, destDir] of Object.entries(config.stage)) {
53
+ const matches = resolveGlob(pattern);
54
+ if (matches.length === 0) {
55
+ continue;
56
+ }
57
+
58
+ const dest = join(outputDir, "runtime", destDir);
59
+ mkdirSync(dest, { recursive: true });
60
+
61
+ for (const match of matches) {
62
+ if (statSync(match).isDirectory()) {
63
+ cpSync(match, dest, { recursive: true });
64
+ } else {
65
+ copyFileSync(match, join(dest, basename(match)));
66
+ }
67
+ }
68
+ }
69
+ }
70
+
5
71
  rmSync(outputDir, { recursive: true, force: true });
6
72
  mkdirSync(`${outputDir}/runtime`, { recursive: true });
7
73
 
8
74
  cpSync("node_modules/@effindomv2/runtime/dist", `${outputDir}/runtime/dist`, { recursive: true });
9
75
  cpSync("node_modules/@effindomv2/runtime/dist/fonts", `${outputDir}/runtime/fonts`, { recursive: true });
76
+ stageProjectAssets(outputDir);
10
77
  copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js", `${outputDir}/bridge.js`);
11
78
  if (existsSync("node_modules/@effindomv2/runtime/dist/bridge.js.map")) {
12
79
  copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js.map", `${outputDir}/bridge.js.map`);
@@ -0,0 +1,3 @@
1
+ {
2
+ "stage": {}
3
+ }
@@ -12,6 +12,54 @@ This template scaffolds a multi-route FUI-AS app with explicit page-level MVC.
12
12
  - `route-shell.html` is the per-route host shell copied for each route path.
13
13
  - `src/host/host-services.ts` and `src/host/host-events.ts` are app-owned bridge contracts; `src/host/generated/*` is generated output.
14
14
 
15
+ ## Staging project assets
16
+
17
+ `stage-assets.json` is a convention-over-configuration manifest for copying project-owned files into `public/runtime/` during `npm run build:assets`. You should never need to edit `scripts/prepare-runtime.ts` — just drop files into your project and declare them here.
18
+
19
+ ```json
20
+ {
21
+ "stage": {
22
+ "fonts/*.ttf": "fonts",
23
+ "images/**": "images"
24
+ }
25
+ }
26
+ ```
27
+
28
+ - **Keys** are **glob patterns** relative to the project root. Supported patterns:
29
+ - `"*.ext"` — all files with a given extension in the project root.
30
+ - `"dir/*.ext"` — all files matching the extension inside `dir/` (non-recursive).
31
+ - `"dir/**"` — copy the entire `dir/` tree recursively.
32
+ - **Values** are the **destination subdirectory** under `public/runtime/`. The value can be a nested path like `"fonts/custom"` and directories are created automatically.
33
+ - The config is optional — if `stage-assets.json` is missing, the build script skips staging with no error.
34
+
35
+ For example, to ship custom fonts alongside the bundled Noto set:
36
+
37
+ 1. Place your `.ttf` files in a `fonts/` directory at the project root.
38
+ 2. Add `"fonts/*.ttf": "fonts"` to `stage-assets.json`.
39
+ 3. Reference them in app code as `/runtime/fonts/YourFont.ttf`.
40
+
41
+ ## Workers
42
+
43
+ Workers are compiled as separate WASM modules and loaded on demand via `Worker.start(...)`. The worker manifest lives at `src/worker-config.ts`; add entries there for each worker entrypoint.
44
+
45
+ ### Adding a worker
46
+
47
+ 1. Create a worker module in `src/workers/` (use `advanced_workers.ts` as a reference).
48
+ 2. Register it in `src/worker-config.ts`.
49
+ 3. Add build scripts in `package.json` (copy the `build:wasm:workers:advanced` pattern).
50
+ 4. Define any host services the worker needs in `src/host/worker-host-services.ts`, then regenerate with `npm run generate:worker-host-services`.
51
+ 5. Ensure `worker-host-services.ts` at the project root registers the services under `globalThis.__fuiWorkerHostServicesModule`.
52
+
53
+ The harness (`harness.ts`) already wires `workerHostServices` to `startRoutedHarness`. Workers are available from any route via:
54
+
55
+ ```ts
56
+ const worker = Worker.start("advanced-workers")
57
+ .onProgress(this, (view, message) => { ... })
58
+ .onComplete(this, (view, result) => { ... })
59
+ .onError(this, (view, message) => { ... });
60
+ worker.sendString("input-data");
61
+ ```
62
+
15
63
  ## Routing + deployment model
16
64
 
17
65
  Each route builds to its own wasm (`home.wasm`, `settings.wasm`) and is mounted by routed harness config. This is designed for true MFE slices: each route app can evolve and deploy independently while still sharing the same browser host/runtime contract.
@@ -19,6 +19,6 @@
19
19
  }
20
20
  },
21
21
  "options": {
22
- "runtime": "stub"
22
+ "runtime": "minimal"
23
23
  }
24
24
  }
@@ -24,4 +24,8 @@ startRoutedHarness<RouteExports>({
24
24
  },
25
25
  hostEvents: appHostEvents,
26
26
  hostServices: appHostServices,
27
+ workerHostServices: {
28
+ scriptUrl: new URL('./worker-host-services.js', import.meta.url).toString(),
29
+ exportName: 'appWorkerHostServices',
30
+ },
27
31
  });
@@ -5,9 +5,9 @@
5
5
  "type": "module",
6
6
  "description": "Scaffolded FUI-AS routed app",
7
7
  "scripts": {
8
- "build": "npm run generate:host && npm run build:assets && npm run build:wasm && npm run build:harness",
9
- "build:dev": "npm run generate:host && npm run build:assets && npm run build:wasm:dev && npm run build:harness",
10
- "publish": "npm run generate:host && npm run build:assets && npm run build:wasm:publish && npm run build:harness && npm run publish:stage",
8
+ "build": "npm run generate:host && npm run build:assets && npm run build:wasm && npm run build:wasm:workers && npm run build:harness && npm run build:worker-bootstrap && npm run build:worker-host-services",
9
+ "build:dev": "npm run generate:host && npm run build:assets && npm run build:wasm:dev && npm run build:wasm:workers:dev && npm run build:harness && npm run build:worker-bootstrap && npm run build:worker-host-services",
10
+ "publish": "npm run generate:host && npm run build:assets && npm run build:wasm:publish && npm run build:wasm:workers:publish && npm run build:harness && npm run build:worker-bootstrap && npm run build:worker-host-services && npm run publish:stage",
11
11
  "build:assets": "tsx scripts/prepare-runtime.ts",
12
12
  "build:wasm": "npm run build:wasm:home && npm run build:wasm:settings",
13
13
  "build:wasm:home": "asc src/routes/HomeApp.ts --config asconfig.json --target release --outFile public/home.wasm",
@@ -19,9 +19,17 @@
19
19
  "build:wasm:publish:home": "asc src/routes/HomeApp.ts --config asconfig.json --target release --optimizeLevel 3 --shrinkLevel 2 --noAssert --outFile public/home.wasm",
20
20
  "build:wasm:publish:settings": "asc src/routes/SettingsApp.ts --config asconfig.json --target release --optimizeLevel 3 --shrinkLevel 2 --noAssert --outFile public/settings.wasm",
21
21
  "build:harness": "esbuild harness.ts --bundle --format=esm --platform=browser --outfile=public/harness.js",
22
+ "build:worker-host-services": "esbuild worker-host-services.ts --bundle --format=esm --platform=browser --outfile=public/worker-host-services.js",
23
+ "build:worker-bootstrap": "esbuild node_modules/@effindomv2/fui-as/browser/src/worker-bootstrap.ts --bundle --format=esm --platform=browser --outfile=public/worker-bootstrap.js",
24
+
25
+ "build:wasm:workers": "tsx scripts/build-worker-wasm.ts --all --target release",
26
+ "build:wasm:workers:dev": "tsx scripts/build-worker-wasm.ts --all --target debug",
27
+ "build:wasm:workers:publish": "tsx scripts/build-worker-wasm.ts --all --target release --publish",
22
28
  "generate:host-services": "tsx ./node_modules/@effindomv2/fui-as/scripts/generate-host-services.ts src/host/host-services.ts appHostServices src/host/generated/HostServices.ts ../../fui/FuiPrimitives",
23
29
  "generate:host-events": "tsx ./node_modules/@effindomv2/fui-as/scripts/generate-host-events.ts src/host/host-events.ts appHostEvents src/host/generated/HostEvents.ts ../../fui/FuiPrimitives",
24
30
  "generate:host": "npm run generate:host-services && npm run generate:host-events",
31
+ "generate:worker-host-services": "tsx ./node_modules/@effindomv2/fui-as/scripts/generate-host-services.ts src/host/worker-host-services.ts appWorkerHostServices src/host/generated/WorkerHostServices.ts ../../fui/FuiPrimitives fui_host_service",
32
+ "generate:all": "npm run generate:host && npm run generate:worker-host-services",
25
33
  "publish:stage": "rm -rf published && mkdir -p published && cp -R public/. published/ && cp -f index.html published/index.html && cp -f route-shell.html published/route-shell.html",
26
34
  "watch": "chokidar \"src/**/*.ts\" \"harness.ts\" \"route-shell.html\" \"index.html\" \"asconfig.json\" --ignore \"src/host/generated/**\" -c \"npm run build:dev\"",
27
35
  "serve": "serve public --listen 8080",
@@ -0,0 +1,66 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { mkdirSync } from "node:fs";
3
+ import { workerManifest } from "../src/worker-config";
4
+
5
+ const args = process.argv.slice(2);
6
+ let target = "release";
7
+ let publish = false;
8
+ let workerKey = "";
9
+ let buildAll = false;
10
+
11
+ for (let index = 0; index < args.length; index += 1) {
12
+ const arg = args[index];
13
+ switch (arg) {
14
+ case "--all":
15
+ buildAll = true;
16
+ break;
17
+ case "--worker":
18
+ workerKey = args[++index] ?? "";
19
+ break;
20
+ case "--target":
21
+ target = args[++index] ?? "release";
22
+ break;
23
+ case "--publish":
24
+ publish = true;
25
+ break;
26
+ default:
27
+ if (workerKey === "") {
28
+ workerKey = arg;
29
+ } else {
30
+ throw new Error(`Unknown argument: ${arg}`);
31
+ }
32
+ break;
33
+ }
34
+ }
35
+
36
+ const selectedWorkers = buildAll
37
+ ? workerManifest
38
+ : workerManifest.filter((worker): boolean => worker.key === workerKey);
39
+
40
+ if (!buildAll && selectedWorkers.length === 0) {
41
+ throw new Error(`Unknown worker key: ${workerKey}`);
42
+ }
43
+
44
+ mkdirSync("public", { recursive: true });
45
+
46
+ for (const worker of selectedWorkers) {
47
+ const ascArgs = [
48
+ worker.entrypoint,
49
+ "--config",
50
+ "asconfig.json",
51
+ "--target",
52
+ target,
53
+ "--outFile",
54
+ `public/${worker.wasmFile}`,
55
+ ];
56
+
57
+ if (publish) {
58
+ ascArgs.push("--optimizeLevel", "3", "--shrinkLevel", "2", "--noAssert");
59
+ }
60
+
61
+ console.log(`Building worker ${worker.key} -> ${worker.wasmFile}`);
62
+ const result = spawnSync("asc", ascArgs, { stdio: "inherit" });
63
+ if (result.status !== 0) {
64
+ process.exit(result.status ?? 1);
65
+ }
66
+ }
@@ -1,4 +1,5 @@
1
- import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { basename, join } from "node:path";
2
3
  import { renderRoutedPageHead, resolveRouteManifest, routeHead } from "@effindomv2/fui-as/browser/routed-app-conventions";
3
4
  import { routeManifest } from "../src/route-config";
4
5
 
@@ -9,6 +10,71 @@ const routeShellTemplate = readFileSync("route-shell.html", "utf8");
9
10
  const loadingOverlayStyles = readFileSync("node_modules/@effindomv2/fui-as/browser/loading-overlay-styles.html", "utf8");
10
11
  const loadingOverlayBody = readFileSync("node_modules/@effindomv2/fui-as/browser/loading-overlay-body.html", "utf8");
11
12
 
13
+ interface StageConfig {
14
+ stage: Record<string, string>;
15
+ }
16
+
17
+ function resolveGlob(pattern: string): string[] {
18
+ const slash = pattern.lastIndexOf("/");
19
+ if (slash === -1) {
20
+ const suffix = pattern.startsWith("*.") ? pattern.slice(1) : "";
21
+ if (suffix.length === 0) return [];
22
+ return readdirSync(".").filter((f) => f.endsWith(suffix));
23
+ }
24
+
25
+ const dir = pattern.slice(0, slash);
26
+ const filePart = pattern.slice(slash + 1);
27
+
28
+ if (filePart === "**") {
29
+ return [dir];
30
+ }
31
+
32
+ if (filePart.startsWith("*.")) {
33
+ const suffix = filePart.slice(1);
34
+ try {
35
+ return readdirSync(dir)
36
+ .filter((f) => f.endsWith(suffix))
37
+ .map((f) => join(dir, f));
38
+ } catch {
39
+ return [];
40
+ }
41
+ }
42
+
43
+ if (existsSync(pattern)) {
44
+ return [pattern];
45
+ }
46
+
47
+ return [];
48
+ }
49
+
50
+ function stageProjectAssets(outputDir: string): void {
51
+ const configPath = "stage-assets.json";
52
+ if (!existsSync(configPath)) {
53
+ return;
54
+ }
55
+
56
+ const raw = readFileSync(configPath, "utf8");
57
+ const config: StageConfig = JSON.parse(raw);
58
+
59
+ for (const [pattern, destDir] of Object.entries(config.stage)) {
60
+ const matches = resolveGlob(pattern);
61
+ if (matches.length === 0) {
62
+ continue;
63
+ }
64
+
65
+ const dest = join(outputDir, "runtime", destDir);
66
+ mkdirSync(dest, { recursive: true });
67
+
68
+ for (const match of matches) {
69
+ if (statSync(match).isDirectory()) {
70
+ cpSync(match, dest, { recursive: true });
71
+ } else {
72
+ copyFileSync(match, join(dest, basename(match)));
73
+ }
74
+ }
75
+ }
76
+ }
77
+
12
78
  function renderLoadingOverlay(template: string): string {
13
79
  return template
14
80
  .replace("{{LOADING_OVERLAY_STYLES}}", loadingOverlayStyles)
@@ -23,6 +89,7 @@ for (const route of resolvedManifest.routes) {
23
89
 
24
90
  cpSync("node_modules/@effindomv2/runtime/dist", `${outputDir}/runtime/dist`, { recursive: true });
25
91
  cpSync("node_modules/@effindomv2/runtime/dist/fonts", `${outputDir}/runtime/fonts`, { recursive: true });
92
+ stageProjectAssets(outputDir);
26
93
  copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js", `${outputDir}/bridge.js`);
27
94
  if (existsSync("node_modules/@effindomv2/runtime/dist/bridge.js.map")) {
28
95
  copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js.map", `${outputDir}/bridge.js.map`);
@@ -33,6 +100,7 @@ writeFileSync(
33
100
  "utf8",
34
101
  );
35
102
  copyFileSync("favicon.ico", `${outputDir}/favicon.ico`);
103
+ copyFileSync("worker-manifest.json", `${outputDir}/worker-manifest.json`);
36
104
  const defaultRoute = resolvedManifest.routes.length == 0 ? undefined : resolvedManifest.routes[0];
37
105
  const defaultRouteTitle = defaultRoute == null ? "FUI-AS Routed App" : defaultRoute.title;
38
106
  const defaultRouteHref = defaultRoute == null ? "/home/" : defaultRoute.publishedRoutePath;
@@ -0,0 +1,9 @@
1
+ // @ts-nocheck
2
+ // Generated by scripts/generate-host-services.ts from ./src/host/worker-host-services.ts#appWorkerHostServices.
3
+
4
+ @external("fui_host_service", "appWorkerClockWallClockSinceEpochMs")
5
+ declare function __host_appWorkerClockWallClockSinceEpochMs(): f64;
6
+
7
+ export function appWorkerClockWallClockSinceEpochMs(): f64 {
8
+ return __host_appWorkerClockWallClockSinceEpochMs();
9
+ }
@@ -0,0 +1,13 @@
1
+ import { defineHostServices, hostService } from "@effindomv2/fui-as/browser/host-services";
2
+
3
+ export const appWorkerHostServices = defineHostServices({
4
+ appWorkerClock: {
5
+ wallClockSinceEpochMs: hostService({
6
+ args: [] as const,
7
+ returns: "f64",
8
+ implementation() {
9
+ return Date.now();
10
+ },
11
+ }),
12
+ },
13
+ });
@@ -0,0 +1,13 @@
1
+ export interface WorkerEntry {
2
+ readonly key: string;
3
+ readonly entrypoint: string;
4
+ readonly wasmFile: string;
5
+ }
6
+
7
+ export const workerManifest: readonly WorkerEntry[] = [
8
+ {
9
+ key: "advanced-workers",
10
+ entrypoint: "src/workers/advanced_workers.ts",
11
+ wasmFile: "advanced-workers.wasm",
12
+ },
13
+ ];
@@ -0,0 +1,96 @@
1
+ export * from "@effindomv2/fui-as/src/FuiWorkerExports";
2
+
3
+ import { WorkerJob } from "@effindomv2/fui-as/src/FuiWorker";
4
+ import { appWorkerClockWallClockSinceEpochMs } from "../host/generated/WorkerHostServices";
5
+
6
+ const PRIME_SEARCH_TOTAL_MS: f64 = 5000.0;
7
+ const PRIME_SEARCH_YIELD_INTERVAL_MS: f64 = 1000.0;
8
+ const PRIME_TIME_CHECK_MASK: i32 = 127;
9
+
10
+ function parsePrimeSearchPercent(startedAtMs: f64, nowMs: f64): i32 {
11
+ const elapsedMs = nowMs - startedAtMs;
12
+ if (elapsedMs <= 0.0) {
13
+ return 0;
14
+ }
15
+ if (elapsedMs >= PRIME_SEARCH_TOTAL_MS) {
16
+ return 100;
17
+ }
18
+ return <i32>((elapsedMs * 100.0) / PRIME_SEARCH_TOTAL_MS);
19
+ }
20
+
21
+ function isPrime(value: i32): bool {
22
+ if (value < 2) {
23
+ return false;
24
+ }
25
+ if (value == 2) {
26
+ return true;
27
+ }
28
+ if ((value & 1) == 0) {
29
+ return false;
30
+ }
31
+ let divisor: i32 = 3;
32
+ while (divisor <= value / divisor) {
33
+ if (value % divisor == 0) {
34
+ return false;
35
+ }
36
+ divisor += 2;
37
+ }
38
+ return true;
39
+ }
40
+
41
+ class LargestPrimeCalculatorJob extends WorkerJob {
42
+ private startedAtMs: f64 = 0.0;
43
+ private deadlineMs: f64 = 0.0;
44
+ private nextYieldAtMs: f64 = 0.0;
45
+ private candidate: i32 = 2;
46
+ private largestPrime: i32 = 2;
47
+
48
+ protected onStart(): void {
49
+ this.receiveMessage();
50
+ const now = appWorkerClockWallClockSinceEpochMs();
51
+ this.startedAtMs = now;
52
+ this.deadlineMs = now + PRIME_SEARCH_TOTAL_MS;
53
+ this.nextYieldAtMs = now + PRIME_SEARCH_YIELD_INTERVAL_MS;
54
+ this.candidate = 2;
55
+ this.largestPrime = 2;
56
+ }
57
+
58
+ run(): void {
59
+ if (this.isCancelled()) {
60
+ this.fail("cancelled:" + parsePrimeSearchPercent(this.startedAtMs, appWorkerClockWallClockSinceEpochMs()).toString());
61
+ return;
62
+ }
63
+ let now = appWorkerClockWallClockSinceEpochMs();
64
+ const sliceDeadline = this.nextYieldAtMs < this.deadlineMs ? this.nextYieldAtMs : this.deadlineMs;
65
+ while (now < sliceDeadline) {
66
+ if (isPrime(this.candidate)) {
67
+ this.largestPrime = this.candidate;
68
+ }
69
+ this.candidate += 1;
70
+ if ((this.candidate & PRIME_TIME_CHECK_MASK) == 0) {
71
+ now = appWorkerClockWallClockSinceEpochMs();
72
+ }
73
+ }
74
+ now = appWorkerClockWallClockSinceEpochMs();
75
+ this.reportProgress(parsePrimeSearchPercent(this.startedAtMs, now).toString());
76
+ if (now >= this.deadlineMs) {
77
+ this.complete(this.largestPrime.toString());
78
+ return;
79
+ }
80
+ this.nextYieldAtMs += PRIME_SEARCH_YIELD_INTERVAL_MS;
81
+ if (this.nextYieldAtMs > this.deadlineMs) {
82
+ this.nextYieldAtMs = this.deadlineMs;
83
+ }
84
+ this.yield();
85
+ }
86
+ }
87
+
88
+ let largestPrimeCalculatorJob: LargestPrimeCalculatorJob | null = null;
89
+
90
+ export function largestPrimeCalculatorWorker(): void {
91
+ let activeJob = largestPrimeCalculatorJob;
92
+ if (activeJob === null) {
93
+ activeJob = new LargestPrimeCalculatorJob();
94
+ }
95
+ largestPrimeCalculatorJob = WorkerJob.resume<LargestPrimeCalculatorJob>(activeJob);
96
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "stage": {}
3
+ }
@@ -0,0 +1,11 @@
1
+ import { appWorkerHostServices } from "./src/host/worker-host-services";
2
+
3
+ declare global {
4
+ // eslint-disable-next-line no-var
5
+ var __fuiWorkerHostServicesModule: Record<string, unknown> | undefined;
6
+ }
7
+
8
+ globalThis.__fuiWorkerHostServicesModule = {
9
+ ...(globalThis.__fuiWorkerHostServicesModule ?? {}),
10
+ appWorkerHostServices,
11
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 1,
3
+ "entries": {
4
+ "largestPrimeCalculatorWorker": "./advanced-workers.wasm"
5
+ }
6
+ }