GfxService v1.2 + fixes (#2305)

* GfxService v1.2 + fixes

* gfx::synchronize before mod_shutdown
This commit is contained in:
Luke Street
2026-08-15 11:50:13 -06:00
committed by GitHub
parent c722cb1be0
commit 9d38af22ee
10 changed files with 208 additions and 58 deletions
+1 -1
+5
View File
@@ -70,6 +70,11 @@ void JUTVideo::preRetraceProc(u32 retrace_count) {
OSTick tick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
sVideoInterval = tick - sVideoLastTick;
#if TARGET_PC
if (sVideoInterval <= 0) {
sVideoInterval = 1;
}
#endif
sVideoLastTick = tick;
JUTXfb* xfb = JUTXfb::getManager();
+12 -10
View File
@@ -53,6 +53,7 @@ ResourceBuffer g_denoiseSource = RESOURCE_BUFFER_INIT;
ResourceBuffer g_compositeSource = RESOURCE_BUFFER_INIT;
GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT;
GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT;
WGPUComputePipeline g_preprocessPipeline = nullptr;
WGPUComputePipeline g_mip4Pipeline = nullptr;
WGPUComputePipeline g_gtaoPipeline = nullptr;
@@ -226,19 +227,17 @@ bool build_composite_pipeline(
.dstFactor = WGPUBlendFactor_One,
},
};
WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT;
colorTarget.format = g_deviceInfo.color_format;
if (blend) {
colorTarget.blend = &blendState;
}
WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS];
const uint32_t colorTargetCount = gfx_init_color_target_states(
&g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All);
WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT;
fragment.module = module;
fragment.entryPoint = {"fs_main", WGPU_STRLEN};
fragment.targetCount = 1;
fragment.targets = &colorTarget;
fragment.targetCount = colorTargetCount;
fragment.targets = colorTargets;
// Depth state must match the EFB pass despite never touching depth.
WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT;
depthStencil.format = g_deviceInfo.depth_format;
depthStencil.format = g_sceneTargetLayout.depth_stencil_format;
depthStencil.depthWriteEnabled = WGPUOptionalBool_False;
depthStencil.depthCompare = WGPUCompareFunction_Always;
@@ -248,7 +247,7 @@ bool build_composite_pipeline(
pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN};
pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
pipelineDesc.depthStencil = &depthStencil;
pipelineDesc.multisample.count = g_deviceInfo.sample_count;
pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count;
pipelineDesc.fragment = &fragment;
outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc);
wgpuShaderModuleRelease(module);
@@ -504,7 +503,7 @@ void on_compute(
// Render worker thread: composite the AO over the scene (or show it, in debug view).
void on_draw(
ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) {
if (payloadSize != sizeof(CompositePayload)) {
if (payloadSize != sizeof(CompositePayload) || ctx->layout.key != g_sceneTargetLayout.key) {
return;
}
CompositePayload data;
@@ -816,6 +815,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) {
return mods::set_error(error, MOD_ERROR, "failed to query device info");
}
if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) {
return mods::set_error(error, MOD_ERROR, "failed to query scene target layout");
}
if (!build_compute_pipeline("AO preprocess depth", g_preprocessSource, "preprocess_depth",
g_preprocessPipeline, g_preprocessLayout) ||
!build_compute_pipeline("AO downsample mip4", g_preprocessSource, "downsample_mip4",
+13 -11
View File
@@ -20,12 +20,12 @@
#include "dolphin/gx/GXPixel.h"
#include "dolphin/gx/GXTransform.h"
#include "m_Do/m_Do_mtx.h"
#include "mods/svc/hook.hpp"
#include "mods/service.hpp"
#include "mods/svc/camera.h"
#include "mods/svc/config.h"
#include "mods/svc/gfx.h"
#include "mods/svc/hook.h"
#include "mods/svc/hook.hpp"
#include "mods/svc/log.h"
#include "mods/svc/resource.h"
#include "mods/svc/ui.h"
@@ -69,6 +69,7 @@ GfxStageHookHandle g_frameBeforeHudHook = 0;
UiWindowHandle g_controlsWindow = 0;
ResourceBuffer g_shaderSource = RESOURCE_BUFFER_INIT;
GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT;
GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT;
WGPURenderPipeline g_compositePipeline = nullptr; // multiply blend
WGPURenderPipeline g_compositeDebugPipeline = nullptr; // no blend (debug views)
WGPUBindGroupLayout g_compositeLayout = nullptr;
@@ -399,18 +400,16 @@ bool build_composite_pipeline(
.srcFactor = WGPUBlendFactor_Zero,
.dstFactor = WGPUBlendFactor_One},
};
WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT;
colorTarget.format = g_deviceInfo.color_format;
if (blend) {
colorTarget.blend = &blendState;
}
WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS];
const uint32_t colorTargetCount = gfx_init_color_target_states(
&g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All);
WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT;
fragment.module = module;
fragment.entryPoint = {"fs_main", WGPU_STRLEN};
fragment.targetCount = 1;
fragment.targets = &colorTarget;
fragment.targetCount = colorTargetCount;
fragment.targets = colorTargets;
WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT;
depthStencil.format = g_deviceInfo.depth_format;
depthStencil.format = g_sceneTargetLayout.depth_stencil_format;
depthStencil.depthWriteEnabled = WGPUOptionalBool_False;
depthStencil.depthCompare = WGPUCompareFunction_Always;
@@ -420,7 +419,7 @@ bool build_composite_pipeline(
pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN};
pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
pipelineDesc.depthStencil = &depthStencil;
pipelineDesc.multisample.count = g_deviceInfo.sample_count;
pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count;
pipelineDesc.fragment = &fragment;
outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc);
wgpuShaderModuleRelease(module);
@@ -518,7 +517,7 @@ WGPUBindGroup create_composite_bind_group(WGPUDevice device, WGPUBindGroupLayout
// Render worker thread: fullscreen deferred-shadow composite.
void on_draw(
ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) {
if (payloadSize != sizeof(DrawPayload)) {
if (payloadSize != sizeof(DrawPayload) || ctx->layout.key != g_sceneTargetLayout.key) {
return;
}
DrawPayload data;
@@ -1243,6 +1242,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) {
return mods::set_error(error, MOD_ERROR, "failed to query device info");
}
if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) {
return mods::set_error(error, MOD_ERROR, "failed to query scene target layout");
}
if (!build_composite_pipeline(true, g_compositePipeline, g_compositeLayout) ||
!build_composite_pipeline(false, g_compositeDebugPipeline, g_compositeDebugLayout))
{
-6
View File
@@ -189,12 +189,6 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
return mods::set_error(error, MOD_ERROR, "failed to register mod panel");
}
if (open_window() != MOD_OK) {
svc_gfx->unregister_stage_hook(mod_ctx, g_stageHook);
g_stageHook = 0;
return mods::set_error(error, MOD_ERROR, "failed to open auxiliary window");
}
mods::log::info("auxiliary WebGPU window ready");
return MOD_OK;
}
+71 -3
View File
@@ -33,10 +33,68 @@
#define GFX_SERVICE_ID "dev.twilitrealm.dusklight.gfx"
#define GFX_SERVICE_MAJOR 1u
#define GFX_SERVICE_MINOR 1u
#define GFX_SERVICE_MINOR 2u
/* Maximum size for push_draw payload */
#define GFX_INLINE_DRAW_PAYLOAD_SIZE 128u
#define GFX_MAX_COLOR_ATTACHMENTS 8u
#define GFX_SCENE_COLOR_ATTACHMENT_INDEX 0u
typedef enum GfxAttachmentSemantic {
GFX_ATTACHMENT_SCENE_COLOR,
GFX_ATTACHMENT_NORMAL,
GFX_ATTACHMENT_AUXILIARY,
} GfxAttachmentSemantic;
typedef struct GfxColorAttachmentLayout {
GfxAttachmentSemantic semantic;
WGPUTextureFormat format;
uint32_t width;
uint32_t height;
} GfxColorAttachmentLayout;
typedef struct GfxRenderTargetLayout {
uint32_t struct_size;
uint64_t key;
/* At least one; scene color is at GFX_SCENE_COLOR_ATTACHMENT_INDEX. */
uint32_t color_attachment_count;
GfxColorAttachmentLayout color_attachments[GFX_MAX_COLOR_ATTACHMENTS];
WGPUTextureFormat depth_stencil_format;
uint32_t sample_count;
} GfxRenderTargetLayout;
#define GFX_RENDER_TARGET_LAYOUT_INIT \
{sizeof(GfxRenderTargetLayout), 0u, 0u, \
{{GFX_ATTACHMENT_AUXILIARY, WGPUTextureFormat_Undefined}}, WGPUTextureFormat_Undefined, \
1u}
/*
* Initializes pipeline color targets for a render-target layout. Only scene color is writable;
* callers that write another semantic should override that target afterward.
*/
static uint32_t gfx_init_color_target_states(const GfxRenderTargetLayout* layout,
WGPUColorTargetState targets[GFX_MAX_COLOR_ATTACHMENTS], const WGPUBlendState* scene_blend,
WGPUColorWriteMask scene_write_mask) {
if (layout == NULL || targets == NULL) {
return 0;
}
const uint32_t count = layout->color_attachment_count < GFX_MAX_COLOR_ATTACHMENTS ?
layout->color_attachment_count :
GFX_MAX_COLOR_ATTACHMENTS;
for (uint32_t i = 0; i < GFX_MAX_COLOR_ATTACHMENTS; ++i) {
WGPUColorTargetState target = WGPU_COLOR_TARGET_STATE_INIT;
if (i < count) {
target.format = layout->color_attachments[i].format;
target.writeMask = WGPUColorWriteMask_None;
}
targets[i] = target;
}
if (count != 0) {
targets[GFX_SCENE_COLOR_ATTACHMENT_INDEX].blend = scene_blend;
targets[GFX_SCENE_COLOR_ATTACHMENT_INDEX].writeMask = scene_write_mask;
}
return count;
}
/* 0 is never a valid handle. */
typedef uint64_t GfxDrawTypeHandle;
@@ -51,8 +109,8 @@ typedef struct GfxRange {
} GfxRange;
/*
* Device and scene pass configuration. Valid from mod_initialize onward and stable for the
* session. Offscreen passes from create_pass are always single-sample.
* Device and legacy primary scene-pass configuration. Use get_scene_target_layout when creating
* scene pipelines. Offscreen passes from create_pass are always single-sample.
*/
typedef struct GfxDeviceInfo {
uint32_t struct_size;
@@ -83,12 +141,18 @@ typedef struct GfxDrawContext {
WGPUBuffer index_buffer;
WGPUBuffer uniform_buffer;
WGPUBuffer storage_buffer;
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].format */
WGPUTextureFormat color_format;
/* deprecated: use layout.depth_stencil_format */
WGPUTextureFormat depth_format;
/* deprecated: use layout.sample_count */
uint32_t sample_count;
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].width */
uint32_t target_width;
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].height */
uint32_t target_height;
bool uses_reversed_z;
GfxRenderTargetLayout layout; /* added in GfxService 1.2 */
} GfxDrawContext;
typedef void (*GfxDrawFn)(ModContext* ctx, const GfxDrawContext* draw_ctx, const void* payload,
@@ -269,6 +333,10 @@ typedef struct GfxService {
*/
ModResult (*push_present)(
ModContext* ctx, GfxPresentTargetHandle handle, const void* payload, size_t payload_size);
/* Minor version 2 */
ModResult (*get_scene_target_layout)(ModContext* ctx, GfxRenderTargetLayout* out_layout);
} GfxService;
MOD_DECLARE_SERVICE(GfxService, svc_gfx, GFX_SERVICE_ID, GFX_SERVICE_MAJOR, GFX_SERVICE_MINOR);
+1
View File
@@ -899,6 +899,7 @@ bool ModLoader::activate_mod(LoadedMod& mod) {
}
void ModLoader::deactivate_mod(LoadedMod& mod) {
svc::modules_mod_deactivating(mod);
if (mod.initialized && mod.native && mod.native->fn_shutdown) {
log::write(mod.metadata.id, LOG_LEVEL_TRACE, "calling mod_shutdown");
try {
+92 -26
View File
@@ -9,8 +9,10 @@
#include <aurora/gfx.hpp>
#include <aurora/webgpu.hpp>
#include <dolphin/gx/GXAurora.h>
#include <fmt/format.h>
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstdint>
@@ -126,23 +128,34 @@ GfxSlot* resolve_owned_slot_locked(LoadedMod& mod, uint64_t handle, GfxSlotKind
return &entry->value;
}
void collect_mod_types_locked(LoadedMod& owner, std::vector<aurora::gfx::DrawTypeId>& drawIds,
void take_mod_types_locked(LoadedMod& owner, std::vector<aurora::gfx::DrawTypeId>& drawIds,
std::vector<aurora::gfx::EncoderTaskId>& taskIds) {
s_slots.for_each([&](uint64_t, const auto& entry) {
std::vector<uint64_t> drawHandles;
std::vector<uint64_t> taskHandles;
s_slots.for_each([&](uint64_t handle, const auto& entry) {
if (entry.owner != &owner) {
return;
}
const auto& slot = entry.value;
if (slot.kind == GfxSlotKind::DrawType && slot.auroraDrawId != aurora::gfx::InvalidDrawType)
{
drawIds.push_back(slot.auroraDrawId);
drawHandles.push_back(handle);
} else if ((slot.kind == GfxSlotKind::ComputeType ||
slot.kind == GfxSlotKind::PresentTarget) &&
slot.auroraTaskId != aurora::gfx::InvalidEncoderTask)
{
taskIds.push_back(slot.auroraTaskId);
taskHandles.push_back(handle);
}
});
for (const auto handle : drawHandles) {
auto* entry = s_slots.find(handle);
drawIds.push_back(std::exchange(entry->value.auroraDrawId, aurora::gfx::InvalidDrawType));
}
for (const auto handle : taskHandles) {
auto* entry = s_slots.find(handle);
taskIds.push_back(
std::exchange(entry->value.auroraTaskId, aurora::gfx::InvalidEncoderTask));
}
}
void unregister_aurora_types(const std::vector<aurora::gfx::DrawTypeId>& drawIds,
@@ -155,6 +168,49 @@ void unregister_aurora_types(const std::vector<aurora::gfx::DrawTypeId>& drawIds
}
}
void gfx_mod_deactivating(LoadedMod& mod) {
std::vector<aurora::gfx::DrawTypeId> drawIds;
std::vector<aurora::gfx::EncoderTaskId> taskIds;
{
std::lock_guard lock{s_mutex};
take_mod_types_locked(mod, drawIds, taskIds);
}
unregister_aurora_types(drawIds, taskIds);
if (!drawIds.empty() || !taskIds.empty()) {
aurora::gfx::synchronize();
}
}
GfxAttachmentSemantic gfx_attachment_semantic(aurora::gfx::ColorAttachmentSemantic semantic) {
switch (semantic) {
case aurora::gfx::ColorAttachmentSemantic::SceneColor:
return GFX_ATTACHMENT_SCENE_COLOR;
case aurora::gfx::ColorAttachmentSemantic::Normal:
return GFX_ATTACHMENT_NORMAL;
case aurora::gfx::ColorAttachmentSemantic::Auxiliary:
return GFX_ATTACHMENT_AUXILIARY;
}
return GFX_ATTACHMENT_AUXILIARY;
}
GfxRenderTargetLayout gfx_render_target_layout(const aurora::gfx::RenderTargetLayout& layout) {
GfxRenderTargetLayout result = GFX_RENDER_TARGET_LAYOUT_INIT;
result.key = layout.key;
result.color_attachment_count =
std::min<uint32_t>(layout.colorAttachmentCount, GFX_MAX_COLOR_ATTACHMENTS);
for (uint32_t i = 0; i < result.color_attachment_count; ++i) {
result.color_attachments[i] = {
.semantic = gfx_attachment_semantic(layout.colorAttachments[i].semantic),
.format = static_cast<WGPUTextureFormat>(layout.colorAttachments[i].format),
.width = layout.colorAttachments[i].width,
.height = layout.colorAttachments[i].height,
};
}
result.depth_stencil_format = static_cast<WGPUTextureFormat>(layout.depthStencilFormat);
result.sample_count = layout.sampleCount;
return result;
}
void draw_trampoline(const aurora::gfx::DrawContext& ctx, const wgpu::RenderPassEncoder& pass,
const void* payload, size_t payloadSize, void* userdata) {
const auto handle = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(userdata));
@@ -184,12 +240,12 @@ void draw_trampoline(const aurora::gfx::DrawContext& ctx, const wgpu::RenderPass
.index_buffer = ctx.indexBuffer.Get(),
.uniform_buffer = ctx.uniformBuffer.Get(),
.storage_buffer = ctx.storageBuffer.Get(),
.color_format = static_cast<WGPUTextureFormat>(ctx.colorFormat),
.depth_format = static_cast<WGPUTextureFormat>(ctx.depthFormat),
.sample_count = ctx.sampleCount,
.target_width = ctx.targetWidth,
.target_height = ctx.targetHeight,
.color_format = static_cast<WGPUTextureFormat>(
ctx.layout.colorAttachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].format),
.depth_format = static_cast<WGPUTextureFormat>(ctx.layout.depthStencilFormat),
.sample_count = ctx.layout.sampleCount,
.uses_reversed_z = aurora::gfx::uses_reversed_z(),
.layout = gfx_render_target_layout(ctx.layout),
};
std::string failure;
@@ -778,8 +834,10 @@ ModResult gfx_unregister_present_target(LoadedMod& mod, uint64_t handle) {
auroraId = slot->auroraTaskId;
}
aurora::gfx::unregister_encoder_task_type(auroraId);
aurora::gfx::synchronize();
if (auroraId != aurora::gfx::InvalidEncoderTask) {
aurora::gfx::unregister_encoder_task_type(auroraId);
aurora::gfx::synchronize();
}
std::optional<GfxSlotMap::Entry> removed;
{
@@ -903,6 +961,7 @@ void gfx_run_stage(
.game_viewport = gameViewport,
};
AuroraGXSync();
for (const auto& entry : entries) {
{
std::lock_guard lock{s_mutex};
@@ -924,6 +983,7 @@ void gfx_run_stage(
fail_mod(*entry.owner, MOD_ERROR, "unknown exception in gfx stage callback");
}
AuroraGXSync();
if (aurora::gfx::is_offscreen() != wasOffscreen) {
aurora::gfx::ResolvedTargets discarded;
aurora::gfx::resolve_pass(
@@ -935,17 +995,8 @@ void gfx_run_stage(
}
}
void gfx_remove_mod(LoadedMod& mod) {
std::vector<aurora::gfx::DrawTypeId> drawIds;
std::vector<aurora::gfx::EncoderTaskId> taskIds;
{
std::lock_guard lock{s_mutex};
collect_mod_types_locked(mod, drawIds, taskIds);
}
unregister_aurora_types(drawIds, taskIds);
if (!drawIds.empty() || !taskIds.empty()) {
aurora::gfx::synchronize();
}
void gfx_mod_detached(LoadedMod& mod) {
gfx_mod_deactivating(mod);
std::vector<GfxSlotMap::Entry> entries;
{
@@ -966,7 +1017,7 @@ void gfx_remove_mod(LoadedMod& mod) {
}
}
void gfx_drain_worker_failures() {
void gfx_frame_begin() {
std::vector<WorkerFailure> failures;
{
std::lock_guard lock{s_mutex};
@@ -979,7 +1030,7 @@ void gfx_drain_worker_failures() {
for (const auto& failure : failures) {
for (auto& mod : ModLoader::instance().mods()) {
if (mod.metadata.id == failure.modId && mod.active) {
gfx_remove_mod(mod);
gfx_mod_detached(mod);
fail_mod(mod, MOD_ERROR, failure.message);
break;
}
@@ -1030,6 +1081,19 @@ ModResult gfx_get_device_info(ModContext* context, GfxDeviceInfo* outInfo) {
return MOD_OK;
}
ModResult gfx_get_scene_target_layout(ModContext* context, GfxRenderTargetLayout* outLayout) {
if (outLayout == nullptr || outLayout->struct_size < sizeof(GfxRenderTargetLayout) ||
mod_from_context(context) == nullptr)
{
return MOD_INVALID_ARGUMENT;
}
const uint32_t structSize = outLayout->struct_size;
*outLayout = gfx_render_target_layout(aurora::gfx::scene_render_target_layout());
outLayout->struct_size = structSize;
return MOD_OK;
}
void* gfx_get_proc_address(ModContext* context, const char* name) {
if (mod_from_context(context) == nullptr || name == nullptr) {
return nullptr;
@@ -1327,6 +1391,7 @@ constexpr GfxService s_gfxService{
.resize_present_target = gfx_resize_present_target_impl,
.unregister_present_target = gfx_unregister_present_target_impl,
.push_present = gfx_push_present_impl,
.get_scene_target_layout = gfx_get_scene_target_layout,
};
} // namespace
@@ -1336,8 +1401,9 @@ constinit const ServiceModule g_gfxModule{
.majorVersion = GFX_SERVICE_MAJOR,
.minorVersion = GFX_SERVICE_MINOR,
.service = &s_gfxService,
.modDetached = gfx_remove_mod,
.frameBegin = gfx_drain_worker_failures,
.modDeactivating = gfx_mod_deactivating,
.modDetached = gfx_mod_detached,
.frameBegin = gfx_frame_begin,
};
} // namespace dusk::mods::svc
+8
View File
@@ -150,6 +150,14 @@ ModResult register_module(const ServiceModule& module) {
return MOD_OK;
}
void modules_mod_deactivating(LoadedMod& mod) {
for (const auto* module : s_modules | std::views::reverse) {
if (module->modDeactivating != nullptr) {
module->modDeactivating(mod);
}
}
}
void modules_mod_detached(LoadedMod& mod) {
for (const auto* module : s_modules | std::views::reverse) {
if (module->modDetached != nullptr) {
+5 -1
View File
@@ -19,7 +19,7 @@ struct ServiceRecord {
};
// A host service and its lifecycle hooks. Every hook is optional. Frame and lifecycle hooks run in
// registration order, modDetached in reverse registration order.
// registration order, teardown hooks in reverse registration order.
struct ServiceModule {
const char* id = nullptr;
uint16_t majorVersion = 0;
@@ -28,6 +28,9 @@ struct ServiceModule {
// One-time setup, at registration (ModLoader::init_services).
void (*initialize)() = nullptr;
// A mod is beginning deactivation: stop callbacks that may execute concurrently. Service state
// remains registered so mod_shutdown may release it normally.
void (*modDeactivating)(LoadedMod& mod) = nullptr;
// A mod is going away (deactivation or failed activation): drop all state held for it.
// Runs after the mod's mod_shutdown and before its library unloads, so pointers into
// the mod are still valid but must not be called.
@@ -55,6 +58,7 @@ const ServiceRecord* find_service(
const ServiceRecord* find_service_record(const char* serviceId, uint16_t majorVersion);
ModResult register_module(const ServiceModule& module);
void modules_mod_deactivating(LoadedMod& mod);
void modules_mod_detached(LoadedMod& mod);
void modules_lifecycle_applied();
void modules_frame_begin();