pcbjam/wasm/bindings/kicad_editor_embind.cpp
Istvan Matejcsok bbeef6d20e feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2)
All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame
choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared
wx/common/boost linked once). One editor per page load, as before; frames pcb /
fpedit / sch / symedit.

- wasm/editor/: the merged executable target (single_top + both kiface library sets,
  whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by
  KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine
  Kiface/getter binding + ODR renames + dual-kiface launcher).
- wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names
  unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations
  guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each
  shared JS name once, dispatching on the live frame.
- Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile
  with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" =
  kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug
  apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run
  on kicad bumps).
- Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to
  kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish
  list = the 4 real bundles.
- Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens;
  PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they
  now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec
  covers all four frames from the one bundle.

Validated so far: frame-runtime 4/4 (each frame boots with the right title, no
aborts, no duplicate embind registration); 24-spec merged-module regression green;
3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock,
fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:56:02 +02:00

136 lines
5.1 KiB
C++

/*
* Embind dispatcher for the merged kicad_editor WASM image (editor-unification Part 2).
*
* The pcbnew and eeschema binding TUs each implement the collab bridge for their own
* frame and, standalone, register the SAME JS-facing names. In the merged image both
* TUs are compiled with -DKICAD_MERGED_EMBIND, which
* - compiles out their duplicate frame-agnostic definitions (kicadOpenFile,
* extern "C" kicadCollabOnSave) — the single definitions live HERE, and
* - compiles out their shared-name EMSCRIPTEN_BINDINGS registrations — registered
* once HERE, dispatching to the renamed per-editor entries (pcbCollab… and
* schCollab…) on whichever editor frame is live. Per-editor unique names
* (kicadSaveBoard, kicadSaveSchematic, kicadCollabTestItemBlob, Board_…) keep
* flowing from the per-editor blocks unchanged.
*
* With the one-frame-per-page-load model exactly one of pcbEditorActive() /
* schEditorActive() is true — the same dynamic_cast probe every per-editor entry
* already starts with. JS-facing names and signatures are IDENTICAL to the standalone
* bundles, so the web app and tests need no per-bundle API differences.
*
* Deliberately header-light: no pcbnew/eeschema headers (avoids mixing both include
* roots in one TU); only the generic KIWAY_PLAYER surface + the extern declarations.
*/
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/bind.h>
#include <string>
#include <vector>
#include <wx/app.h>
#include <wx/string.h>
#include <wx/window.h>
#include <kiway.h>
#include <kiway_player.h>
using namespace emscripten;
// Per-editor entry points and frame probes — defined (with external linkage) in
// pcbnew_embind.cpp / eeschema_embind.cpp.
bool pcbEditorActive();
void pcbCollabApply( std::string aJson );
void pcbCollabApplyItems( std::string aJson );
std::string pcbCollabSnapshot();
std::string pcbCollabSnapshotItems();
std::string pcbCollabTestMoveFirst( int aDx, int aDy );
std::string pcbCollabGetPos( std::string aId );
bool schEditorActive();
void schCollabApply( std::string aJson );
void schCollabApplyItems( std::string aJson );
std::string schCollabSnapshot();
std::string schCollabSnapshotItems();
std::string schCollabTestMoveFirst( int aDx, int aDy );
std::string schCollabGetPos( std::string aId );
// Programmatically open a project file in the running editor frame, without UI
// automation. Frame-agnostic (any KIWAY_PLAYER); byte-identical to the definition the
// standalone bundles compile from their own binding TU.
static bool kicadOpenFile( std::string path )
{
KIWAY_PLAYER* frame =
wxTheApp ? static_cast<KIWAY_PLAYER*>( wxTheApp->GetTopWindow() ) : nullptr;
if( !frame )
return false;
if( wxWindow* blocking = frame->Kiway().GetBlockingDialog() )
blocking->Close( true );
return frame->OpenProjectFiles(
std::vector<wxString>( 1, wxString::FromUTF8( path.c_str() ) ) );
}
// C++ → JS save notification. Called from BOTH fork save chokepoints
// (PCB_EDIT_FRAME::SavePcbFile and SCH_EDIT_FRAME::saveSchematicFile) — one shared
// definition serves the merged image. No-op without a JS listener.
extern "C" void kicadCollabOnSave( const char* aPath )
{
EM_ASM( {
if( window.kicadCollab && window.kicadCollab.onSave )
window.kicadCollab.onSave( UTF8ToString( $0 ) );
}, aPath );
}
// Dispatch shims: route each shared JS name to the live editor's implementation.
// The sch path is the fallback arm so a JS call with NO frame up behaves like the
// standalone bundles (the per-editor impls no-op / return empty on a null frame).
static void collabApply( std::string aJson )
{
pcbEditorActive() ? pcbCollabApply( aJson ) : schCollabApply( aJson );
}
static void collabApplyItems( std::string aJson )
{
pcbEditorActive() ? pcbCollabApplyItems( aJson ) : schCollabApplyItems( aJson );
}
static std::string collabSnapshot()
{
return pcbEditorActive() ? pcbCollabSnapshot() : schCollabSnapshot();
}
static std::string collabSnapshotItems()
{
return pcbEditorActive() ? pcbCollabSnapshotItems() : schCollabSnapshotItems();
}
static std::string collabTestMoveFirst( int aDx, int aDy )
{
return pcbEditorActive() ? pcbCollabTestMoveFirst( aDx, aDy )
: schCollabTestMoveFirst( aDx, aDy );
}
static std::string collabGetPos( std::string aId )
{
return pcbEditorActive() ? pcbCollabGetPos( aId ) : schCollabGetPos( aId );
}
EMSCRIPTEN_BINDINGS(kicad_editor) {
// Programmatic file open (preferred over UI automation from the web app).
function("kicadOpenFile", &kicadOpenFile);
// Yjs collaborative bridge entry points — same JS contract as the standalone
// bundles, dispatched on the active editor frame.
function("kicadCollabApply", &collabApply);
function("kicadCollabSnapshot", &collabSnapshot);
function("kicadCollabApplyItems", &collabApplyItems);
function("kicadCollabSnapshotItems", &collabSnapshotItems);
function("kicadCollabTestMoveFirst", &collabTestMoveFirst);
function("kicadCollabGetPos", &collabGetPos);
}
#endif // __EMSCRIPTEN__