Skip to content

Commit 6f6bfc4

Browse files
zaloclaude
andcommitted
Repin opencascade.js to the real-raw_destructor fork; 5% growth step; docs
The repeat-run ratchet was never the growth ladder: upstream opencascade.js's generator emitted a NO-OP raw_destructor<T> for every class with a 2-arg operator delete (DEFINE_STANDARD_ALLOC ⇒ ~all of OCCT), so .delete() on method-returned embind values never freed — the mesher extraction alone leaked ~0.6-0.9 KB/node/run (the 85-100 MB/run ratchet) and pysrc=real evaluation carried ~40 MB more. Root-caused at the heap-byte level (probe-free4/5/6, probe-stride: ctor objects reused their chunk, method returns never did), fixed in the fork (3767f28), plus -sMEMORY_GROWTH_GEOMETRIC_STEP=0.05 (6eeb02f). pyodide+real heat_exchanger: single run 238.5 → 175.9 MB (eval-end 95.8 → 55.1), repeat ratchet 85-105 → 18-24 MB/run; JS-mode cached-scene repeats now dead flat; lite legs 137.8 / ~24.6 per-run; eval times unchanged. Gates on the final build: mesh payload hashes identical on 5 corpus models (mesh-sig.mjs), starter screenshot correct, fast specs 16/16, full suite 101 passed, harness pyodide+real 217/2/2/1 (per-script identical to the committed results.json except docs-rst/tips/b04 MISMATCH→PASS — the documented complete-tie traversal flap moving PASS-ward under changed pointer-hash enumeration), micropython default 216/2/3/1 (strict subset of the documented band), Brython control exactly 206/10/5/1. Ownership audit before enabling real frees (probe-own.mjs): embind COPIES class-type const-ref returns, so every existing delete-site deletes an owned copy; Handle.get() raws remain non-owning and are never deleted. Records: runtime-comparison.md §13, experiments/heavy-memory/STATE.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8c27269 commit 6f6bfc4

24 files changed

Lines changed: 1124 additions & 7 deletions

experiments/heavy-memory/STATE.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,54 @@ The first harness pass surfaced two ENVIRONMENTAL failures (import_step
159159
assets: /tmp/b123d had been tmp-cleaned — restored as a symlink to
160160
~/Desktop/build123d; run.sh/collect.py expect that clone) and ONE real
161161
regression (arg-wrap handle lifetime, fixed above).
162+
163+
---
164+
165+
# Mesh-transients round — state (2026-08-21, branch feat/mesh-transients)
166+
167+
Full story + final matrix in runtime-comparison.md §13. Short version:
168+
169+
1. **Attribution**: the whole ~140 MB heavy mesh transient is INSIDE the
170+
BRepMesh_IncrementalMesh ctor (marks: eval-end → mesh-tri-done carries
171+
it all; extraction/normals/isos/Clean add zero high-water; retained
172+
triangulations ~9 MB; JS extraction arrays ~14 MB JS-heap). Census is
173+
emitted per run in the `mesh-census` mark.
174+
2. **Streaming mesher landed** (commit 8c27269): mesh → extract per
175+
compound child, algo deleted per chunk, single end-of-mesh Clean (so
176+
shared subshapes are meshed once — per-chunk Clean would re-mesh them
177+
differently). Payload emission-order and hash IDENTICAL (mesh-sig.mjs).
178+
Multi-solid scenes: 365→78 MB. heat_exchanger = one chunk: unchanged.
179+
3. **The ratchet was NEVER the growth ladder**: 0.05 growth step (fork
180+
6eeb02f) bought one rung (238.5→207.5 single) but repeats still
181+
ratcheted ~85/run; 64 KB free census showed the space genuinely gone;
182+
dlmalloc == emmalloc creep (mimalloc worse floor — rejected, reverted).
183+
4. **ROOT CAUSE: upstream ocjs generator no-ops raw_destructor<T> for any
184+
class with a 2-arg operator delete — DEFINE_STANDARD_ALLOC matches on
185+
~every OCCT class ⇒ .delete() NEVER freed method-returned values**
186+
(heap-byte proof in probe-free4/5/6; per-pattern reuse in
187+
probe-stride). Only ctor-overload subclasses (gp_Pnt_1...) ever freed.
188+
Fork fix 3767f28 (bindings.py): no-op only for non-public dtors /
189+
placement-only deletes; nested select_overload return types qualified
190+
(Bnd_Box::Limits — regen used to silently drop the class). 2,044
191+
classes now really destruct. d.ts identical, 400 handles, wasm +349 B.
192+
5. **Results** (pyodide+real heavy): single 238.5 → **175.9 MB** (target
193+
≤180 MET), ratchet 85-105 → **18-24 MB/run** (target ≤20 ~met), eval
194+
time unchanged. JS-mode cached-scene repeats now DEAD FLAT. Lite legs:
195+
137.8 single / ~24.6 per-run (glue-side retention — future round).
196+
mp+upstream unchanged (no __del__ — interpreter follow-up).
197+
6. **Gates**: mesh-sig 5/5 identical; specs 16/16; suite 101; harness
198+
pyodide+real 217/2/2/1 = committed per-script except tips/b04
199+
MISMATCH→PASS (documented complete-tie traversal flap, PASS-ward);
200+
mp 216/2/3/1 (subset of documented band); brython exactly 206/10/5/1.
201+
7. **Ownership rules verified before enabling real frees** (probe-own):
202+
embind COPIES const-ref class returns (Current/Nodes/XYZ are owned
203+
copies — safe to delete); Handle.get() raws stay non-owning (never
204+
delete — now a REAL double-free). All existing delete-sites audited
205+
clean.
206+
207+
New tools: mesh-sig.mjs (payload-equivalence gate), probe-stream.mjs
208+
(streaming A/B), probe-meshiso*.mjs (leak bisection), probe-pair/ptr/
209+
stride/free3-7/own/delcheck*/jsfree/embind (the discovery chain), shot.mjs
210+
(starter screenshot). probe-marks/free2 now take CS_TEST_PORT; probe-marks
211+
free census is opt-in (CS_FREE_PROBE=1 — the census itself forces growth
212+
rungs).

