@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.3

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.
Files changed (131) hide show
  1. package/README.md +223 -786
  2. package/THIRD_PARTY.md +40 -0
  3. package/bld/THIRD_PARTY_NOTICES.txt +182 -0
  4. package/bld/chunks/chunk-BF55AIEQ.js +31 -0
  5. package/bld/chunks/chunk-BF55AIEQ.js.map +7 -0
  6. package/bld/chunks/chunk-P5VGKAEM.js +30 -0
  7. package/bld/chunks/chunk-P5VGKAEM.js.map +7 -0
  8. package/bld/chunks/chunk-RPY2WVCB.js +24834 -0
  9. package/bld/chunks/chunk-RPY2WVCB.js.map +7 -0
  10. package/bld/chunks/chunk-TJIN6S4R.js +1 -0
  11. package/bld/chunks/chunk-TJIN6S4R.js.map +7 -0
  12. package/bld/chunks/chunk-U5G2VU2I.js +19 -0
  13. package/bld/chunks/chunk-U5G2VU2I.js.map +7 -0
  14. package/bld/library/cached.d.ts +33 -0
  15. package/bld/library/cached.d.ts.map +1 -0
  16. package/bld/library/font.d.ts +39 -0
  17. package/bld/library/font.d.ts.map +1 -0
  18. package/bld/library/google-font.d.ts +14 -0
  19. package/bld/library/google-font.d.ts.map +1 -0
  20. package/bld/library/index.d.ts +8 -2
  21. package/bld/library/index.d.ts.map +1 -1
  22. package/bld/library/index.js +60 -3
  23. package/bld/library/index.js.map +7 -1
  24. package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
  25. package/bld/library/kernel-cache.d.ts +24 -6
  26. package/bld/library/kernel-cache.d.ts.map +1 -1
  27. package/bld/library/material.d.ts +1 -1
  28. package/bld/library/material.d.ts.map +1 -1
  29. package/bld/library/open-cascade.d.ts +4 -0
  30. package/bld/library/open-cascade.d.ts.map +1 -0
  31. package/bld/library/replicad.d.ts +3 -2
  32. package/bld/library/replicad.d.ts.map +1 -1
  33. package/bld/library/replicad.js +10 -24
  34. package/bld/library/replicad.js.map +7 -1
  35. package/bld/library/retained-memory.d.ts.map +1 -1
  36. package/bld/library/runtime.d.ts +146 -80
  37. package/bld/library/runtime.d.ts.map +1 -1
  38. package/bld/library/sketch-solver.d.ts +1 -1
  39. package/bld/library/sketch-solver.d.ts.map +1 -1
  40. package/bld/library/sketch.d.ts +9 -1
  41. package/bld/library/sketch.d.ts.map +1 -1
  42. package/bld/library/text.d.ts +53 -0
  43. package/bld/library/text.d.ts.map +1 -0
  44. package/bld/library/three.js +3 -3
  45. package/bld/library/three.js.map +7 -1
  46. package/bld/library/topology.d.ts.map +1 -1
  47. package/bld/node/index.d.ts.map +1 -1
  48. package/bld/node/index.js +62 -14
  49. package/bld/node/index.js.map +7 -1
  50. package/bld/node/replicad.js +13 -3
  51. package/bld/node/replicad.js.map +7 -1
  52. package/bld/tooling/index.d.ts +17 -15
  53. package/bld/tooling/index.d.ts.map +1 -1
  54. package/bld/tooling/index.js +147 -19
  55. package/bld/tooling/index.js.map +7 -1
  56. package/package.json +18 -6
  57. package/src/library/cached.ts +132 -0
  58. package/src/library/font.ts +186 -0
  59. package/src/library/google-font.ts +97 -0
  60. package/src/library/index.ts +8 -3
  61. package/src/library/kernel-artifact-codec.ts +129 -20
  62. package/src/library/kernel-cache.ts +128 -34
  63. package/src/library/loft.ts +4 -0
  64. package/src/library/material.ts +3 -4
  65. package/src/library/open-cascade.ts +13 -0
  66. package/src/library/replicad.ts +5 -5
  67. package/src/library/retained-memory.ts +6 -1
  68. package/src/library/runtime.ts +2064 -1730
  69. package/src/library/sketch-solver.ts +3 -1
  70. package/src/library/sketch.ts +81 -13
  71. package/src/library/text.ts +477 -0
  72. package/src/library/topology.ts +24 -0
  73. package/src/library/tsconfig.json +2 -1
  74. package/src/node/index.ts +14 -3
  75. package/src/node/tsconfig.json +2 -1
  76. package/src/tooling/index.ts +48 -46
  77. package/src/tooling/tsconfig.json +2 -1
  78. package/bld/library/alignment-geometry.js +0 -322
  79. package/bld/library/alignment-geometry.js.map +0 -1
  80. package/bld/library/bound-solver.js +0 -252
  81. package/bld/library/bound-solver.js.map +0 -1
  82. package/bld/library/extrude.js +0 -28
  83. package/bld/library/extrude.js.map +0 -1
  84. package/bld/library/kernel-artifact-codec.js +0 -148
  85. package/bld/library/kernel-artifact-codec.js.map +0 -1
  86. package/bld/library/kernel-cache.js +0 -245
  87. package/bld/library/kernel-cache.js.map +0 -1
  88. package/bld/library/kernel-shapes.js +0 -108
  89. package/bld/library/kernel-shapes.js.map +0 -1
  90. package/bld/library/loft.js +0 -161
  91. package/bld/library/loft.js.map +0 -1
  92. package/bld/library/material.js +0 -176
  93. package/bld/library/material.js.map +0 -1
  94. package/bld/library/model-color.js +0 -36
  95. package/bld/library/model-color.js.map +0 -1
  96. package/bld/library/open-cascade-error.js +0 -22
  97. package/bld/library/open-cascade-error.js.map +0 -1
  98. package/bld/library/relation-solver.js +0 -258
  99. package/bld/library/relation-solver.js.map +0 -1
  100. package/bld/library/retained-memory.js +0 -27
  101. package/bld/library/retained-memory.js.map +0 -1
  102. package/bld/library/runtime.js +0 -2954
  103. package/bld/library/runtime.js.map +0 -1
  104. package/bld/library/shell.js +0 -221
  105. package/bld/library/shell.js.map +0 -1
  106. package/bld/library/sketch-curve-intersections.js +0 -117
  107. package/bld/library/sketch-curve-intersections.js.map +0 -1
  108. package/bld/library/sketch-curves.js +0 -89
  109. package/bld/library/sketch-curves.js.map +0 -1
  110. package/bld/library/sketch-drag-rules.js +0 -522
  111. package/bld/library/sketch-drag-rules.js.map +0 -1
  112. package/bld/library/sketch-face.js +0 -72
  113. package/bld/library/sketch-face.js.map +0 -1
  114. package/bld/library/sketch-incidence.js +0 -114
  115. package/bld/library/sketch-incidence.js.map +0 -1
  116. package/bld/library/sketch-precision.js +0 -55
  117. package/bld/library/sketch-precision.js.map +0 -1
  118. package/bld/library/sketch-regions.js +0 -193
  119. package/bld/library/sketch-regions.js.map +0 -1
  120. package/bld/library/sketch-solver.js +0 -578
  121. package/bld/library/sketch-solver.js.map +0 -1
  122. package/bld/library/sketch.js +0 -650
  123. package/bld/library/sketch.js.map +0 -1
  124. package/bld/library/spatial.js +0 -195
  125. package/bld/library/spatial.js.map +0 -1
  126. package/bld/library/topology-id.js +0 -67
  127. package/bld/library/topology-id.js.map +0 -1
  128. package/bld/library/topology-inspection.js +0 -267
  129. package/bld/library/topology-inspection.js.map +0 -1
  130. package/bld/library/topology.js +0 -455
  131. package/bld/library/topology.js.map +0 -1
