Add real PinMAME (VPinMAME.Controller) integration for ROM-based tables
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
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
diff --git a/src/common.h b/src/common.h
|
||||
index f0249b6..854bd16 100644
|
||||
--- a/src/common.h
|
||||
+++ b/src/common.h
|
||||
@@ -533,7 +533,7 @@ INLINE unsigned long long rotl_64(const unsigned long long x, const unsigned int
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _rotl64(x, count);
|
||||
-#elif !defined(__arm__) && !defined(__aarch64__) && (defined(__INTEL_COMPILER) || (defined(__GNUC__) && (__GNUC__ > 3)) || defined(__clang__))
|
||||
+#elif !defined(__arm__) && !defined(__aarch64__) && !defined(__EMSCRIPTEN__) && !defined(__wasm__) && (defined(__INTEL_COMPILER) || (defined(__GNUC__) && (__GNUC__ > 3)) || defined(__clang__))
|
||||
return __rolq(x, count);
|
||||
#else
|
||||
return (x<<count) | (x>>( (unsigned int)(-(int)count)&63 )); // -count&63 instead of 64-count to handle count==0
|
||||
@@ -555,7 +555,7 @@ INLINE unsigned long long rotr_64(const unsigned long long x, const unsigned int
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _rotr64(x, count);
|
||||
-#elif !defined(__arm__) && !defined(__aarch64__) && (defined(__INTEL_COMPILER) || (defined(__GNUC__) && (__GNUC__ > 3)) || defined(__clang__))
|
||||
+#elif !defined(__arm__) && !defined(__aarch64__) && !defined(__EMSCRIPTEN__) && !defined(__wasm__) && (defined(__INTEL_COMPILER) || (defined(__GNUC__) && (__GNUC__ > 3)) || defined(__clang__))
|
||||
return __rorq(x, count);
|
||||
#else
|
||||
return (x>>count) | (x<<( (unsigned int)(-(int)count)&63 )); // -count&63 instead of 64-count to handle count==0
|
||||
@@ -0,0 +1,39 @@
|
||||
diff --git a/src/cpuexec.c b/src/cpuexec.c
|
||||
index ca7401f..07db6a1 100644
|
||||
--- a/src/cpuexec.c
|
||||
+++ b/src/cpuexec.c
|
||||
@@ -943,6 +943,34 @@ void time_fence_exit()
|
||||
}
|
||||
}
|
||||
|
||||
+#elif defined(__EMSCRIPTEN__)
|
||||
+
|
||||
+// No cross-thread wait primitive is used here: this build is single-threaded
|
||||
+// (see vpinball-wasm's README on why pthreads/SharedArrayBuffer are out of
|
||||
+// scope), and time_fence is purely an *optional* external-clock-sync feature
|
||||
+// (mirrored by Controller.TimeFence in controller.vbs) - reporting it as
|
||||
+// unsupported just means the emulator paces itself on its own internal
|
||||
+// timing instead of syncing to the host's clock, which is what every other
|
||||
+// platform this library runs on outside of this fence do anyway.
|
||||
+int time_fence_is_supported()
|
||||
+{
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+void time_fence_post()
|
||||
+{
|
||||
+}
|
||||
+
|
||||
+int time_fence_wait(double secs)
|
||||
+{
|
||||
+ (void)secs;
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+void time_fence_exit()
|
||||
+{
|
||||
+}
|
||||
+
|
||||
#else
|
||||
|
||||
#include <semaphore.h>
|
||||
@@ -0,0 +1,20 @@
|
||||
diff --git a/cmake/libpinmame/CMakeLists.txt b/cmake/libpinmame/CMakeLists.txt
|
||||
index a2dfd1a..119e3ec 100644
|
||||
--- a/cmake/libpinmame/CMakeLists.txt
|
||||
+++ b/cmake/libpinmame/CMakeLists.txt
|
||||
@@ -745,6 +745,7 @@ set(PINMAME_INCLUDE_DIRS
|
||||
src/cpu/m68000/generated_by_m68kmake
|
||||
src/unix
|
||||
src/unix/sysdep
|
||||
+ ext/zlib
|
||||
)
|
||||
|
||||
|
||||
@@ -752,7 +753,6 @@ if(PLATFORM STREQUAL "win" OR PLATFORM STREQUAL "win-mingw")
|
||||
list(APPEND PINMAME_INCLUDE_DIRS
|
||||
src/vc
|
||||
src/windows
|
||||
- ext/zlib
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
diff --git a/src/cpuexec.c b/src/cpuexec.c
|
||||
index ca7401f..0bfcbe7 100644
|
||||
--- a/src/cpuexec.c
|
||||
+++ b/src/cpuexec.c
|
||||
@@ -466,6 +466,65 @@ void cpu_run(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
+#if defined(LIBPINMAME) && defined(__EMSCRIPTEN__)
|
||||
+/* Emscripten is single-threaded, so cpu_run()'s own blocking "while
|
||||
+ (!time_to_quit) { ... }" loop above can't run on a real OS thread the way
|
||||
+ it does everywhere else libpinmame runs (see PinmameRun() in
|
||||
+ libpinmame.cpp). These three entry points split that same loop, using the
|
||||
+ exact same globals/logic, into a one-shot init, a step callable once per
|
||||
+ host video frame (bounded to roughly one frame of emulated time via
|
||||
+ timer_get_time(), the same clock cpu_timeslice()'s own time_fence logic
|
||||
+ above already uses), and a one-shot teardown - so the host's own
|
||||
+ per-frame callback can drive it cooperatively instead. */
|
||||
+
|
||||
+void cpu_run_emscripten_init(void)
|
||||
+{
|
||||
+ time_to_quit = 0;
|
||||
+ cpu_pre_run();
|
||||
+ time_to_reset = 0;
|
||||
+ time_fence_global_offset = -options.time_fence;
|
||||
+}
|
||||
+
|
||||
+/* Returns 1 if the emulation is still running (call again next frame), or 0
|
||||
+ once it has fully quit (cpu_post_run() has already been invoked). */
|
||||
+int cpu_run_emscripten_step(void)
|
||||
+{
|
||||
+ const double frame_target = timer_get_time() + (1.0 / 60.0);
|
||||
+
|
||||
+ while (!time_to_quit && !time_to_reset && timer_get_time() < frame_target)
|
||||
+ {
|
||||
+ profiler_mark(PROFILER_EXTRA);
|
||||
+
|
||||
+ if (loadsave_schedule != LOADSAVE_NONE)
|
||||
+ handle_loadsave();
|
||||
+
|
||||
+ cpu_timeslice();
|
||||
+
|
||||
+ extern int libpinmame_time_to_quit(void);
|
||||
+ if (libpinmame_time_to_quit())
|
||||
+ time_to_quit = 1;
|
||||
+
|
||||
+ profiler_mark(PROFILER_END);
|
||||
+ }
|
||||
+
|
||||
+ if (time_to_reset && !time_to_quit)
|
||||
+ {
|
||||
+ cpu_post_run();
|
||||
+ cpu_pre_run();
|
||||
+ time_to_reset = 0;
|
||||
+ time_fence_global_offset = -options.time_fence;
|
||||
+ }
|
||||
+
|
||||
+ if (time_to_quit)
|
||||
+ {
|
||||
+ cpu_post_run();
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ return 1;
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
|
||||
|
||||
/*************************************
|
||||
@@ -943,6 +1002,34 @@ void time_fence_exit()
|
||||
}
|
||||
}
|
||||
|
||||
+#elif defined(__EMSCRIPTEN__)
|
||||
+
|
||||
+// No cross-thread wait primitive is used here: this build is single-threaded
|
||||
+// (see vpinball-wasm's README on why pthreads/SharedArrayBuffer are out of
|
||||
+// scope), and time_fence is purely an *optional* external-clock-sync feature
|
||||
+// (mirrored by Controller.TimeFence in controller.vbs) - reporting it as
|
||||
+// unsupported just means the emulator paces itself on its own internal
|
||||
+// timing instead of syncing to the host's clock, which is what every other
|
||||
+// platform this library runs on outside of this fence do anyway.
|
||||
+int time_fence_is_supported()
|
||||
+{
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+void time_fence_post()
|
||||
+{
|
||||
+}
|
||||
+
|
||||
+int time_fence_wait(double secs)
|
||||
+{
|
||||
+ (void)secs;
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+void time_fence_exit()
|
||||
+{
|
||||
+}
|
||||
+
|
||||
#else
|
||||
|
||||
#include <semaphore.h>
|
||||
diff --git a/src/libpinmame/libpinmame.cpp b/src/libpinmame/libpinmame.cpp
|
||||
index 6966d77..755d762 100644
|
||||
--- a/src/libpinmame/libpinmame.cpp
|
||||
+++ b/src/libpinmame/libpinmame.cpp
|
||||
@@ -1104,11 +1104,52 @@ PINMAMEAPI PINMAME_STATUS PinmameRun(const char* const p_name)
|
||||
|
||||
vp_init();
|
||||
|
||||
+#ifdef __EMSCRIPTEN__
|
||||
+ // No real OS thread: Emscripten builds here are single-threaded (see
|
||||
+ // vpinball-wasm's README on why pthreads/SharedArrayBuffer are out of
|
||||
+ // scope), so run_game()'s own blocking call chain - which StartGame()
|
||||
+ // otherwise runs on _p_gameThread - has been split in mame.c/cpuexec.c
|
||||
+ // into a one-shot init (called synchronously right here) plus a step
|
||||
+ // the host calls once per video frame via PinmameEmscriptenStep().
|
||||
+ memset(_mechInit, 0, sizeof(_mechInit));
|
||||
+ memset(_mechInfo, 0, sizeof(_mechInfo));
|
||||
+
|
||||
+ extern int run_game_emscripten_init(int game);
|
||||
+ if (run_game_emscripten_init(gameNum) != 0)
|
||||
+ {
|
||||
+ OnStateChange(0);
|
||||
+ return PINMAME_STATUS_GAME_NOT_FOUND;
|
||||
+ }
|
||||
+
|
||||
+ OnStateChange(1);
|
||||
+#else
|
||||
_p_gameThread = new std::thread(StartGame, gameNum);
|
||||
+#endif
|
||||
|
||||
return PINMAME_STATUS_OK;
|
||||
}
|
||||
|
||||
+#ifdef __EMSCRIPTEN__
|
||||
+/******************************************************
|
||||
+ * PinmameEmscriptenStep
|
||||
+ *
|
||||
+ * Must be called once per host video frame (e.g. from the same
|
||||
+ * requestAnimationFrame-driven callback that steps vpinball's own frame)
|
||||
+ * while the emulator is running - see PinmameRun's __EMSCRIPTEN__ branch
|
||||
+ * above for why this exists instead of a real thread.
|
||||
+ ******************************************************/
|
||||
+
|
||||
+PINMAMEAPI void PinmameEmscriptenStep(void)
|
||||
+{
|
||||
+ if (!_isRunning)
|
||||
+ return;
|
||||
+
|
||||
+ extern int run_game_emscripten_step(void);
|
||||
+ if (!run_game_emscripten_step())
|
||||
+ OnStateChange(0);
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
/******************************************************
|
||||
* PinmameIsRunning
|
||||
******************************************************/
|
||||
@@ -1161,6 +1202,19 @@ PINMAMEAPI int PinmameIsPaused()
|
||||
|
||||
PINMAMEAPI void PinmameStop()
|
||||
{
|
||||
+#ifdef __EMSCRIPTEN__
|
||||
+ // No game thread to join here (see PinmameRun's __EMSCRIPTEN__ branch) -
|
||||
+ // just request the stop. The next PinmameEmscriptenStep() call notices
|
||||
+ // libpinmame_time_to_quit() via cpu_run_emscripten_step(), runs the same
|
||||
+ // teardown chain a real quit would, and calls OnStateChange(0) itself.
|
||||
+ if (_isRunning)
|
||||
+ {
|
||||
+ g_fPause = 0;
|
||||
+ _timeToQuit = 1;
|
||||
+ }
|
||||
+ return;
|
||||
+#endif
|
||||
+
|
||||
if (!_p_gameThread) {
|
||||
if (_isRunning) {
|
||||
libpinmame_log_error("PinmameStop(): run state is %d but game thread handle is null; forcing stopped state.", _isRunning);
|
||||
diff --git a/src/libpinmame/libpinmame.h b/src/libpinmame/libpinmame.h
|
||||
index dc604df..d3aacc1 100644
|
||||
--- a/src/libpinmame/libpinmame.h
|
||||
+++ b/src/libpinmame/libpinmame.h
|
||||
@@ -453,6 +453,11 @@ PINMAMEAPI PINMAME_STATUS PinmamePause(const int pause);
|
||||
PINMAMEAPI int PinmameIsPaused();
|
||||
PINMAMEAPI PINMAME_STATUS PinmameReset();
|
||||
PINMAMEAPI void PinmameStop();
|
||||
+#ifdef __EMSCRIPTEN__
|
||||
+// Must be called once per host video frame while running - see PinmameRun's
|
||||
+// __EMSCRIPTEN__ branch in libpinmame.cpp for why.
|
||||
+PINMAMEAPI void PinmameEmscriptenStep(void);
|
||||
+#endif
|
||||
PINMAMEAPI PINMAME_HARDWARE_GEN PinmameGetHardwareGen();
|
||||
PINMAMEAPI int PinmameGetSwitch(const int swNo);
|
||||
PINMAMEAPI void PinmameSetSwitch(const int swNo, const int state);
|
||||
diff --git a/src/mame.c b/src/mame.c
|
||||
index d0aea1a..2f8c4d5 100644
|
||||
--- a/src/mame.c
|
||||
+++ b/src/mame.c
|
||||
@@ -229,6 +229,16 @@ static void shutdown_machine(void);
|
||||
static int run_machine(void);
|
||||
static void run_machine_core(void);
|
||||
|
||||
+#if defined(LIBPINMAME) && defined(__EMSCRIPTEN__)
|
||||
+/* See cpu_run_emscripten_init's comment in cpuexec.c for why these exist. */
|
||||
+void cpu_run_emscripten_init(void);
|
||||
+int cpu_run_emscripten_step(void);
|
||||
+void run_machine_core_emscripten_init(void);
|
||||
+int run_machine_core_emscripten_step(void);
|
||||
+int run_machine_emscripten_init(void);
|
||||
+int run_machine_emscripten_step(void);
|
||||
+#endif
|
||||
+
|
||||
#ifdef MAME_DEBUG
|
||||
static int validitychecks(void);
|
||||
#endif
|
||||
@@ -355,6 +365,71 @@ int run_game(int game)
|
||||
return err;
|
||||
}
|
||||
|
||||
+#if defined(LIBPINMAME) && defined(__EMSCRIPTEN__)
|
||||
+/* Mirrors run_game() above, split around run_machine() the same way that's
|
||||
+ split around run_machine_core(). This is the top of the whole init/step/
|
||||
+ teardown chain PinmameRun()/PinmameEmscriptenStep() in libpinmame.cpp
|
||||
+ drive - see cpu_run_emscripten_init's comment in cpuexec.c for why the
|
||||
+ chain exists at all. Returns 0 on success, non-zero on failure (matching
|
||||
+ run_game()'s own convention) - a failure here (e.g. ROM not found) means
|
||||
+ no teardown call is needed, since nothing that needs undoing succeeded. */
|
||||
+int run_game_emscripten_init(int game)
|
||||
+{
|
||||
+ begin_resource_tracking();
|
||||
+
|
||||
+ memset(Machine, 0, sizeof(*Machine));
|
||||
+ Machine->gamedrv = gamedrv = drivers[game];
|
||||
+ expand_machine_driver(gamedrv->drv, &internal_drv);
|
||||
+ Machine->drv = &internal_drv;
|
||||
+
|
||||
+ if (init_game_options())
|
||||
+ return 1;
|
||||
+
|
||||
+ cpu_loadsave_reset();
|
||||
+ bailing = 0;
|
||||
+
|
||||
+ if (osd_init())
|
||||
+ {
|
||||
+ bail_and_print("Unable to initialize system");
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ begin_resource_tracking();
|
||||
+
|
||||
+ if (init_machine())
|
||||
+ {
|
||||
+ bail_and_print("Unable to initialize machine emulation");
|
||||
+ end_resource_tracking();
|
||||
+ osd_exit();
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ if (run_machine_emscripten_init())
|
||||
+ {
|
||||
+ bail_and_print("Unable to start machine emulation");
|
||||
+ shutdown_machine();
|
||||
+ end_resource_tracking();
|
||||
+ osd_exit();
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* Returns 1 while still running, 0 once fully torn down. */
|
||||
+int run_game_emscripten_step(void)
|
||||
+{
|
||||
+ if (run_machine_emscripten_step())
|
||||
+ return 1;
|
||||
+
|
||||
+ shutdown_machine();
|
||||
+ end_resource_tracking();
|
||||
+ osd_exit();
|
||||
+ end_resource_tracking();
|
||||
+ return 0;
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
|
||||
|
||||
/*-------------------------------------------------
|
||||
@@ -547,6 +622,71 @@ static int run_machine(void)
|
||||
return res;
|
||||
}
|
||||
|
||||
+#if defined(LIBPINMAME) && defined(__EMSCRIPTEN__)
|
||||
+/* Mirrors run_machine() above, split around run_machine_core() the same way
|
||||
+ that's split around cpu_run(). Returns 0 on success (matching
|
||||
+ run_machine()'s own convention), non-zero on failure. */
|
||||
+int run_machine_emscripten_init(void)
|
||||
+{
|
||||
+ if (vh_open())
|
||||
+ {
|
||||
+ bail_and_print("Unable to start video emulation");
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ tilemap_init();
|
||||
+
|
||||
+ if (Machine->drv->video_start && (*Machine->drv->video_start)())
|
||||
+ {
|
||||
+ bail_and_print("Unable to start video emulation");
|
||||
+ tilemap_close();
|
||||
+ vh_close();
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ if (sound_start())
|
||||
+ {
|
||||
+ bail_and_print("Unable to start audio emulation");
|
||||
+ if (Machine->drv->video_stop)
|
||||
+ (*Machine->drv->video_stop)();
|
||||
+ tilemap_close();
|
||||
+ vh_close();
|
||||
+ return 1;
|
||||
+ }
|
||||
+
|
||||
+ {
|
||||
+ int region;
|
||||
+ /* free memory regions allocated with REGIONFLAG_DISPOSE (typically gfx roms) */
|
||||
+ for (region = 0; region < MAX_MEMORY_REGIONS; region++)
|
||||
+ if (Machine->memory_region[region].flags & ROMREGION_DISPOSE)
|
||||
+ {
|
||||
+ size_t i;
|
||||
+ for (i = 0; i < memory_region_length(region); i++)
|
||||
+ memory_region(region)[i] = rand();
|
||||
+ free(Machine->memory_region[region].base);
|
||||
+ Machine->memory_region[region].base = 0;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ run_machine_core_emscripten_init();
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* Returns 1 while still running, 0 once fully torn down. */
|
||||
+int run_machine_emscripten_step(void)
|
||||
+{
|
||||
+ if (run_machine_core_emscripten_step())
|
||||
+ return 1;
|
||||
+
|
||||
+ sound_stop();
|
||||
+ if (Machine->drv->video_stop)
|
||||
+ (*Machine->drv->video_stop)();
|
||||
+ tilemap_close();
|
||||
+ vh_close();
|
||||
+ return 0;
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
|
||||
|
||||
/*-------------------------------------------------
|
||||
@@ -620,6 +760,60 @@ void run_machine_core(void)
|
||||
}
|
||||
}
|
||||
|
||||
+#if defined(LIBPINMAME) && defined(__EMSCRIPTEN__)
|
||||
+/* Mirrors run_machine_core() above (same globals, same calls), split around
|
||||
+ cpu_run() - see cpu_run_emscripten_init/_step/PinmameRun's own comment for
|
||||
+ why. The disclaimer/gamewarnings/gameinfo splash screens run_machine_core()
|
||||
+ shows natively are skipped entirely here: gamewarnings is already excluded
|
||||
+ for LIBPINMAME builds above, and disclaimer/gameinfo are native-UI-only
|
||||
+ concerns with nothing meaningful to display in an embedded/headless
|
||||
+ context like this one. */
|
||||
+void run_machine_core_emscripten_init(void)
|
||||
+{
|
||||
+ artwork_enable(0);
|
||||
+ init_user_interface();
|
||||
+ artwork_enable(1);
|
||||
+
|
||||
+ if (!gamedrv->rom)
|
||||
+ options.cheat = 0;
|
||||
+ if (options.cheat)
|
||||
+ InitCheat();
|
||||
+
|
||||
+ if (Machine->drv->nvram_handler)
|
||||
+ {
|
||||
+ mame_file *nvram_file = mame_fopen(Machine->gamedrv->name, 0, FILETYPE_NVRAM, 0);
|
||||
+ (*Machine->drv->nvram_handler)(nvram_file, 0);
|
||||
+ if (nvram_file)
|
||||
+ mame_fclose(nvram_file);
|
||||
+ }
|
||||
+
|
||||
+ cpu_run_emscripten_init();
|
||||
+}
|
||||
+
|
||||
+/* Returns 1 while still running, 0 once cpu_run's own teardown has run. */
|
||||
+int run_machine_core_emscripten_step(void)
|
||||
+{
|
||||
+ if (cpu_run_emscripten_step())
|
||||
+ return 1;
|
||||
+
|
||||
+ if (Machine->drv->nvram_handler)
|
||||
+ {
|
||||
+ mame_file *nvram_file = mame_fopen(Machine->gamedrv->name, 0, FILETYPE_NVRAM, 1);
|
||||
+ if (nvram_file != NULL)
|
||||
+ {
|
||||
+ (*Machine->drv->nvram_handler)(nvram_file, 1);
|
||||
+ mame_fclose(nvram_file);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (options.cheat)
|
||||
+ StopCheat();
|
||||
+
|
||||
+ save_input_port_settings();
|
||||
+ return 0;
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
|
||||
|
||||
/*-------------------------------------------------
|
||||
@@ -0,0 +1,112 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user