@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.10
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 +43 -807
- package/THIRD_PARTY.md +40 -0
- package/bld/THIRD_PARTY_NOTICES.txt +182 -0
- package/bld/chunks/chunk-5IP37Z5U.js +30 -0
- package/bld/chunks/chunk-5IP37Z5U.js.map +7 -0
- package/bld/chunks/chunk-7FIFPADJ.js +1 -0
- package/bld/chunks/chunk-7FIFPADJ.js.map +7 -0
- package/bld/chunks/chunk-HBRLIGQN.js +31 -0
- package/bld/chunks/chunk-HBRLIGQN.js.map +7 -0
- package/bld/chunks/chunk-IJHQ57VL.js +19 -0
- package/bld/chunks/chunk-IJHQ57VL.js.map +7 -0
- package/bld/chunks/chunk-TTJOM4DJ.js +26458 -0
- package/bld/chunks/chunk-TTJOM4DJ.js.map +7 -0
- package/bld/library/bound-solver.d.ts +29 -5
- package/bld/library/bound-solver.d.ts.map +1 -1
- package/bld/library/cached.d.ts +35 -0
- package/bld/library/cached.d.ts.map +1 -0
- package/bld/library/font.d.ts +39 -0
- package/bld/library/font.d.ts.map +1 -0
- package/bld/library/google-font.d.ts +14 -0
- package/bld/library/google-font.d.ts.map +1 -0
- package/bld/library/index.d.ts +10 -2
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +94 -3
- package/bld/library/index.js.map +7 -1
- package/bld/library/inspect.d.ts +98 -0
- package/bld/library/inspect.d.ts.map +1 -0
- package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
- package/bld/library/kernel-cache.d.ts +28 -7
- package/bld/library/kernel-cache.d.ts.map +1 -1
- package/bld/library/loft.d.ts.map +1 -1
- package/bld/library/material.d.ts +1 -1
- package/bld/library/material.d.ts.map +1 -1
- package/bld/library/open-cascade.d.ts +4 -0
- package/bld/library/open-cascade.d.ts.map +1 -0
- package/bld/library/relation-solver.d.ts +19 -8
- package/bld/library/relation-solver.d.ts.map +1 -1
- package/bld/library/replicad.d.ts +3 -2
- package/bld/library/replicad.d.ts.map +1 -1
- package/bld/library/replicad.js +10 -24
- package/bld/library/replicad.js.map +7 -1
- package/bld/library/retained-memory.d.ts.map +1 -1
- package/bld/library/runtime.d.ts +518 -155
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/library/sketch-solver.d.ts +1 -1
- package/bld/library/sketch-solver.d.ts.map +1 -1
- package/bld/library/sketch.d.ts +21 -1
- package/bld/library/sketch.d.ts.map +1 -1
- package/bld/library/text.d.ts +53 -0
- package/bld/library/text.d.ts.map +1 -0
- package/bld/library/three.js +3 -3
- package/bld/library/three.js.map +7 -1
- package/bld/library/topology.d.ts +6 -2
- package/bld/library/topology.d.ts.map +1 -1
- package/bld/node/index.d.ts.map +1 -1
- package/bld/node/index.js +96 -14
- package/bld/node/index.js.map +7 -1
- package/bld/node/replicad.js +13 -3
- package/bld/node/replicad.js.map +7 -1
- package/bld/tooling/index.d.ts +18 -15
- package/bld/tooling/index.d.ts.map +1 -1
- package/bld/tooling/index.js +165 -19
- package/bld/tooling/index.js.map +7 -1
- package/docs/api.md +764 -0
- package/docs/custom-primitives.mdx +58 -0
- package/docs/local-coordinates.md +187 -0
- package/docs/origins-and-rotation.mdx +105 -0
- package/docs/relations.mdx +450 -0
- package/docs/runtime.md +297 -0
- package/docs/shells.mdx +70 -0
- package/docs/sketches.md +83 -0
- package/docs/text.md +23 -0
- package/docs/topology.md +152 -0
- package/docs/values.md +119 -0
- package/package.json +20 -7
- package/src/library/bound-solver.ts +103 -73
- package/src/library/cached.ts +153 -0
- package/src/library/font.ts +186 -0
- package/src/library/google-font.ts +97 -0
- package/src/library/index.ts +53 -5
- package/src/library/inspect.ts +176 -0
- package/src/library/kernel-artifact-codec.ts +129 -20
- package/src/library/kernel-cache.ts +175 -39
- package/src/library/loft.ts +9 -1
- package/src/library/material.ts +3 -4
- package/src/library/open-cascade.ts +13 -0
- package/src/library/relation-solver.ts +230 -113
- package/src/library/replicad.ts +5 -5
- package/src/library/retained-memory.ts +6 -1
- package/src/library/runtime.ts +3753 -1181
- package/src/library/shell.ts +3 -3
- package/src/library/sketch-solver.ts +3 -1
- package/src/library/sketch.ts +112 -13
- package/src/library/text.ts +477 -0
- package/src/library/topology.ts +61 -17
- package/src/library/tsconfig.json +2 -1
- package/src/node/index.ts +14 -3
- package/src/node/tsconfig.json +2 -1
- package/src/tooling/index.ts +64 -51
- package/src/tooling/tsconfig.json +2 -1
- package/bld/library/alignment-geometry.js +0 -322
- package/bld/library/alignment-geometry.js.map +0 -1
- package/bld/library/bound-solver.js +0 -252
- package/bld/library/bound-solver.js.map +0 -1
- package/bld/library/extrude.js +0 -28
- package/bld/library/extrude.js.map +0 -1
- package/bld/library/kernel-artifact-codec.js +0 -148
- package/bld/library/kernel-artifact-codec.js.map +0 -1
- package/bld/library/kernel-cache.js +0 -245
- package/bld/library/kernel-cache.js.map +0 -1
- package/bld/library/kernel-shapes.js +0 -108
- package/bld/library/kernel-shapes.js.map +0 -1
- package/bld/library/loft.js +0 -161
- package/bld/library/loft.js.map +0 -1
- package/bld/library/material.js +0 -176
- package/bld/library/material.js.map +0 -1
- package/bld/library/model-color.js +0 -36
- package/bld/library/model-color.js.map +0 -1
- package/bld/library/open-cascade-error.js +0 -22
- package/bld/library/open-cascade-error.js.map +0 -1
- package/bld/library/relation-solver.js +0 -258
- package/bld/library/relation-solver.js.map +0 -1
- package/bld/library/retained-memory.js +0 -27
- package/bld/library/retained-memory.js.map +0 -1
- package/bld/library/runtime.js +0 -2954
- package/bld/library/runtime.js.map +0 -1
- package/bld/library/shell.js +0 -221
- package/bld/library/shell.js.map +0 -1
- package/bld/library/sketch-curve-intersections.js +0 -117
- package/bld/library/sketch-curve-intersections.js.map +0 -1
- package/bld/library/sketch-curves.js +0 -89
- package/bld/library/sketch-curves.js.map +0 -1
- package/bld/library/sketch-drag-rules.js +0 -522
- package/bld/library/sketch-drag-rules.js.map +0 -1
- package/bld/library/sketch-face.js +0 -72
- package/bld/library/sketch-face.js.map +0 -1
- package/bld/library/sketch-incidence.js +0 -114
- package/bld/library/sketch-incidence.js.map +0 -1
- package/bld/library/sketch-precision.js +0 -55
- package/bld/library/sketch-precision.js.map +0 -1
- package/bld/library/sketch-regions.js +0 -193
- package/bld/library/sketch-regions.js.map +0 -1
- package/bld/library/sketch-solver.js +0 -578
- package/bld/library/sketch-solver.js.map +0 -1
- package/bld/library/sketch.js +0 -650
- package/bld/library/sketch.js.map +0 -1
- package/bld/library/spatial.js +0 -195
- package/bld/library/spatial.js.map +0 -1
- package/bld/library/topology-id.js +0 -67
- package/bld/library/topology-id.js.map +0 -1
- package/bld/library/topology-inspection.js +0 -267
- package/bld/library/topology-inspection.js.map +0 -1
- package/bld/library/topology.js +0 -455
- package/bld/library/topology.js.map +0 -1
|
@@ -8,12 +8,31 @@ const decoder = new TextDecoder('utf-8', {fatal: true});
|
|
|
8
8
|
// references. Native handles never escape their runtime; only BinTools bytes do.
|
|
9
9
|
type Node = null | boolean | string | number | readonly [string, unknown];
|
|
10
10
|
|
|
11
|
+
const binaryTypes: Record<
|
|
12
|
+
string,
|
|
13
|
+
new (buffer: ArrayBuffer, offset?: number, length?: number) => ArrayBufferView
|
|
14
|
+
> = {
|
|
15
|
+
Int8Array,
|
|
16
|
+
Uint8Array,
|
|
17
|
+
Uint8ClampedArray,
|
|
18
|
+
Int16Array,
|
|
19
|
+
Uint16Array,
|
|
20
|
+
Int32Array,
|
|
21
|
+
Uint32Array,
|
|
22
|
+
Float32Array,
|
|
23
|
+
Float64Array,
|
|
24
|
+
BigInt64Array,
|
|
25
|
+
BigUint64Array,
|
|
26
|
+
DataView,
|
|
27
|
+
};
|
|
28
|
+
|
|
11
29
|
export function encodeKernelArtifact(
|
|
12
30
|
signature: string,
|
|
13
31
|
value: unknown,
|
|
14
32
|
): Uint8Array {
|
|
15
33
|
const chunks: Uint8Array[] = [];
|
|
16
34
|
let offset = 0;
|
|
35
|
+
const references = new Map<object, number>();
|
|
17
36
|
function binary(kind: string, bytes: Uint8Array): Node {
|
|
18
37
|
const node: Node = [kind, [offset, bytes.byteLength]];
|
|
19
38
|
chunks.push(bytes);
|
|
@@ -32,21 +51,56 @@ export function encodeKernelArtifact(
|
|
|
32
51
|
typeof value === 'boolean'
|
|
33
52
|
)
|
|
34
53
|
return value;
|
|
54
|
+
if (typeof value === 'bigint') return ['bigint', String(value)];
|
|
55
|
+
if (typeof value !== 'object')
|
|
56
|
+
throw new Error('Unsupported kernel artifact value');
|
|
57
|
+
const existing = references.get(value);
|
|
58
|
+
if (existing !== undefined) return ['reference', existing];
|
|
59
|
+
const id = references.size;
|
|
60
|
+
references.set(value, id);
|
|
61
|
+
return ['value', [id, encodeObject(value)]];
|
|
62
|
+
}
|
|
63
|
+
function encodeObject(value: object): Node {
|
|
35
64
|
if (value instanceof Shape)
|
|
36
65
|
return binary('shape', encodeShape(value as AnyShape));
|
|
37
|
-
if (value
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
66
|
+
if (ArrayBuffer.isView(value)) {
|
|
67
|
+
if (!Object.hasOwn(binaryTypes, value.constructor.name))
|
|
68
|
+
throw new Error('Unsupported kernel artifact view');
|
|
69
|
+
return [
|
|
70
|
+
'view',
|
|
71
|
+
[
|
|
72
|
+
value.constructor.name,
|
|
73
|
+
encode(value.buffer),
|
|
74
|
+
value.byteOffset,
|
|
75
|
+
value instanceof DataView
|
|
76
|
+
? value.byteLength
|
|
77
|
+
: (value as unknown as {length: number}).length,
|
|
78
|
+
],
|
|
79
|
+
];
|
|
42
80
|
}
|
|
43
|
-
if (
|
|
81
|
+
if (value instanceof ArrayBuffer)
|
|
82
|
+
return binary('ArrayBuffer', new Uint8Array(value));
|
|
83
|
+
if (value instanceof Date) return ['date', encode(value.getTime())];
|
|
84
|
+
if (value instanceof Map)
|
|
85
|
+
return [
|
|
86
|
+
'map',
|
|
87
|
+
[...value].map(([key, item]) => [encode(key), encode(item)]),
|
|
88
|
+
];
|
|
89
|
+
if (value instanceof Set) return ['set', [...value].map(encode)];
|
|
90
|
+
if (Array.isArray(value))
|
|
91
|
+
return [
|
|
92
|
+
'array',
|
|
93
|
+
[
|
|
94
|
+
value.length,
|
|
95
|
+
Object.entries(value).map(([key, item]) => [key, encode(item)]),
|
|
96
|
+
],
|
|
97
|
+
];
|
|
44
98
|
if (
|
|
45
|
-
|
|
46
|
-
Object.getPrototypeOf(value) ===
|
|
99
|
+
Object.getPrototypeOf(value) === Object.prototype ||
|
|
100
|
+
Object.getPrototypeOf(value) === null
|
|
47
101
|
) {
|
|
48
102
|
return [
|
|
49
|
-
'object',
|
|
103
|
+
Object.getPrototypeOf(value) === null ? 'record' : 'object',
|
|
50
104
|
Object.entries(value).map(([key, item]) => [key, encode(item)]),
|
|
51
105
|
];
|
|
52
106
|
}
|
|
@@ -81,23 +135,79 @@ export function decodeKernelArtifact<Value>(
|
|
|
81
135
|
if (storedSignature !== signature)
|
|
82
136
|
throw new Error('Kernel artifact signature mismatch');
|
|
83
137
|
const shapes: AnyShape[] = [];
|
|
84
|
-
|
|
138
|
+
const references = new Map<number, unknown>();
|
|
139
|
+
function decode(node: Node, referenceId?: number): unknown {
|
|
140
|
+
const register = <T>(value: T): T => {
|
|
141
|
+
if (referenceId !== undefined) references.set(referenceId, value);
|
|
142
|
+
return value;
|
|
143
|
+
};
|
|
85
144
|
if (!Array.isArray(node)) return node;
|
|
86
145
|
const [kind, data] = node;
|
|
87
146
|
switch (kind) {
|
|
147
|
+
case 'reference':
|
|
148
|
+
if (!references.has(data as number))
|
|
149
|
+
throw new Error('Invalid kernel artifact reference');
|
|
150
|
+
return references.get(data as number);
|
|
151
|
+
case 'value': {
|
|
152
|
+
const [id, value] = data as [number, Node];
|
|
153
|
+
return decode(value, id);
|
|
154
|
+
}
|
|
88
155
|
case 'undefined':
|
|
89
156
|
return undefined;
|
|
90
157
|
case 'number':
|
|
91
158
|
return Number(data);
|
|
159
|
+
case 'bigint':
|
|
160
|
+
return BigInt(data as string);
|
|
161
|
+
case 'date':
|
|
162
|
+
return register(new Date(decode(data as Node) as number));
|
|
163
|
+
case 'map': {
|
|
164
|
+
const map = register(new Map());
|
|
165
|
+
for (const [key, item] of data as [Node, Node][])
|
|
166
|
+
map.set(decode(key), decode(item));
|
|
167
|
+
return map;
|
|
168
|
+
}
|
|
169
|
+
case 'set': {
|
|
170
|
+
const set = register(new Set());
|
|
171
|
+
for (const item of data as Node[]) set.add(decode(item));
|
|
172
|
+
return set;
|
|
173
|
+
}
|
|
92
174
|
case 'array':
|
|
93
|
-
|
|
94
|
-
case 'object':
|
|
95
|
-
|
|
96
|
-
|
|
175
|
+
case 'record':
|
|
176
|
+
case 'object': {
|
|
177
|
+
const object = register(
|
|
178
|
+
kind === 'array'
|
|
179
|
+
? new Array((data as [number, unknown])[0])
|
|
180
|
+
: kind === 'record'
|
|
181
|
+
? Object.create(null)
|
|
182
|
+
: {},
|
|
97
183
|
);
|
|
184
|
+
const entries = (
|
|
185
|
+
kind === 'array' ? (data as [number, unknown])[1] : data
|
|
186
|
+
) as [string, Node][];
|
|
187
|
+
for (const [key, item] of entries)
|
|
188
|
+
Object.defineProperty(object, key, {
|
|
189
|
+
value: decode(item),
|
|
190
|
+
writable: true,
|
|
191
|
+
configurable: true,
|
|
192
|
+
enumerable: true,
|
|
193
|
+
});
|
|
194
|
+
return object;
|
|
195
|
+
}
|
|
196
|
+
case 'view': {
|
|
197
|
+
const [name, buffer, offset, length] = data as [
|
|
198
|
+
string,
|
|
199
|
+
Node,
|
|
200
|
+
number,
|
|
201
|
+
number,
|
|
202
|
+
];
|
|
203
|
+
if (!Object.hasOwn(binaryTypes, name))
|
|
204
|
+
throw new Error('Invalid kernel artifact view');
|
|
205
|
+
return register(
|
|
206
|
+
new binaryTypes[name](decode(buffer) as ArrayBuffer, offset, length),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
98
209
|
case 'shape':
|
|
99
|
-
case '
|
|
100
|
-
case 'uint32': {
|
|
210
|
+
case 'ArrayBuffer': {
|
|
101
211
|
const [offset, length] = data as number[];
|
|
102
212
|
if (
|
|
103
213
|
!Number.isSafeInteger(offset) ||
|
|
@@ -108,13 +218,12 @@ export function decodeKernelArtifact<Value>(
|
|
|
108
218
|
) {
|
|
109
219
|
throw new Error('Invalid kernel artifact binary range');
|
|
110
220
|
}
|
|
111
|
-
// Each
|
|
221
|
+
// Each restored backing buffer owns its source bytes, not the disk record.
|
|
112
222
|
const chunk = bytes.slice(start + offset, start + offset + length);
|
|
113
|
-
if (kind === '
|
|
114
|
-
if (kind === 'uint32') return new Uint32Array(chunk.buffer);
|
|
223
|
+
if (kind === 'ArrayBuffer') return register(chunk.buffer);
|
|
115
224
|
const shape = decodeShape(chunk);
|
|
116
225
|
shapes.push(shape);
|
|
117
|
-
return shape;
|
|
226
|
+
return register(shape);
|
|
118
227
|
}
|
|
119
228
|
default:
|
|
120
229
|
throw new Error('Invalid kernel artifact node');
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import type {OpenCascadeInstance} from '@code3d/opencascade';
|
|
2
|
-
import {getOC} from 'replicad';
|
|
3
|
-
import {estimateRetainedBytes} from './retained-memory.js';
|
|
4
1
|
import {
|
|
5
2
|
decodeKernelArtifact,
|
|
6
3
|
encodeKernelArtifact,
|
|
7
4
|
} from './kernel-artifact-codec.js';
|
|
5
|
+
import {estimateRetainedBytes} from './retained-memory.js';
|
|
8
6
|
|
|
9
7
|
/** The host opens storage before synchronous evaluation and owns its lifetime. */
|
|
10
8
|
export interface KernelArtifactStore {
|
|
9
|
+
/** Encoded writes retained by the host for the current working version. */
|
|
10
|
+
readonly pendingWriteBytes?: number;
|
|
11
11
|
get(id: string): Uint8Array | undefined;
|
|
12
|
+
getMany(ids: readonly string[]): readonly (Uint8Array | undefined)[];
|
|
12
13
|
set(id: string, bytes: Uint8Array): void;
|
|
13
14
|
touch(id: string): boolean;
|
|
15
|
+
/** Update access order together, returning existence in the same order. */
|
|
16
|
+
touchMany(ids: readonly string[]): readonly boolean[];
|
|
14
17
|
delete(id: string): void;
|
|
18
|
+
/** Schedule persistence; hosts may complete disk I/O in the background. */
|
|
15
19
|
flush(): void;
|
|
16
20
|
}
|
|
17
21
|
|
|
@@ -23,6 +27,11 @@ export type KernelArtifact<Value> = Readonly<{
|
|
|
23
27
|
value: Value;
|
|
24
28
|
}>;
|
|
25
29
|
|
|
30
|
+
export type CacheCodec<Value> = Readonly<{
|
|
31
|
+
encoder(value: Value): Uint8Array;
|
|
32
|
+
decoder(bytes: Uint8Array): Value;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
26
35
|
export type KernelValueLifecycle<Value> = Readonly<{
|
|
27
36
|
estimateBytes(value: Value): number;
|
|
28
37
|
retain(value: Value): Value;
|
|
@@ -31,6 +40,7 @@ export type KernelValueLifecycle<Value> = Readonly<{
|
|
|
31
40
|
}>;
|
|
32
41
|
|
|
33
42
|
type CacheEntry<Value> = Readonly<{
|
|
43
|
+
persistenceEligible: boolean;
|
|
34
44
|
estimatedBytes: number;
|
|
35
45
|
signature: string;
|
|
36
46
|
value: Value;
|
|
@@ -38,11 +48,13 @@ type CacheEntry<Value> = Readonly<{
|
|
|
38
48
|
release(value: Value): void;
|
|
39
49
|
}>;
|
|
40
50
|
|
|
41
|
-
export function
|
|
51
|
+
export function createComputationCache({
|
|
42
52
|
maximumBytes = 2 * 1024 ** 3,
|
|
53
|
+
minimumPersistenceMilliseconds = 1,
|
|
43
54
|
nativeAllocatedBytes,
|
|
44
55
|
}: {
|
|
45
56
|
maximumBytes?: number;
|
|
57
|
+
minimumPersistenceMilliseconds?: number;
|
|
46
58
|
nativeAllocatedBytes: () => number;
|
|
47
59
|
}) {
|
|
48
60
|
const entries = new Map<string, CacheEntry<unknown>>();
|
|
@@ -56,10 +68,23 @@ export function createKernelOperationCache({
|
|
|
56
68
|
let store: KernelArtifactStore | undefined;
|
|
57
69
|
let persistentHits = 0;
|
|
58
70
|
let persistentWrites = 0;
|
|
71
|
+
let persistenceEncodeMilliseconds = 0;
|
|
59
72
|
let persistenceErrors = 0;
|
|
60
73
|
const persisted = new Set<string>();
|
|
74
|
+
const pendingPersistence = new Map<string, () => Uint8Array>();
|
|
61
75
|
let externalBytes = 0;
|
|
62
76
|
|
|
77
|
+
/** Adjust historical retention without invalidating the current model. */
|
|
78
|
+
function setKernelCacheBudget(bytes: number): void {
|
|
79
|
+
maximumBytes = bytes;
|
|
80
|
+
evictHistoricalEntries();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Apply to future computations without changing existing entries' admission. */
|
|
84
|
+
function setKernelCachePersistenceThreshold(milliseconds: number): void {
|
|
85
|
+
minimumPersistenceMilliseconds = milliseconds;
|
|
86
|
+
}
|
|
87
|
+
|
|
63
88
|
/** The host accounts for in-flight inputs and all auxiliary native heaps. */
|
|
64
89
|
function setKernelExternalBytes(bytes: number): void {
|
|
65
90
|
externalBytes = bytes;
|
|
@@ -69,6 +94,7 @@ export function createKernelOperationCache({
|
|
|
69
94
|
function setKernelArtifactStore(next: KernelArtifactStore | undefined): void {
|
|
70
95
|
store = next;
|
|
71
96
|
persisted.clear();
|
|
97
|
+
pendingPersistence.clear();
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
function accessStore<Result>(
|
|
@@ -93,60 +119,101 @@ export function createKernelOperationCache({
|
|
|
93
119
|
*/
|
|
94
120
|
function beginKernelOperationEvaluation(
|
|
95
121
|
checkCancelled?: () => void,
|
|
122
|
+
kind: 'model' | 'inspect' = 'model',
|
|
96
123
|
): () => void {
|
|
97
124
|
const used = new Set<string>();
|
|
98
125
|
currentEvaluation = {used, checkCancelled};
|
|
99
126
|
return () => {
|
|
100
|
-
|
|
101
|
-
|
|
127
|
+
flushPendingPersistence();
|
|
128
|
+
if (kind === 'model') {
|
|
129
|
+
for (const id of retainedEvaluation) {
|
|
130
|
+
if (!used.has(id)) historicalEntries.set(id, entries.get(id)!);
|
|
131
|
+
}
|
|
132
|
+
retainedEvaluation = used;
|
|
133
|
+
} else {
|
|
134
|
+
// Inspection owns temporary geometry, without replacing the model's
|
|
135
|
+
// retained working set or accumulating every previously inspected scene.
|
|
136
|
+
for (const id of used) {
|
|
137
|
+
if (!retainedEvaluation.has(id))
|
|
138
|
+
historicalEntries.set(id, entries.get(id)!);
|
|
139
|
+
}
|
|
102
140
|
}
|
|
103
|
-
retainedEvaluation = used;
|
|
104
141
|
currentEvaluation = undefined;
|
|
105
142
|
evictHistoricalEntries();
|
|
106
143
|
accessStore(store => store.flush());
|
|
107
144
|
};
|
|
108
145
|
}
|
|
109
146
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* scalar semantics; inputs carry the content identities of prior operations.
|
|
113
|
-
* The lifecycle keeps the retained value independent from each disposable use.
|
|
114
|
-
*/
|
|
115
|
-
function evaluateKernelOperation<Value>(
|
|
116
|
-
operation: string,
|
|
117
|
-
arguments_: readonly KernelKeyPart[],
|
|
118
|
-
inputs: readonly KernelArtifact<unknown>[],
|
|
147
|
+
function evaluateCachedArtifact<Value>(
|
|
148
|
+
key: KernelOperationKey,
|
|
119
149
|
lifecycle: KernelValueLifecycle<Value>,
|
|
120
150
|
compute: () => Value,
|
|
151
|
+
codec?: CacheCodec<Value> | false,
|
|
121
152
|
): KernelArtifact<Value> {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
const hit = findKernelOperation(key, lifecycle, codec);
|
|
154
|
+
if (hit) return hit;
|
|
155
|
+
const started = performance.now();
|
|
125
156
|
const value = compute();
|
|
126
|
-
|
|
157
|
+
const milliseconds = performance.now() - started;
|
|
158
|
+
acceptKernelOperation(key, lifecycle, value, milliseconds, codec);
|
|
127
159
|
return {id: key.id, value};
|
|
128
160
|
}
|
|
129
161
|
|
|
130
162
|
function findKernelOperation<Value>(
|
|
131
163
|
key: KernelOperationKey,
|
|
132
164
|
lifecycle: KernelValueLifecycle<Value>,
|
|
165
|
+
codec?: CacheCodec<Value> | false,
|
|
166
|
+
): KernelArtifact<Value> | undefined {
|
|
167
|
+
return lookup(key, lifecycle, codec, () =>
|
|
168
|
+
accessStore(store => store.get(key.id)),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function findKernelOperations<Value>(
|
|
173
|
+
keys: readonly KernelOperationKey[],
|
|
174
|
+
lifecycle: KernelValueLifecycle<Value>,
|
|
175
|
+
codec?: CacheCodec<Value> | false,
|
|
176
|
+
): readonly (KernelArtifact<Value> | undefined)[] {
|
|
177
|
+
currentEvaluation?.checkCancelled?.();
|
|
178
|
+
const missing =
|
|
179
|
+
codec === false ? [] : keys.filter(key => !entries.has(key.id));
|
|
180
|
+
const bytes = missing.length
|
|
181
|
+
? accessStore(store => store.getMany(missing.map(key => key.id)))
|
|
182
|
+
: undefined;
|
|
183
|
+
const restored = new Map(
|
|
184
|
+
missing.map((key, index) => [key.id, bytes?.[index]]),
|
|
185
|
+
);
|
|
186
|
+
return keys.map(key =>
|
|
187
|
+
lookup(key, lifecycle, codec, () => restored.get(key.id)),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function lookup<Value>(
|
|
192
|
+
key: KernelOperationKey,
|
|
193
|
+
lifecycle: KernelValueLifecycle<Value>,
|
|
194
|
+
codec: CacheCodec<Value> | false | undefined,
|
|
195
|
+
read: () => Uint8Array | undefined,
|
|
133
196
|
): KernelArtifact<Value> | undefined {
|
|
134
197
|
currentEvaluation?.checkCancelled?.();
|
|
135
198
|
const {id, signature} = key;
|
|
136
199
|
let cached = entries.get(id) as CacheEntry<Value> | undefined;
|
|
137
|
-
if (!cached) {
|
|
138
|
-
const bytes =
|
|
200
|
+
if (!cached && codec !== false) {
|
|
201
|
+
const bytes = read();
|
|
139
202
|
if (bytes) {
|
|
140
203
|
let restored: Value;
|
|
141
204
|
try {
|
|
142
|
-
restored =
|
|
205
|
+
restored = codec
|
|
206
|
+
? codec.decoder(decodeKernelArtifact<Uint8Array>(bytes, signature))
|
|
207
|
+
: decodeKernelArtifact<Value>(bytes, signature);
|
|
143
208
|
} catch {
|
|
144
209
|
persistenceErrors += 1;
|
|
145
210
|
accessStore(store => store.delete(id));
|
|
146
211
|
misses += 1;
|
|
147
212
|
return undefined;
|
|
148
213
|
}
|
|
149
|
-
|
|
214
|
+
// Existing disk records have already been admitted; reading one must
|
|
215
|
+
// not reinterpret decode time as its original computation cost.
|
|
216
|
+
cached = retainEntry(key, lifecycle, restored, true);
|
|
150
217
|
persistentHits += 1;
|
|
151
218
|
persisted.add(id);
|
|
152
219
|
}
|
|
@@ -159,7 +226,8 @@ export function createKernelOperationCache({
|
|
|
159
226
|
throw new Error(`Kernel operation cache identity collision: ${id}`);
|
|
160
227
|
hits += 1;
|
|
161
228
|
const value = cached.instantiate(cached.value);
|
|
162
|
-
|
|
229
|
+
if (cached.persistenceEligible && codec !== false)
|
|
230
|
+
persist(key, cached.value, codec, true);
|
|
163
231
|
touchEntry(id, cached as CacheEntry<unknown>);
|
|
164
232
|
return {id, value};
|
|
165
233
|
}
|
|
@@ -169,6 +237,8 @@ export function createKernelOperationCache({
|
|
|
169
237
|
key: KernelOperationKey,
|
|
170
238
|
lifecycle: KernelValueLifecycle<Value>,
|
|
171
239
|
value: Value,
|
|
240
|
+
milliseconds: number,
|
|
241
|
+
codec?: CacheCodec<Value> | false,
|
|
172
242
|
): void {
|
|
173
243
|
const existing = entries.get(key.id);
|
|
174
244
|
if (existing) {
|
|
@@ -184,28 +254,74 @@ export function createKernelOperationCache({
|
|
|
184
254
|
lifecycle.release(value);
|
|
185
255
|
throw error;
|
|
186
256
|
}
|
|
187
|
-
const entry = retainEntry(
|
|
188
|
-
|
|
257
|
+
const entry = retainEntry(
|
|
258
|
+
key,
|
|
259
|
+
lifecycle,
|
|
260
|
+
retained,
|
|
261
|
+
milliseconds >= minimumPersistenceMilliseconds,
|
|
262
|
+
);
|
|
263
|
+
if (entry.persistenceEligible && codec !== false)
|
|
264
|
+
persist(key, retained, codec);
|
|
189
265
|
touchEntry(key.id, entry as CacheEntry<unknown>);
|
|
190
266
|
}
|
|
191
267
|
|
|
192
|
-
function persist(
|
|
268
|
+
function persist<Value>(
|
|
269
|
+
key: KernelOperationKey,
|
|
270
|
+
value: Value,
|
|
271
|
+
codec?: CacheCodec<Value>,
|
|
272
|
+
defer = false,
|
|
273
|
+
): void {
|
|
193
274
|
if (!store || persisted.has(key.id)) return;
|
|
275
|
+
const encode = () => {
|
|
276
|
+
const started = performance.now();
|
|
277
|
+
try {
|
|
278
|
+
return encodeKernelArtifact(
|
|
279
|
+
key.signature,
|
|
280
|
+
codec ? Uint8Array.from(codec.encoder(value)) : value,
|
|
281
|
+
);
|
|
282
|
+
} finally {
|
|
283
|
+
persistenceEncodeMilliseconds += performance.now() - started;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
if (defer && currentEvaluation) {
|
|
287
|
+
pendingPersistence.set(key.id, encode);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
194
290
|
accessStore(store => {
|
|
195
|
-
|
|
196
|
-
|
|
291
|
+
// A completed computation follows a miss; write it without another lookup.
|
|
292
|
+
if (!defer || !store.touch(key.id)) {
|
|
293
|
+
store.set(key.id, encode());
|
|
197
294
|
persistentWrites += 1;
|
|
198
295
|
}
|
|
199
296
|
persisted.add(key.id);
|
|
200
297
|
});
|
|
201
298
|
}
|
|
202
299
|
|
|
300
|
+
/** Memory hits need one transaction, while newly computed work is saved immediately. */
|
|
301
|
+
function flushPendingPersistence(): void {
|
|
302
|
+
if (!pendingPersistence.size) return;
|
|
303
|
+
accessStore(store => {
|
|
304
|
+
const ids = [...pendingPersistence.keys()];
|
|
305
|
+
const present = store.touchMany(ids);
|
|
306
|
+
ids.forEach((id, index) => {
|
|
307
|
+
if (!present[index]) {
|
|
308
|
+
store.set(id, pendingPersistence.get(id)!());
|
|
309
|
+
persistentWrites += 1;
|
|
310
|
+
}
|
|
311
|
+
persisted.add(id);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
pendingPersistence.clear();
|
|
315
|
+
}
|
|
316
|
+
|
|
203
317
|
function retainEntry<Value>(
|
|
204
318
|
key: KernelOperationKey,
|
|
205
319
|
lifecycle: KernelValueLifecycle<Value>,
|
|
206
320
|
retained: Value,
|
|
321
|
+
persistenceEligible: boolean,
|
|
207
322
|
): CacheEntry<Value> {
|
|
208
323
|
const entry: CacheEntry<Value> = {
|
|
324
|
+
persistenceEligible,
|
|
209
325
|
estimatedBytes:
|
|
210
326
|
256 +
|
|
211
327
|
estimateRetainedBytes(key.signature) +
|
|
@@ -244,8 +360,10 @@ export function createKernelOperationCache({
|
|
|
244
360
|
estimatedJavaScriptBytes = 0;
|
|
245
361
|
persistentHits = 0;
|
|
246
362
|
persistentWrites = 0;
|
|
363
|
+
persistenceEncodeMilliseconds = 0;
|
|
247
364
|
persistenceErrors = 0;
|
|
248
365
|
persisted.clear();
|
|
366
|
+
pendingPersistence.clear();
|
|
249
367
|
}
|
|
250
368
|
|
|
251
369
|
function kernelOperationCacheStats() {
|
|
@@ -258,8 +376,10 @@ export function createKernelOperationCache({
|
|
|
258
376
|
nativeAllocatedBytes: nativeAllocatedBytes(),
|
|
259
377
|
maximumBytes,
|
|
260
378
|
externalBytes,
|
|
379
|
+
pendingPersistenceBytes: store?.pendingWriteBytes ?? 0,
|
|
261
380
|
persistentHits,
|
|
262
381
|
persistentWrites,
|
|
382
|
+
persistenceEncodeMilliseconds,
|
|
263
383
|
persistenceErrors,
|
|
264
384
|
};
|
|
265
385
|
}
|
|
@@ -267,7 +387,10 @@ export function createKernelOperationCache({
|
|
|
267
387
|
function evictHistoricalEntries(): void {
|
|
268
388
|
while (
|
|
269
389
|
historicalEntries.size &&
|
|
270
|
-
nativeAllocatedBytes() +
|
|
390
|
+
nativeAllocatedBytes() +
|
|
391
|
+
estimatedJavaScriptBytes +
|
|
392
|
+
externalBytes +
|
|
393
|
+
(store?.pendingWriteBytes ?? 0) >
|
|
271
394
|
maximumBytes
|
|
272
395
|
) {
|
|
273
396
|
const [id, entry] = historicalEntries.entries().next().value!;
|
|
@@ -279,29 +402,41 @@ export function createKernelOperationCache({
|
|
|
279
402
|
}
|
|
280
403
|
|
|
281
404
|
return {
|
|
405
|
+
evaluateCachedArtifact,
|
|
282
406
|
beginKernelOperationEvaluation,
|
|
283
|
-
evaluateKernelOperation,
|
|
284
407
|
clearKernelOperationCache,
|
|
285
408
|
kernelOperationCacheStats,
|
|
409
|
+
setKernelCacheBudget,
|
|
410
|
+
setKernelCachePersistenceThreshold,
|
|
286
411
|
setKernelArtifactStore,
|
|
287
412
|
findKernelOperation,
|
|
413
|
+
findKernelOperations,
|
|
288
414
|
acceptKernelOperation,
|
|
289
415
|
setKernelExternalBytes,
|
|
290
416
|
};
|
|
291
417
|
}
|
|
292
418
|
|
|
419
|
+
let nativeMemoryCounter = () => 0;
|
|
420
|
+
|
|
421
|
+
/** The installed backend supplies native accounting; pure caches need no kernel. */
|
|
422
|
+
export function setKernelNativeMemoryCounter(read: () => number): void {
|
|
423
|
+
nativeMemoryCounter = read;
|
|
424
|
+
}
|
|
425
|
+
|
|
293
426
|
export const {
|
|
427
|
+
evaluateCachedArtifact,
|
|
294
428
|
beginKernelOperationEvaluation,
|
|
295
|
-
evaluateKernelOperation,
|
|
296
429
|
clearKernelOperationCache,
|
|
297
430
|
kernelOperationCacheStats,
|
|
431
|
+
setKernelCacheBudget,
|
|
432
|
+
setKernelCachePersistenceThreshold,
|
|
298
433
|
setKernelArtifactStore,
|
|
299
434
|
findKernelOperation,
|
|
435
|
+
findKernelOperations,
|
|
300
436
|
acceptKernelOperation,
|
|
301
437
|
setKernelExternalBytes,
|
|
302
|
-
} =
|
|
303
|
-
nativeAllocatedBytes: () =>
|
|
304
|
-
(getOC() as OpenCascadeInstance).Code3dMemory.AllocatedBytes(),
|
|
438
|
+
} = createComputationCache({
|
|
439
|
+
nativeAllocatedBytes: () => nativeMemoryCounter(),
|
|
305
440
|
});
|
|
306
441
|
|
|
307
442
|
export type KernelOperationKey = Readonly<{id: string; signature: string}>;
|
|
@@ -316,16 +451,17 @@ export function kernelOperationKey(
|
|
|
316
451
|
arguments_,
|
|
317
452
|
inputs.map(input => input.id),
|
|
318
453
|
]);
|
|
319
|
-
return {id:
|
|
454
|
+
return {id: kernelContentId(signature), signature};
|
|
320
455
|
}
|
|
321
456
|
|
|
322
|
-
function
|
|
457
|
+
export function kernelContentId(value: string | Uint8Array): string {
|
|
323
458
|
let first = 0x811c9dc5;
|
|
324
459
|
let second = 0x9e3779b9;
|
|
325
460
|
let third = 0x85ebca6b;
|
|
326
461
|
let fourth = 0xc2b2ae35;
|
|
327
462
|
for (let index = 0; index < value.length; index += 1) {
|
|
328
|
-
const code =
|
|
463
|
+
const code =
|
|
464
|
+
typeof value === 'string' ? value.charCodeAt(index) : value[index];
|
|
329
465
|
first = Math.imul(first ^ code, 0x01000193);
|
|
330
466
|
second = Math.imul(second ^ code, 0x27d4eb2d);
|
|
331
467
|
third = Math.imul(third ^ code, 0x165667b1);
|
package/src/library/loft.ts
CHANGED
|
@@ -62,7 +62,11 @@ export function loftWithTopology(
|
|
|
62
62
|
ruled,
|
|
63
63
|
holes.map(wires => wires[0]),
|
|
64
64
|
);
|
|
65
|
-
return booleanWithTopology(
|
|
65
|
+
return booleanWithTopology(
|
|
66
|
+
{...outer, namespace: 'intermediate'},
|
|
67
|
+
{...inner, namespace: 'intermediate'},
|
|
68
|
+
'cut',
|
|
69
|
+
);
|
|
66
70
|
} finally {
|
|
67
71
|
inner?.shape.delete();
|
|
68
72
|
outer?.shape.delete();
|
|
@@ -104,6 +108,10 @@ function loftContoursWithTopology(
|
|
|
104
108
|
builder.SetMutableInput(false);
|
|
105
109
|
for (const wire of wires) builder.AddWire(wire.wrapped);
|
|
106
110
|
builder.Build();
|
|
111
|
+
if (!builder.IsDone())
|
|
112
|
+
throw new Error(
|
|
113
|
+
'Could not construct a solid loft through these sections. Adjust their positions, orientations, or profiles.',
|
|
114
|
+
);
|
|
107
115
|
}
|
|
108
116
|
result = castOwnedShape3D(builder.Shape());
|
|
109
117
|
caps[0] = castOwnedShape(builder.FirstShape()) as Face;
|
package/src/library/material.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type SerializedImage,
|
|
7
7
|
type Texture,
|
|
8
8
|
type TypedArray,
|
|
9
|
-
} from '
|
|
9
|
+
} from 'three';
|
|
10
10
|
import {parseModelColor} from './model-color.js';
|
|
11
11
|
|
|
12
12
|
/** A color selects the default material for each geometry kind. */
|
|
@@ -52,14 +52,13 @@ export function captureModelMaterial(
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
if ('isShaderMaterial' in input) {
|
|
55
|
-
const shader = input as import('
|
|
55
|
+
const shader = input as import('three').ShaderMaterial;
|
|
56
56
|
if (
|
|
57
57
|
shader.uniformsGroups.length ||
|
|
58
58
|
shader.index0AttributeName !== undefined ||
|
|
59
59
|
JSON.stringify(shader.defaultAttributeValues) !==
|
|
60
60
|
JSON.stringify(
|
|
61
|
-
(restored as import('
|
|
62
|
-
.defaultAttributeValues,
|
|
61
|
+
(restored as import('three').ShaderMaterial).defaultAttributeValues,
|
|
63
62
|
)
|
|
64
63
|
) {
|
|
65
64
|
throw new Error(
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {OpenCascadeInstance} from '@code3d/opencascade';
|
|
2
|
+
import {setOC} from 'replicad';
|
|
3
|
+
import {
|
|
4
|
+
clearKernelOperationCache,
|
|
5
|
+
setKernelNativeMemoryCounter,
|
|
6
|
+
} from './kernel-cache.js';
|
|
7
|
+
|
|
8
|
+
/** Install the backend and its memory accounting for Node and hosted runtimes. */
|
|
9
|
+
export function installOpenCascade(openCascade: OpenCascadeInstance): void {
|
|
10
|
+
clearKernelOperationCache();
|
|
11
|
+
setOC(openCascade);
|
|
12
|
+
setKernelNativeMemoryCounter(() => openCascade.Code3dMemory.AllocatedBytes());
|
|
13
|
+
}
|