@@ -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;
@@ -38,7 +47,7 @@ type CacheEntry<Value> = Readonly<{
38
47
  release(value: Value): void;
39
48
  }>;
40
49
 
41
- export function createKernelOperationCache({
50
+ export function createComputationCache({
42
51
  maximumBytes = 2 * 1024 ** 3,
43
52
  nativeAllocatedBytes,
44
53
  }: {
@@ -56,8 +65,10 @@ export function createKernelOperationCache({
56
65
  let store: KernelArtifactStore | undefined;
57
66
  let persistentHits = 0;
58
67
  let persistentWrites = 0;
68
+ let persistenceEncodeMilliseconds = 0;
59
69
  let persistenceErrors = 0;
60
70
  const persisted = new Set<string>();
71
+ const pendingPersistence = new Map<string, () => Uint8Array>();
61
72
  let externalBytes = 0;
62
73
 
63
74
  /** The host accounts for in-flight inputs and all auxiliary native heaps. */
@@ -69,6 +80,7 @@ export function createKernelOperationCache({
69
80
  function setKernelArtifactStore(next: KernelArtifactStore | undefined): void {
70
81
  store = next;
71
82
  persisted.clear();
83
+ pendingPersistence.clear();
72
84
  }
73
85
 
74
86
  function accessStore<Result>(
@@ -97,6 +109,7 @@ export function createKernelOperationCache({
97
109
  const used = new Set<string>();
98
110
  currentEvaluation = {used, checkCancelled};
99
111
  return () => {
112
+ flushPendingPersistence();
100
113
  for (const id of retainedEvaluation) {
101
114
  if (!used.has(id)) historicalEntries.set(id, entries.get(id)!);
102
115
  }
@@ -107,39 +120,65 @@ export function createKernelOperationCache({
107
120
  };
108
121
  }
109
122
 
110
- /**
111
- * Reuses one complete, deterministic kernel operation. Arguments describe
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>[],
123
+ function evaluateCachedArtifact<Value>(
124
+ key: KernelOperationKey,
119
125
  lifecycle: KernelValueLifecycle<Value>,
120
126
  compute: () => Value,
127
+ codec?: CacheCodec<Value> | false,
121
128
  ): KernelArtifact<Value> {
122
- const key = kernelOperationKey(operation, arguments_, inputs);
123
- const cached = findKernelOperation(key, lifecycle);
124
- if (cached) return cached;
129
+ const hit = findKernelOperation(key, lifecycle, codec);
130
+ if (hit) return hit;
125
131
  const value = compute();
126
- acceptKernelOperation(key, lifecycle, value);
132
+ acceptKernelOperation(key, lifecycle, value, codec);
127
133
  return {id: key.id, value};
128
134
  }
129
135
 
130
136
  function findKernelOperation<Value>(
131
137
  key: KernelOperationKey,
132
138
  lifecycle: KernelValueLifecycle<Value>,
139
+ codec?: CacheCodec<Value> | false,
140
+ ): KernelArtifact<Value> | undefined {
141
+ return lookup(key, lifecycle, codec, () =>
142
+ accessStore(store => store.get(key.id)),
143
+ );
144
+ }
145
+
146
+ function findKernelOperations<Value>(
147
+ keys: readonly KernelOperationKey[],
148
+ lifecycle: KernelValueLifecycle<Value>,
149
+ codec?: CacheCodec<Value> | false,
150
+ ): readonly (KernelArtifact<Value> | undefined)[] {
151
+ currentEvaluation?.checkCancelled?.();
152
+ const missing =
153
+ codec === false ? [] : keys.filter(key => !entries.has(key.id));
154
+ const bytes = missing.length
155
+ ? accessStore(store => store.getMany(missing.map(key => key.id)))
156
+ : undefined;
157
+ const restored = new Map(
158
+ missing.map((key, index) => [key.id, bytes?.[index]]),
159
+ );
160
+ return keys.map(key =>
161
+ lookup(key, lifecycle, codec, () => restored.get(key.id)),
162
+ );
163
+ }
164
+
165
+ function lookup<Value>(
166
+ key: KernelOperationKey,
167
+ lifecycle: KernelValueLifecycle<Value>,
168
+ codec: CacheCodec<Value> | false | undefined,
169
+ read: () => Uint8Array | undefined,
133
170
  ): KernelArtifact<Value> | undefined {
134
171
  currentEvaluation?.checkCancelled?.();
135
172
  const {id, signature} = key;
136
173
  let cached = entries.get(id) as CacheEntry<Value> | undefined;
137
- if (!cached) {
138
- const bytes = accessStore(store => store.get(id));
174
+ if (!cached && codec !== false) {
175
+ const bytes = read();
139
176
  if (bytes) {
140
177
  let restored: Value;
141
178
  try {
142
- restored = decodeKernelArtifact<Value>(bytes, signature);
179
+ restored = codec
180
+ ? codec.decoder(decodeKernelArtifact<Uint8Array>(bytes, signature))
181
+ : decodeKernelArtifact<Value>(bytes, signature);
143
182
  } catch {
144
183
  persistenceErrors += 1;
145
184
  accessStore(store => store.delete(id));
@@ -159,7 +198,7 @@ export function createKernelOperationCache({
159
198
  throw new Error(`Kernel operation cache identity collision: ${id}`);
160
199
  hits += 1;
161
200
  const value = cached.instantiate(cached.value);
162
- persist(key, cached.value);
201
+ if (codec !== false) persist(key, cached.value, codec, true);
163
202
  touchEntry(id, cached as CacheEntry<unknown>);
164
203
  return {id, value};
165
204
  }
@@ -169,6 +208,7 @@ export function createKernelOperationCache({
169
208
  key: KernelOperationKey,
170
209
  lifecycle: KernelValueLifecycle<Value>,
171
210
  value: Value,
211
+ codec?: CacheCodec<Value> | false,
172
212
  ): void {
173
213
  const existing = entries.get(key.id);
174
214
  if (existing) {
@@ -185,21 +225,59 @@ export function createKernelOperationCache({
185
225
  throw error;
186
226
  }
187
227
  const entry = retainEntry(key, lifecycle, retained);
188
- persist(key, retained);
228
+ if (codec !== false) persist(key, retained, codec);
189
229
  touchEntry(key.id, entry as CacheEntry<unknown>);
190
230
  }
191
231
 
192
- function persist(key: KernelOperationKey, value: unknown): void {
232
+ function persist<Value>(
233
+ key: KernelOperationKey,
234
+ value: Value,
235
+ codec?: CacheCodec<Value>,
236
+ defer = false,
237
+ ): void {
193
238
  if (!store || persisted.has(key.id)) return;
239
+ const encode = () => {
240
+ const started = performance.now();
241
+ try {
242
+ return encodeKernelArtifact(
243
+ key.signature,
244
+ codec ? Uint8Array.from(codec.encoder(value)) : value,
245
+ );
246
+ } finally {
247
+ persistenceEncodeMilliseconds += performance.now() - started;
248
+ }
249
+ };
250
+ if (defer && currentEvaluation) {
251
+ pendingPersistence.set(key.id, encode);
252
+ return;
253
+ }
194
254
  accessStore(store => {
195
- if (!store.touch(key.id)) {
196
- store.set(key.id, encodeKernelArtifact(key.signature, value));
255
+ // A completed computation follows a miss; write it without another lookup.
256
+ if (!defer || !store.touch(key.id)) {
257
+ store.set(key.id, encode());
197
258
  persistentWrites += 1;
198
259
  }
199
260
  persisted.add(key.id);
200
261
  });
201
262
  }
202
263
 
264
+ /** Memory hits need one transaction, while newly computed work is saved immediately. */
265
+ function flushPendingPersistence(): void {
266
+ if (!pendingPersistence.size) return;
267
+ accessStore(store => {
268
+ const ids = [...pendingPersistence.keys()];
269
+ const present = store.touchMany(ids);
270
+ ids.forEach((id, index) => {
271
+ if (!present[index]) {
272
+ store.set(id, pendingPersistence.get(id)!());
273
+ persistentWrites += 1;
274
+ }
275
+ persisted.add(id);
276
+ });
277
+ });
278
+ pendingPersistence.clear();
279
+ }
280
+
203
281
  function retainEntry<Value>(
204
282
  key: KernelOperationKey,
205
283
  lifecycle: KernelValueLifecycle<Value>,
@@ -244,8 +322,10 @@ export function createKernelOperationCache({
244
322
  estimatedJavaScriptBytes = 0;
245
323
  persistentHits = 0;
246
324
  persistentWrites = 0;
325
+ persistenceEncodeMilliseconds = 0;
247
326
  persistenceErrors = 0;
248
327
  persisted.clear();
328
+ pendingPersistence.clear();
249
329
  }
250
330
 
251
331
  function kernelOperationCacheStats() {
@@ -258,8 +338,10 @@ export function createKernelOperationCache({
258
338
  nativeAllocatedBytes: nativeAllocatedBytes(),
259
339
  maximumBytes,
260
340
  externalBytes,
341
+ pendingPersistenceBytes: store?.pendingWriteBytes ?? 0,
261
342
  persistentHits,
262
343
  persistentWrites,
344
+ persistenceEncodeMilliseconds,
263
345
  persistenceErrors,
264
346
  };
265
347
  }
@@ -267,7 +349,10 @@ export function createKernelOperationCache({
267
349
  function evictHistoricalEntries(): void {
268
350
  while (
269
351
  historicalEntries.size &&
270
- nativeAllocatedBytes() + estimatedJavaScriptBytes + externalBytes >
352
+ nativeAllocatedBytes() +
353
+ estimatedJavaScriptBytes +
354
+ externalBytes +
355
+ (store?.pendingWriteBytes ?? 0) >
271
356
  maximumBytes
272
357
  ) {
273
358
  const [id, entry] = historicalEntries.entries().next().value!;
@@ -279,29 +364,37 @@ export function createKernelOperationCache({
279
364
  }
280
365
 
281
366
  return {
367
+ evaluateCachedArtifact,
282
368
  beginKernelOperationEvaluation,
283
- evaluateKernelOperation,
284
369
  clearKernelOperationCache,
285
370
  kernelOperationCacheStats,
286
371
  setKernelArtifactStore,
287
372
  findKernelOperation,
373
+ findKernelOperations,
288
374
  acceptKernelOperation,
289
375
  setKernelExternalBytes,
290
376
  };
291
377
  }
292
378
 
379
+ let nativeMemoryCounter = () => 0;
380
+
381
+ /** The installed backend supplies native accounting; pure caches need no kernel. */
382
+ export function setKernelNativeMemoryCounter(read: () => number): void {
383
+ nativeMemoryCounter = read;
384
+ }
385
+
293
386
  export const {
387
+ evaluateCachedArtifact,
294
388
  beginKernelOperationEvaluation,
295
- evaluateKernelOperation,
296
389
  clearKernelOperationCache,
297
390
  kernelOperationCacheStats,
298
391
  setKernelArtifactStore,
299
392
  findKernelOperation,
393
+ findKernelOperations,
300
394
  acceptKernelOperation,
301
395
  setKernelExternalBytes,
302
- } = createKernelOperationCache({
303
- nativeAllocatedBytes: () =>
304
- (getOC() as OpenCascadeInstance).Code3dMemory.AllocatedBytes(),
396
+ } = createComputationCache({
397
+ nativeAllocatedBytes: () => nativeMemoryCounter(),
305
398
  });
306
399
 
307
400
  export type KernelOperationKey = Readonly<{id: string; signature: string}>;
@@ -316,16 +409,17 @@ export function kernelOperationKey(
316
409
  arguments_,
317
410
  inputs.map(input => input.id),
318
411
  ]);
319
- return {id: contentId(signature), signature};
412
+ return {id: kernelContentId(signature), signature};
320
413
  }
321
414
 
322
- function contentId(value: string): string {
415
+ export function kernelContentId(value: string | Uint8Array): string {
323
416
  let first = 0x811c9dc5;
324
417
  let second = 0x9e3779b9;
325
418
  let third = 0x85ebca6b;
326
419
  let fourth = 0xc2b2ae35;
327
420
  for (let index = 0; index < value.length; index += 1) {
328
- const code = value.charCodeAt(index);
421
+ const code =
422
+ typeof value === 'string' ? value.charCodeAt(index) : value[index];
329
423
  first = Math.imul(first ^ code, 0x01000193);
330
424
  second = Math.imul(second ^ code, 0x27d4eb2d);
331
425
  third = Math.imul(third ^ code, 0x165667b1);
@@ -104,6 +104,10 @@ function loftContoursWithTopology(
104
104
  builder.SetMutableInput(false);
105
105
  for (const wire of wires) builder.AddWire(wire.wrapped);
106
106
  builder.Build();
107
+ if (!builder.IsDone())
108
+ throw new Error(
109
+ 'Could not construct a solid loft through these sections. Adjust their positions, orientations, or profiles.',
110
+ );
107
111
  }
108
112
  result = castOwnedShape3D(builder.Shape());
109
113
  caps[0] = castOwnedShape(builder.FirstShape()) as Face;
@@ -6,7 +6,7 @@ import {
6
6
  type SerializedImage,
7
7
  type Texture,
8
8
  type TypedArray,
9
- } from './three.js';
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('./three.js').ShaderMaterial;
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('./three.js').ShaderMaterial)
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
+ }
@@ -1,5 +1,5 @@
1
1
  import * as replicadModule from 'replicad';
2
- import {modelFromReplicadSolid} from './runtime.js';
2
+ import {primitiveConstructor} from './runtime.js';
3
3
  import {castOwnedShape} from './kernel-shapes.js';
4
4
  import type {SolidModel} from './index.js';
5
5
  import type {Shape3D} from 'replicad';
@@ -29,12 +29,12 @@ export const replicad: Replicad = Object.freeze({
29
29
 
30
30
  /**
31
31
  * Defines a synchronous model constructor with the builder's parameters.
32
- * The builder runs on every call and transfers ownership of its returned solid
33
- * to code3d. Intermediate shapes remain the builder's responsibility.
32
+ * Reuses deterministic geometry by arguments; pass changing captured state as
33
+ * explicit parameters. Each invocation creates a fresh model. The builder
34
+ * transfers its returned solid to code3d and owns its intermediate shapes.
34
35
  */
35
36
  export function definePrimitive<
36
37
  Builder extends (...arguments_: never[]) => Shape3D,
37
38
  >(build: Builder): (...arguments_: Parameters<Builder>) => SolidModel {
38
- return (...arguments_: Parameters<Builder>) =>
39
- modelFromReplicadSolid(build(...arguments_));
39
+ return primitiveConstructor(build);
40
40
  }
@@ -12,7 +12,12 @@ export function estimateRetainedBytes(value: unknown): number {
12
12
  seen.add(value);
13
13
  if (ArrayBuffer.isView(value)) return 64 + visit(value.buffer);
14
14
  if (value instanceof ArrayBuffer) return 32 + value.byteLength;
15
- const values = Object.values(value);
15
+ const values =
16
+ value instanceof Map
17
+ ? [...value].flat()
18
+ : value instanceof Set
19
+ ? [...value]
20
+ : Object.values(value);
16
21
  return (
17
22
  32 +
18
23
  values.length * 8 +