mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-10-10 13:00:42 -04:00
Mods: interpolation service (#2636)
This exposes the basic matrix API to mods through a new service. I also included a RAII C++ wrapper since, well, convenient.
This commit is contained in:
committed by
GitHub
parent
1056b9e8d2
commit
7d8b945e30
@@ -1538,6 +1538,7 @@ set(DUSK_FILES
|
||||
src/dusk/mods/svc/item.hpp
|
||||
src/dusk/mods/svc/id_allocator.cpp
|
||||
src/dusk/mods/svc/id_allocator.hpp
|
||||
src/dusk/mods/svc/interp.cpp
|
||||
src/dusk/mods/svc/log.cpp
|
||||
src/dusk/mods/svc/overlay.cpp
|
||||
src/dusk/mods/svc/registry.cpp
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <mods/service.hpp>
|
||||
#endif
|
||||
|
||||
#define INTERP_SERVICE_ID DUSKLIGHT_SERVICE_ID_PREFIX "interp"
|
||||
#define INTERP_SERVICE_MAJOR 1u
|
||||
#define INTERP_SERVICE_MINOR 0u
|
||||
|
||||
// This is the same type as Mtx in the Dolphin types.
|
||||
/**
|
||||
* A row-major 3x4 (3 rows, 4 columns) matrix for usage in the interpolation system.
|
||||
*/
|
||||
typedef float InterpMtx[3][4];
|
||||
|
||||
/**
|
||||
* Defines APIs for interpolating render data between multiple simulation frames.
|
||||
*/
|
||||
typedef struct InterpService {
|
||||
ServiceHeader header;
|
||||
|
||||
/*
|
||||
* Matrix API.
|
||||
*
|
||||
* Using this API is pretty simple: you "record" a matrix during sim frames,
|
||||
* then later look up the interpolated "replacement" matrix during rendering.
|
||||
*
|
||||
* A matrix is identified between frames via an arbitrary pointer. While this is typically
|
||||
* the address of the matrix itself, it can be any unique pointer-sized value.
|
||||
* You should "unregister" a matrix key with @ref forget_mtx when you are done with it.
|
||||
*
|
||||
* Note: when working with actors, the draw callback is called from simulation.
|
||||
* You should access the interpolated matrices from the packet callback instead.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Record a matrix for interpolation, with an arbitrary key to identify it.
|
||||
*
|
||||
* @remarks Recorded keys should be unregistered with @ref forget_mtx to avoid memory leaks.
|
||||
*
|
||||
* @param mod Your mod's context.
|
||||
* @param matrix The matrix value to record.
|
||||
* @param key The key value used to identify the recorded matrix. Cannot be null.
|
||||
*/
|
||||
ModResult (*record_mtx_keyed)(ModContext* mod, InterpMtx matrix, void const* key);
|
||||
|
||||
/**
|
||||
* Record a matrix for interpolation.
|
||||
*
|
||||
* @remarks This is effectively equivalent to calling @ref record_mtx_keyed with the matrix
|
||||
* as both arguments.
|
||||
* @remarks Recorded keys should be unregistered with @ref forget_mtx to avoid memory leaks.
|
||||
*
|
||||
* @param mod Your mod's context.
|
||||
* @param matrix The matrix value to record. Its address is also used as a key to identify it. Cannot be null.
|
||||
*/
|
||||
ModResult (*record_mtx)(ModContext* mod, InterpMtx matrix);
|
||||
|
||||
/**
|
||||
* Forgets an interpolated matrix that was previously recorded.
|
||||
*
|
||||
* @remarks This API does not cause an error if the key is not known.
|
||||
*/
|
||||
ModResult (*forget_mtx)(ModContext* mod, void const* key);
|
||||
|
||||
/**
|
||||
* Look up the interpolated replacement for a matrix that was previously recorded.
|
||||
*
|
||||
* @remarks It is legal to look up replacements for matrices that were registered by other
|
||||
* mods or base game code.
|
||||
*
|
||||
* @param key Key to look up the replacement for.
|
||||
* @param out Pointer that will receive the interpolated replacement matrix.
|
||||
* Not written if the operation fails.
|
||||
*
|
||||
* @returns false if the matrix is unknown.
|
||||
*/
|
||||
bool (*lookup_replacement_mtx)(void const* key, InterpMtx out);
|
||||
} InterpService;
|
||||
|
||||
MOD_DECLARE_SERVICE(InterpService, svc_interp, INTERP_SERVICE_ID, INTERP_SERVICE_MAJOR,
|
||||
INTERP_SERVICE_MINOR);
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "interp.h"
|
||||
|
||||
namespace mods::interp {
|
||||
|
||||
/**
|
||||
* RAII wrapper for interpolation matrices.
|
||||
*
|
||||
* @remarks This is a simple wrapper around a @ref InterpMtx.
|
||||
* The destructor automatically calls @ref forget_mtx, while the assignment operators
|
||||
* automatically call @ref record.
|
||||
*/
|
||||
struct InterpMatrix {
|
||||
InterpMtx mtx{};
|
||||
|
||||
InterpMatrix() = default;
|
||||
|
||||
explicit(false) InterpMatrix(InterpMtx const mtx) {
|
||||
std::memcpy(&this->mtx, mtx, sizeof(this->mtx));
|
||||
}
|
||||
|
||||
InterpMatrix(InterpMatrix const& src) : InterpMatrix(src.mtx) {}
|
||||
|
||||
InterpMatrix& operator=(InterpMatrix const& other) { return operator=(other.mtx); }
|
||||
|
||||
InterpMatrix& operator=(InterpMtx const other) {
|
||||
std::memcpy(&this->mtx, other, sizeof(this->mtx));
|
||||
record();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the current value of the matrix.
|
||||
*/
|
||||
void record() { svc_interp->record_mtx(mod_ctx, mtx); }
|
||||
|
||||
/**
|
||||
* Read the interpolated value of the matrix.
|
||||
*/
|
||||
void readInterpolated(InterpMtx& out) const {
|
||||
if (!svc_interp->lookup_replacement_mtx(&mtx, out)) {
|
||||
std::memcpy(out, mtx, sizeof(mtx));
|
||||
}
|
||||
}
|
||||
|
||||
~InterpMatrix() { svc_interp->forget_mtx(mod_ctx, &mtx); }
|
||||
};
|
||||
|
||||
} // namespace mods::interp
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "mods/svc/interp.h"
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "dusk/interp/menus.h"
|
||||
#include "internal.hpp"
|
||||
#include "registry.hpp"
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
|
||||
namespace {
|
||||
|
||||
struct ModDatum {
|
||||
absl::flat_hash_set<void const*> registered_matrices;
|
||||
};
|
||||
|
||||
absl::flat_hash_map<LoadedMod const*, ModDatum> modData;
|
||||
|
||||
static_assert(std::is_same_v<InterpMtx, Mtx>);
|
||||
|
||||
bool lookup_replacement_impl(const void* key, InterpMtx out) {
|
||||
return interp::lookup_replacement(key, out);
|
||||
}
|
||||
|
||||
ModResult record_final_mtx_keyed(ModContext* context, InterpMtx matrix, void const* key) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !key || !matrix) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto& modDatum = modData[mod];
|
||||
modDatum.registered_matrices.insert(key);
|
||||
|
||||
interp::record_final_mtx(matrix, key);
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult record_final_mtx(ModContext* context, InterpMtx matrix) {
|
||||
return record_final_mtx_keyed(context, matrix, matrix);
|
||||
}
|
||||
|
||||
ModResult forget_mtx(ModContext* context, void const* key) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !key) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto& modDatum = modData[mod];
|
||||
if (!modDatum.registered_matrices.erase(key)) {
|
||||
// Don't report an error in this case — avoids overcomplicating lifecycle management by
|
||||
// requiring callers to track if they've ever called record_final_mtx.
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
interp::forget_mtx(key);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void mod_detached(LoadedMod& mod) {
|
||||
auto const found = modData.find(&mod);
|
||||
if (found == modData.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& datum = found->second;
|
||||
for (auto const key : datum.registered_matrices) {
|
||||
interp::forget_mtx(key);
|
||||
}
|
||||
|
||||
modData.erase(found);
|
||||
}
|
||||
|
||||
constexpr InterpService s_interpService {
|
||||
.header = SERVICE_HEADER(InterpService, INTERP_SERVICE_MAJOR, INTERP_SERVICE_MINOR),
|
||||
.record_mtx_keyed = record_final_mtx_keyed,
|
||||
.record_mtx = record_final_mtx,
|
||||
.forget_mtx = forget_mtx,
|
||||
.lookup_replacement_mtx = lookup_replacement_impl,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
ServiceModule const g_interpModule {
|
||||
.id = INTERP_SERVICE_ID,
|
||||
.majorVersion = INTERP_SERVICE_MAJOR,
|
||||
.minorVersion = INTERP_SERVICE_MINOR,
|
||||
.service = &s_interpService,
|
||||
.modDetached = mod_detached
|
||||
};
|
||||
|
||||
}
|
||||
@@ -268,6 +268,7 @@ void ModLoader::init_services() {
|
||||
&svc::g_logModule,
|
||||
&svc::g_resourceModule,
|
||||
&svc::g_fileModule,
|
||||
&svc::g_interpModule,
|
||||
&svc::g_httpModule,
|
||||
&svc::g_netModule,
|
||||
&svc::g_websocketModule,
|
||||
|
||||
@@ -76,6 +76,7 @@ extern const ServiceModule g_hostModule;
|
||||
extern const ServiceModule g_logModule;
|
||||
extern const ServiceModule g_resourceModule;
|
||||
extern const ServiceModule g_fileModule;
|
||||
extern const ServiceModule g_interpModule;
|
||||
extern const ServiceModule g_httpModule;
|
||||
extern const ServiceModule g_netModule;
|
||||
extern const ServiceModule g_websocketModule;
|
||||
|
||||
Reference in New Issue
Block a user