Phase E complete: K4-K6 become token waits — the bridge set is converted, suite at baseline
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2SU74acXyviwSxBhMunFe
This commit is contained in:
parent
75209dc07e
commit
d6c4efcf1f
5 changed files with 190 additions and 86 deletions
|
|
@ -53,16 +53,28 @@ extern "C" void Pcbjam_SetExportJobJson( const char* aJson )
|
|||
s_nextJobJson = aJson ? aJson : "";
|
||||
}
|
||||
|
||||
// Suspends pcbnew (Asyncify; the __asyncjs__* import is auto-covered by
|
||||
// scripts/common/asyncify-imports.txt) while the worker exports. JS returns a
|
||||
// malloc'd JSON string: { ok, report } — the download already happened there.
|
||||
EM_ASYNC_JS( char*, js_occExportRequest,
|
||||
( const char* aBoardPath, const char* aJobJson, const char* aFileName ),
|
||||
// Phase E shape (docs/features/async/22 §5, K4): waits for the worker export
|
||||
// via a token wait (context park when the frame stands on a scheduler context)
|
||||
// instead of Asyncify-parking the stack in place. The wait result is a
|
||||
// malloc'd JSON string: { ok, report } — the download already happened in JS.
|
||||
// Every resolution defers to at least a microtask (the early-resolve
|
||||
// contract, doc 22 §10 Phase E retry entry).
|
||||
EM_JS( void, js_occExportStart,
|
||||
( int aToken, const char* aBoardPath, const char* aJobJson, const char* aFileName ),
|
||||
{
|
||||
const boardPath = UTF8ToString( aBoardPath );
|
||||
const jobJson = UTF8ToString( aJobJson );
|
||||
const fileName = UTF8ToString( aFileName );
|
||||
let res;
|
||||
|
||||
const finish = ( res ) => {
|
||||
const s = JSON.stringify( res || { ok: false, report: 'occ_service: no response' } );
|
||||
const n = lengthBytesUTF8( s ) + 1;
|
||||
const p = _malloc( n );
|
||||
stringToUTF8( s, p, n );
|
||||
globalThis.__wxScheduler.resolveWait( aToken, p );
|
||||
};
|
||||
|
||||
let req;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -70,27 +82,30 @@ EM_ASYNC_JS( char*, js_occExportRequest,
|
|||
|
||||
if( !hook || typeof hook.request !== 'function' )
|
||||
{
|
||||
res = { ok: false, report: 'occ_service provider not installed' };
|
||||
req = Promise.resolve( { ok: false, report: 'occ_service provider not installed' } );
|
||||
}
|
||||
else
|
||||
{
|
||||
const board = FS.readFile( boardPath ); // Uint8Array copy — transferable
|
||||
res = await hook.request( { kind: 'export', board, jobJson, fileName } );
|
||||
req = Promise.resolve( hook.request( { kind: 'export', board, jobJson, fileName } ) );
|
||||
}
|
||||
}
|
||||
catch( e )
|
||||
{
|
||||
console.error( '[pcbjam-occ] export request failed:', e );
|
||||
res = { ok: false, report: 'occ_service request failed: ' + e };
|
||||
req = Promise.resolve( { ok: false, report: 'occ_service request failed: ' + e } );
|
||||
}
|
||||
|
||||
const s = JSON.stringify( res || { ok: false, report: 'occ_service: no response' } );
|
||||
const n = lengthBytesUTF8( s ) + 1;
|
||||
const p = _malloc( n );
|
||||
stringToUTF8( s, p, n );
|
||||
return p;
|
||||
req.then( finish ).catch( ( e ) => {
|
||||
console.error( '[pcbjam-occ] export request failed:', e );
|
||||
finish( { ok: false, report: 'occ_service request failed: ' + e } );
|
||||
} );
|
||||
} )
|
||||
|
||||
// Token waits live in the wx wasm port (evtloop.cpp).
|
||||
extern "C" int wxWasmBeginWait( const char* aKind );
|
||||
extern "C" int wxWasmYieldUntil( int aToken );
|
||||
|
||||
|
||||
EXPORTER_STEP::EXPORTER_STEP( BOARD* aBoard, const EXPORTER_STEP_PARAMS& aParams,
|
||||
REPORTER* aReporter ) :
|
||||
|
|
@ -152,8 +167,11 @@ bool EXPORTER_STEP::Export()
|
|||
|
||||
const wxString downloadName = wxFileName( m_outputFile ).GetFullName();
|
||||
|
||||
char* response = js_occExportRequest( TMP_BOARD, jobJson.c_str(),
|
||||
downloadName.utf8_string().c_str() );
|
||||
const int token = wxWasmBeginWait( "occ" );
|
||||
js_occExportStart( token, TMP_BOARD, jobJson.c_str(), downloadName.utf8_string().c_str() );
|
||||
|
||||
// The malloc'd JSON pointer rides the wait as an int32.
|
||||
char* response = (char*) (uintptr_t) (uint32_t) wxWasmYieldUntil( token );
|
||||
|
||||
bool ok = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,13 +58,23 @@ bool acceptAnyCacheTag( const char*, void* )
|
|||
|
||||
} // namespace
|
||||
|
||||
// Suspends pcbnew while the worker parses + tessellates the model. JS returns
|
||||
// a malloc'd path string: the scenegraph-cache file it wrote into this
|
||||
// module's MEMFS ("" on failure).
|
||||
EM_ASYNC_JS( char*, js_occLoadModelRequest, ( const char* aModelPath ),
|
||||
// Phase E shape (docs/features/async/22 §5, K5): waits for the worker
|
||||
// parse+tessellate via a token wait instead of Asyncify-parking in place. The
|
||||
// wait result is a malloc'd path string: the scenegraph-cache file written
|
||||
// into this module's MEMFS ("" on failure). Every resolution defers to at
|
||||
// least a microtask (the early-resolve contract, doc 22 §10 Phase E retry).
|
||||
EM_JS( void, js_occLoadModelStart, ( int aToken, const char* aModelPath ),
|
||||
{
|
||||
const modelPath = UTF8ToString( aModelPath );
|
||||
let cachePath = '';
|
||||
|
||||
const finish = ( cachePath ) => {
|
||||
const n = lengthBytesUTF8( cachePath ) + 1;
|
||||
const p = _malloc( n );
|
||||
stringToUTF8( cachePath, p, n );
|
||||
globalThis.__wxScheduler.resolveWait( aToken, p );
|
||||
};
|
||||
|
||||
let req;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -73,37 +83,46 @@ EM_ASYNC_JS( char*, js_occLoadModelRequest, ( const char* aModelPath ),
|
|||
if( !hook || typeof hook.request !== 'function' )
|
||||
{
|
||||
console.error( '[pcbjam-occ] loadModel: occ_service provider not installed' );
|
||||
req = Promise.resolve( null );
|
||||
}
|
||||
else
|
||||
{
|
||||
const bytes = FS.readFile( modelPath ); // Uint8Array copy — transferable
|
||||
const dot = modelPath.lastIndexOf( '.' );
|
||||
const ext = dot >= 0 ? modelPath.slice( dot + 1 ) : 'step';
|
||||
const res = await hook.request( { kind: 'loadModel', bytes, ext } );
|
||||
|
||||
if( res && res.ok && res.bytes && res.bytes.length )
|
||||
{
|
||||
cachePath = '/tmp/pcbjam_occ_model_cache.3dc';
|
||||
FS.writeFile( cachePath, res.bytes );
|
||||
}
|
||||
else if( res && res.report )
|
||||
{
|
||||
console.error( '[pcbjam-occ] loadModel failed:', res.report );
|
||||
}
|
||||
req = Promise.resolve( hook.request( { kind: 'loadModel', bytes, ext } ) );
|
||||
}
|
||||
}
|
||||
catch( e )
|
||||
{
|
||||
console.error( '[pcbjam-occ] loadModel request failed:', e );
|
||||
cachePath = '';
|
||||
req = Promise.resolve( null );
|
||||
}
|
||||
|
||||
const n = lengthBytesUTF8( cachePath ) + 1;
|
||||
const p = _malloc( n );
|
||||
stringToUTF8( cachePath, p, n );
|
||||
return p;
|
||||
req.then( ( res ) => {
|
||||
let cachePath = '';
|
||||
|
||||
if( res && res.ok && res.bytes && res.bytes.length )
|
||||
{
|
||||
cachePath = '/tmp/pcbjam_occ_model_cache.3dc';
|
||||
FS.writeFile( cachePath, res.bytes );
|
||||
}
|
||||
else if( res && res.report )
|
||||
{
|
||||
console.error( '[pcbjam-occ] loadModel failed:', res.report );
|
||||
}
|
||||
|
||||
finish( cachePath );
|
||||
} ).catch( ( e ) => {
|
||||
console.error( '[pcbjam-occ] loadModel request failed:', e );
|
||||
finish( '' );
|
||||
} );
|
||||
} )
|
||||
|
||||
// Token waits live in the wx wasm port (evtloop.cpp).
|
||||
extern "C" int wxWasmBeginWait( const char* aKind );
|
||||
extern "C" int wxWasmYieldUntil( int aToken );
|
||||
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
|
@ -205,7 +224,11 @@ SCENEGRAPH* oce3d_Load( char const* aFileName )
|
|||
if( !aFileName )
|
||||
return nullptr;
|
||||
|
||||
char* cachePath = js_occLoadModelRequest( aFileName );
|
||||
const int token = wxWasmBeginWait( "occ" );
|
||||
js_occLoadModelStart( token, aFileName );
|
||||
|
||||
// The malloc'd path pointer rides the wait as an int32.
|
||||
char* cachePath = (char*) (uintptr_t) (uint32_t) wxWasmYieldUntil( token );
|
||||
|
||||
if( !cachePath || !*cachePath )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,69 +56,88 @@ using nlohmann::json;
|
|||
|
||||
// Generic request: JSON in, JSON out (malloc'd; caller frees). Vector data
|
||||
// never travels this path — see js_ngspice_get_vec.
|
||||
//
|
||||
// Phase E shape (docs/features/async/22 §5, K6): token wait instead of an
|
||||
// in-place Asyncify park; resolution ALWAYS deferred to at least a microtask
|
||||
// (the early-resolve contract, doc 22 §10 Phase E retry entry).
|
||||
// clang-format off
|
||||
EM_ASYNC_JS( char*, js_ngspice_request, ( const char* aReqJson ), {
|
||||
let res;
|
||||
EM_JS( void, js_ngspice_request_start, ( int aToken, const char* aReqJson ), {
|
||||
const finish = ( res ) => {
|
||||
const s = JSON.stringify( res ?? {} );
|
||||
const n = lengthBytesUTF8( s ) + 1;
|
||||
const p = _malloc( n );
|
||||
stringToUTF8( s, p, n );
|
||||
globalThis.__wxScheduler.resolveWait( aToken, p );
|
||||
};
|
||||
let req;
|
||||
try {
|
||||
const svc = globalThis.ngspiceService;
|
||||
if( !svc )
|
||||
res = { error: 'ngspiceService provider not installed' };
|
||||
req = Promise.resolve( { error: 'ngspiceService provider not installed' } );
|
||||
else
|
||||
res = await svc.request( JSON.parse( UTF8ToString( aReqJson ) ) );
|
||||
req = Promise.resolve( svc.request( JSON.parse( UTF8ToString( aReqJson ) ) ) );
|
||||
} catch( e ) {
|
||||
res = { error: String( e ) };
|
||||
req = Promise.resolve( { error: String( e ) } );
|
||||
}
|
||||
const s = JSON.stringify( res ?? {} );
|
||||
const n = lengthBytesUTF8( s ) + 1;
|
||||
const p = _malloc( n );
|
||||
stringToUTF8( s, p, n );
|
||||
return p;
|
||||
req.then( finish ).catch( ( e ) => finish( { error: String( e ) } ) );
|
||||
} );
|
||||
|
||||
// Vector fetch: fills editor-heap buffers directly (no JSON for MB arrays).
|
||||
// aMeta: int[4] = { found, vtype, flags, length }; aReal/aComp receive
|
||||
// malloc'd double buffers (comp interleaved re,im — the ngcomplex_t layout);
|
||||
// aVName receives a malloc'd name string. Returns non-zero on transport error.
|
||||
EM_ASYNC_JS( int, js_ngspice_get_vec,
|
||||
( const char* aName, int* aMeta, double** aReal, double** aComp, char** aVName ), {
|
||||
let res;
|
||||
// aVName receives a malloc'd name string. The wait result is non-zero on
|
||||
// transport error. All output writes happen in the resolve callback, BEFORE
|
||||
// resolveWait — the parked caller reads them only after it resumes, the same
|
||||
// ordering the in-place park had. Phase E shape, resolution always deferred.
|
||||
EM_JS( void, js_ngspice_get_vec_start,
|
||||
( int aToken, const char* aName, int* aMeta, double** aReal, double** aComp,
|
||||
char** aVName ), {
|
||||
const finish = ( status ) => globalThis.__wxScheduler.resolveWait( aToken, status );
|
||||
let req;
|
||||
try {
|
||||
const svc = globalThis.ngspiceService;
|
||||
res = svc ? await svc.request( { kind: 'get_vec_info', name: UTF8ToString( aName ) } )
|
||||
: { error: 'ngspiceService provider not installed' };
|
||||
req = svc ? Promise.resolve( svc.request( { kind: 'get_vec_info',
|
||||
name: UTF8ToString( aName ) } ) )
|
||||
: Promise.resolve( { error: 'ngspiceService provider not installed' } );
|
||||
} catch( e ) {
|
||||
res = { error: String( e ) };
|
||||
req = Promise.resolve( { error: String( e ) } );
|
||||
}
|
||||
HEAP32[aMeta >> 2] = 0;
|
||||
HEAPU32[aReal >> 2] = 0;
|
||||
HEAPU32[aComp >> 2] = 0;
|
||||
HEAPU32[aVName >> 2] = 0;
|
||||
if( !res || res.error )
|
||||
return 1;
|
||||
if( !res.found )
|
||||
return 0;
|
||||
HEAP32[( aMeta >> 2 ) + 1] = res.vtype | 0;
|
||||
HEAP32[( aMeta >> 2 ) + 2] = res.flags | 0;
|
||||
HEAP32[( aMeta >> 2 ) + 3] = res.length | 0;
|
||||
if( res.real && res.real.length ) {
|
||||
const p = _malloc( res.real.length * 8 );
|
||||
HEAPF64.set( res.real, p >> 3 );
|
||||
HEAPU32[aReal >> 2] = p;
|
||||
}
|
||||
if( res.comp && res.comp.length ) {
|
||||
const p = _malloc( res.comp.length * 8 );
|
||||
HEAPF64.set( res.comp, p >> 3 );
|
||||
HEAPU32[aComp >> 2] = p;
|
||||
}
|
||||
const s = res.vname || '';
|
||||
const n = lengthBytesUTF8( s ) + 1;
|
||||
const vp = _malloc( n );
|
||||
stringToUTF8( s, vp, n );
|
||||
HEAPU32[aVName >> 2] = vp;
|
||||
HEAP32[aMeta >> 2] = 1;
|
||||
return 0;
|
||||
req.catch( ( e ) => ( { error: String( e ) } ) ).then( ( res ) => {
|
||||
HEAP32[aMeta >> 2] = 0;
|
||||
HEAPU32[aReal >> 2] = 0;
|
||||
HEAPU32[aComp >> 2] = 0;
|
||||
HEAPU32[aVName >> 2] = 0;
|
||||
if( !res || res.error )
|
||||
return finish( 1 );
|
||||
if( !res.found )
|
||||
return finish( 0 );
|
||||
HEAP32[( aMeta >> 2 ) + 1] = res.vtype | 0;
|
||||
HEAP32[( aMeta >> 2 ) + 2] = res.flags | 0;
|
||||
HEAP32[( aMeta >> 2 ) + 3] = res.length | 0;
|
||||
if( res.real && res.real.length ) {
|
||||
const p = _malloc( res.real.length * 8 );
|
||||
HEAPF64.set( res.real, p >> 3 );
|
||||
HEAPU32[aReal >> 2] = p;
|
||||
}
|
||||
if( res.comp && res.comp.length ) {
|
||||
const p = _malloc( res.comp.length * 8 );
|
||||
HEAPF64.set( res.comp, p >> 3 );
|
||||
HEAPU32[aComp >> 2] = p;
|
||||
}
|
||||
const s = res.vname || '';
|
||||
const n = lengthBytesUTF8( s ) + 1;
|
||||
const vp = _malloc( n );
|
||||
stringToUTF8( s, vp, n );
|
||||
HEAPU32[aVName >> 2] = vp;
|
||||
HEAP32[aMeta >> 2] = 1;
|
||||
finish( 0 );
|
||||
} );
|
||||
} );
|
||||
|
||||
// Token waits live in the wx wasm port (evtloop.cpp).
|
||||
extern "C" int wxWasmBeginWait( const char* aKind );
|
||||
extern "C" int wxWasmYieldUntil( int aToken );
|
||||
|
||||
// Event dispatcher: provider `{ evt }` frames -> KiCad's registered callbacks
|
||||
// via the exported pcbjam_ngspice_event (fresh wasm entries; see header
|
||||
// comment). Installed once, at first pcbjam_ngSpice_Init.
|
||||
|
|
@ -166,7 +185,11 @@ std::atomic<bool> s_bgRunning{ false };
|
|||
|
||||
json rpc( const json& aReq )
|
||||
{
|
||||
char* raw = js_ngspice_request( aReq.dump().c_str() );
|
||||
const int token = wxWasmBeginWait( "ngspice" );
|
||||
js_ngspice_request_start( token, aReq.dump().c_str() );
|
||||
|
||||
// The malloc'd JSON pointer rides the wait as an int32.
|
||||
char* raw = (char*) (uintptr_t) (uint32_t) wxWasmYieldUntil( token );
|
||||
json res = json::parse( raw ? raw : "{}", nullptr, /* allow_exceptions */ false );
|
||||
std::free( raw );
|
||||
|
||||
|
|
@ -408,7 +431,10 @@ pvector_info pcbjam_ngGet_Vec_Info( char* aVecName )
|
|||
double* comp = nullptr;
|
||||
char* vname = nullptr;
|
||||
|
||||
if( js_ngspice_get_vec( aVecName ? aVecName : "", meta, &real, &comp, &vname ) != 0 )
|
||||
const int token = wxWasmBeginWait( "ngspice" );
|
||||
js_ngspice_get_vec_start( token, aVecName ? aVecName : "", meta, &real, &comp, &vname );
|
||||
|
||||
if( wxWasmYieldUntil( token ) != 0 )
|
||||
return nullptr;
|
||||
|
||||
if( !meta[0] )
|
||||
|
|
|
|||
Loading…
Reference in a new issue