fix(load): close the dispatch-interlock hole at open + open gerbers from a project route
- kicadOpenFile now holds wxWasmDispatchGuard (open_gate.h). It enters through embind, so the interlock read "nothing parked" for the whole load and wx timers dispatched into the half-built board — the residual prod "index out of bounds" that survived the settle gate. - new wasm/bindings/gerbview_embind.cpp (the bundle had no embind surface at all): kicadOpenFile / kicadOpenFiles / kicadOpenFileBusy. Clicking one gerber opens the whole fabrication set in its folder, since a lone layer is not a useful view. - cross-app presence rejoins in the boot fan-out (network-only; the wasm-bound half still waits for the open to settle) — it had been pushed behind the board load. - tests: gerber-set selection units + a gerbview multi-file open e2e. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0137pGo8W7asomGUTRMB7RzM
This commit is contained in:
parent
a26ef4ebeb
commit
d35cf4f4eb
7 changed files with 372 additions and 23 deletions
91
wasm/bindings/gerbview_embind.cpp
Normal file
91
wasm/bindings/gerbview_embind.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* GerbView embind bindings.
|
||||
*
|
||||
* GerbView boots without a document (it is one of `FILELESS_TOOLS`), but a
|
||||
* project route CAN name a gerber: clicking `Production/gerbers/board-F_Cu.gbr`
|
||||
* on the project page deep-links here. Until this TU existed the bundle had no
|
||||
* embind surface at all, so the shell staged the file into MEMFS and then had
|
||||
* no way to say "open it" — GerbView came up empty and the user had to walk
|
||||
* File→Open themselves.
|
||||
*
|
||||
* A gerber is rarely useful alone (a fabrication set is a stack of layers plus
|
||||
* drill files), and `GERBVIEW_FRAME::OpenProjectFiles` already takes a LIST and
|
||||
* auto-routes each entry by filename to the gerber / Excellon / job / archive
|
||||
* loader, then zoom-fits. So the primary entry point here is the multi-file
|
||||
* one: the shell hands over every gerber+drill sibling in the clicked file's
|
||||
* folder, and the whole board renders.
|
||||
*/
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/bind.h>
|
||||
#include <gerbview_frame.h>
|
||||
#include <kiway_player.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <wx/app.h>
|
||||
#include <wx/string.h>
|
||||
#include "open_gate.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using json = nlohmann::json;
|
||||
|
||||
static GERBVIEW_FRAME* gerbFrame()
|
||||
{
|
||||
return wxTheApp ? dynamic_cast<GERBVIEW_FRAME*>( wxTheApp->GetTopWindow() ) : nullptr;
|
||||
}
|
||||
|
||||
static bool openFileSet( const std::vector<wxString>& aFiles )
|
||||
{
|
||||
// Held across every Asyncify park of the load (open_gate.h): the layer load
|
||||
// parks, and a wx timer dispatched into a half-built layer set traps.
|
||||
pcbjam_open::BusyGuard busy;
|
||||
|
||||
GERBVIEW_FRAME* frame = gerbFrame();
|
||||
|
||||
if( !frame || aFiles.empty() )
|
||||
return false;
|
||||
|
||||
return frame->OpenProjectFiles( aFiles, 0 );
|
||||
}
|
||||
|
||||
/** Open ONE gerber/drill file (the generic single-file entry every app has). */
|
||||
static bool kicadOpenFile( std::string path )
|
||||
{
|
||||
return openFileSet( { wxString::FromUTF8( path.c_str() ) } );
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a whole fabrication set: a JSON array of MEMFS paths. Order is the
|
||||
* caller's (the shell sorts, so layer order is stable across reloads).
|
||||
* GERBVIEW_FRAME caps the set at GERBER_DRAWLAYERS_COUNT internally.
|
||||
*/
|
||||
static bool kicadOpenFiles( std::string pathsJson )
|
||||
{
|
||||
json paths = json::parse( pathsJson, nullptr, /*allow_exceptions*/ false );
|
||||
|
||||
if( !paths.is_array() )
|
||||
return false;
|
||||
|
||||
std::vector<wxString> files;
|
||||
|
||||
for( const auto& entry : paths )
|
||||
{
|
||||
if( entry.is_string() )
|
||||
files.push_back( wxString::FromUTF8( entry.get<std::string>().c_str() ) );
|
||||
}
|
||||
|
||||
return openFileSet( files );
|
||||
}
|
||||
|
||||
/** JS-pollable open-in-flight probe — same contract as the editors. */
|
||||
static bool kicadOpenFileBusy()
|
||||
{
|
||||
return pcbjam_open::busy();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS( gerbview )
|
||||
{
|
||||
function( "kicadOpenFile", &kicadOpenFile );
|
||||
function( "kicadOpenFiles", &kicadOpenFiles );
|
||||
function( "kicadOpenFileBusy", &kicadOpenFileBusy );
|
||||
}
|
||||
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include <wx/wasm/private/dispatch.h>
|
||||
|
||||
namespace pcbjam_open
|
||||
{
|
||||
|
||||
|
|
@ -27,10 +29,30 @@ inline int& busyCount()
|
|||
return s_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Held for the whole open. Two counters, same Asyncify-RAII trick:
|
||||
*
|
||||
* - `busyCount` is OURS: it answers kicadOpenFileBusy() for the web shell and
|
||||
* gates the collab entries (JS → embind reentry).
|
||||
* - `wxWasmDispatchGuard` enrolls the open in the WX DISPATCH INTERLOCK. This
|
||||
* matters because `kicadOpenFile` enters through embind, not through a wx
|
||||
* dispatch entry point, so without it `wxWasmDispatchParked()` reads FALSE
|
||||
* for the entire load: every park (progress pump, thread-pool futex wait,
|
||||
* lib bridge) lets the pump dispatch a QUEUED WX TIMER into the half-built
|
||||
* board — src/wasm/timer.cpp fires it because nothing looks parked — and
|
||||
* the handler walks half-mutated widget/board state ("index out of bounds",
|
||||
* the same signature as the symbol-chooser crash the interlock was built
|
||||
* for). Holding the guard makes those timers defer (retry 17 ms later)
|
||||
* until the load truly completes. Paints keep running; the progress
|
||||
* dialog's own pump is the designed exception (it zeroes the count).
|
||||
*/
|
||||
struct BusyGuard
|
||||
{
|
||||
BusyGuard() { ++busyCount(); }
|
||||
~BusyGuard() { --busyCount(); }
|
||||
|
||||
private:
|
||||
wxWasmDispatchGuard m_dispatch;
|
||||
};
|
||||
|
||||
/** JS-pollable: is a kicadOpenFile chain still in flight (possibly parked)? */
|
||||
|
|
|
|||
Loading…
Reference in a new issue