@bldrs-ai/conway 1.401.1237 → 1.404.1241
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/compiled/examples/browser-bundled.cjs +1 -1
- package/compiled/examples/cli-bundled.cjs +1 -1
- package/compiled/examples/cli-step-bundled.cjs +1 -1
- package/compiled/examples/validator-bundled.cjs +1 -1
- package/compiled/src/core/demand_geometry_queue.d.ts +147 -0
- package/compiled/src/core/demand_geometry_queue.d.ts.map +1 -0
- package/compiled/src/core/demand_geometry_queue.js +223 -0
- package/compiled/src/core/demand_geometry_queue.test.d.ts +2 -0
- package/compiled/src/core/demand_geometry_queue.test.d.ts.map +1 -0
- package/compiled/src/core/demand_geometry_queue.test.js +133 -0
- package/compiled/src/step/parsing/index_sidecar.d.ts +53 -0
- package/compiled/src/step/parsing/index_sidecar.d.ts.map +1 -0
- package/compiled/src/step/parsing/index_sidecar.js +166 -0
- package/compiled/src/step/parsing/index_sidecar.test.d.ts +2 -0
- package/compiled/src/step/parsing/index_sidecar.test.d.ts.map +1 -0
- package/compiled/src/step/parsing/index_sidecar.test.js +92 -0
- package/compiled/src/step/parsing/range_byte_source.d.ts +69 -0
- package/compiled/src/step/parsing/range_byte_source.d.ts.map +1 -0
- package/compiled/src/step/parsing/range_byte_source.js +78 -0
- package/compiled/src/step/parsing/range_byte_source.test.d.ts +2 -0
- package/compiled/src/step/parsing/range_byte_source.test.d.ts.map +1 -0
- package/compiled/src/step/parsing/range_byte_source.test.js +70 -0
- package/compiled/src/version/version.js +1 -1
- package/compiled/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -30,7 +30,7 @@ var import_node_process = require("node:process");
|
|
|
30
30
|
var readline = __toESM(require("node:readline"), 1);
|
|
31
31
|
|
|
32
32
|
// compiled/src/version/version.js
|
|
33
|
-
var versionString = "Conway v1.
|
|
33
|
+
var versionString = "Conway v1.404.1241";
|
|
34
34
|
|
|
35
35
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
36
36
|
var wasmType = "";
|
|
@@ -14965,7 +14965,7 @@ ${t5.join("\n")}` : "";
|
|
|
14965
14965
|
var import_process = require("process");
|
|
14966
14966
|
|
|
14967
14967
|
// compiled/src/version/version.js
|
|
14968
|
-
var versionString = "Conway v1.
|
|
14968
|
+
var versionString = "Conway v1.404.1241";
|
|
14969
14969
|
|
|
14970
14970
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
14971
14971
|
function pThreadsAllowed() {
|
|
@@ -15943,7 +15943,7 @@ var ParsingBuffer = class {
|
|
|
15943
15943
|
};
|
|
15944
15944
|
|
|
15945
15945
|
// compiled/src/version/version.js
|
|
15946
|
-
var versionString = "Conway v1.
|
|
15946
|
+
var versionString = "Conway v1.404.1241";
|
|
15947
15947
|
|
|
15948
15948
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
15949
15949
|
function pThreadsAllowed() {
|
|
@@ -944,7 +944,7 @@ var EntityTypesIfcCount = 909;
|
|
|
944
944
|
var entity_types_ifc_gen_default = EntityTypesIfc;
|
|
945
945
|
|
|
946
946
|
// compiled/src/version/version.js
|
|
947
|
-
var versionString = "Conway v1.
|
|
947
|
+
var versionString = "Conway v1.404.1241";
|
|
948
948
|
|
|
949
949
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
950
950
|
var wasmType = "";
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demand-driven, budgeted geometry materialisation (M3).
|
|
3
|
+
*
|
|
4
|
+
* The streaming loader inverts geometry: instead of extracting every product
|
|
5
|
+
* up front, extraction becomes a **cache fill keyed by product**, ordered by
|
|
6
|
+
* demand (viewport frustum + distance, explicit selection, prefetch hints)
|
|
7
|
+
* and bounded by an explicit byte budget. This module is the scheduler: a
|
|
8
|
+
* priority queue over product local IDs plus an evictable resident set, with
|
|
9
|
+
* the actual extract / release delegated to a pluggable {@link GeometryTiles}.
|
|
10
|
+
*
|
|
11
|
+
* The heavy half — the wasm-side extract → tessellate → upload and the
|
|
12
|
+
* per-product native reclaim — lives behind {@link GeometryTiles}. Its
|
|
13
|
+
* production implementation needs a **conway-geom API to free one product's
|
|
14
|
+
* native geometry** (today the wasm heap has no per-product reclaim); scoping
|
|
15
|
+
* that C++ surface is the M3 blocker tracked in the design doc. This scheduler
|
|
16
|
+
* is engine-side, fully deterministic, and tested against a mock tiles impl,
|
|
17
|
+
* so the queue/budget/eviction policy is settled independently of the wasm
|
|
18
|
+
* work.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The per-product geometry work behind the queue. Implementations do the
|
|
22
|
+
* expensive materialisation; the queue owns only ordering and the budget.
|
|
23
|
+
*/
|
|
24
|
+
export interface GeometryTiles {
|
|
25
|
+
/**
|
|
26
|
+
* Materialise a product's geometry tile and return its resident cost in
|
|
27
|
+
* bytes (GPU/scene + any retained wasm working set the budget should count).
|
|
28
|
+
* Called at most once per product between evictions.
|
|
29
|
+
*
|
|
30
|
+
* @param productLocalID The product's local ID.
|
|
31
|
+
* @return {number} The tile's resident byte cost (≥ 0).
|
|
32
|
+
*/
|
|
33
|
+
extract(productLocalID: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* Release a product's materialised tile, freeing its resident bytes — the
|
|
36
|
+
* scene mesh and, crucially, the product's native (wasm) geometry. This is
|
|
37
|
+
* the per-product reclaim the production conway-geom surface must provide.
|
|
38
|
+
*
|
|
39
|
+
* @param productLocalID The product's local ID.
|
|
40
|
+
*/
|
|
41
|
+
release(productLocalID: number): void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Runtime counters for a queue, for tests and telemetry.
|
|
45
|
+
*/
|
|
46
|
+
export interface DemandQueueStats {
|
|
47
|
+
extractions: number;
|
|
48
|
+
evictions: number;
|
|
49
|
+
residentBytes: number;
|
|
50
|
+
residentCount: number;
|
|
51
|
+
pendingCount: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A budgeted, demand-ordered geometry materialisation queue.
|
|
55
|
+
*
|
|
56
|
+
* Usage: `request(productLocalID, priority)` as demand changes (higher
|
|
57
|
+
* priority = more wanted); `pump()` to materialise the most-wanted pending
|
|
58
|
+
* products until the queue drains or the byte budget forces eviction of
|
|
59
|
+
* lower-priority resident tiles. Re-requesting an evicted product re-fills it.
|
|
60
|
+
* A steady stream of `request` + `pump` keeps a bounded working set of the
|
|
61
|
+
* highest-priority products resident — the "full model navigable under a
|
|
62
|
+
* fixed budget" behaviour.
|
|
63
|
+
*/
|
|
64
|
+
export declare class DemandGeometryQueue {
|
|
65
|
+
private readonly tiles_;
|
|
66
|
+
private readonly budgetBytes_;
|
|
67
|
+
/** Highest priority a product currently wants materialising at. */
|
|
68
|
+
private readonly pending_;
|
|
69
|
+
/** Resident tiles by product local ID. */
|
|
70
|
+
private readonly resident_;
|
|
71
|
+
private residentBytes_;
|
|
72
|
+
private extractions_;
|
|
73
|
+
private evictions_;
|
|
74
|
+
/**
|
|
75
|
+
* @param tiles The extract/release backend.
|
|
76
|
+
* @param budgetBytes Maximum resident tile bytes before eviction kicks in.
|
|
77
|
+
*/
|
|
78
|
+
constructor(tiles: GeometryTiles, budgetBytes: number);
|
|
79
|
+
/**
|
|
80
|
+
* Request a product be materialised at the given demand priority. Updates
|
|
81
|
+
* the priority if already pending (keeping the higher of the two) or already
|
|
82
|
+
* resident (so eviction ranks it correctly). Requesting a resident product
|
|
83
|
+
* does not re-extract it.
|
|
84
|
+
*
|
|
85
|
+
* @param productLocalID The product to materialise.
|
|
86
|
+
* @param priority The demand priority (higher = more wanted).
|
|
87
|
+
*/
|
|
88
|
+
request(productLocalID: number, priority: number): void;
|
|
89
|
+
/**
|
|
90
|
+
* Materialise pending products in descending priority order, evicting the
|
|
91
|
+
* lowest-priority resident tiles whenever the budget is exceeded. A pending
|
|
92
|
+
* product whose priority is below every resident tile AND that can't fit is
|
|
93
|
+
* left pending (it will fill once demand raises it or resident tiles drop).
|
|
94
|
+
*
|
|
95
|
+
* @param maxExtractions Optional cap on how many tiles to extract this pump
|
|
96
|
+
* (e.g. a per-frame budget); unbounded when omitted.
|
|
97
|
+
* @return {number} The number of tiles extracted this pump.
|
|
98
|
+
*/
|
|
99
|
+
pump(maxExtractions?: number): number;
|
|
100
|
+
/**
|
|
101
|
+
* Evict every resident tile (e.g. on model close), releasing all bytes.
|
|
102
|
+
*/
|
|
103
|
+
evictAll(): void;
|
|
104
|
+
/**
|
|
105
|
+
* @return {boolean} True if the product's tile is resident.
|
|
106
|
+
* @param productLocalID The product to check.
|
|
107
|
+
*/
|
|
108
|
+
isResident(productLocalID: number): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* @return {DemandQueueStats} A snapshot of runtime counters.
|
|
111
|
+
*/
|
|
112
|
+
get stats(): DemandQueueStats;
|
|
113
|
+
/**
|
|
114
|
+
* Pop the highest-priority pending request. Linear scan — swap for a heap
|
|
115
|
+
* if a real workload shows the pending set growing large per pump.
|
|
116
|
+
*
|
|
117
|
+
* @return {PendingRequest | undefined} The request, or undefined if none.
|
|
118
|
+
*/
|
|
119
|
+
private popHighestPending_;
|
|
120
|
+
/**
|
|
121
|
+
* Ensure there is conceptual room for a tile requested at `priority` by
|
|
122
|
+
* evicting resident tiles strictly lower in priority while over budget.
|
|
123
|
+
* Returns false if the budget is full of tiles at least as wanted as this
|
|
124
|
+
* request (so it shouldn't displace them).
|
|
125
|
+
*
|
|
126
|
+
* @param priority The incoming request's priority.
|
|
127
|
+
* @return {boolean} True if extraction should proceed.
|
|
128
|
+
*/
|
|
129
|
+
private ensureRoomFor_;
|
|
130
|
+
/**
|
|
131
|
+
* Evict lowest-priority resident tiles until within budget (used after an
|
|
132
|
+
* extraction that pushed us over — e.g. a large tile).
|
|
133
|
+
*/
|
|
134
|
+
private evictToBudget_;
|
|
135
|
+
/**
|
|
136
|
+
* @return {{ productLocalID: number, priority: number } | undefined} The
|
|
137
|
+
* lowest-priority resident tile, or undefined if none.
|
|
138
|
+
*/
|
|
139
|
+
private lowestPriorityResident_;
|
|
140
|
+
/**
|
|
141
|
+
* Release one resident tile.
|
|
142
|
+
*
|
|
143
|
+
* @param productLocalID The tile to evict.
|
|
144
|
+
*/
|
|
145
|
+
private evictOne_;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=demand_geometry_queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demand_geometry_queue.d.ts","sourceRoot":"","sources":["../../../src/core/demand_geometry_queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;GAGG;AACH,MAAM,WAAW,aAAa;IAE5B;;;;;;;OAOG;IACH,OAAO,CAAE,cAAc,EAAE,MAAM,GAAI,MAAM,CAAA;IAEzC;;;;;;OAMG;IACH,OAAO,CAAE,cAAc,EAAE,MAAM,GAAI,IAAI,CAAA;CACxC;AAiBD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;CACrB;AAGD;;;;;;;;;;GAUG;AACH,qBAAa,mBAAmB;IAE9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IAErC,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IAErD,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAE5D,OAAO,CAAC,cAAc,CAAI;IAE1B,OAAO,CAAC,YAAY,CAAI;IAExB,OAAO,CAAC,UAAU,CAAI;IAEtB;;;OAGG;gBACU,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM;IAUtD;;;;;;;;OAQG;IACI,OAAO,CAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAI,IAAI;IAiBhE;;;;;;;;;OASG;IACI,IAAI,CAAE,cAAc,GAAE,MAAiC,GAAI,MAAM;IAoCxE;;OAEG;IACI,QAAQ,IAAI,IAAI;IASvB;;;OAGG;IACI,UAAU,CAAE,cAAc,EAAE,MAAM,GAAI,OAAO;IAIpD;;OAEG;IACH,IAAW,KAAK,IAAI,gBAAgB,CAQnC;IAED;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAqB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IAuBtB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAYtB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;;;OAIG;IACH,OAAO,CAAC,SAAS;CAYlB"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demand-driven, budgeted geometry materialisation (M3).
|
|
3
|
+
*
|
|
4
|
+
* The streaming loader inverts geometry: instead of extracting every product
|
|
5
|
+
* up front, extraction becomes a **cache fill keyed by product**, ordered by
|
|
6
|
+
* demand (viewport frustum + distance, explicit selection, prefetch hints)
|
|
7
|
+
* and bounded by an explicit byte budget. This module is the scheduler: a
|
|
8
|
+
* priority queue over product local IDs plus an evictable resident set, with
|
|
9
|
+
* the actual extract / release delegated to a pluggable {@link GeometryTiles}.
|
|
10
|
+
*
|
|
11
|
+
* The heavy half — the wasm-side extract → tessellate → upload and the
|
|
12
|
+
* per-product native reclaim — lives behind {@link GeometryTiles}. Its
|
|
13
|
+
* production implementation needs a **conway-geom API to free one product's
|
|
14
|
+
* native geometry** (today the wasm heap has no per-product reclaim); scoping
|
|
15
|
+
* that C++ surface is the M3 blocker tracked in the design doc. This scheduler
|
|
16
|
+
* is engine-side, fully deterministic, and tested against a mock tiles impl,
|
|
17
|
+
* so the queue/budget/eviction policy is settled independently of the wasm
|
|
18
|
+
* work.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* A budgeted, demand-ordered geometry materialisation queue.
|
|
22
|
+
*
|
|
23
|
+
* Usage: `request(productLocalID, priority)` as demand changes (higher
|
|
24
|
+
* priority = more wanted); `pump()` to materialise the most-wanted pending
|
|
25
|
+
* products until the queue drains or the byte budget forces eviction of
|
|
26
|
+
* lower-priority resident tiles. Re-requesting an evicted product re-fills it.
|
|
27
|
+
* A steady stream of `request` + `pump` keeps a bounded working set of the
|
|
28
|
+
* highest-priority products resident — the "full model navigable under a
|
|
29
|
+
* fixed budget" behaviour.
|
|
30
|
+
*/
|
|
31
|
+
export class DemandGeometryQueue {
|
|
32
|
+
/**
|
|
33
|
+
* @param tiles The extract/release backend.
|
|
34
|
+
* @param budgetBytes Maximum resident tile bytes before eviction kicks in.
|
|
35
|
+
*/
|
|
36
|
+
constructor(tiles, budgetBytes) {
|
|
37
|
+
/** Highest priority a product currently wants materialising at. */
|
|
38
|
+
this.pending_ = new Map();
|
|
39
|
+
/** Resident tiles by product local ID. */
|
|
40
|
+
this.resident_ = new Map();
|
|
41
|
+
this.residentBytes_ = 0;
|
|
42
|
+
this.extractions_ = 0;
|
|
43
|
+
this.evictions_ = 0;
|
|
44
|
+
if (budgetBytes <= 0) {
|
|
45
|
+
throw new Error(`Invalid budgetBytes ${budgetBytes}`);
|
|
46
|
+
}
|
|
47
|
+
this.tiles_ = tiles;
|
|
48
|
+
this.budgetBytes_ = budgetBytes;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Request a product be materialised at the given demand priority. Updates
|
|
52
|
+
* the priority if already pending (keeping the higher of the two) or already
|
|
53
|
+
* resident (so eviction ranks it correctly). Requesting a resident product
|
|
54
|
+
* does not re-extract it.
|
|
55
|
+
*
|
|
56
|
+
* @param productLocalID The product to materialise.
|
|
57
|
+
* @param priority The demand priority (higher = more wanted).
|
|
58
|
+
*/
|
|
59
|
+
request(productLocalID, priority) {
|
|
60
|
+
const tile = this.resident_.get(productLocalID);
|
|
61
|
+
if (tile !== void 0) {
|
|
62
|
+
// Already resident — just refresh its eviction ranking.
|
|
63
|
+
tile.priority = Math.max(tile.priority, priority);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const existing = this.pending_.get(productLocalID);
|
|
67
|
+
this.pending_.set(productLocalID, existing === void 0 ? priority : Math.max(existing, priority));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Materialise pending products in descending priority order, evicting the
|
|
71
|
+
* lowest-priority resident tiles whenever the budget is exceeded. A pending
|
|
72
|
+
* product whose priority is below every resident tile AND that can't fit is
|
|
73
|
+
* left pending (it will fill once demand raises it or resident tiles drop).
|
|
74
|
+
*
|
|
75
|
+
* @param maxExtractions Optional cap on how many tiles to extract this pump
|
|
76
|
+
* (e.g. a per-frame budget); unbounded when omitted.
|
|
77
|
+
* @return {number} The number of tiles extracted this pump.
|
|
78
|
+
*/
|
|
79
|
+
pump(maxExtractions = Number.POSITIVE_INFINITY) {
|
|
80
|
+
let extractedThisPump = 0;
|
|
81
|
+
while (extractedThisPump < maxExtractions && this.pending_.size > 0) {
|
|
82
|
+
const next = this.popHighestPending_();
|
|
83
|
+
if (next === void 0) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
// If the working set is full and this request can't beat the cheapest
|
|
87
|
+
// evictable (lower-priority) resident tile, stop — nothing more fits.
|
|
88
|
+
if (!this.ensureRoomFor_(next.priority)) {
|
|
89
|
+
// Put it back; it stays pending for a future pump.
|
|
90
|
+
this.pending_.set(next.productLocalID, next.priority);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
const bytes = this.tiles_.extract(next.productLocalID);
|
|
94
|
+
this.resident_.set(next.productLocalID, { bytes, priority: next.priority });
|
|
95
|
+
this.residentBytes_ += bytes;
|
|
96
|
+
++this.extractions_;
|
|
97
|
+
++extractedThisPump;
|
|
98
|
+
// A single oversized tile can still exceed budget; evict what we can.
|
|
99
|
+
this.evictToBudget_();
|
|
100
|
+
}
|
|
101
|
+
return extractedThisPump;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Evict every resident tile (e.g. on model close), releasing all bytes.
|
|
105
|
+
*/
|
|
106
|
+
evictAll() {
|
|
107
|
+
for (const productLocalID of this.resident_.keys()) {
|
|
108
|
+
this.tiles_.release(productLocalID);
|
|
109
|
+
++this.evictions_;
|
|
110
|
+
}
|
|
111
|
+
this.resident_.clear();
|
|
112
|
+
this.residentBytes_ = 0;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* @return {boolean} True if the product's tile is resident.
|
|
116
|
+
* @param productLocalID The product to check.
|
|
117
|
+
*/
|
|
118
|
+
isResident(productLocalID) {
|
|
119
|
+
return this.resident_.has(productLocalID);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* @return {DemandQueueStats} A snapshot of runtime counters.
|
|
123
|
+
*/
|
|
124
|
+
get stats() {
|
|
125
|
+
return {
|
|
126
|
+
extractions: this.extractions_,
|
|
127
|
+
evictions: this.evictions_,
|
|
128
|
+
residentBytes: this.residentBytes_,
|
|
129
|
+
residentCount: this.resident_.size,
|
|
130
|
+
pendingCount: this.pending_.size,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Pop the highest-priority pending request. Linear scan — swap for a heap
|
|
135
|
+
* if a real workload shows the pending set growing large per pump.
|
|
136
|
+
*
|
|
137
|
+
* @return {PendingRequest | undefined} The request, or undefined if none.
|
|
138
|
+
*/
|
|
139
|
+
popHighestPending_() {
|
|
140
|
+
let bestID;
|
|
141
|
+
let bestPriority = Number.NEGATIVE_INFINITY;
|
|
142
|
+
for (const [productLocalID, priority] of this.pending_) {
|
|
143
|
+
if (priority > bestPriority) {
|
|
144
|
+
bestPriority = priority;
|
|
145
|
+
bestID = productLocalID;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (bestID === void 0) {
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
this.pending_.delete(bestID);
|
|
152
|
+
return { productLocalID: bestID, priority: bestPriority };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Ensure there is conceptual room for a tile requested at `priority` by
|
|
156
|
+
* evicting resident tiles strictly lower in priority while over budget.
|
|
157
|
+
* Returns false if the budget is full of tiles at least as wanted as this
|
|
158
|
+
* request (so it shouldn't displace them).
|
|
159
|
+
*
|
|
160
|
+
* @param priority The incoming request's priority.
|
|
161
|
+
* @return {boolean} True if extraction should proceed.
|
|
162
|
+
*/
|
|
163
|
+
ensureRoomFor_(priority) {
|
|
164
|
+
if (this.residentBytes_ < this.budgetBytes_) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
// Over/at budget: only proceed if there's a lower-priority tile to evict.
|
|
168
|
+
let evictedAny = false;
|
|
169
|
+
while (this.residentBytes_ >= this.budgetBytes_) {
|
|
170
|
+
const victim = this.lowestPriorityResident_();
|
|
171
|
+
if (victim === void 0 || victim.priority >= priority) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
this.evictOne_(victim.productLocalID);
|
|
175
|
+
evictedAny = true;
|
|
176
|
+
}
|
|
177
|
+
return this.residentBytes_ < this.budgetBytes_ || evictedAny;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Evict lowest-priority resident tiles until within budget (used after an
|
|
181
|
+
* extraction that pushed us over — e.g. a large tile).
|
|
182
|
+
*/
|
|
183
|
+
evictToBudget_() {
|
|
184
|
+
while (this.residentBytes_ > this.budgetBytes_) {
|
|
185
|
+
const victim = this.lowestPriorityResident_();
|
|
186
|
+
if (victim === void 0) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
this.evictOne_(victim.productLocalID);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* @return {{ productLocalID: number, priority: number } | undefined} The
|
|
194
|
+
* lowest-priority resident tile, or undefined if none.
|
|
195
|
+
*/
|
|
196
|
+
lowestPriorityResident_() {
|
|
197
|
+
let worstID;
|
|
198
|
+
let worstPriority = Number.POSITIVE_INFINITY;
|
|
199
|
+
for (const [productLocalID, tile] of this.resident_) {
|
|
200
|
+
if (tile.priority < worstPriority) {
|
|
201
|
+
worstPriority = tile.priority;
|
|
202
|
+
worstID = productLocalID;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return worstID === void 0 ?
|
|
206
|
+
void 0 : { productLocalID: worstID, priority: worstPriority };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Release one resident tile.
|
|
210
|
+
*
|
|
211
|
+
* @param productLocalID The tile to evict.
|
|
212
|
+
*/
|
|
213
|
+
evictOne_(productLocalID) {
|
|
214
|
+
const tile = this.resident_.get(productLocalID);
|
|
215
|
+
if (tile === void 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.tiles_.release(productLocalID);
|
|
219
|
+
this.resident_.delete(productLocalID);
|
|
220
|
+
this.residentBytes_ -= tile.bytes;
|
|
221
|
+
++this.evictions_;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demand_geometry_queue.test.d.ts","sourceRoot":"","sources":["../../../src/core/demand_geometry_queue.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
// M3: the demand-geometry scheduler — priority ordering, byte budget, and
|
|
3
|
+
// eviction — tested against a mock tiles backend so the policy is settled
|
|
4
|
+
// independently of the wasm extract/reclaim work.
|
|
5
|
+
import { describe, expect, test } from "@jest/globals";
|
|
6
|
+
import { DemandGeometryQueue } from "./demand_geometry_queue.js";
|
|
7
|
+
/**
|
|
8
|
+
* A mock tiles backend: every product costs a fixed number of bytes and
|
|
9
|
+
* records the extract/release calls so tests can assert on them.
|
|
10
|
+
*
|
|
11
|
+
* @param costBytes Per-tile resident cost.
|
|
12
|
+
* @return {object} `{ tiles, extracted, released }`.
|
|
13
|
+
*/
|
|
14
|
+
function mockTiles(costBytes) {
|
|
15
|
+
const extracted = [];
|
|
16
|
+
const released = [];
|
|
17
|
+
const tiles = {
|
|
18
|
+
extract(id) {
|
|
19
|
+
extracted.push(id);
|
|
20
|
+
return costBytes;
|
|
21
|
+
},
|
|
22
|
+
release(id) {
|
|
23
|
+
released.push(id);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
return { tiles, extracted, released };
|
|
27
|
+
}
|
|
28
|
+
describe("DemandGeometryQueue", () => {
|
|
29
|
+
test("materialises highest-priority requests first", () => {
|
|
30
|
+
const { tiles, extracted } = mockTiles(10);
|
|
31
|
+
const q = new DemandGeometryQueue(tiles, 1000);
|
|
32
|
+
q.request(1, 5);
|
|
33
|
+
q.request(2, 50);
|
|
34
|
+
q.request(3, 20);
|
|
35
|
+
q.pump();
|
|
36
|
+
// Extracted in descending priority: 2 (50), 3 (20), 1 (5).
|
|
37
|
+
expect(extracted).toEqual([2, 3, 1]);
|
|
38
|
+
expect(q.stats.residentCount).toBe(3);
|
|
39
|
+
expect(q.stats.residentBytes).toBe(30);
|
|
40
|
+
});
|
|
41
|
+
test("evicts the lowest-priority tile when the budget is exceeded", () => {
|
|
42
|
+
const { tiles, released } = mockTiles(100);
|
|
43
|
+
// Budget holds 3 tiles (300).
|
|
44
|
+
const q = new DemandGeometryQueue(tiles, 300);
|
|
45
|
+
for (const [id, pri] of [[1, 10], [2, 20], [3, 30]]) {
|
|
46
|
+
q.request(id, pri);
|
|
47
|
+
}
|
|
48
|
+
q.pump();
|
|
49
|
+
expect(q.stats.residentCount).toBe(3);
|
|
50
|
+
// A 4th, higher-priority request must evict the lowest (id 1, pri 10).
|
|
51
|
+
q.request(4, 40);
|
|
52
|
+
q.pump();
|
|
53
|
+
expect(q.isResident(4)).toBe(true);
|
|
54
|
+
expect(q.isResident(1)).toBe(false);
|
|
55
|
+
expect(released).toContain(1);
|
|
56
|
+
expect(q.stats.residentBytes).toBeLessThanOrEqual(300);
|
|
57
|
+
});
|
|
58
|
+
test("a request below every resident tile does not displace them", () => {
|
|
59
|
+
const { tiles, extracted } = mockTiles(100);
|
|
60
|
+
const q = new DemandGeometryQueue(tiles, 200); // holds 2
|
|
61
|
+
q.request(1, 50);
|
|
62
|
+
q.request(2, 40);
|
|
63
|
+
q.pump();
|
|
64
|
+
expect(q.stats.residentCount).toBe(2);
|
|
65
|
+
// Low-priority newcomer can't beat either resident tile → stays pending.
|
|
66
|
+
q.request(3, 5);
|
|
67
|
+
const n = q.pump();
|
|
68
|
+
expect(n).toBe(0);
|
|
69
|
+
expect(q.isResident(3)).toBe(false);
|
|
70
|
+
expect(extracted).toEqual([1, 2]);
|
|
71
|
+
expect(q.stats.pendingCount).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
test("re-requesting an evicted product re-extracts it", () => {
|
|
74
|
+
const { tiles, extracted } = mockTiles(100);
|
|
75
|
+
const q = new DemandGeometryQueue(tiles, 100); // holds 1
|
|
76
|
+
q.request(1, 10);
|
|
77
|
+
q.pump();
|
|
78
|
+
q.request(2, 20); // evicts 1
|
|
79
|
+
q.pump();
|
|
80
|
+
expect(q.isResident(1)).toBe(false);
|
|
81
|
+
q.request(1, 30); // evicts 2, re-extracts 1
|
|
82
|
+
q.pump();
|
|
83
|
+
expect(q.isResident(1)).toBe(true);
|
|
84
|
+
// 1 extracted twice (first fill, then re-fill after eviction).
|
|
85
|
+
expect(extracted.filter((id) => id === 1).length).toBe(2);
|
|
86
|
+
});
|
|
87
|
+
test("requesting an already-resident product does not re-extract", () => {
|
|
88
|
+
const { tiles, extracted } = mockTiles(10);
|
|
89
|
+
const q = new DemandGeometryQueue(tiles, 1000);
|
|
90
|
+
q.request(1, 10);
|
|
91
|
+
q.pump();
|
|
92
|
+
q.request(1, 99); // already resident — just bumps ranking
|
|
93
|
+
q.pump();
|
|
94
|
+
expect(extracted).toEqual([1]);
|
|
95
|
+
});
|
|
96
|
+
test("a per-pump extraction cap bounds work per call", () => {
|
|
97
|
+
const { tiles } = mockTiles(1);
|
|
98
|
+
const q = new DemandGeometryQueue(tiles, 1000000);
|
|
99
|
+
for (let id = 0; id < 100; ++id) {
|
|
100
|
+
q.request(id, id);
|
|
101
|
+
}
|
|
102
|
+
expect(q.pump(10)).toBe(10);
|
|
103
|
+
expect(q.stats.residentCount).toBe(10);
|
|
104
|
+
expect(q.stats.pendingCount).toBe(90);
|
|
105
|
+
});
|
|
106
|
+
test("evictAll releases everything", () => {
|
|
107
|
+
const { tiles, released } = mockTiles(10);
|
|
108
|
+
const q = new DemandGeometryQueue(tiles, 1000);
|
|
109
|
+
for (let id = 0; id < 5; ++id) {
|
|
110
|
+
q.request(id, id);
|
|
111
|
+
}
|
|
112
|
+
q.pump();
|
|
113
|
+
q.evictAll();
|
|
114
|
+
expect(q.stats.residentCount).toBe(0);
|
|
115
|
+
expect(q.stats.residentBytes).toBe(0);
|
|
116
|
+
expect(released.sort()).toEqual([0, 1, 2, 3, 4]);
|
|
117
|
+
});
|
|
118
|
+
test("keeps the resident set bounded by the budget across a demand stream", () => {
|
|
119
|
+
const { tiles } = mockTiles(100);
|
|
120
|
+
const q = new DemandGeometryQueue(tiles, 500); // holds 5
|
|
121
|
+
// A moving "viewport": 200 products stream past with rising priority.
|
|
122
|
+
for (let id = 0; id < 200; ++id) {
|
|
123
|
+
q.request(id, id);
|
|
124
|
+
q.pump();
|
|
125
|
+
expect(q.stats.residentBytes).toBeLessThanOrEqual(500);
|
|
126
|
+
}
|
|
127
|
+
// The 5 most-wanted (highest ids) remain.
|
|
128
|
+
expect(q.stats.residentCount).toBe(5);
|
|
129
|
+
for (const id of [195, 196, 197, 198, 199]) {
|
|
130
|
+
expect(q.isResident(id)).toBe(true);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { StepIndexEntry } from "./step_parser.js";
|
|
2
|
+
/**
|
|
3
|
+
* The decoded sidecar: the source identity it was built against, plus the
|
|
4
|
+
* reconstructed top-level element index.
|
|
5
|
+
*/
|
|
6
|
+
export interface DecodedSidecar<TypeIDType extends number> {
|
|
7
|
+
version: number;
|
|
8
|
+
sourceByteLength: number;
|
|
9
|
+
sourceHash: number;
|
|
10
|
+
elements: StepIndexEntry<TypeIDType>[];
|
|
11
|
+
/**
|
|
12
|
+
* True for records the sidecar under-describes (they had inline / multi
|
|
13
|
+
* children the v1 format doesn't serialise). Same order as `elements`.
|
|
14
|
+
*/
|
|
15
|
+
hasChildren: boolean[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A placeholder strong-ish hash of the source bytes (FNV-1a, 32-bit). Stands
|
|
19
|
+
* in for a production digest (SHA-256); only the fixed slot matters to the
|
|
20
|
+
* format. Sufficient to demonstrate the mismatch → cold-scan handshake.
|
|
21
|
+
*
|
|
22
|
+
* @param source The source bytes.
|
|
23
|
+
* @return {number} A 32-bit unsigned hash.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hashSource(source: Uint8Array): number;
|
|
26
|
+
/**
|
|
27
|
+
* Serialise a model's top-level element index to a sidecar blob.
|
|
28
|
+
*
|
|
29
|
+
* @param elements The top-level element index.
|
|
30
|
+
* @param sourceByteLength The length of the source it was built from.
|
|
31
|
+
* @param sourceHash The source hash (see {@link hashSource}).
|
|
32
|
+
* @return {Uint8Array} The sidecar bytes.
|
|
33
|
+
*/
|
|
34
|
+
export declare function serializeIndexSidecar<TypeIDType extends number>(elements: StepIndexEntry<TypeIDType>[], sourceByteLength: number, sourceHash: number): Uint8Array;
|
|
35
|
+
/**
|
|
36
|
+
* Deserialise a sidecar blob back to the top-level element index.
|
|
37
|
+
*
|
|
38
|
+
* @param bytes The sidecar bytes.
|
|
39
|
+
* @return {DecodedSidecar} The decoded source identity + element index.
|
|
40
|
+
*/
|
|
41
|
+
export declare function deserializeIndexSidecar<TypeIDType extends number>(bytes: Uint8Array): DecodedSidecar<TypeIDType>;
|
|
42
|
+
/**
|
|
43
|
+
* Validate a decoded sidecar against the actual source identity. The
|
|
44
|
+
* index-first open path calls this before trusting the sidecar; on false it
|
|
45
|
+
* must fall back to a cold scan.
|
|
46
|
+
*
|
|
47
|
+
* @param sidecar The decoded sidecar.
|
|
48
|
+
* @param sourceByteLength The actual source length.
|
|
49
|
+
* @param sourceHash The actual source hash.
|
|
50
|
+
* @return {boolean} True if the sidecar matches the source.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sidecarMatchesSource<TypeIDType extends number>(sidecar: DecodedSidecar<TypeIDType>, sourceByteLength: number, sourceHash: number): boolean;
|
|
53
|
+
//# sourceMappingURL=index_sidecar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index_sidecar.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/index_sidecar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AA0D9C;;;GAGG;AACH,MAAM,WAAW,cAAc,CAAC,UAAU,SAAS,MAAM;IACvD,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,CAAA;IAEtC;;;OAGG;IACH,WAAW,EAAE,OAAO,EAAE,CAAA;CACvB;AAGD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAE,MAAM,EAAE,UAAU,GAAI,MAAM,CASvD;AAGD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,SAAS,MAAM,EAC3D,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,EACtC,gBAAgB,EAAE,MAAM,EACxB,UAAU,EAAE,MAAM,GAAI,UAAU,CAsCnC;AAGD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,SAAS,MAAM,EAC7D,KAAK,EAAE,UAAU,GAAI,cAAc,CAAC,UAAU,CAAC,CA0ClD;AAGD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,SAAS,MAAM,EAC1D,OAAO,EAAE,cAAc,CAAC,UAAU,CAAC,EACnC,gBAAgB,EAAE,MAAM,EACxB,UAAU,EAAE,MAAM,GAAI,OAAO,CAIhC"}
|