diff --git a/Module.mk b/Module.mk
index da18e50..b6fa2f3 100644
--- a/Module.mk
+++ b/Module.mk
@@ -154,6 +154,7 @@ include src/main/iidxhook6/Module.mk
include src/main/iidxhook7/Module.mk
include src/main/iidxhook8/Module.mk
include src/main/iidxhook9/Module.mk
+include src/main/iidxio-async/Module.mk
include src/main/iidxio-bio2/Module.mk
include src/main/iidxio-ezusb/Module.mk
include src/main/iidxio-ezusb2/Module.mk
@@ -460,6 +461,7 @@ $(zipdir)/iidx-27-to-30.zip: \
$(zipdir)/iidx-hwio-x86.zip: \
build/bin/indep-32/aciomgr.dll \
build/bin/indep-32/eamio-icca.dll \
+ build/bin/indep-32/iidxio-async.dll \
build/bin/indep-32/iidxio-bio2.dll \
build/bin/indep-32/iidxio-ezusb.dll \
build/bin/indep-32/iidxio-ezusb2.dll \
@@ -473,6 +475,7 @@ $(zipdir)/iidx-hwio-x86.zip: \
$(zipdir)/iidx-hwio-x64.zip: \
build/bin/indep-64/aciomgr.dll \
build/bin/indep-64/eamio-icca.dll \
+ build/bin/indep-64/iidxio-async.dll \
build/bin/indep-64/iidxio-bio2.dll \
build/bin/indep-64/iidxio-ezusb.dll \
build/bin/indep-64/iidxio-ezusb2.dll \
diff --git a/doc/iidxhook/README.md b/doc/iidxhook/README.md
index cd1e68d..e39f56e 100644
--- a/doc/iidxhook/README.md
+++ b/doc/iidxhook/README.md
@@ -48,6 +48,8 @@ Available implementations that can be swapped out depending on which kind of IO
use:
- `iidxio`: Default implementation supporting keyboard, mouse and USB game controllers
+- [iidxio-async](iidxhook/iidxio-async.md): Shim implementation that runs another iidxio implementation in a dedicated
+ thread
- [iidxio-bio2](iidxhook/iidxio-bio2.md): Support BIO2 hardware
- [iidxio-ezusb](iidxhook/iidxio-ezusb.md): Support C02 ezusb FX hardware
- [iidxio-ezusb2](iidxhook/iidxio-ezusb2.md): Support IO2 ezusb FX2 hardware
diff --git a/doc/iidxhook/iidxio-async.md b/doc/iidxhook/iidxio-async.md
new file mode 100644
index 0000000..82f7c21
--- /dev/null
+++ b/doc/iidxhook/iidxio-async.md
@@ -0,0 +1,22 @@
+# IIDXIO async API implementation
+
+This implementation of the iidxio API is a shim library that takes another iidxio library and
+runs the functions `iidx_io_ep1_send`, `iidx_io_ep2_recv` and `iidx_io_ep3_write_16seg` in a
+dedicated thread. State synchronization to the getter and setter functions is also handled
+transparently.
+
+Usage of this **may** improve performance of certain iidxio implementations or when using them
+in certain integrations, e.g. the send and receive functions of a iidxio implementation for some
+target IO hardware calls are synchronous and expensive.
+
+This is not a fix/solution to a badly implemented iidxio library with poor performance as it cannot
+make it go faster and address potential latency issues, for example.
+
+Use with caution and know why and when you need to use it.
+
+## Setup
+
+* Add `iidxio-async.dll` in the same folder as your `iidxhookX.dll`
+* Rename your `iidxio.dll` to `iidxio-async-child.dll`
+* Rename `iidxio-async.dll` to `iidxio.dll`
+* Run the game
\ No newline at end of file
diff --git a/lib/cimgui/cimgui/imgui/examples/example_glfw_wgpu/web/index.html b/lib/cimgui/cimgui/imgui/examples/example_glfw_wgpu/web/index.html
new file mode 100644
index 0000000..a2a91c4
--- /dev/null
+++ b/lib/cimgui/cimgui/imgui/examples/example_glfw_wgpu/web/index.html
@@ -0,0 +1,84 @@
+
+
+
+
+
+ Dear ImGui Emscripten+GLFW+WebGPU example
+
+
+
+
+
+
+
diff --git a/lib/cimgui/cimgui/imgui/examples/libs/glfw/lib-vc2010-32/glfw3.lib b/lib/cimgui/cimgui/imgui/examples/libs/glfw/lib-vc2010-32/glfw3.lib
new file mode 100644
index 0000000..348abec
Binary files /dev/null and b/lib/cimgui/cimgui/imgui/examples/libs/glfw/lib-vc2010-32/glfw3.lib differ
diff --git a/lib/cimgui/cimgui/imgui/examples/libs/glfw/lib-vc2010-64/glfw3.lib b/lib/cimgui/cimgui/imgui/examples/libs/glfw/lib-vc2010-64/glfw3.lib
new file mode 100644
index 0000000..768f308
Binary files /dev/null and b/lib/cimgui/cimgui/imgui/examples/libs/glfw/lib-vc2010-64/glfw3.lib differ
diff --git a/src/main/iidxio-async/Module.mk b/src/main/iidxio-async/Module.mk
new file mode 100644
index 0000000..d852e4e
--- /dev/null
+++ b/src/main/iidxio-async/Module.mk
@@ -0,0 +1,9 @@
+dlls += iidxio-async
+
+ldflags_iidxio-async := \
+
+libs_iidxio-async := \
+ util \
+
+src_iidxio-async := \
+ iidxio.c \
diff --git a/src/main/iidxio-async/iidxio-async.def b/src/main/iidxio-async/iidxio-async.def
new file mode 100644
index 0000000..403900d
--- /dev/null
+++ b/src/main/iidxio-async/iidxio-async.def
@@ -0,0 +1,18 @@
+LIBRARY iidxio
+
+EXPORTS
+ iidx_io_ep1_send
+ iidx_io_ep1_set_deck_lights
+ iidx_io_ep1_set_panel_lights
+ iidx_io_ep1_set_top_lamps
+ iidx_io_ep1_set_top_neons
+ iidx_io_ep2_get_keys
+ iidx_io_ep2_get_panel
+ iidx_io_ep2_get_sys
+ iidx_io_ep2_get_slider
+ iidx_io_ep2_get_turntable
+ iidx_io_ep2_recv
+ iidx_io_ep3_write_16seg
+ iidx_io_fini
+ iidx_io_init
+ iidx_io_set_loggers
diff --git a/src/main/iidxio-async/iidxio.c b/src/main/iidxio-async/iidxio.c
new file mode 100644
index 0000000..b81ec6b
--- /dev/null
+++ b/src/main/iidxio-async/iidxio.c
@@ -0,0 +1,407 @@
+#define LOG_MODULE "iidxio-async"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "bemanitools/iidxio.h"
+
+#include "util/log.h"
+#include "util/thread.h"
+#include "util/time.h"
+
+typedef void (*iidx_io_set_loggers_t)(
+ log_formatter_t misc,
+ log_formatter_t info,
+ log_formatter_t warning,
+ log_formatter_t fatal);
+typedef bool (*iidx_io_init_t)(
+ thread_create_t thread_create,
+ thread_join_t thread_join,
+ thread_destroy_t thread_destroy);
+typedef void (*iidx_io_fini_t)(void);
+typedef void (*iidx_io_ep1_set_deck_lights_t)(uint16_t deck_lights);
+typedef void (*iidx_io_ep1_set_panel_lights_t)(uint8_t panel_lights);
+typedef void (*iidx_io_ep1_set_top_lamps_t)(uint8_t top_lamps);
+typedef void (*iidx_io_ep1_set_top_neons_t)(bool top_neons);
+typedef bool (*iidx_io_ep1_send_t)(void);
+typedef bool (*iidx_io_ep2_recv_t)(void);
+typedef uint8_t (*iidx_io_ep2_get_turntable_t)(uint8_t player_no);
+typedef uint8_t (*iidx_io_ep2_get_slider_t)(uint8_t slider_no);
+typedef uint8_t (*iidx_io_ep2_get_sys_t)(void);
+typedef uint8_t (*iidx_io_ep2_get_panel_t)(void);
+typedef uint16_t (*iidx_io_ep2_get_keys_t)(void);
+typedef bool (*iidx_io_ep3_write_16seg_t)(const char *text);
+
+static HMODULE _child_iidx_io_module;
+
+static iidx_io_set_loggers_t _child_iidx_io_set_loggers;
+static iidx_io_init_t _child_iidx_io_init;
+static iidx_io_fini_t _child_iidx_io_fini;
+
+static iidx_io_ep1_set_deck_lights_t _child_iidx_io_ep1_set_deck_lights;
+static iidx_io_ep1_set_panel_lights_t _child_iidx_io_ep1_set_panel_lights;
+static iidx_io_ep1_set_top_lamps_t _child_iidx_io_ep1_set_top_lamps;
+static iidx_io_ep1_set_top_neons_t _child_iidx_io_ep1_set_top_neons;
+static iidx_io_ep1_send_t _child_iidx_io_ep1_send;
+static iidx_io_ep2_recv_t _child_iidx_io_ep2_recv;
+static iidx_io_ep2_get_turntable_t _child_iidx_io_ep2_get_turntable;
+static iidx_io_ep2_get_slider_t _child_iidx_io_ep2_get_slider;
+static iidx_io_ep2_get_sys_t _child_iidx_io_ep2_get_sys;
+static iidx_io_ep2_get_panel_t _child_iidx_io_ep2_get_panel;
+static iidx_io_ep2_get_keys_t _child_iidx_io_ep2_get_keys;
+static iidx_io_ep3_write_16seg_t _child_iidx_io_ep3_write_16seg;
+
+static log_formatter_t _log_formatter_misc;
+static log_formatter_t _log_formatter_info;
+static log_formatter_t _log_formatter_warning;
+static log_formatter_t _log_formatter_fatal;
+
+static _Atomic(bool) _io_thread_proc_loop;
+static _Atomic(bool) _io_thread_proc_running;
+
+static _Atomic(uint16_t) _child_iidx_io_deck_lights;
+static _Atomic(uint8_t) _child_iidx_io_panel_lights;
+static _Atomic(uint8_t) _child_iidx_io_top_lamps;
+static _Atomic(bool) _child_iidx_io_top_neons;
+
+static _Atomic(uint8_t) _child_iidx_io_turntable_p1;
+static _Atomic(uint8_t) _child_iidx_io_turntable_p2;
+static _Atomic(uint8_t) _child_iidx_io_slider_1;
+static _Atomic(uint8_t) _child_iidx_io_slider_2;
+static _Atomic(uint8_t) _child_iidx_io_slider_3;
+static _Atomic(uint8_t) _child_iidx_io_slider_4;
+static _Atomic(uint8_t) _child_iidx_io_slider_5;
+static _Atomic(uint8_t) _child_iidx_io_sys;
+static _Atomic(uint8_t) _child_iidx_io_panel;
+static _Atomic(uint16_t) _child_iidx_io_keys;
+
+static HANDLE _child_iidx_io_16seg_mutex;
+static char _child_iidx_16seg[9];
+static bool _child_iidx_io_16seg_dirty;
+
+static int _io_thread_proc(void *ctx)
+{
+ uint64_t time_start;
+ uint64_t time_end;
+ uint64_t loop_counter;
+ uint64_t total_time;
+ char text_16seg[9];
+
+ bool result;
+
+ atomic_store_explicit(&_io_thread_proc_running, true, memory_order_seq_cst);
+
+ log_info("IO thread running");
+
+ time_start = time_get_counter();
+ loop_counter = 0;
+
+ while (atomic_load_explicit(&_io_thread_proc_loop, memory_order_seq_cst)) {
+ result = _child_iidx_io_ep2_recv();
+
+ if (!result) {
+ log_warning("_child_iidx_io_ep2_recv returned false");
+ atomic_store_explicit(
+ &_io_thread_proc_running, false, memory_order_seq_cst);
+
+ log_info("IO thread shut down");
+
+ return 0;
+ }
+
+ atomic_store_explicit(&_child_iidx_io_turntable_p1, _child_iidx_io_ep2_get_turntable(0), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_turntable_p2, _child_iidx_io_ep2_get_turntable(1), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_slider_1, _child_iidx_io_ep2_get_slider(0), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_slider_2, _child_iidx_io_ep2_get_slider(1), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_slider_3, _child_iidx_io_ep2_get_slider(2), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_slider_4, _child_iidx_io_ep2_get_slider(3), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_slider_5, _child_iidx_io_ep2_get_slider(4), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_sys, _child_iidx_io_ep2_get_sys(), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_panel, _child_iidx_io_ep2_get_panel(), memory_order_relaxed);
+ atomic_store_explicit(&_child_iidx_io_keys, _child_iidx_io_ep2_get_keys(), memory_order_relaxed);
+
+ _child_iidx_io_ep1_set_deck_lights(atomic_load_explicit(&_child_iidx_io_deck_lights, memory_order_relaxed));
+ _child_iidx_io_ep1_set_panel_lights(atomic_load_explicit(&_child_iidx_io_panel_lights, memory_order_relaxed));
+ _child_iidx_io_ep1_set_top_lamps(atomic_load_explicit(&_child_iidx_io_top_lamps, memory_order_relaxed));
+ _child_iidx_io_ep1_set_top_neons(atomic_load_explicit(&_child_iidx_io_top_neons, memory_order_relaxed));
+
+ result = _child_iidx_io_ep1_send();
+
+ if (!result) {
+ log_warning("_child_iidx_io_ep1_send returned false");
+ atomic_store_explicit(
+ &_io_thread_proc_running, false, memory_order_seq_cst);
+
+ log_info("IO thread shut down");
+
+ return 0;
+ }
+
+ if (_child_iidx_io_16seg_dirty) {
+ WaitForSingleObject(_child_iidx_io_16seg_mutex, INFINITE);
+ memcpy(text_16seg, _child_iidx_16seg, sizeof(text_16seg));
+ _child_iidx_io_16seg_dirty = false;
+ ReleaseMutex(_child_iidx_io_16seg_mutex);
+
+ result = _child_iidx_io_ep3_write_16seg(text_16seg);
+
+ if (!result) {
+ log_warning("_child_iidx_io_ep3_write_16seg returned false");
+ atomic_store_explicit(
+ &_io_thread_proc_running, false, memory_order_seq_cst);
+
+ log_info("IO thread shut down");
+
+ return 0;
+ }
+ }
+
+ // Don't hog the CPU
+ SwitchToThread();
+
+ loop_counter++;
+ }
+
+ time_end = time_get_counter();
+ total_time = time_get_elapsed_us(time_end - time_start);
+
+ log_info(
+ "IO thread performance: total iterations %lld, avg. loop cycle time %f "
+ "us",
+ loop_counter,
+ ((double) total_time) / loop_counter);
+
+ atomic_store_explicit(
+ &_io_thread_proc_running, false, memory_order_seq_cst);
+
+ log_info("IO thread shut down");
+
+ return 0;
+}
+
+static void *_load_function(HMODULE module, const char *name)
+{
+ void *ptr;
+
+ ptr = GetProcAddress(module, name);
+
+ if (ptr == NULL) {
+ log_fatal("Could not find function %s in iidxio child library", name);
+ }
+
+ return ptr;
+}
+
+void iidx_io_set_loggers(
+ log_formatter_t misc,
+ log_formatter_t info,
+ log_formatter_t warning,
+ log_formatter_t fatal)
+{
+ _log_formatter_misc = misc;
+ _log_formatter_info = info;
+ _log_formatter_warning = warning;
+ _log_formatter_fatal = fatal;
+
+ log_to_external(misc, info, warning, fatal);
+}
+
+bool iidx_io_init(
+ thread_create_t thread_create,
+ thread_join_t thread_join,
+ thread_destroy_t thread_destroy)
+{
+ log_info("Loading iidxio-async-child.dll as child iidxio library...");
+
+ _child_iidx_io_module = LoadLibraryA("iidxio-async-child.dll");
+
+ if (_child_iidx_io_module == NULL) {
+ log_warning("Loading iidxio-async-child.dll failed");
+ return false;
+ }
+
+ _child_iidx_io_set_loggers =
+ _load_function(_child_iidx_io_module, "iidx_io_set_loggers");
+ _child_iidx_io_init = _load_function(_child_iidx_io_module, "iidx_io_init");
+ _child_iidx_io_fini = _load_function(_child_iidx_io_module, "iidx_io_fini");
+
+ _child_iidx_io_ep1_set_deck_lights =
+ _load_function(_child_iidx_io_module, "iidx_io_ep1_set_deck_lights");
+ _child_iidx_io_ep1_set_panel_lights =
+ _load_function(_child_iidx_io_module, "iidx_io_ep1_set_panel_lights");
+ _child_iidx_io_ep1_set_top_lamps =
+ _load_function(_child_iidx_io_module, "iidx_io_ep1_set_top_lamps");
+ _child_iidx_io_ep1_set_top_neons =
+ _load_function(_child_iidx_io_module, "iidx_io_ep1_set_top_neons");
+ _child_iidx_io_ep1_send =
+ _load_function(_child_iidx_io_module, "iidx_io_ep1_send");
+ _child_iidx_io_ep2_recv =
+ _load_function(_child_iidx_io_module, "iidx_io_ep2_recv");
+ _child_iidx_io_ep2_get_turntable =
+ _load_function(_child_iidx_io_module, "iidx_io_ep2_get_turntable");
+ _child_iidx_io_ep2_get_slider =
+ _load_function(_child_iidx_io_module, "iidx_io_ep2_get_slider");
+ _child_iidx_io_ep2_get_sys =
+ _load_function(_child_iidx_io_module, "iidx_io_ep2_get_sys");
+ _child_iidx_io_ep2_get_panel =
+ _load_function(_child_iidx_io_module, "iidx_io_ep2_get_panel");
+ _child_iidx_io_ep2_get_keys =
+ _load_function(_child_iidx_io_module, "iidx_io_ep2_get_keys");
+ _child_iidx_io_ep3_write_16seg =
+ _load_function(_child_iidx_io_module, "iidx_io_ep3_write_16seg");
+
+ _child_iidx_io_set_loggers(
+ _log_formatter_misc,
+ _log_formatter_info,
+ _log_formatter_warning,
+ _log_formatter_fatal);
+
+ _child_iidx_io_16seg_mutex = CreateMutex(NULL, FALSE, NULL);
+
+ log_info("Calling child iidx_io_init...");
+
+ if (!_child_iidx_io_init(thread_create, thread_join, thread_destroy)) {
+ log_warning("Child iidx_io_init failed");
+ FreeLibrary(_child_iidx_io_module);
+ CloseHandle(_child_iidx_io_16seg_mutex);
+
+ return false;
+ }
+
+ atomic_store_explicit(&_io_thread_proc_loop, true, memory_order_seq_cst);
+
+ if (!thread_create(_io_thread_proc, NULL, 16384, 0)) {
+ log_warning("Creating IO thread failed");
+
+ _child_iidx_io_fini();
+ FreeLibrary(_child_iidx_io_module);
+ CloseHandle(_child_iidx_io_16seg_mutex);
+ return false;
+ }
+
+ return true;
+}
+
+void iidx_io_fini(void)
+{
+ atomic_store_explicit(&_io_thread_proc_loop, false, memory_order_seq_cst);
+
+ log_info("Shutting down IO thread and waiting for it to finish...");
+
+ while (
+ atomic_load_explicit(&_io_thread_proc_running, memory_order_seq_cst)) {
+ Sleep(1);
+ }
+
+ log_info("IO thread finished");
+
+ _child_iidx_io_fini();
+
+ FreeLibrary(_child_iidx_io_module);
+ CloseHandle(_child_iidx_io_16seg_mutex);
+}
+
+void iidx_io_ep1_set_deck_lights(uint16_t deck_lights)
+{
+ atomic_store_explicit(
+ &_child_iidx_io_deck_lights, deck_lights, memory_order_relaxed);
+}
+
+void iidx_io_ep1_set_panel_lights(uint8_t panel_lights)
+{
+ atomic_store_explicit(
+ &_child_iidx_io_panel_lights, panel_lights, memory_order_relaxed);
+}
+
+void iidx_io_ep1_set_top_lamps(uint8_t top_lamps)
+{
+ atomic_store_explicit(
+ &_child_iidx_io_top_lamps, top_lamps, memory_order_relaxed);
+}
+
+void iidx_io_ep1_set_top_neons(bool top_neons)
+{
+ atomic_store_explicit(
+ &_child_iidx_io_top_neons, top_neons, memory_order_relaxed);
+}
+
+bool iidx_io_ep1_send(void)
+{
+ // Any sending and receiving is executed async in a separate thread
+ return true;
+}
+
+bool iidx_io_ep2_recv(void)
+{
+ // Any sending and receiving is executed async in a separate thread
+ return true;
+}
+
+uint8_t iidx_io_ep2_get_turntable(uint8_t player_no)
+{
+ switch (player_no)
+ {
+ case 0:
+ return atomic_load_explicit(&_child_iidx_io_turntable_p1, memory_order_relaxed);
+ case 1:
+ return atomic_load_explicit(&_child_iidx_io_turntable_p2, memory_order_relaxed);
+ default:
+ return 0;
+ }
+}
+
+uint8_t iidx_io_ep2_get_slider(uint8_t slider_no)
+{
+ switch (slider_no)
+ {
+ case 0:
+ return atomic_load_explicit(&_child_iidx_io_slider_1, memory_order_relaxed);
+ case 1:
+ return atomic_load_explicit(&_child_iidx_io_slider_2, memory_order_relaxed);
+ case 2:
+ return atomic_load_explicit(&_child_iidx_io_slider_3, memory_order_relaxed);
+ case 3:
+ return atomic_load_explicit(&_child_iidx_io_slider_4, memory_order_relaxed);
+ case 4:
+ return atomic_load_explicit(&_child_iidx_io_slider_5, memory_order_relaxed);
+ default:
+ return 0;
+ }
+}
+
+uint8_t iidx_io_ep2_get_sys(void)
+{
+ return atomic_load_explicit(&_child_iidx_io_sys, memory_order_relaxed);
+}
+
+uint8_t iidx_io_ep2_get_panel(void)
+{
+ return atomic_load_explicit(&_child_iidx_io_panel, memory_order_relaxed);
+}
+
+uint16_t iidx_io_ep2_get_keys(void)
+{
+ return atomic_load_explicit(&_child_iidx_io_keys, memory_order_relaxed);
+}
+
+bool iidx_io_ep3_write_16seg(const char *text)
+{
+ // This section is only producer while the thread is the only consumer
+ // Utilize this to optimize the 16seg writing and only write if the text has actually changed
+ if (!strcmp(text, _child_iidx_16seg)) {
+ return true;
+ }
+
+ WaitForSingleObject(_child_iidx_io_16seg_mutex, INFINITE);
+ memcpy(_child_iidx_16seg, text, sizeof(_child_iidx_16seg));
+ _child_iidx_io_16seg_dirty = true;
+ ReleaseMutex(_child_iidx_io_16seg_mutex);
+
+ return true;
+}
\ No newline at end of file