experiments/heavy-memory/mesh-sig.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ async function main() {
9999
};
100100
});
101101

102+
// settle: let any late starter render land before sampling counts
103+
await new Promise((r) => setTimeout(r, 2000));
102104
const results = {};
103105
for (const m of MODELS) {
104106
const code = m.code || await page.evaluate(() =>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// probe-delcheck.mjs — count del() failures / double-ptr anomalies per iteration
2+
import { chromium } from 'playwright';
3+
const PORT = process.env.CS_TEST_PORT || '8452';
4+
const CODE = `
5+
let oc = self.oc;
6+
let failures = 0, attempts = 0, noptr = 0;
7+
let del = (...a) => { for (const o of a) {
8+
attempts++;
9+
try {
10+
if (o && o.$$ && o.$$.ptr) { o.delete(); }
11+
else { noptr++; }
12+
} catch (e) { failures++; }
13+
} };
14+
let t = Text3D("CascadeStudio0", 40, 8);
15+
let mb = () => (self.ocMemory.buffer.byteLength / 1048576).toFixed(1);
16+
let mesher = new oc.BRepMesh_IncrementalMesh_2(t, 0.1, false, 0.5, false);
17+
del(mesher);
18+
console.log('DELCHK start ' + mb());
19+
for (let i = 0; i < 20; i++) {
20+
failures = 0; attempts = 0; noptr = 0;
21+
self.ForEachFace(t, (fi, face) => {
22+
let loc = new oc.TopLoc_Location_1();
23+
let myT = oc.BRep_Tool.Triangulation(face, loc, 0);
24+
if (!myT.IsNull()) {
25+
const T = myT.get();
26+
const trsf = loc.Transformation();
27+
let n = T.NbNodes();
28+
for (let k = 1; k <= n; k++) { let p0 = T.Node(k); let p = p0.Transformed(trsf); p.X(); del(p0, p); }
29+
if (!T.HasNormals()) { T.ComputeNormals(); }
30+
for (let k = 0; k < n; k++) { let d0 = T.Normal_1(k + 1); let d = d0.Transformed(trsf); d.X(); del(d0, d); }
31+
let nt = T.NbTriangles();
32+
for (let k = 1; k <= nt; k++) { let tr = T.Triangle(k); tr.Value(1); del(tr); }
33+
del(trsf);
34+
}
35+
del(myT, loc);
36+
});
37+
console.log('DELCHK iter' + i + ' ' + mb() + ' attempts ' + attempts + ' failures ' + failures + ' noptr ' + noptr);
38+
}
39+
`;
40+
async function main() {
41+
const browser = await chromium.launch({ headless: !process.env.CS_TEST_HEADFUL,
42+
args: ['--use-gl=angle', '--use-angle=swiftshader'] });
43+
const page = await (await browser.newContext()).newPage();
44+
page.on('pageerror', () => {});
45+
const seen = new Set();
46+
page.on('console', (m) => { const t = m.text(); if (t.includes('DELCHK') && !seen.has(t)) { seen.add(t); console.log(t); } });
47+
await page.goto(`http://localhost:${PORT}/?mode=cascadestudio`, { timeout: 60000 });
48+
await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 120000 });
49+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 300000 });
50+
await page.evaluate((c) => window.CascadeAPI.runCode(c), CODE);
51+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 600000 });
52+
await new Promise((r) => setTimeout(r, 1500));
53+
await browser.close();
54+
}
55+
main().catch((e) => { console.error(e); process.exit(1); });
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// probe-delcheck2.mjs — which sub-loop retains memory (post-onset slope)?
2+
import { chromium } from 'playwright';
3+
const PORT = process.env.CS_TEST_PORT || '8452';
4+
const V = process.argv[2] || 'nodes';
5+
const INNER = {
6+
nodes: `let n = T.NbNodes();
7+
for (let k = 1; k <= n; k++) { let p0 = T.Node(k); let p = p0.Transformed(trsf); p.X(); del(p0, p); }`,
8+
nodesplain: `let n = T.NbNodes();
9+
for (let k = 1; k <= n; k++) { let p0 = T.Node(k); p0.X(); del(p0); }`,
10+
ctorplain: `let n = T.NbNodes();
11+
for (let k = 1; k <= n; k++) { let p0 = new oc.gp_Pnt_1(); p0.X(); del(p0); }`,
12+
tris: `let nt = T.NbTriangles();
13+
for (let k = 1; k <= nt; k++) { let tr = T.Triangle(k); tr.Value(1); del(tr); }`,
14+
facesonly: ``,
15+
};
16+
const CODE = `
17+
let oc = self.oc;
18+
let del = (...a) => { for (const o of a) { try { if (o && o.$$ && o.$$.ptr) o.delete(); } catch (e) {} } };
19+
let t = Text3D("CascadeStudio0", 40, 8);
20+
let mb = () => (self.ocMemory.buffer.byteLength / 1048576).toFixed(1);
21+
let mesher = new oc.BRepMesh_IncrementalMesh_2(t, 0.1, false, 0.5, false);
22+
del(mesher);
23+
console.log('DEL2 ${V} start ' + mb());
24+
for (let i = 0; i < 40; i++) {
25+
self.ForEachFace(t, (fi, face) => {
26+
let loc = new oc.TopLoc_Location_1();
27+
let myT = oc.BRep_Tool.Triangulation(face, loc, 0);
28+
if (!myT.IsNull()) {
29+
const T = myT.get();
30+
const trsf = loc.Transformation();
31+
${INNER[V]}
32+
del(trsf);
33+
}
34+
del(myT, loc);
35+
});
36+
if (i % 4 === 3) console.log('DEL2 ${V} iter' + i + ' ' + mb());
37+
}
38+
`;
39+
async function main() {
40+
const browser = await chromium.launch({ headless: !process.env.CS_TEST_HEADFUL,
41+
args: ['--use-gl=angle', '--use-angle=swiftshader'] });
42+
const page = await (await browser.newContext()).newPage();
43+
page.on('pageerror', () => {});
44+
const seen = new Set();
45+
page.on('console', (m) => { const t = m.text(); if (t.includes('DEL2') && !seen.has(t)) { seen.add(t); console.log(t); } });
46+
await page.goto(`http://localhost:${PORT}/?mode=cascadestudio`, { timeout: 60000 });
47+
await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 120000 });
48+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 300000 });
49+
await page.evaluate((c) => window.CascadeAPI.runCode(c), CODE);
50+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 600000 });
51+
await new Promise((r) => setTimeout(r, 1500));
52+
await browser.close();
53+
}
54+
main().catch((e) => { console.error(e); process.exit(1); });
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// probe-embind.mjs — per-call wasm leak isolation on candidate embind paths
2+
import { chromium } from 'playwright';
3+
const PORT = process.env.CS_TEST_PORT || '8452';
4+
const CODE = `
5+
let oc = self.oc;
6+
let mb = () => self.ocMemory.buffer.byteLength;
7+
let del = (o) => { try { if (o && o.$$ && o.$$.ptr) o.delete(); } catch (e) {} };
8+
let box = Box(10, 10, 10);
9+
let mesher = new oc.BRepMesh_IncrementalMesh_2(box, 0.1, false, 0.5, false);
10+
del(mesher);
11+
let loc = new oc.TopLoc_Location_1();
12+
let face = null;
13+
self.ForEachFace(box, (i, f) => { if (!face) face = f; });
14+
let myT = oc.BRep_Tool.Triangulation(face, loc, 0);
15+
let T = myT.get();
16+
let trsf = loc.Transformation();
17+
let scratch = new oc.gp_Pnt_1();
18+
const N = 100000;
19+
const tests = {
20+
'ctor gp_Pnt_1': () => { let p = new oc.gp_Pnt_1(); del(p); },
21+
'T.Node(1)': () => { let p = T.Node(1); del(p); },
22+
'T.NbNodes()': () => { T.NbNodes(); },
23+
'scratch.Transformed(trsf)': () => { let p = scratch.Transformed(trsf); del(p); },
24+
'scratch.X()': () => { scratch.X(); },
25+
'T.Triangle(1)': () => { let t = T.Triangle(1); del(t); },
26+
'trsf2 = loc.Transformation()': () => { let t2 = loc.Transformation(); del(t2); },
27+
};
28+
for (const [name, fn] of Object.entries(tests)) {
29+
for (let i = 0; i < 1000; i++) fn(); // warm the free lists
30+
const b0 = mb();
31+
for (let i = 0; i < N; i++) fn();
32+
const d = mb() - b0;
33+
console.log('EMBINDLEAK ' + name + ': ' + (d / N).toFixed(2) + ' B/call (total ' + (d / 1048576).toFixed(1) + ' MB)');
34+
}
35+
del(scratch); del(trsf); del(T); del(myT); del(loc);
36+
Box(1, 1, 1);
37+
`;
38+
async function main() {
39+
const browser = await chromium.launch({ headless: !process.env.CS_TEST_HEADFUL,
40+
args: ['--use-gl=angle', '--use-angle=swiftshader'] });
41+
const page = await (await browser.newContext()).newPage();
42+
page.on('pageerror', () => {});
43+
const seen = new Set();
44+
page.on('console', (m) => { const t = m.text(); if (t.includes('EMBINDLEAK') && !seen.has(t)) { seen.add(t); console.log(t); } });
45+
await page.goto(`http://localhost:${PORT}/?mode=cascadestudio`, { timeout: 60000 });
46+
await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 120000 });
47+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 300000 });
48+
await page.evaluate((c) => window.CascadeAPI.runCode(c), CODE);
49+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 600000 });
50+
await new Promise((r) => setTimeout(r, 1500));
51+
await browser.close();
52+
}
53+
main().catch((e) => { console.error(e); process.exit(1); });

