1
0
mirror of https://github.com/djhackersdev/bemanitools.git synced 2025-02-21 21:00:02 +01:00

feat: Add iidxio-async implementation

A shim library implementing the same concept as the already
existing ddrio-async library. The iidxio implementation takes
another iidxio library and runs it asynchronously which may
improve performance for certain iidxio implementations,
e.g. if the send and receive functions are driving actual IO
calls synchrously and are expensive.

This is not a replacement for a well engineered and proper
implementation of a iidxio library for any specific use-case.
It does not fix bad performance of existing implementations,
i.e. if the poll rate is too low because actual IO is too slow.

Use with caution and know why and when you need to use
it.
This commit is contained in:
icex2 2025-02-08 22:42:52 +01:00 committed by icex2
parent 64381a84c5
commit b64dea0849
6 changed files with 461 additions and 0 deletions

View File

@ -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
@ -456,6 +457,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 \
@ -469,6 +471,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 \

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,9 @@
dlls += iidxio-async
ldflags_iidxio-async := \
libs_iidxio-async := \
util \
src_iidxio-async := \
iidxio.c \

View File

@ -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

View File

@ -0,0 +1,407 @@
#define LOG_MODULE "iidxio-async"
#include <windows.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#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;
}