mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Scope package overrides and defer snapshots across their native stacks
This commit is contained in:
@@ -190,6 +190,17 @@ void func_override_call_original(struct CPUState *cpu);
|
||||
int func_override_add_package(const char *id, uint32_t addr, FuncOverrideFn fn,
|
||||
const uint32_t *expected_words, int n_words,
|
||||
int32_t credit);
|
||||
/* Scoped package variant. The owner supplies a stable plan context and
|
||||
* invokes fn inside that plugin's resource/completion context. The plan must
|
||||
* disarm package entries before replacing that context. */
|
||||
typedef int (*FuncOverrideInvoker)(struct CPUState *cpu, FuncOverrideFn fn,
|
||||
const void *context);
|
||||
int func_override_add_package_scoped(const char *id, uint32_t addr, FuncOverrideFn fn,
|
||||
const uint32_t *expected_words, int n_words, int32_t credit,
|
||||
FuncOverrideInvoker invoke, const void *context);
|
||||
/* Nonzero while a native override stack is live, including nested guest
|
||||
* calls. Save/load and rewind must defer until this returns to zero. */
|
||||
int func_override_active(void);
|
||||
|
||||
/* Install the dispatcher hook. Call once at startup AFTER registering (the
|
||||
* mod runtime calls it again after arming package-gated overrides — safe).
|
||||
|
||||
@@ -145,7 +145,9 @@ uint32_t psx_mod_guest_cycle_scale(void);
|
||||
* selects that plugin — the same gating as vblank/activation callbacks. The
|
||||
* callback obeys the guest ABI (args cpu->gpr[4..7], result cpu->gpr[2]);
|
||||
* return 1 = handled (guest resumes at $ra), 0 = decline (original runs).
|
||||
* Wrap semantics via func_override_call_original(); optional residency
|
||||
* The owning plugin resource context is active throughout the callback and
|
||||
* restored across nested callbacks. Snapshots wait until its native stack
|
||||
* unwinds. Wrap semantics via func_override_call_original(); optional residency
|
||||
* guard via expected_words (NULL/0 = unguarded).
|
||||
*
|
||||
* `id` may carry an optional ":label" suffix ("pkg.feature:aim") that names
|
||||
|
||||
@@ -35,6 +35,8 @@ typedef struct {
|
||||
char id[FO_MAX_ID];
|
||||
uint32_t phys; /* normalised: KSEG bits masked off */
|
||||
FuncOverrideFn fn;
|
||||
FuncOverrideInvoker invoke;
|
||||
const void *context;
|
||||
uint64_t calls;
|
||||
uint64_t guard_misses;
|
||||
uint32_t guard[FO_MAX_GUARD_WORDS];
|
||||
@@ -127,6 +129,22 @@ int func_override_add_package(const char *id, uint32_t addr, FuncOverrideFn fn,
|
||||
1, credit);
|
||||
}
|
||||
|
||||
int func_override_add_package_scoped(const char *id, uint32_t addr, FuncOverrideFn fn,
|
||||
const uint32_t *expected_words, int n_words, int32_t credit,
|
||||
FuncOverrideInvoker invoke, const void *context)
|
||||
{
|
||||
if (!invoke || !context) return FO_ERR_ARGS;
|
||||
const int rc = func_override_add_package(id,addr,fn,expected_words,n_words,credit);
|
||||
if (rc == FO_OK) {
|
||||
Entry *entry = &s_entries[s_count-1];
|
||||
entry->invoke = invoke;
|
||||
entry->context = context;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int func_override_active(void) { return s_active_phys != 0; }
|
||||
|
||||
/* The hook. Runs on EVERY dispatch, so the common path — nothing registered
|
||||
* for this address — must stay cheap. A linear scan over a handful of
|
||||
* entries beats a hash for realistic counts and keeps the code honest;
|
||||
@@ -168,7 +186,7 @@ static int hook(CPUState *cpu, uint32_t phys)
|
||||
{
|
||||
const uint32_t saved_active = s_active_phys;
|
||||
s_active_phys = phys;
|
||||
const int handled = e->fn(cpu) ? 1 : 0;
|
||||
const int handled = (e->invoke ? e->invoke(cpu,e->fn,e->context) : e->fn(cpu)) ? 1 : 0;
|
||||
s_active_phys = saved_active;
|
||||
/* Charge the declared credit only when HANDLED: a decline runs
|
||||
* the original, which accrues its exact cycles by executing.
|
||||
|
||||
@@ -3003,6 +3003,12 @@ extern "C" const char* mod_runtime_activation_error(void) {
|
||||
return s_override_activation_error.empty() ? nullptr : s_override_activation_error.c_str();
|
||||
}
|
||||
|
||||
static int invoke_package_override(CPUState* cpu, FuncOverrideFn fn, const void* context) {
|
||||
using namespace PSXRecompV4;
|
||||
PluginCallbackScope scope(state(),static_cast<const ModResolution::Plugin*>(context),cpu);
|
||||
return fn(cpu);
|
||||
}
|
||||
|
||||
extern "C" void mod_runtime_activate_plugins(void) {
|
||||
using namespace PSXRecompV4;
|
||||
HostLaunchTimingScope timing(HOST_LAUNCH_PLUGIN_ACTIVATION);
|
||||
@@ -3025,12 +3031,13 @@ extern "C" void mod_runtime_activate_plugins(void) {
|
||||
s.extent_start = 0;
|
||||
/* Arm the entire tier before activation callbacks change host state. */
|
||||
for (auto& pending : function_override_plugins()) {
|
||||
const bool selected = std::any_of(s.plan.plugins.begin(),s.plan.plugins.end(),
|
||||
const auto owner = std::find_if(s.plan.plugins.begin(),s.plan.plugins.end(),
|
||||
[&](const ModResolution::Plugin& p) { return p.id == pending.plugin; });
|
||||
if (!selected) continue;
|
||||
const int rc = s.netplay_view_plan ? FO_ERR_ARGS : func_override_add_package(
|
||||
if (owner == s.plan.plugins.end()) continue;
|
||||
const int rc = s.netplay_view_plan ? FO_ERR_ARGS : func_override_add_package_scoped(
|
||||
pending.id.c_str(),pending.address,pending.fn,
|
||||
pending.n_guard ? pending.guard : nullptr,pending.n_guard,pending.credit);
|
||||
pending.n_guard ? pending.guard : nullptr,pending.n_guard,pending.credit,
|
||||
invoke_package_override,&*owner);
|
||||
if (rc != FO_OK) {
|
||||
s_override_activation_error = "Cannot arm function override " + pending.id +
|
||||
" (registration error " + std::to_string(rc) + ")";
|
||||
@@ -3777,7 +3784,7 @@ static int game_netplay_function_entry(CPUState* cpu, uint32_t address) {
|
||||
}
|
||||
|
||||
extern "C" int psx_mod_function_entry_active(void) {
|
||||
return PSXRecompV4::function_entry_depth != 0;
|
||||
return PSXRecompV4::function_entry_depth != 0 || func_override_active();
|
||||
}
|
||||
|
||||
extern "C" void mod_runtime_function_entry_context_save(ModFunctionEntryContext *out) {
|
||||
|
||||
@@ -98,6 +98,7 @@ static int s_impl_calls, s_decline_calls, s_wrap_calls;
|
||||
|
||||
static int impl_handles(CPUState *cpu)
|
||||
{
|
||||
CHECK(func_override_active(),"handled native override stack is active");
|
||||
s_impl_calls++;
|
||||
cpu->gpr[2] = 0x1234u;
|
||||
return 1;
|
||||
@@ -106,6 +107,7 @@ static int impl_handles(CPUState *cpu)
|
||||
static int impl_declines(CPUState *cpu)
|
||||
{
|
||||
(void)cpu;
|
||||
CHECK(func_override_active(),"declining native override stack is active");
|
||||
s_decline_calls++;
|
||||
return 0; /* the decline-only probe idiom the TCP inventory relies on */
|
||||
}
|
||||
@@ -135,7 +137,9 @@ static void reset_all(void)
|
||||
static int consult(CPUState *cpu, uint32_t addr)
|
||||
{
|
||||
if (!g_psx_func_override_hook) return 0;
|
||||
return g_psx_func_override_hook(cpu, addr & 0x1FFFFFFFu);
|
||||
const int handled = g_psx_func_override_hook(cpu, addr & 0x1FFFFFFFu);
|
||||
CHECK(!func_override_active(),"top-level native override stack has unwound");
|
||||
return handled;
|
||||
}
|
||||
|
||||
/* ---- tests -------------------------------------------------------------- */
|
||||
|
||||
@@ -31,11 +31,21 @@
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static std::array<uint8_t, 2 * 1024 * 1024> ram;
|
||||
extern "C" int (*g_psx_func_override_hook)(CPUState*, uint32_t);
|
||||
static int failures;
|
||||
static int activation_calls;
|
||||
static int override_activation_calls;
|
||||
static void override_activate() { ++override_activation_calls; }
|
||||
static int override_body(CPUState*) { return 1; }
|
||||
static int override_scope_checks;
|
||||
static int override_body(CPUState*) {
|
||||
char option[16]{};
|
||||
if (psx_mod_current_option_value("scope",option,sizeof option) &&
|
||||
!std::strcmp(option,"owned") && psx_mod_function_entry_active()) ++override_scope_checks;
|
||||
mod_runtime_on_vblank();
|
||||
if (psx_mod_current_option_value("scope",option,sizeof option) &&
|
||||
!std::strcmp(option,"owned") && psx_mod_function_entry_active()) ++override_scope_checks;
|
||||
return 1;
|
||||
}
|
||||
static int plugin_calls;
|
||||
static int restore_calls;
|
||||
static int option_renderer_activations;
|
||||
@@ -2459,6 +2469,18 @@ game_id = "SLUS-OVERRIDE"
|
||||
id = "replace"
|
||||
name = "Replace"
|
||||
default_enabled = true
|
||||
[[option]]
|
||||
feature = "replace"
|
||||
id = "scope"
|
||||
label = "Scope"
|
||||
type = "choice"
|
||||
default = "owned"
|
||||
[[option.choice]]
|
||||
value = "owned"
|
||||
label = "Owned"
|
||||
[[option.choice]]
|
||||
value = "other"
|
||||
label = "Other"
|
||||
[[plugin]]
|
||||
feature = "replace"
|
||||
id = "override.test"
|
||||
@@ -2471,6 +2493,12 @@ id = "override.test"
|
||||
check(PSXRecompV4::mod_runtime_commit({},&error),error.c_str());
|
||||
mod_runtime_activate_plugins();
|
||||
check(!mod_runtime_activation_error() && func_override_count()==2 && override_activation_calls==1,"selected overrides arm together");
|
||||
CPUState override_cpu{};
|
||||
check(g_psx_func_override_hook && g_psx_func_override_hook(&override_cpu,0x6004),"invoke package override");
|
||||
check(override_scope_checks==2 && !psx_mod_function_entry_active(),
|
||||
"override owns its resource context across VBlank and blocks mid-stack snapshots");
|
||||
char no_scope[16]{};
|
||||
check(!psx_mod_current_option_value("scope",no_scope,sizeof no_scope),"override restores its caller's context");
|
||||
mod_runtime_activate_plugins();
|
||||
check(!mod_runtime_activation_error() && func_override_count()==2 && override_activation_calls==2,"session reactivation replaces entries without duplicates");
|
||||
check(PSXRecompV4::mod_runtime_clear_for_netplay(&error),error.c_str());
|
||||
|
||||
Reference in New Issue
Block a user