Statically links the real PinMAME emulation core (libpinmame) instead of leaving VPinMAME.Controller creation to fail, which was crashing table scripts on ROM-based tables before they could spawn a ball. PinMAME's own run_game()->cpu_run() loop is split into a one-shot init, a per-frame step, and a one-shot teardown (patches/pinmame/0004) so it runs cooperatively on the same frame callback as vpinball's own loop instead of on a real std::thread, which hard-aborts under Emscripten's single-threaded runtime - three smaller wasm32 portability fixes to libpinmame itself round out the patch set (0001-0003). Adds pinball.loadRom() to supply a ROM zip, written to the table-relative pinmame/roms/ path vpinball's own plugin already checks. Confirmed against a real community ROM-based table: Controller creation and game identification succeed, and a missing ROM now fails cleanly instead of crashing the page - actual ROM-driven gameplay is still unconfirmed since no ROM was available (or sought out) to test with. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SoSarxLgY33Kax5UNXcafZ
113 lines
4.7 KiB
Diff
113 lines
4.7 KiB
Diff
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
index 745b91b..edb4112 100644
|
|
--- a/CMakeLists.txt
|
|
+++ b/CMakeLists.txt
|
|
@@ -810,8 +810,29 @@ elseif(PLATFORM STREQUAL "emscripten")
|
|
src/input/OpenPinDevHandler.cpp
|
|
)
|
|
|
|
+ # PinMAME VPX-side plugin glue (registers VPinMAME.Controller for real
|
|
+ # ROM-driven scoring/switches/solenoids) - statically linked and
|
|
+ # registered from player.cpp instead of desktop VP's dynamic /plugins
|
|
+ # folder scan, same as the built-in "vpx" plugin already is. The real
|
|
+ # emulation core it drives (libpinmame.a) is staged into
|
|
+ # VPINBALL_WASM_DEPS_DIR by vpinball-wasm's own build-deps.sh (see
|
|
+ # patches/pinmame/ for the 3 portability fixes that wasm32 build needed).
|
|
+ set(VPX_PINMAME_PLUGIN_SOURCES
|
|
+ plugins/pinmame/common.cpp
|
|
+ plugins/pinmame/PinMAMEPlugin.cpp
|
|
+ plugins/pinmame/Controller.cpp
|
|
+ plugins/pinmame/ControllerSettings.cpp
|
|
+ plugins/pinmame/Game.cpp
|
|
+ plugins/pinmame/Games.cpp
|
|
+ plugins/pinmame/GameSettings.cpp
|
|
+ plugins/pinmame/Rom.cpp
|
|
+ plugins/pinmame/Roms.cpp
|
|
+ plugins/pinmame/Settings.cpp
|
|
+ )
|
|
+
|
|
add_executable(vpinball
|
|
${VPX_STANDALONE_SOURCES}
|
|
+ ${VPX_PINMAME_PLUGIN_SOURCES}
|
|
${CMAKE_SOURCE_DIR}/src/core/EmscriptenBridge.cpp
|
|
)
|
|
|
|
@@ -824,6 +845,7 @@ elseif(PLATFORM STREQUAL "emscripten")
|
|
${VPINBALL_WASM_DEPS_DIR}/include/libwinevbs/wine/include
|
|
src
|
|
plugins
|
|
+ plugins/pinmame
|
|
)
|
|
|
|
target_compile_definitions(vpinball PRIVATE
|
|
@@ -863,6 +885,7 @@ elseif(PLATFORM STREQUAL "emscripten")
|
|
freetype
|
|
freeimage
|
|
winevbs
|
|
+ pinmame
|
|
glad
|
|
)
|
|
|
|
diff --git a/src/core/player.cpp b/src/core/player.cpp
|
|
index 60c7d5f..ec9ebdd 100644
|
|
--- a/src/core/player.cpp
|
|
+++ b/src/core/player.cpp
|
|
@@ -79,6 +79,19 @@ using namespace VPX;
|
|
// leave as-is as e.g. VPM relies on this
|
|
#define WIN32_PLAYER_WND_CLASSNAME _T("VPPlayer")
|
|
|
|
+#ifdef __EMSCRIPTEN__
|
|
+// Statically-linked player plugin entry points (see the __EMSCRIPTEN__
|
|
+// branch in Player::Player below) - same declaration shape used for the
|
|
+// iOS/Android static plugin table in lib/src/VPinballLib.cpp, just declared
|
|
+// at file scope here since MSGPI_EXPORT's __attribute__((visibility(...)))
|
|
+// isn't accepted on a block-scope declaration.
|
|
+MSGPI_EXPORT void MSGPIAPI PinMAMEPluginLoad(const uint32_t sessionId, const MsgPluginAPI* api);
|
|
+MSGPI_EXPORT void MSGPIAPI PinMAMEPluginUnload();
|
|
+
|
|
+// For PinmameEmscriptenStep(), called once per frame in EmscriptenStepFrame()
|
|
+// below - see that function's own comment.
|
|
+#include "pinmame/libpinmame.h"
|
|
+#endif
|
|
|
|
Player::Player(PinTable *const table, const PlayMode playMode)
|
|
: m_ptable(table)
|
|
@@ -128,6 +141,24 @@ Player::Player(PinTable *const table, const PlayMode playMode)
|
|
|
|
#ifdef __LIBVPINBALL__
|
|
VPinballLib::VPinballLib::SetupStaticPlugins(m_pluginManager);
|
|
+#elif defined(__EMSCRIPTEN__)
|
|
+ // A browser sandbox has no dynamic-library-loading /plugins folder to
|
|
+ // scan (see the ScanPluginFolder call in the #else branch below), so
|
|
+ // player plugins have to be statically linked into this same wasm binary
|
|
+ // and registered by function pointer instead - the same technique
|
|
+ // VPinballLib::SetupStaticPlugins already uses for the iOS/Android
|
|
+ // library build (lib/src/VPinballLib.cpp), just scoped here to the one
|
|
+ // plugin vpinball-wasm actually ships: PinMAME, which is what lets
|
|
+ // table scripts get a real (non-Nothing) VPinMAME.Controller for ROM-
|
|
+ // driven scoring/switches/solenoids instead of failing on
|
|
+ // CreateObject("VPinMAME.Controller") - see patches/pinmame/ for the
|
|
+ // wasm32 portability fixes its emulation core (libpinmame) needed.
|
|
+ //
|
|
+ // Registered only, not Load()-ed here: the per-plugin enable/Load loop
|
|
+ // just below (shared with the dynamic-scan #else branch) already does
|
|
+ // that for every entry in m_pluginManager.GetPlugins(), so calling Load()
|
|
+ // here too would just load it twice.
|
|
+ m_pluginManager.RegisterPlugin("PinMAME", "PinMAME", "PinMAME", "", "", "https://github.com/vpinball/pinmame", &PinMAMEPluginLoad, &PinMAMEPluginUnload);
|
|
#else
|
|
class SDLModuleLoader final : public MsgPI::MsgModuleLoader
|
|
{
|
|
@@ -2002,6 +2033,12 @@ bool Player::EmscriptenStepFrame()
|
|
if (GetCloseState() != CS_PLAYING && GetCloseState() != CS_USER_INPUT)
|
|
return false;
|
|
|
|
+ // PinMAME (VPinMAME.Controller for ROM-driven tables) is likewise
|
|
+ // single-threaded here instead of running on its own real thread - see
|
|
+ // PinmameEmscriptenStep's own comment in libpinmame.cpp. A no-op when no
|
|
+ // ROM-based table has called Controller.Run().
|
|
+ PinmameEmscriptenStep();
|
|
+
|
|
UpdateGameLogic();
|
|
PrepareFrame();
|
|
UpdateGameLogic();
|