@cubingcenter/scrambler 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +10 -2
- package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
- package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/search.js +3 -3
- package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
- package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/tables.js +16 -2
- package/dist/puzzles/cube3/index.d.ts +1 -0
- package/dist/puzzles/cube3/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.js +104 -18
- package/dist/puzzles/cube3/scrambler.d.ts +16 -1
- package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube3/scrambler.js +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +48 -22
- package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist/puzzles/cube3/solver/index.d.ts +5 -3
- package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/index.js +2 -1
- package/dist/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist/puzzles/cube3/solver/phase1.d.ts +37 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +156 -15
- package/dist/puzzles/cube3/solver/phase2.d.ts +75 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +279 -68
- package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
- package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
- package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/symmetry.js +74 -0
- package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/tables.js +14 -12
- package/dist/puzzles/cube3/validation.d.ts.map +1 -1
- package/dist/puzzles/cube3/validation.js +15 -7
- package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
- package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
- package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
- package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker.js +31 -0
- package/dist/puzzles/cube4/index.d.ts +10 -2
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +6 -2
- package/dist/puzzles/cube4/moves.d.ts +5 -0
- package/dist/puzzles/cube4/moves.d.ts.map +1 -1
- package/dist/puzzles/cube4/moves.js +212 -11
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +29 -1
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/browser-loader.js +227 -32
- package/dist/puzzles/cube4/reduction/center2.d.ts +44 -3
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/center2.js +152 -10
- package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
- package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/edge3.js +23 -3
- package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
- package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/finish3.js +393 -0
- package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
- package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +52 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.js +220 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +15 -1
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/node-loader.js +199 -25
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
- package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts +116 -7
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/phase3-search.js +458 -32
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
- package/dist/puzzles/cube4/reduction/solve.d.ts +172 -2
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/solve.js +1201 -180
- package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
- package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
- package/dist/puzzles/cube4/scramble-cache.js +150 -0
- package/dist/puzzles/cube4/scrambler.d.ts +201 -6
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +414 -176
- package/dist/puzzles/cube4/state.d.ts.map +1 -1
- package/dist/puzzles/cube4/state.js +4 -1
- package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
- package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-pool.js +405 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-protocol.js +58 -0
- package/dist/puzzles/cube4/worker.d.ts +26 -0
- package/dist/puzzles/cube4/worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker.js +49 -0
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/search.js +3 -6
- package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
- package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/tables.js +17 -0
- package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
- package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
- package/dist/puzzles/shared/compressed-fetch.js +226 -31
- package/dist/puzzles/shared/load-once.d.ts +16 -0
- package/dist/puzzles/shared/load-once.d.ts.map +1 -0
- package/dist/puzzles/shared/load-once.js +11 -0
- package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
- package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist/puzzles/skewb/solver/search.js +3 -3
- package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
- package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/tables.js +17 -0
- package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
- package/dist-cjs/core/api.js +10 -2
- package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
- package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
- package/dist-cjs/puzzles/cube3/moves.js +104 -18
- package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
- package/dist-cjs/puzzles/cube3/solver/browser-loader.js +48 -22
- package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
- package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist-cjs/puzzles/cube3/solver/phase1.js +157 -15
- package/dist-cjs/puzzles/cube3/solver/phase2.js +276 -65
- package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
- package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
- package/dist-cjs/puzzles/cube3/validation.js +15 -7
- package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
- package/dist-cjs/puzzles/cube4/index.js +23 -1
- package/dist-cjs/puzzles/cube4/moves.js +213 -11
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +227 -32
- package/dist-cjs/puzzles/cube4/reduction/center2.js +155 -10
- package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
- package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +232 -0
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +200 -25
- package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
- package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +460 -33
- package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
- package/dist-cjs/puzzles/cube4/reduction/solve.js +1203 -172
- package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
- package/dist-cjs/puzzles/cube4/scrambler.js +416 -174
- package/dist-cjs/puzzles/cube4/state.js +4 -1
- package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
- package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
- package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
- package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
- package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
- package/dist-cjs/puzzles/shared/load-once.js +14 -0
- package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
- package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
- package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
- package/docs/development.md +19 -7
- package/package.json +17 -7
|
@@ -44,8 +44,11 @@ function cube4StatesEqual(a, b) {
|
|
|
44
44
|
typedArrayEqual(a.wp, b.wp) &&
|
|
45
45
|
typedArrayEqual(a.centers, b.centers));
|
|
46
46
|
}
|
|
47
|
+
// Read-only canonical solved state for comparisons (never mutated, so a
|
|
48
|
+
// single shared instance is safe and avoids allocating 4 arrays per call).
|
|
49
|
+
const SOLVED_STATE = createSolvedCube4State();
|
|
47
50
|
function isSolvedCube4State(state) {
|
|
48
|
-
return cube4StatesEqual(state,
|
|
51
|
+
return cube4StatesEqual(state, SOLVED_STATE);
|
|
49
52
|
}
|
|
50
53
|
// ---------------------------------------------------------------------------
|
|
51
54
|
// Helpers
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Worker pool for 4x4 random-state scrambles (Node.js `worker_threads`).
|
|
4
|
+
*
|
|
5
|
+
* Why: generation is CPU-bound in a single event loop — a `Promise.all`
|
|
6
|
+
* batch of N scrambles does not scale. This pool spreads one batch across
|
|
7
|
+
* N threads so `generateScrambles`-style batches grow roughly linearly
|
|
8
|
+
* with core count, while keeping the (seed, complete-options) → moves
|
|
9
|
+
* contract byte-identical to the inline path: tasks forward the FULL
|
|
10
|
+
* options object verbatim and `worker.ts` calls the very same
|
|
11
|
+
* `generateCube4Scramble`.
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle:
|
|
14
|
+
* - Lazy spawn: threads are created on demand (one per queued job, up to
|
|
15
|
+
* `size`), never in the constructor. A cold first generation pays for
|
|
16
|
+
* table loading (≈37 MB) per spawned thread, once; the thread then
|
|
17
|
+
* reuses its tables for every later job (per-thread promise cache inside
|
|
18
|
+
* `scrambler.ts`).
|
|
19
|
+
* - Idle threads are `unref()`d, so a pool that is never terminated does
|
|
20
|
+
* not keep the process alive; in-flight jobs `ref()` their thread so an
|
|
21
|
+
* awaited batch always completes.
|
|
22
|
+
* - One task in flight per thread; extra tasks queue FIFO.
|
|
23
|
+
* - `terminate()` rejects everything queued/in-flight with
|
|
24
|
+
* `Cube4WorkerPoolClosedError` and joins all threads — after it
|
|
25
|
+
* resolves there are no open handles.
|
|
26
|
+
*
|
|
27
|
+
* Failure: a crashed thread only fails ITS in-flight task and is dropped
|
|
28
|
+
* from the pool; the next dispatch spawns a fresh thread (which reloads
|
|
29
|
+
* tables). Typed solver errors from inside a worker arrive as an `Error`
|
|
30
|
+
* with the original `name`, `message` and `code` preserved (see
|
|
31
|
+
* `worker-protocol.ts`).
|
|
32
|
+
*
|
|
33
|
+
* Dual-build note: this file intentionally contains no bare `import.meta`
|
|
34
|
+
* (it must also compile in the CommonJS build that `index.ts` is part of).
|
|
35
|
+
* The worker entry is located from `__filename` (CJS) or this module's top
|
|
36
|
+
* stack frame (ESM), and `dist-cjs` falls back to the sibling ESM tree,
|
|
37
|
+
* which always ships `worker.js`.
|
|
38
|
+
*/
|
|
39
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
40
|
+
if (k2 === undefined) k2 = k;
|
|
41
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
42
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
43
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
44
|
+
}
|
|
45
|
+
Object.defineProperty(o, k2, desc);
|
|
46
|
+
}) : (function(o, m, k, k2) {
|
|
47
|
+
if (k2 === undefined) k2 = k;
|
|
48
|
+
o[k2] = m[k];
|
|
49
|
+
}));
|
|
50
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
51
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
52
|
+
}) : function(o, v) {
|
|
53
|
+
o["default"] = v;
|
|
54
|
+
});
|
|
55
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
56
|
+
var ownKeys = function(o) {
|
|
57
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
58
|
+
var ar = [];
|
|
59
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
60
|
+
return ar;
|
|
61
|
+
};
|
|
62
|
+
return ownKeys(o);
|
|
63
|
+
};
|
|
64
|
+
return function (mod) {
|
|
65
|
+
if (mod && mod.__esModule) return mod;
|
|
66
|
+
var result = {};
|
|
67
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
68
|
+
__setModuleDefault(result, mod);
|
|
69
|
+
return result;
|
|
70
|
+
};
|
|
71
|
+
})();
|
|
72
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
73
|
+
exports.Cube4WorkerPool = exports.Cube4WorkerPoolClosedError = void 0;
|
|
74
|
+
exports.deriveBatchCube4Options = deriveBatchCube4Options;
|
|
75
|
+
exports.getDefaultCube4WorkerPool = getDefaultCube4WorkerPool;
|
|
76
|
+
exports.closeDefaultCube4WorkerPool = closeDefaultCube4WorkerPool;
|
|
77
|
+
exports.generateCube4ScrambleBatch = generateCube4ScrambleBatch;
|
|
78
|
+
const errors_js_1 = require("../../core/errors.js");
|
|
79
|
+
const scrambler_js_1 = require("./scrambler.js");
|
|
80
|
+
const worker_protocol_js_1 = require("./worker-protocol.js");
|
|
81
|
+
/** Typed rejection used for post-terminate operations and shutdown itself. */
|
|
82
|
+
class Cube4WorkerPoolClosedError extends Error {
|
|
83
|
+
code = "cube4-pool-closed";
|
|
84
|
+
constructor(action = "operation") {
|
|
85
|
+
super(`[cube4] worker pool is closed (${action} rejected).`);
|
|
86
|
+
this.name = "Cube4WorkerPoolClosedError";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.Cube4WorkerPoolClosedError = Cube4WorkerPoolClosedError;
|
|
90
|
+
/**
|
|
91
|
+
* Batch seed derivation — byte-for-byte the semantics of
|
|
92
|
+
* `deriveBatchScrambleOptions` in `src/core/api.ts` (`generateScrambles`):
|
|
93
|
+
* with a `seed` and `count > 1`, item i uses `${seed}:${i}`; otherwise the
|
|
94
|
+
* options pass through untouched. Exported so tests can assert the pool
|
|
95
|
+
* batch equals the sequential/core batch for the same inputs.
|
|
96
|
+
*/
|
|
97
|
+
function deriveBatchCube4Options(options, index, count) {
|
|
98
|
+
if (count === 1 || options.seed === undefined) {
|
|
99
|
+
return options;
|
|
100
|
+
}
|
|
101
|
+
return { ...options, seed: `${options.seed}:${index}` };
|
|
102
|
+
}
|
|
103
|
+
const TSX_ARGV = ["--import", "tsx"];
|
|
104
|
+
/**
|
|
105
|
+
* Locates this module's own file (extension included), dual-build safe:
|
|
106
|
+
* CJS knows it as `__filename`; in ESM we take the top stack frame, which
|
|
107
|
+
* V8 reports as the module URL (`file://…`) of `worker-pool.ts|js` — the
|
|
108
|
+
* path is correct whether or not source maps are enabled (they only move
|
|
109
|
+
* line/column, never the module URL).
|
|
110
|
+
*/
|
|
111
|
+
function resolveCurrentModuleLocation() {
|
|
112
|
+
if (typeof __filename === "string" && __filename.length > 0) {
|
|
113
|
+
return __filename;
|
|
114
|
+
}
|
|
115
|
+
const holder = new Error();
|
|
116
|
+
Error.captureStackTrace?.(holder);
|
|
117
|
+
const raw = holder.stack;
|
|
118
|
+
const candidates = [];
|
|
119
|
+
if (typeof raw === "string") {
|
|
120
|
+
for (const line of raw.split("\n")) {
|
|
121
|
+
const match = /^\s*at (?:(?:.*?\s+\())?(.+?):\d+:\d+\)?$/.exec(line.trim());
|
|
122
|
+
if (match && match[1] !== undefined)
|
|
123
|
+
candidates.push(match[1]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (Array.isArray(raw)) {
|
|
127
|
+
for (const frame of raw) {
|
|
128
|
+
const name = frame?.getFileName?.();
|
|
129
|
+
if (typeof name === "string")
|
|
130
|
+
candidates.push(name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const candidate of candidates) {
|
|
134
|
+
if (candidate.includes("worker-pool.")) {
|
|
135
|
+
return candidate.startsWith("file://") ? fileUrlToPath(candidate) : candidate;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
/** Minimal `file://` → path conversion (global `URL`, no node:url import). */
|
|
141
|
+
function fileUrlToPath(value) {
|
|
142
|
+
try {
|
|
143
|
+
let pathname = decodeURIComponent(new URL(value).pathname);
|
|
144
|
+
// Windows file URLs keep a leading slash before the drive letter.
|
|
145
|
+
if (/^\/[A-Za-z]:/.test(pathname))
|
|
146
|
+
pathname = pathname.slice(1);
|
|
147
|
+
return pathname;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return value.replace(/^file:\/\//, "");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
class Cube4WorkerPool {
|
|
154
|
+
workers = [];
|
|
155
|
+
queue = [];
|
|
156
|
+
explicitSize;
|
|
157
|
+
workerPathOption;
|
|
158
|
+
sizePromise;
|
|
159
|
+
/** Last resolved size, kept sync-readable for the pump's headroom check. */
|
|
160
|
+
sizeSync = 0;
|
|
161
|
+
pumping;
|
|
162
|
+
terminatePromise;
|
|
163
|
+
nextId = 0;
|
|
164
|
+
terminated = false;
|
|
165
|
+
constructor(options = {}) {
|
|
166
|
+
if (options.size !== undefined) {
|
|
167
|
+
if (!Number.isInteger(options.size) || options.size < 1) {
|
|
168
|
+
throw new RangeError(`[cube4] pool size must be an integer >= 1 (got ${options.size}).`);
|
|
169
|
+
}
|
|
170
|
+
this.sizeSync = options.size;
|
|
171
|
+
}
|
|
172
|
+
this.explicitSize = options.size;
|
|
173
|
+
this.workerPathOption = options.workerPath;
|
|
174
|
+
}
|
|
175
|
+
/** Maximum thread count (resolves the default lazily on first use). */
|
|
176
|
+
get size() {
|
|
177
|
+
return this.resolveSize();
|
|
178
|
+
}
|
|
179
|
+
/** Threads spawned so far (lazily grown; 0 before the first job). */
|
|
180
|
+
get workerCount() {
|
|
181
|
+
return this.workers.length;
|
|
182
|
+
}
|
|
183
|
+
get queuedCount() {
|
|
184
|
+
return this.queue.length;
|
|
185
|
+
}
|
|
186
|
+
get busyCount() {
|
|
187
|
+
return this.workers.reduce((n, rec) => n + (rec.task !== null ? 1 : 0), 0);
|
|
188
|
+
}
|
|
189
|
+
get closed() {
|
|
190
|
+
return this.terminated;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Generates one scramble on an available thread. Rejects (typed) if the
|
|
194
|
+
* pool is closed, the worker crashes, the options cannot be
|
|
195
|
+
* structured-cloned, or generation fails inside the worker.
|
|
196
|
+
*/
|
|
197
|
+
generate(options = {}) {
|
|
198
|
+
if (this.terminated) {
|
|
199
|
+
return Promise.reject(new Cube4WorkerPoolClosedError("generate"));
|
|
200
|
+
}
|
|
201
|
+
const id = this.nextId++;
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
this.queue.push({ id, options, resolve, reject });
|
|
204
|
+
this.pump();
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Batch generation with EXACTLY the seed derivation of core
|
|
209
|
+
* `generateScrambles` (see `deriveBatchCube4Options`), so results are
|
|
210
|
+
* identical — move for move — to the sequential path for the same
|
|
211
|
+
* inputs. Order of the returned array matches request order regardless
|
|
212
|
+
* of completion order.
|
|
213
|
+
*/
|
|
214
|
+
generateBatch(count, options = {}) {
|
|
215
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
216
|
+
return Promise.reject(new errors_js_1.InvalidCountError(count));
|
|
217
|
+
}
|
|
218
|
+
return Promise.all(Array.from({ length: count }, (_, index) => this.generate(deriveBatchCube4Options(options, index, count))));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Prompt shutdown: rejects queued and in-flight tasks with
|
|
222
|
+
* `Cube4WorkerPoolClosedError`, terminates every thread and resolves
|
|
223
|
+
* when none is left. Idempotent.
|
|
224
|
+
*/
|
|
225
|
+
terminate() {
|
|
226
|
+
if (this.terminatePromise !== undefined)
|
|
227
|
+
return this.terminatePromise;
|
|
228
|
+
this.terminated = true;
|
|
229
|
+
for (const task of this.queue.splice(0)) {
|
|
230
|
+
task.reject(new Cube4WorkerPoolClosedError("queued task"));
|
|
231
|
+
}
|
|
232
|
+
const records = this.workers.splice(0);
|
|
233
|
+
const joins = [];
|
|
234
|
+
for (const rec of records) {
|
|
235
|
+
if (rec.task !== null) {
|
|
236
|
+
rec.task.reject(new Cube4WorkerPoolClosedError("in-flight task"));
|
|
237
|
+
rec.task = null;
|
|
238
|
+
}
|
|
239
|
+
joins.push(rec.worker.terminate().then(() => undefined));
|
|
240
|
+
}
|
|
241
|
+
this.terminatePromise = Promise.all(joins).then(() => undefined);
|
|
242
|
+
return this.terminatePromise;
|
|
243
|
+
}
|
|
244
|
+
// ── internals ────────────────────────────────────────────────────────
|
|
245
|
+
resolveSize() {
|
|
246
|
+
if (this.explicitSize !== undefined)
|
|
247
|
+
return Promise.resolve(this.explicitSize);
|
|
248
|
+
this.sizePromise ??= (async () => {
|
|
249
|
+
try {
|
|
250
|
+
// @vite-ignore: Node-only; the browser bundle never executes this.
|
|
251
|
+
const os = await Promise.resolve().then(() => __importStar(require(/* webpackIgnore: true */ /* @vite-ignore */ "node:os")));
|
|
252
|
+
const parallelism = typeof os.availableParallelism === "function"
|
|
253
|
+
? os.availableParallelism()
|
|
254
|
+
: os.cpus().length;
|
|
255
|
+
this.sizeSync = Math.max(1, parallelism - 1);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
this.sizeSync = 4;
|
|
259
|
+
}
|
|
260
|
+
return this.sizeSync;
|
|
261
|
+
})();
|
|
262
|
+
return this.sizePromise;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Serialised dispatcher: grows the pool (lazy spawn), hands queued jobs
|
|
266
|
+
* to idle threads, and stops when every thread is busy at capacity.
|
|
267
|
+
* Re-entry while a spawn is awaited is a no-op — the running loop's
|
|
268
|
+
* `while` re-checks the queue, and every completion event (`response`,
|
|
269
|
+
* `crash`) calls `pump()` again.
|
|
270
|
+
*/
|
|
271
|
+
pump() {
|
|
272
|
+
if (this.pumping !== undefined || this.terminated)
|
|
273
|
+
return;
|
|
274
|
+
this.pumping = this.runPump()
|
|
275
|
+
.catch(() => undefined) // runPump handles failures per task; belt and braces
|
|
276
|
+
.finally(() => {
|
|
277
|
+
this.pumping = undefined;
|
|
278
|
+
// A task pushed by a microtask that landed between runPump's last
|
|
279
|
+
// queue check and this finally would have been skipped (pump was
|
|
280
|
+
// still "running"). Re-pump only when dispatch can make actual
|
|
281
|
+
// progress — an idle thread or spawn headroom — so tasks parked at
|
|
282
|
+
// capacity (which a completion event will pump) cannot spin here.
|
|
283
|
+
if (this.queue.length > 0 && !this.terminated && this.hasDispatchHeadroom()) {
|
|
284
|
+
this.pump();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
hasDispatchHeadroom() {
|
|
289
|
+
if (this.workers.some((rec) => rec.task === null))
|
|
290
|
+
return true;
|
|
291
|
+
return this.sizeSync > 0 && this.workers.length < this.sizeSync;
|
|
292
|
+
}
|
|
293
|
+
async runPump() {
|
|
294
|
+
while (!this.terminated && this.queue.length > 0) {
|
|
295
|
+
const idle = this.workers.find((rec) => rec.task === null);
|
|
296
|
+
if (idle !== undefined) {
|
|
297
|
+
this.dispatch(idle, this.queue.shift());
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (this.workers.length < (await this.resolveSize())) {
|
|
301
|
+
let rec;
|
|
302
|
+
try {
|
|
303
|
+
rec = await this.spawnWorker();
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
// Spawning failed (missing entry, bad workerPath, …): fail THIS
|
|
307
|
+
// task and keep trying for the rest of the queue.
|
|
308
|
+
this.queue.shift()?.reject(error);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
continue; // the fresh thread is idle → dispatched on the next pass
|
|
312
|
+
}
|
|
313
|
+
break; // at capacity with every thread busy — wait for a response
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async spawnWorker() {
|
|
317
|
+
// @vite-ignore: Node-only; never executed in a browser bundle.
|
|
318
|
+
const { Worker: WorkerCtor } = await Promise.resolve().then(() => __importStar(require(
|
|
319
|
+
/* webpackIgnore: true */ /* @vite-ignore */ "node:worker_threads")));
|
|
320
|
+
if (this.terminated)
|
|
321
|
+
throw new Cube4WorkerPoolClosedError("spawn");
|
|
322
|
+
const entry = this.resolveWorkerEntry();
|
|
323
|
+
const worker = new WorkerCtor(entry.path, { execArgv: [...entry.execArgv] });
|
|
324
|
+
const rec = { worker, task: null };
|
|
325
|
+
worker.on("message", (response) => {
|
|
326
|
+
this.handleResponse(rec, response);
|
|
327
|
+
});
|
|
328
|
+
worker.on("error", (error) => {
|
|
329
|
+
this.handleWorkerGone(rec, error);
|
|
330
|
+
});
|
|
331
|
+
worker.on("exit", (code) => {
|
|
332
|
+
this.handleWorkerGone(rec, new Error(`[cube4] worker exited unexpectedly (code ${code}).`));
|
|
333
|
+
});
|
|
334
|
+
this.workers.push(rec);
|
|
335
|
+
worker.unref(); // idle threads must not keep the process alive
|
|
336
|
+
return rec;
|
|
337
|
+
}
|
|
338
|
+
dispatch(rec, task) {
|
|
339
|
+
rec.task = task;
|
|
340
|
+
rec.worker.ref(); // an awaited job keeps the event loop alive
|
|
341
|
+
const message = { id: task.id, options: task.options };
|
|
342
|
+
try {
|
|
343
|
+
rec.worker.postMessage(message);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
// e.g. DataCloneError for non-cloneable options.metadata — the
|
|
347
|
+
// thread is fine; fail only this task.
|
|
348
|
+
rec.task = null;
|
|
349
|
+
rec.worker.unref();
|
|
350
|
+
task.reject(error);
|
|
351
|
+
this.pump();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
handleResponse(rec, response) {
|
|
355
|
+
const task = rec.task;
|
|
356
|
+
if (task === null || task.id !== response.id)
|
|
357
|
+
return; // stale/unknown id
|
|
358
|
+
rec.task = null;
|
|
359
|
+
if (this.terminated)
|
|
360
|
+
return;
|
|
361
|
+
if (response.ok)
|
|
362
|
+
task.resolve(response.result);
|
|
363
|
+
else
|
|
364
|
+
task.reject((0, worker_protocol_js_1.deserializeCube4Error)(response.error));
|
|
365
|
+
const next = this.queue.shift();
|
|
366
|
+
if (next !== undefined)
|
|
367
|
+
this.dispatch(rec, next);
|
|
368
|
+
else
|
|
369
|
+
rec.worker.unref();
|
|
370
|
+
this.pump();
|
|
371
|
+
}
|
|
372
|
+
handleWorkerGone(rec, error) {
|
|
373
|
+
const index = this.workers.indexOf(rec);
|
|
374
|
+
if (index === -1)
|
|
375
|
+
return; // already removed ('error' precedes 'exit')
|
|
376
|
+
this.workers.splice(index, 1);
|
|
377
|
+
const task = rec.task;
|
|
378
|
+
rec.task = null;
|
|
379
|
+
if (task !== null && !this.terminated) {
|
|
380
|
+
// Only THIS thread's job fails; the rest of the queue keeps running
|
|
381
|
+
// (a replacement thread is spawned lazily by pump()).
|
|
382
|
+
task.reject(error);
|
|
383
|
+
}
|
|
384
|
+
if (!this.terminated)
|
|
385
|
+
this.pump();
|
|
386
|
+
}
|
|
387
|
+
resolveWorkerEntry() {
|
|
388
|
+
if (this.workerPathOption !== undefined) {
|
|
389
|
+
return {
|
|
390
|
+
path: this.workerPathOption,
|
|
391
|
+
execArgv: this.workerPathOption.endsWith(".ts") ? [...TSX_ARGV] : []
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const hereRaw = resolveCurrentModuleLocation();
|
|
395
|
+
if (hereRaw === undefined) {
|
|
396
|
+
throw new Error("[cube4] cannot locate worker.ts/worker.js next to worker-pool.js; " +
|
|
397
|
+
"pass an explicit `workerPath` to Cube4WorkerPool.");
|
|
398
|
+
}
|
|
399
|
+
// Normalise Windows separators: forward slashes are valid in every
|
|
400
|
+
// Node path/URL consumer here (Worker entry, fs lookups by Node).
|
|
401
|
+
const here = hereRaw.replace(/\\/g, "/");
|
|
402
|
+
const separator = here.lastIndexOf("/");
|
|
403
|
+
const dir = separator === -1 ? "." : here.slice(0, separator);
|
|
404
|
+
if (here.endsWith(".ts")) {
|
|
405
|
+
// Running from source under tsx → the thread needs the tsx loader.
|
|
406
|
+
return { path: `${dir}/worker.ts`, execArgv: [...TSX_ARGV] };
|
|
407
|
+
}
|
|
408
|
+
const distCjs = dir.lastIndexOf("/dist-cjs");
|
|
409
|
+
if (distCjs !== -1) {
|
|
410
|
+
// The CJS build excludes the worker entry (it is Node-ESM-only); the
|
|
411
|
+
// ESM tree is always published alongside and always contains it.
|
|
412
|
+
const esmDir = `${dir.slice(0, distCjs)}/dist${dir.slice(distCjs + "/dist-cjs".length)}`;
|
|
413
|
+
return { path: `${esmDir}/worker.js`, execArgv: [] };
|
|
414
|
+
}
|
|
415
|
+
return { path: `${dir}/worker.js`, execArgv: [] };
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
exports.Cube4WorkerPool = Cube4WorkerPool;
|
|
419
|
+
// ── module-level conveniences ──────────────────────────────────────────
|
|
420
|
+
let defaultPool;
|
|
421
|
+
/** Shared lazily-created pool (used by `generateCube4ScrambleBatch`). */
|
|
422
|
+
function getDefaultCube4WorkerPool() {
|
|
423
|
+
defaultPool ??= new Cube4WorkerPool();
|
|
424
|
+
return defaultPool;
|
|
425
|
+
}
|
|
426
|
+
/** Terminates (and forgets) the shared pool — call on process shutdown. */
|
|
427
|
+
async function closeDefaultCube4WorkerPool() {
|
|
428
|
+
const pool = defaultPool;
|
|
429
|
+
defaultPool = undefined;
|
|
430
|
+
if (pool !== undefined)
|
|
431
|
+
await pool.terminate();
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Batch convenience mirroring core `generateScrambles` for `444`:
|
|
435
|
+
* - `count <= 1` runs INLINE in the caller (no thread spawn, no cold
|
|
436
|
+
* start — a single scramble must never get slower);
|
|
437
|
+
* - `count > 1` fans out over the shared pool (same seed derivation).
|
|
438
|
+
*/
|
|
439
|
+
async function generateCube4ScrambleBatch(count, options = {}) {
|
|
440
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
441
|
+
throw new errors_js_1.InvalidCountError(count);
|
|
442
|
+
}
|
|
443
|
+
if (count === 1) {
|
|
444
|
+
return [await (0, scrambler_js_1.generateCube4Scramble)(options)];
|
|
445
|
+
}
|
|
446
|
+
return getDefaultCube4WorkerPool().generateBatch(count, options);
|
|
447
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Message protocol shared by the 4x4 Node worker (`worker.ts`), its pool
|
|
4
|
+
* (`worker-pool.ts`), and the browser equivalents (`browser-worker.ts`,
|
|
5
|
+
* `browser-worker-pool.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Design notes:
|
|
8
|
+
* - Tasks carry the FULL `generateCube4Scramble` options object. The
|
|
9
|
+
* scrambler is a pure function of (seed, complete options), so forwarding
|
|
10
|
+
* every option verbatim is what makes the pool output bit-identical to
|
|
11
|
+
* the inline path for the same inputs.
|
|
12
|
+
* - Errors cross the thread boundary as data (`Cube4SerializedError`):
|
|
13
|
+
* `name`, `message`, `code` and `stack` survive, so typed solver errors
|
|
14
|
+
* (e.g. `Cube4TargetMismatchError` with `code: "cube4-target-mismatch"`)
|
|
15
|
+
* remain discriminated on the consumer side by name/code — the same
|
|
16
|
+
* classification keys `scrambler.ts` itself uses. Classes cannot be
|
|
17
|
+
* structured-cloned, so `instanceof` does not cross the boundary
|
|
18
|
+
* (documented contract).
|
|
19
|
+
*
|
|
20
|
+
* This module is deliberately free of runtime imports (type-only imports
|
|
21
|
+
* are erased at emit) so it can be bundled for the browser worker without
|
|
22
|
+
* dragging Node-only code into the graph.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.serializeCube4Error = serializeCube4Error;
|
|
26
|
+
exports.deserializeCube4Error = deserializeCube4Error;
|
|
27
|
+
/**
|
|
28
|
+
* Flattens an unknown thrown value into the structured-clone-safe shape
|
|
29
|
+
* above. Non-Error values are stringified so nothing is lost silently.
|
|
30
|
+
*/
|
|
31
|
+
function serializeCube4Error(error) {
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
const code = error.code;
|
|
34
|
+
const primitiveCode = typeof code === "string" || typeof code === "number" ? code : undefined;
|
|
35
|
+
const serialized = {
|
|
36
|
+
name: error.name,
|
|
37
|
+
message: error.message
|
|
38
|
+
};
|
|
39
|
+
if (primitiveCode !== undefined)
|
|
40
|
+
serialized.code = primitiveCode;
|
|
41
|
+
if (typeof error.stack === "string")
|
|
42
|
+
serialized.stack = error.stack;
|
|
43
|
+
return serialized;
|
|
44
|
+
}
|
|
45
|
+
return { name: "Error", message: String(error) };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Rebuilds an Error from the wire shape, restoring `name`, `code` and
|
|
49
|
+
* `stack` (stack kept only as a debugging aid — its file locations point
|
|
50
|
+
* into the worker thread).
|
|
51
|
+
*/
|
|
52
|
+
function deserializeCube4Error(serialized) {
|
|
53
|
+
const error = new Error(serialized.message);
|
|
54
|
+
error.name = serialized.name;
|
|
55
|
+
if (serialized.code !== undefined) {
|
|
56
|
+
error.code = serialized.code;
|
|
57
|
+
}
|
|
58
|
+
if (serialized.stack !== undefined) {
|
|
59
|
+
error.stack = serialized.stack;
|
|
60
|
+
}
|
|
61
|
+
return error;
|
|
62
|
+
}
|
|
@@ -5,23 +5,43 @@ exports.clearCachedBrowserTables = clearCachedBrowserTables;
|
|
|
5
5
|
exports.loadBrowserTables = loadBrowserTables;
|
|
6
6
|
const table_metadata_js_1 = require("./table-metadata.js");
|
|
7
7
|
const compressed_fetch_js_1 = require("../../shared/compressed-fetch.js");
|
|
8
|
+
const TABLES_DIR = "puzzles/pyraminx/solver/generated";
|
|
9
|
+
const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
|
|
8
10
|
let cachedBrowserTables;
|
|
11
|
+
let cachedBrowserTablesPromise;
|
|
9
12
|
function getCachedBrowserTables() {
|
|
10
13
|
return cachedBrowserTables;
|
|
11
14
|
}
|
|
12
15
|
function clearCachedBrowserTables() {
|
|
13
16
|
cachedBrowserTables = undefined;
|
|
17
|
+
cachedBrowserTablesPromise = undefined;
|
|
14
18
|
}
|
|
15
19
|
async function loadBrowserTables(baseUrl) {
|
|
16
20
|
if (cachedBrowserTables)
|
|
17
21
|
return cachedBrowserTables;
|
|
22
|
+
if (cachedBrowserTablesPromise) {
|
|
23
|
+
// One shared in-flight promise: concurrent calls trigger exactly one
|
|
24
|
+
// fetch pass (dedup).
|
|
25
|
+
return cachedBrowserTablesPromise;
|
|
26
|
+
}
|
|
27
|
+
const promise = fetchTables(baseUrl);
|
|
28
|
+
cachedBrowserTablesPromise = promise;
|
|
29
|
+
promise.then(() => { }, () => {
|
|
30
|
+
// A failed load must not poison the dedup slot: purge the rejected
|
|
31
|
+
// promise so a later call retries from scratch.
|
|
32
|
+
if (cachedBrowserTablesPromise === promise)
|
|
33
|
+
cachedBrowserTablesPromise = undefined;
|
|
34
|
+
});
|
|
35
|
+
return promise;
|
|
36
|
+
}
|
|
37
|
+
async function fetchTables(baseUrl) {
|
|
18
38
|
const resolved = resolveBaseUrl(baseUrl);
|
|
19
39
|
const manifest = await fetchManifest(resolved);
|
|
20
40
|
const [centersTable, permutationTable, orientationTable, policyTable] = await Promise.all([
|
|
21
|
-
fetchUint8Table(resolved, manifest.files.centersTable, table_metadata_js_1.PYRAMINX_SOLVER_TABLE_METADATA.centersTable
|
|
22
|
-
fetchUint16Table(resolved, manifest.files.permutationTable, table_metadata_js_1.PYRAMINX_SOLVER_TABLE_METADATA.permutationTable
|
|
23
|
-
fetchUint8Table(resolved, manifest.files.orientationTable, table_metadata_js_1.PYRAMINX_SOLVER_TABLE_METADATA.orientationTable
|
|
24
|
-
fetchUint8Table(resolved, manifest.files.policyTable, table_metadata_js_1.PYRAMINX_SOLVER_TABLE_METADATA.policyTable
|
|
41
|
+
fetchUint8Table(resolved, manifest.files.centersTable, table_metadata_js_1.PYRAMINX_SOLVER_TABLE_METADATA.centersTable),
|
|
42
|
+
fetchUint16Table(resolved, manifest.files.permutationTable, table_metadata_js_1.PYRAMINX_SOLVER_TABLE_METADATA.permutationTable),
|
|
43
|
+
fetchUint8Table(resolved, manifest.files.orientationTable, table_metadata_js_1.PYRAMINX_SOLVER_TABLE_METADATA.orientationTable),
|
|
44
|
+
fetchUint8Table(resolved, manifest.files.policyTable, table_metadata_js_1.PYRAMINX_SOLVER_TABLE_METADATA.policyTable)
|
|
25
45
|
]);
|
|
26
46
|
cachedBrowserTables = { centersTable, permutationTable, orientationTable, policyTable };
|
|
27
47
|
return cachedBrowserTables;
|
|
@@ -48,19 +68,28 @@ async function fetchManifest(baseUrl) {
|
|
|
48
68
|
throw new Error(`Unsupported pyraminx solver table manifest version: ${manifest.version}`);
|
|
49
69
|
return manifest;
|
|
50
70
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Fetch + verify with the SAME checks as node-loader.ts: byte length from
|
|
73
|
+
* the manifest entries, FNV-1a checksum against the table-metadata
|
|
74
|
+
* constants (the values node asserts against).
|
|
75
|
+
*/
|
|
76
|
+
async function fetchUint8Table(baseUrl, entry, meta) {
|
|
77
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
78
|
+
cacheName: CACHE_NAME,
|
|
79
|
+
compressed: entry.compressed !== false,
|
|
80
|
+
expectedBytes: entry.entries,
|
|
81
|
+
expectedFnv1a: { value: meta.checksum, elementSize: 1 }
|
|
82
|
+
});
|
|
57
83
|
return new Uint8Array(buffer);
|
|
58
84
|
}
|
|
59
|
-
async function fetchUint16Table(baseUrl, entry,
|
|
60
|
-
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
async function fetchUint16Table(baseUrl, entry, meta) {
|
|
86
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
87
|
+
cacheName: CACHE_NAME,
|
|
88
|
+
compressed: entry.compressed !== false,
|
|
89
|
+
expectedBytes: entry.entries * 2,
|
|
90
|
+
expectedFnv1a: { value: meta.checksum, elementSize: 2 }
|
|
91
|
+
});
|
|
92
|
+
const values = new Uint16Array(entry.entries);
|
|
64
93
|
const view = new DataView(buffer);
|
|
65
94
|
for (let i = 0; i < values.length; i++)
|
|
66
95
|
values[i] = view.getUint16(i * 2, true);
|
|
@@ -35,7 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.loadGeneratedPyraminxSolverTables = loadGeneratedPyraminxSolverTables;
|
|
37
37
|
const browser_loader_js_1 = require("./browser-loader.js");
|
|
38
|
-
|
|
38
|
+
const load_once_js_1 = require("../../shared/load-once.js");
|
|
39
|
+
// One shared in-flight promise for the node fs load (same pattern as
|
|
40
|
+
// cube3/solver/generated-assets.ts): the previous `cached ??= await …` raced —
|
|
41
|
+
// concurrent callers interleaving on the await both ran `loadNodeTables()`
|
|
42
|
+
// (two filesystem reads, last assignment wins). On rejection the slot is
|
|
43
|
+
// purged so the next call can retry.
|
|
44
|
+
const nodeTablesSlot = { promise: undefined };
|
|
39
45
|
function isBrowser() {
|
|
40
46
|
return typeof self !== "undefined";
|
|
41
47
|
}
|
|
@@ -48,8 +54,7 @@ async function loadGeneratedPyraminxSolverTables() {
|
|
|
48
54
|
"In browser environments, call `initializeScrambler(baseUrl)` " +
|
|
49
55
|
"before generating scrambles.");
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
return cachedTables;
|
|
57
|
+
return (0, load_once_js_1.loadOnce)(nodeTablesSlot, loadNodeTables);
|
|
53
58
|
}
|
|
54
59
|
async function loadNodeTables() {
|
|
55
60
|
const loader = await Promise.resolve().then(() => __importStar(require(
|
|
@@ -38,7 +38,7 @@ function solveWithTablesIndices(state) {
|
|
|
38
38
|
const centersTable = tables_js_1.CENTERS_TABLE;
|
|
39
39
|
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
40
40
|
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
41
|
-
const policy = tables_js_1.
|
|
41
|
+
const policy = tables_js_1.POLICY_TABLE_EXPANDED;
|
|
42
42
|
let c = (0, tables_js_1.getCentersIndex)(state.centers);
|
|
43
43
|
let p = (0, tables_js_1.getPermutationCompactIndex)(state.edgesPerm);
|
|
44
44
|
let o = (0, tables_js_1.getOrientationIndex)(state.edgesOrient);
|
|
@@ -47,10 +47,7 @@ function solveWithTablesIndices(state) {
|
|
|
47
47
|
return [];
|
|
48
48
|
const moves = [];
|
|
49
49
|
while (stateIndex !== 0) {
|
|
50
|
-
const
|
|
51
|
-
const byteIndex = bits >> 3;
|
|
52
|
-
const offset = bits & 7;
|
|
53
|
-
const m = ((policy[byteIndex] | ((policy[byteIndex + 1] ?? 0) << 8)) >> offset) & 0x07;
|
|
50
|
+
const m = policy[stateIndex];
|
|
54
51
|
moves.push(m);
|
|
55
52
|
c = centersTable[c * tables_js_1.MOVE_COUNT + m];
|
|
56
53
|
p = permTable[p * tables_js_1.MOVE_COUNT + m];
|