experiments/heavy-memory/probe-free2.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ async function main() {
3333
const page = await (await browser.newContext()).newPage();
3434
page.on('pageerror', () => {});
3535
page.on('console', (m) => { const t = m.text(); if (t.includes('FREEMB:')) console.log('SAMPLE ' + t); });
36-
await page.goto('http://localhost:8441/?mode=python&pyruntime=pyodide&pysrc=real', { timeout: 60000 });
36+
await page.goto(`http://localhost:${process.env.CS_TEST_PORT || '8441'}/?mode=python&pyruntime=pyodide&pysrc=real`, { timeout: 60000 });
3737
await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 90000 });
3838
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 300000 });
39-
for (let i = 0; i < 2; i++) {
39+
for (let i = 0; i < 3; i++) {
4040
await page.evaluate(async (c) => { window.CascadeAPI.setMode('python'); return window.CascadeAPI.runCode(c); }, HEAVY);
4141
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 600000 });
4242
await page.evaluate(async (c) => {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// probe-free3.mjs — does delete() actually free method-returned objects?
2+
import { chromium } from 'playwright';
3+
const PORT = process.env.CS_TEST_PORT || '8452';
4+
const CODE = `
5+
let oc = self.oc;
6+
let t = Text3D("CascadeStudio0", 40, 8);
7+
let mesher = new oc.BRepMesh_IncrementalMesh_2(t, 0.1, false, 0.5, false);
8+
mesher.delete();
9+
let face0 = null, loc = new oc.TopLoc_Location_1();
10+
self.ForEachFace(t, (i, f) => { if (!face0) { face0 = f; } });
11+
let myT = oc.BRep_Tool.Triangulation(face0, loc, 0);
12+
let T = myT.get();
13+
// ctor path
14+
let c1 = new oc.gp_Pnt_1(); const pc1 = c1.$$.ptr; const cnt1 = c1.$$.count.value; c1.delete();
15+
let c2 = new oc.gp_Pnt_1(); const pc2 = c2.$$.ptr; c2.delete();
16+
console.log('FREE3 ctor: ptr1=' + pc1 + ' ptr2=' + pc2 + ' reused=' + (pc1 === pc2) + ' count=' + cnt1);
17+
// method-return path
18+
let a = T.Node(1); const pa = a.$$.ptr; const cnta = a.$$.count.value;
19+
console.log('FREE3 node pre-del: ptr=' + pa + ' count=' + cnta + ' smart=' + (!!a.$$.smartPtr) + ' preserve=' + (!!a.$$.preservePointerOnDelete));
20+
a.delete();
21+
console.log('FREE3 node post-del: ptr=' + a.$$.ptr + ' smartPtr=' + a.$$.smartPtr);
22+
let c3 = new oc.gp_Pnt_1(); const pc3 = c3.$$.ptr; c3.delete();
23+
console.log('FREE3 after-node ctor: ptr=' + pc3 + ' reusedNodeChunk=' + (pc3 === pa));
24+
// second method return immediately after deleting the first
25+
let b1 = T.Node(1); const pb1 = b1.$$.ptr; b1.delete();
26+
let b2 = T.Node(1); const pb2 = b2.$$.ptr; b2.delete();
27+
console.log('FREE3 node-node: ptr1=' + pb1 + ' ptr2=' + pb2 + ' reused=' + (pb1 === pb2));
28+
Box(1,1,1);
29+
`;
30+
async function main() {
31+
const browser = await chromium.launch({ headless: !process.env.CS_TEST_HEADFUL,
32+
args: ['--use-gl=angle', '--use-angle=swiftshader'] });
33+
const page = await (await browser.newContext()).newPage();
34+
page.on('pageerror', (e) => console.log('pageerror: ' + String(e).slice(0, 300)));
35+
const seen = new Set();
36+
page.on('console', (m) => { const t = m.text(); if (t.includes('FREE3') && !seen.has(t)) { seen.add(t); console.log(t); } });
37+
await page.goto(`http://localhost:${PORT}/?mode=cascadestudio`, { timeout: 60000 });
38+
await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 120000 });
39+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 300000 });
40+
await page.evaluate((c) => window.CascadeAPI.runCode(c), CODE);
41+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 600000 });
42+
await new Promise((r) => setTimeout(r, 1500));
43+
await browser.close();
44+
}
45+
main().catch((e) => { console.error(e); process.exit(1); });
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// probe-free4.mjs — read heap bytes at the chunk before/after delete()
2+
import { chromium } from 'playwright';
3+
const PORT = process.env.CS_TEST_PORT || '8452';
4+
const CODE = `
5+
let oc = self.oc;
6+
let t = Text3D("CascadeStudio0", 40, 8);
7+
let mesher = new oc.BRepMesh_IncrementalMesh_2(t, 0.1, false, 0.5, false);
8+
mesher.delete();
9+
let face0 = null, loc = new oc.TopLoc_Location_1();
10+
self.ForEachFace(t, (i, f) => { if (!face0) { face0 = f; } });
11+
let myT = oc.BRep_Tool.Triangulation(face0, loc, 0);
12+
let T = myT.get();
13+
const words = (ptr, n) => Array.from(new Uint32Array(self.ocMemory.buffer, ptr - 8, n)).join(',');
14+
// ctor case
15+
let c = new oc.gp_Pnt_1(); const pc = c.$$.ptr;
16+
const preC = words(pc, 8); c.delete(); const postC = words(pc, 8);
17+
console.log('FREE4 ctor ptr=' + pc + ' pre=[' + preC + '] post=[' + postC + '] changed=' + (preC !== postC));
18+
// method-return case
19+
let a = T.Node(1); const pa = a.$$.ptr;
20+
const preA = words(pa, 8); a.delete(); const postA = words(pa, 8);
21+
console.log('FREE4 node ptr=' + pa + ' pre=[' + preA + '] post=[' + postA + '] changed=' + (preA !== postA));
22+
Box(1,1,1);
23+
`;
24+
async function main() {
25+
const browser = await chromium.launch({ headless: !process.env.CS_TEST_HEADFUL,
26+
args: ['--use-gl=angle', '--use-angle=swiftshader'] });
27+
const page = await (await browser.newContext()).newPage();
28+
page.on('pageerror', (e) => console.log('pageerror: ' + String(e).slice(0, 300)));
29+
const seen = new Set();
30+
page.on('console', (m) => { const t = m.text(); if (t.includes('FREE4') && !seen.has(t)) { seen.add(t); console.log(t); } });
31+
await page.goto(`http://localhost:${PORT}/?mode=cascadestudio`, { timeout: 60000 });
32+
await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 120000 });
33+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 300000 });
34+
await page.evaluate((c) => window.CascadeAPI.runCode(c), CODE);
35+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 600000 });
36+
await new Promise((r) => setTimeout(r, 1500));
37+
await browser.close();
38+
}
39+
main().catch((e) => { console.error(e); process.exit(1); });
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// probe-free5.mjs — compare ptrType/rawDestructor between ctor and method-return handles
2+
import { chromium } from 'playwright';
3+
const PORT = process.env.CS_TEST_PORT || '8452';
4+
const CODE = `
5+
let oc = self.oc;
6+
let t = Text3D("CascadeStudio0", 40, 8);
7+
let mesher = new oc.BRepMesh_IncrementalMesh_2(t, 0.1, false, 0.5, false);
8+
mesher.delete();
9+
let face0 = null, loc = new oc.TopLoc_Location_1();
10+
self.ForEachFace(t, (i, f) => { if (!face0) { face0 = f; } });
11+
let myT = oc.BRep_Tool.Triangulation(face0, loc, 0);
12+
let T = myT.get();
13+
const words = (ptr) => Array.from(new Uint32Array(self.ocMemory.buffer, ptr - 8, 4)).join(',');
14+
let c = new oc.gp_Pnt_1();
15+
let a = T.Node(1);
16+
console.log('FREE5 ctor ptrType.name=' + c.$$.ptrType.name + ' | node ptrType.name=' + a.$$.ptrType.name);
17+
console.log('FREE5 sameRegisteredClass=' + (c.$$.ptrType.registeredClass === a.$$.ptrType.registeredClass) +
18+
' samePtrType=' + (c.$$.ptrType === a.$$.ptrType));
19+
console.log('FREE5 ctor destructorFunction=' + typeof c.$$.ptrType.destructorFunction +
20+
' node destructorFunction=' + typeof a.$$.ptrType.destructorFunction);
21+
console.log('FREE5 rawDestructor typeof=' + typeof a.$$.ptrType.registeredClass.rawDestructor);
22+
// manual rawDestructor on the node object's ptr
23+
const pa = a.$$.ptr;
24+
const pre = words(pa);
25+
try { a.$$.ptrType.registeredClass.rawDestructor(pa); } catch (e) { console.log('FREE5 manual rawDestructor threw: ' + e); }
26+
const post = words(pa);
27+
console.log('FREE5 manual-destroy pre=[' + pre + '] post=[' + post + '] changed=' + (pre !== post));
28+
c.delete();
29+
Box(1,1,1);
30+
`;
31+
async function main() {
32+
const browser = await chromium.launch({ headless: !process.env.CS_TEST_HEADFUL,
33+
args: ['--use-gl=angle', '--use-angle=swiftshader'] });
34+
const page = await (await browser.newContext()).newPage();
35+
page.on('pageerror', (e) => console.log('pageerror: ' + String(e).slice(0, 300)));
36+
const seen = new Set();
37+
page.on('console', (m) => { const t = m.text(); if (t.includes('FREE5') && !seen.has(t)) { seen.add(t); console.log(t); } });
38+
await page.goto(`http://localhost:${PORT}/?mode=cascadestudio`, { timeout: 60000 });
39+
await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 120000 });
40+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 300000 });
41+
await page.evaluate((c) => window.CascadeAPI.runCode(c), CODE);
42+
await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 600000 });
43+
await new Promise((r) => setTimeout(r, 1500));
44+
await browser.close();
45+
}
46+
main().catch((e) => { console.error(e); process.exit(1); });

0 commit comments

Comments
 (0)