mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-11 05:04:40 -04:00
SlotMap abstraction & log fix
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "config.hpp"
|
||||
|
||||
#include "registry.hpp"
|
||||
#include "slot_map.hpp"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
@@ -15,7 +16,6 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
@@ -23,25 +23,22 @@ namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::config");
|
||||
|
||||
struct ModConfigVarEntry {
|
||||
uint64_t handle = 0;
|
||||
ConfigVarType type = CONFIG_VAR_BOOL;
|
||||
std::unique_ptr<config::ConfigVarBase> var;
|
||||
enum class ConfigSlotKind : uint8_t {
|
||||
Var,
|
||||
Subscription,
|
||||
};
|
||||
|
||||
struct ModConfigSubscription {
|
||||
uint64_t handle = 0;
|
||||
struct ConfigSlot {
|
||||
ConfigSlotKind kind = ConfigSlotKind::Var;
|
||||
// Var payload
|
||||
ConfigVarType type = CONFIG_VAR_BOOL;
|
||||
std::unique_ptr<config::ConfigVarBase> var;
|
||||
// Subscription payload
|
||||
uint64_t varHandle = 0;
|
||||
config::Subscription coreSubscription = 0;
|
||||
};
|
||||
|
||||
struct ModConfigRecord {
|
||||
std::vector<ModConfigVarEntry> vars;
|
||||
std::vector<ModConfigSubscription> subscriptions;
|
||||
};
|
||||
|
||||
std::unordered_map<const LoadedMod*, ModConfigRecord> s_modConfig;
|
||||
uint64_t s_nextHandle = 1;
|
||||
SlotMap<ConfigSlot> s_slots;
|
||||
bool s_dirty = false;
|
||||
std::chrono::steady_clock::time_point s_lastSave{};
|
||||
constexpr std::chrono::seconds kSaveDebounce{2};
|
||||
@@ -130,17 +127,13 @@ bool valid_var_fragment(const char* name) {
|
||||
}
|
||||
|
||||
config::ConfigVarBase* find_var(LoadedMod& mod, const uint64_t handle, uint32_t expectedType) {
|
||||
const auto recordIt = s_modConfig.find(&mod);
|
||||
if (recordIt == s_modConfig.end()) {
|
||||
const auto* entry = s_slots.find_owned(handle, mod);
|
||||
if (entry == nullptr || entry->value.kind != ConfigSlotKind::Var ||
|
||||
entry->value.type != expectedType)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const auto& vars = recordIt->second.vars;
|
||||
const auto entry =
|
||||
std::ranges::find_if(vars, [&](const auto& e) { return e.handle == handle; });
|
||||
if (entry == vars.end() || entry->type != expectedType) {
|
||||
return nullptr;
|
||||
}
|
||||
return entry->var.get();
|
||||
return entry->value.var.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -154,17 +147,17 @@ ConfigVar<T>* find_typed_var(ModContext* context, ConfigVarHandle handle, uint32
|
||||
}
|
||||
|
||||
void config_remove_mod(LoadedMod& mod) {
|
||||
const auto it = s_modConfig.find(&mod);
|
||||
if (it == s_modConfig.end()) {
|
||||
return;
|
||||
const auto entries = s_slots.take_all(mod);
|
||||
for (const auto& entry : entries) {
|
||||
if (entry.value.kind == ConfigSlotKind::Subscription) {
|
||||
config::unsubscribe(entry.value.coreSubscription);
|
||||
}
|
||||
}
|
||||
for (const auto& sub : it->second.subscriptions) {
|
||||
config::unsubscribe(sub.coreSubscription);
|
||||
for (const auto& entry : entries) {
|
||||
if (entry.value.kind == ConfigSlotKind::Var) {
|
||||
config::unregister(*entry.value.var);
|
||||
}
|
||||
}
|
||||
for (const auto& entry : it->second.vars) {
|
||||
config::unregister(*entry.var);
|
||||
}
|
||||
s_modConfig.erase(it);
|
||||
}
|
||||
|
||||
ModResult config_register_var(
|
||||
@@ -190,16 +183,16 @@ ModResult config_register_var(
|
||||
std::unique_ptr<config::ConfigVarBase> var;
|
||||
switch (desc->type) {
|
||||
case CONFIG_VAR_BOOL:
|
||||
var = std::make_unique<ConfigVar<bool> >(fullName, desc->default_bool);
|
||||
var = std::make_unique<ConfigVar<bool>>(fullName, desc->default_bool);
|
||||
break;
|
||||
case CONFIG_VAR_INT:
|
||||
var = std::make_unique<ConfigVar<s64> >(fullName, desc->default_int);
|
||||
var = std::make_unique<ConfigVar<s64>>(fullName, desc->default_int);
|
||||
break;
|
||||
case CONFIG_VAR_FLOAT:
|
||||
var = std::make_unique<ConfigVar<f64> >(fullName, desc->default_float);
|
||||
var = std::make_unique<ConfigVar<f64>>(fullName, desc->default_float);
|
||||
break;
|
||||
case CONFIG_VAR_STRING:
|
||||
var = std::make_unique<ConfigVar<std::string> >(
|
||||
var = std::make_unique<ConfigVar<std::string>>(
|
||||
fullName, desc->default_string != nullptr ? desc->default_string : "");
|
||||
break;
|
||||
default:
|
||||
@@ -211,13 +204,10 @@ ModResult config_register_var(
|
||||
// reads the value right after registration anyway.
|
||||
config::Register(*var);
|
||||
|
||||
auto& record = s_modConfig[mod];
|
||||
auto& entry = record.vars.emplace_back();
|
||||
entry.handle = s_nextHandle++;
|
||||
entry.type = desc->type;
|
||||
entry.var = std::move(var);
|
||||
const auto handle = s_slots.emplace(
|
||||
*mod, ConfigSlot{.kind = ConfigSlotKind::Var, .type = desc->type, .var = std::move(var)});
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = entry.handle;
|
||||
*outHandle = handle;
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -227,29 +217,26 @@ ModResult config_unregister_var(ModContext* context, ConfigVarHandle var) {
|
||||
if (mod == nullptr || var == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
const auto recordIt = s_modConfig.find(mod);
|
||||
if (recordIt != s_modConfig.end()) {
|
||||
auto& record = recordIt->second;
|
||||
const auto entry =
|
||||
std::ranges::find_if(record.vars, [&](const auto& e) { return e.handle == var; });
|
||||
if (entry != record.vars.end()) {
|
||||
std::erase_if(record.subscriptions, [&](const ModConfigSubscription& sub) {
|
||||
if (sub.varHandle != var) {
|
||||
return false;
|
||||
}
|
||||
config::unsubscribe(sub.coreSubscription);
|
||||
return true;
|
||||
});
|
||||
|
||||
// The persisted value is stashed and restored by a future registration of the
|
||||
// same name.
|
||||
config::unregister(*entry->var);
|
||||
record.vars.erase(entry);
|
||||
return MOD_OK;
|
||||
}
|
||||
const auto* entry = s_slots.find_owned(var, *mod);
|
||||
if (entry == nullptr || entry->value.kind != ConfigSlotKind::Var) {
|
||||
Log.error("[{}] config unregister failed: unknown handle {}", mod->metadata.id, var);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
Log.error("[{}] config unregister failed: unknown handle {}", mod->metadata.id, var);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
|
||||
// Only the owning mod can (currently) subscribe to a var
|
||||
std::vector<uint64_t> bound;
|
||||
s_slots.for_each([&](const uint64_t handle, const auto& e) {
|
||||
if (e.value.kind == ConfigSlotKind::Subscription && e.value.varHandle == var) {
|
||||
bound.push_back(handle);
|
||||
}
|
||||
});
|
||||
for (const auto handle : bound) {
|
||||
config::unsubscribe(s_slots.take(handle)->value.coreSubscription);
|
||||
}
|
||||
|
||||
// The persisted value is stashed and restored by a future registration of the same name.
|
||||
config::unregister(*s_slots.take(var)->value.var);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult config_get_bool(ModContext* context, ConfigVarHandle var, bool* outValue) {
|
||||
@@ -361,22 +348,13 @@ ModResult config_subscribe(ModContext* context, ConfigVarHandle var, ConfigChang
|
||||
if (mod == nullptr || var == 0 || callback == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
const auto recordIt = s_modConfig.find(mod);
|
||||
if (recordIt == s_modConfig.end()) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
auto& record = recordIt->second;
|
||||
const auto entry =
|
||||
std::ranges::find_if(record.vars, [&](const auto& e) { return e.handle == var; });
|
||||
if (entry == record.vars.end()) {
|
||||
const auto* entry = s_slots.find_owned(var, *mod);
|
||||
if (entry == nullptr || entry->value.kind != ConfigSlotKind::Var) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto& sub = record.subscriptions.emplace_back();
|
||||
sub.handle = s_nextHandle++;
|
||||
sub.varHandle = var;
|
||||
sub.coreSubscription = config::subscribe(entry->var->getName(),
|
||||
[modPtr = mod, callback, userData, varHandle = var, type = entry->type](
|
||||
const auto coreSubscription = config::subscribe(entry->value.var->getName(),
|
||||
[modPtr = mod, callback, userData, varHandle = var, type = entry->value.type](
|
||||
config::ConfigVarBase& varBase, const void* previous) {
|
||||
const ConfigVarValue previousValue = translate_previous(type, previous);
|
||||
std::string stringStorage;
|
||||
@@ -390,8 +368,13 @@ ModResult config_subscribe(ModContext* context, ConfigVarHandle var, ConfigChang
|
||||
fail_mod(*modPtr, MOD_ERROR, "Unknown exception in config change callback");
|
||||
}
|
||||
});
|
||||
const auto handle = s_slots.emplace(*mod, ConfigSlot{
|
||||
.kind = ConfigSlotKind::Subscription,
|
||||
.varHandle = var,
|
||||
.coreSubscription = coreSubscription,
|
||||
});
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = sub.handle;
|
||||
*outHandle = handle;
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -401,19 +384,14 @@ ModResult config_unsubscribe(ModContext* context, ConfigSubscriptionHandle handl
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
const auto recordIt = s_modConfig.find(mod);
|
||||
if (recordIt != s_modConfig.end()) {
|
||||
auto& subscriptions = recordIt->second.subscriptions;
|
||||
const auto sub =
|
||||
std::ranges::find_if(subscriptions, [&](const auto& s) { return s.handle == handle; });
|
||||
if (sub != subscriptions.end()) {
|
||||
config::unsubscribe(sub->coreSubscription);
|
||||
subscriptions.erase(sub);
|
||||
return MOD_OK;
|
||||
}
|
||||
const auto* entry = s_slots.find_owned(handle, *mod);
|
||||
if (entry == nullptr || entry->value.kind != ConfigSlotKind::Subscription) {
|
||||
Log.error("[{}] config unsubscribe failed: unknown handle {}", mod->metadata.id, handle);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
Log.error("[{}] config unsubscribe failed: unknown handle {}", mod->metadata.id, handle);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
config::unsubscribe(entry->value.coreSubscription);
|
||||
s_slots.erase(handle);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
constexpr ConfigService s_configService{
|
||||
|
||||
+70
-118
@@ -1,4 +1,5 @@
|
||||
#include "registry.hpp"
|
||||
#include "slot_map.hpp"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/gfx.hpp"
|
||||
@@ -35,9 +36,6 @@ enum class GfxStreamBuffer : uint8_t {
|
||||
|
||||
struct GfxSlot {
|
||||
GfxSlotKind kind = GfxSlotKind::DrawType;
|
||||
uint32_t generation = 1;
|
||||
bool alive = false;
|
||||
LoadedMod* owner = nullptr;
|
||||
ModContext* ownerContext = nullptr;
|
||||
std::string ownerId;
|
||||
void* userData = nullptr;
|
||||
@@ -60,71 +58,37 @@ struct WorkerFailure {
|
||||
};
|
||||
|
||||
std::mutex s_mutex;
|
||||
std::vector<GfxSlot> s_slots;
|
||||
std::vector<uint32_t> s_freeSlots;
|
||||
using GfxSlotMap = svc::SlotMap<GfxSlot>;
|
||||
GfxSlotMap s_slots;
|
||||
std::vector<WorkerFailure> s_workerFailures;
|
||||
bool s_modOffscreenOpen = false;
|
||||
|
||||
constexpr uint64_t make_handle(uint32_t index, uint32_t generation) {
|
||||
return static_cast<uint64_t>(generation) << 32 | index;
|
||||
GfxSlotMap::Entry* resolve_entry_locked(uint64_t handle, GfxSlotKind kind) {
|
||||
auto* entry = s_slots.find(handle);
|
||||
if (entry == nullptr || entry->value.kind != kind) {
|
||||
return nullptr;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
GfxSlot* resolve_slot_locked(uint64_t handle, GfxSlotKind kind) {
|
||||
const auto index = static_cast<uint32_t>(handle & 0xFFFFFFFFu);
|
||||
const auto generation = static_cast<uint32_t>(handle >> 32);
|
||||
if (handle == 0 || index >= s_slots.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& slot = s_slots[index];
|
||||
if (!slot.alive || slot.generation != generation || slot.kind != kind) {
|
||||
return nullptr;
|
||||
}
|
||||
return &slot;
|
||||
auto* entry = resolve_entry_locked(handle, kind);
|
||||
return entry != nullptr ? &entry->value : nullptr;
|
||||
}
|
||||
|
||||
GfxSlot* resolve_owned_slot_locked(LoadedMod& mod, uint64_t handle, GfxSlotKind kind) {
|
||||
auto* slot = resolve_slot_locked(handle, kind);
|
||||
if (slot == nullptr || slot->owner != &mod) {
|
||||
auto* entry = s_slots.find_owned(handle, mod);
|
||||
if (entry == nullptr || entry->value.kind != kind) {
|
||||
return nullptr;
|
||||
}
|
||||
return slot;
|
||||
return &entry->value;
|
||||
}
|
||||
|
||||
uint32_t alloc_slot_locked() {
|
||||
if (!s_freeSlots.empty()) {
|
||||
const auto index = s_freeSlots.back();
|
||||
s_freeSlots.pop_back();
|
||||
return index;
|
||||
}
|
||||
const auto index = static_cast<uint32_t>(s_slots.size());
|
||||
s_slots.emplace_back();
|
||||
return index;
|
||||
}
|
||||
|
||||
void free_slot_locked(uint32_t index) {
|
||||
auto& slot = s_slots[index];
|
||||
slot.alive = false;
|
||||
++slot.generation;
|
||||
slot.owner = nullptr;
|
||||
slot.ownerContext = nullptr;
|
||||
slot.ownerId.clear();
|
||||
slot.userData = nullptr;
|
||||
slot.drawFn = nullptr;
|
||||
slot.auroraDrawId = aurora::gfx::InvalidDrawType;
|
||||
slot.stageFn = nullptr;
|
||||
slot.stage = GFX_STAGE_SCENE_AFTER_TERRAIN;
|
||||
slot.computeFn = nullptr;
|
||||
slot.auroraTaskId = aurora::gfx::InvalidEncoderTask;
|
||||
s_freeSlots.push_back(index);
|
||||
}
|
||||
|
||||
void collect_mod_slots_locked(LoadedMod* owner, std::vector<aurora::gfx::DrawTypeId>& drawIds,
|
||||
void collect_mod_slots_locked(LoadedMod& owner, std::vector<aurora::gfx::DrawTypeId>& drawIds,
|
||||
std::vector<aurora::gfx::EncoderTaskId>& taskIds) {
|
||||
for (uint32_t i = 0; i < s_slots.size(); ++i) {
|
||||
auto& slot = s_slots[i];
|
||||
if (!slot.alive || slot.owner != owner) {
|
||||
continue;
|
||||
}
|
||||
auto entries = s_slots.take_all(owner);
|
||||
for (auto& entry : entries) {
|
||||
const auto& slot = entry.value;
|
||||
if (slot.kind == GfxSlotKind::DrawType && slot.auroraDrawId != aurora::gfx::InvalidDrawType)
|
||||
{
|
||||
drawIds.push_back(slot.auroraDrawId);
|
||||
@@ -133,7 +97,6 @@ void collect_mod_slots_locked(LoadedMod* owner, std::vector<aurora::gfx::DrawTyp
|
||||
{
|
||||
taskIds.push_back(slot.auroraTaskId);
|
||||
}
|
||||
free_slot_locked(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,15 +120,16 @@ void draw_trampoline(const aurora::gfx::DrawContext& ctx, const wgpu::RenderPass
|
||||
std::string ownerId;
|
||||
{
|
||||
std::lock_guard lock{s_mutex};
|
||||
auto* slot = resolve_slot_locked(handle, GfxSlotKind::DrawType);
|
||||
if (slot == nullptr) {
|
||||
auto* entry = resolve_entry_locked(handle, GfxSlotKind::DrawType);
|
||||
if (entry == nullptr) {
|
||||
return;
|
||||
}
|
||||
fn = slot->drawFn;
|
||||
userData = slot->userData;
|
||||
modContext = slot->ownerContext;
|
||||
owner = slot->owner;
|
||||
ownerId = slot->ownerId;
|
||||
const auto& slot = entry->value;
|
||||
fn = slot.drawFn;
|
||||
userData = slot.userData;
|
||||
modContext = slot.ownerContext;
|
||||
owner = entry->owner;
|
||||
ownerId = slot.ownerId;
|
||||
}
|
||||
|
||||
GfxDrawContext drawContext{
|
||||
@@ -200,7 +164,7 @@ void draw_trampoline(const aurora::gfx::DrawContext& ctx, const wgpu::RenderPass
|
||||
.modId = std::move(ownerId),
|
||||
.message = std::move(failure),
|
||||
};
|
||||
collect_mod_slots_locked(owner, record.drawIds, record.taskIds);
|
||||
collect_mod_slots_locked(*owner, record.drawIds, record.taskIds);
|
||||
s_workerFailures.push_back(std::move(record));
|
||||
}
|
||||
|
||||
@@ -214,15 +178,16 @@ void compute_trampoline(const aurora::gfx::EncoderTaskContext& ctx, const wgpu::
|
||||
std::string ownerId;
|
||||
{
|
||||
std::lock_guard lock{s_mutex};
|
||||
auto* slot = resolve_slot_locked(handle, GfxSlotKind::ComputeType);
|
||||
if (slot == nullptr) {
|
||||
auto* entry = resolve_entry_locked(handle, GfxSlotKind::ComputeType);
|
||||
if (entry == nullptr) {
|
||||
return;
|
||||
}
|
||||
fn = slot->computeFn;
|
||||
userData = slot->userData;
|
||||
modContext = slot->ownerContext;
|
||||
owner = slot->owner;
|
||||
ownerId = slot->ownerId;
|
||||
const auto& slot = entry->value;
|
||||
fn = slot.computeFn;
|
||||
userData = slot.userData;
|
||||
modContext = slot.ownerContext;
|
||||
owner = entry->owner;
|
||||
ownerId = slot.ownerId;
|
||||
}
|
||||
|
||||
GfxComputeContext computeContext{
|
||||
@@ -251,7 +216,7 @@ void compute_trampoline(const aurora::gfx::EncoderTaskContext& ctx, const wgpu::
|
||||
.modId = std::move(ownerId),
|
||||
.message = std::move(failure),
|
||||
};
|
||||
collect_mod_slots_locked(owner, record.drawIds, record.taskIds);
|
||||
collect_mod_slots_locked(*owner, record.drawIds, record.taskIds);
|
||||
s_workerFailures.push_back(std::move(record));
|
||||
}
|
||||
|
||||
@@ -264,16 +229,13 @@ ModResult gfx_register_draw_type(
|
||||
uint64_t handle = 0;
|
||||
{
|
||||
std::lock_guard lock{s_mutex};
|
||||
const auto index = alloc_slot_locked();
|
||||
auto& slot = s_slots[index];
|
||||
slot.kind = GfxSlotKind::DrawType;
|
||||
slot.alive = true;
|
||||
slot.owner = &mod;
|
||||
slot.ownerContext = mod.context.get();
|
||||
slot.ownerId = mod.metadata.id;
|
||||
slot.userData = userData;
|
||||
slot.drawFn = draw;
|
||||
handle = make_handle(index, slot.generation);
|
||||
handle = s_slots.emplace(mod, GfxSlot{
|
||||
.kind = GfxSlotKind::DrawType,
|
||||
.ownerContext = mod.context.get(),
|
||||
.ownerId = mod.metadata.id,
|
||||
.userData = userData,
|
||||
.drawFn = draw,
|
||||
});
|
||||
}
|
||||
|
||||
const auto auroraId = aurora::gfx::register_draw_type(aurora::gfx::DrawTypeDescriptor{
|
||||
@@ -283,9 +245,7 @@ ModResult gfx_register_draw_type(
|
||||
});
|
||||
if (auroraId == aurora::gfx::InvalidDrawType) {
|
||||
std::lock_guard lock{s_mutex};
|
||||
if (resolve_owned_slot_locked(mod, handle, GfxSlotKind::DrawType) != nullptr) {
|
||||
free_slot_locked(static_cast<uint32_t>(handle & 0xFFFFFFFFu));
|
||||
}
|
||||
s_slots.erase_owned(handle, mod);
|
||||
return MOD_ERROR;
|
||||
}
|
||||
|
||||
@@ -308,7 +268,7 @@ ModResult gfx_unregister_draw_type(LoadedMod& mod, uint64_t handle) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
auroraId = slot->auroraDrawId;
|
||||
free_slot_locked(static_cast<uint32_t>(handle & 0xFFFFFFFFu));
|
||||
s_slots.erase_owned(handle, mod);
|
||||
}
|
||||
aurora::gfx::unregister_draw_type(auroraId);
|
||||
return MOD_OK;
|
||||
@@ -359,17 +319,14 @@ ModResult gfx_register_stage_hook(
|
||||
LoadedMod& mod, GfxStage stage, GfxStageFn callback, void* userData, uint64_t& outHandle) {
|
||||
outHandle = 0;
|
||||
std::lock_guard lock{s_mutex};
|
||||
const auto index = alloc_slot_locked();
|
||||
auto& slot = s_slots[index];
|
||||
slot.kind = GfxSlotKind::StageHook;
|
||||
slot.alive = true;
|
||||
slot.owner = &mod;
|
||||
slot.ownerContext = mod.context.get();
|
||||
slot.ownerId = mod.metadata.id;
|
||||
slot.userData = userData;
|
||||
slot.stageFn = callback;
|
||||
slot.stage = stage;
|
||||
outHandle = make_handle(index, slot.generation);
|
||||
outHandle = s_slots.emplace(mod, GfxSlot{
|
||||
.kind = GfxSlotKind::StageHook,
|
||||
.ownerContext = mod.context.get(),
|
||||
.ownerId = mod.metadata.id,
|
||||
.userData = userData,
|
||||
.stageFn = callback,
|
||||
.stage = stage,
|
||||
});
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
@@ -379,7 +336,7 @@ ModResult gfx_unregister_stage_hook(LoadedMod& mod, uint64_t handle) {
|
||||
if (slot == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
free_slot_locked(static_cast<uint32_t>(handle & 0xFFFFFFFFu));
|
||||
s_slots.erase_owned(handle, mod);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
@@ -429,16 +386,13 @@ ModResult gfx_register_compute_type(
|
||||
uint64_t handle = 0;
|
||||
{
|
||||
std::lock_guard lock{s_mutex};
|
||||
const auto index = alloc_slot_locked();
|
||||
auto& slot = s_slots[index];
|
||||
slot.kind = GfxSlotKind::ComputeType;
|
||||
slot.alive = true;
|
||||
slot.owner = &mod;
|
||||
slot.ownerContext = mod.context.get();
|
||||
slot.ownerId = mod.metadata.id;
|
||||
slot.userData = userData;
|
||||
slot.computeFn = callback;
|
||||
handle = make_handle(index, slot.generation);
|
||||
handle = s_slots.emplace(mod, GfxSlot{
|
||||
.kind = GfxSlotKind::ComputeType,
|
||||
.ownerContext = mod.context.get(),
|
||||
.ownerId = mod.metadata.id,
|
||||
.userData = userData,
|
||||
.computeFn = callback,
|
||||
});
|
||||
}
|
||||
|
||||
const auto auroraId =
|
||||
@@ -449,9 +403,7 @@ ModResult gfx_register_compute_type(
|
||||
});
|
||||
if (auroraId == aurora::gfx::InvalidEncoderTask) {
|
||||
std::lock_guard lock{s_mutex};
|
||||
if (resolve_owned_slot_locked(mod, handle, GfxSlotKind::ComputeType) != nullptr) {
|
||||
free_slot_locked(static_cast<uint32_t>(handle & 0xFFFFFFFFu));
|
||||
}
|
||||
s_slots.erase_owned(handle, mod);
|
||||
return MOD_ERROR;
|
||||
}
|
||||
|
||||
@@ -474,7 +426,7 @@ ModResult gfx_unregister_compute_type(LoadedMod& mod, uint64_t handle) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
auroraId = slot->auroraTaskId;
|
||||
free_slot_locked(static_cast<uint32_t>(handle & 0xFFFFFFFFu));
|
||||
s_slots.erase_owned(handle, mod);
|
||||
}
|
||||
aurora::gfx::unregister_encoder_task_type(auroraId);
|
||||
return MOD_OK;
|
||||
@@ -510,18 +462,18 @@ void gfx_run_stage(
|
||||
std::vector<StageEntry> entries;
|
||||
{
|
||||
std::lock_guard lock{s_mutex};
|
||||
for (uint32_t i = 0; i < s_slots.size(); ++i) {
|
||||
const auto& slot = s_slots[i];
|
||||
if (slot.alive && slot.kind == GfxSlotKind::StageHook && slot.stage == stage) {
|
||||
s_slots.for_each([&](uint64_t handle, const auto& slotEntry) {
|
||||
const auto& slot = slotEntry.value;
|
||||
if (slot.kind == GfxSlotKind::StageHook && slot.stage == stage) {
|
||||
entries.push_back(StageEntry{
|
||||
.handle = make_handle(i, slot.generation),
|
||||
.handle = handle,
|
||||
.fn = slot.stageFn,
|
||||
.userData = slot.userData,
|
||||
.context = slot.ownerContext,
|
||||
.owner = slot.owner,
|
||||
.owner = slotEntry.owner,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (entries.empty()) {
|
||||
return;
|
||||
@@ -600,7 +552,7 @@ void gfx_remove_mod(LoadedMod& mod) {
|
||||
std::vector<aurora::gfx::EncoderTaskId> taskIds;
|
||||
{
|
||||
std::lock_guard lock{s_mutex};
|
||||
collect_mod_slots_locked(&mod, drawIds, taskIds);
|
||||
collect_mod_slots_locked(mod, drawIds, taskIds);
|
||||
}
|
||||
if (drawIds.empty() && taskIds.empty()) {
|
||||
return;
|
||||
|
||||
+29
-20
@@ -1,4 +1,5 @@
|
||||
#include "registry.hpp"
|
||||
#include "slot_map.hpp"
|
||||
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "dusk/mods/manifest.hpp"
|
||||
@@ -63,14 +64,13 @@ const char* host_mod_dir(ModContext* context) {
|
||||
}
|
||||
|
||||
struct LifecycleWatcher {
|
||||
uint64_t handle = 0;
|
||||
LoadedMod* owner = nullptr;
|
||||
ModLifecycleFn fn = nullptr;
|
||||
void* userData = nullptr;
|
||||
uint64_t order = 0;
|
||||
};
|
||||
|
||||
std::vector<LifecycleWatcher> s_watchers;
|
||||
uint64_t s_nextWatchHandle = 1;
|
||||
SlotMap<LifecycleWatcher> s_watchers;
|
||||
uint64_t s_nextWatchOrder = 0;
|
||||
|
||||
ModResult host_watch_mod_lifecycle(
|
||||
ModContext* context, ModLifecycleFn fn, void* userData, uint64_t* outHandle) {
|
||||
@@ -78,8 +78,8 @@ ModResult host_watch_mod_lifecycle(
|
||||
if (mod == nullptr || fn == nullptr || outHandle == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
const auto handle = s_nextWatchHandle++;
|
||||
s_watchers.push_back({handle, mod, fn, userData});
|
||||
const auto handle = s_watchers.emplace(
|
||||
*mod, LifecycleWatcher{.fn = fn, .userData = userData, .order = s_nextWatchOrder++});
|
||||
*outHandle = handle;
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -89,32 +89,41 @@ ModResult host_unwatch_mod_lifecycle(ModContext* context, const uint64_t handle)
|
||||
if (mod == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
const auto erased = std::erase_if(s_watchers,
|
||||
[&](const LifecycleWatcher& w) { return w.handle == handle && w.owner == mod; });
|
||||
return erased != 0 ? MOD_OK : MOD_INVALID_ARGUMENT;
|
||||
return s_watchers.erase_owned(handle, *mod) ? MOD_OK : MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
void host_mod_detached(LoadedMod& mod) {
|
||||
// The subject's own watches go first: a mod is never notified about its own teardown.
|
||||
std::erase_if(s_watchers, [&](const LifecycleWatcher& w) { return w.owner == &mod; });
|
||||
s_watchers.erase_all(mod);
|
||||
|
||||
// Iterate a snapshot: callbacks may watch/unwatch, and a failing callback erases the
|
||||
// failing mod's services.
|
||||
const auto snapshot = s_watchers;
|
||||
for (const auto& watcher : snapshot) {
|
||||
const bool alive = std::ranges::any_of(
|
||||
s_watchers, [&](const LifecycleWatcher& w) { return w.handle == watcher.handle; });
|
||||
if (!alive) {
|
||||
// Iterate a snapshot in registration order: callbacks may watch/unwatch, and a failing
|
||||
// callback erases the failing mod's services.
|
||||
struct PendingNotify {
|
||||
uint64_t order;
|
||||
uint64_t handle;
|
||||
};
|
||||
std::vector<PendingNotify> snapshot;
|
||||
s_watchers.for_each([&](const uint64_t handle, const auto& entry) {
|
||||
snapshot.push_back({.order = entry.value.order, .handle = handle});
|
||||
});
|
||||
std::ranges::sort(snapshot, {}, &PendingNotify::order);
|
||||
|
||||
for (const auto& pending : snapshot) {
|
||||
const auto* entry = s_watchers.find(pending.handle);
|
||||
if (entry == nullptr) {
|
||||
continue;
|
||||
}
|
||||
// Do not retain pointers into SlotMap across a callback that may mutate it.
|
||||
auto* owner = entry->owner;
|
||||
const auto watcher = entry->value;
|
||||
try {
|
||||
watcher.fn(watcher.owner->context.get(), mod.context.get(), mod.metadata.id.c_str(),
|
||||
watcher.fn(owner->context.get(), mod.context.get(), mod.metadata.id.c_str(),
|
||||
MOD_LIFECYCLE_DETACHED, watcher.userData);
|
||||
} catch (const std::exception& e) {
|
||||
fail_mod(*watcher.owner, MOD_ERROR,
|
||||
fail_mod(*owner, MOD_ERROR,
|
||||
fmt::format("Exception in mod lifecycle callback: {}", e.what()));
|
||||
} catch (...) {
|
||||
fail_mod(*watcher.owner, MOD_ERROR, "Unknown exception in mod lifecycle callback");
|
||||
fail_mod(*owner, MOD_ERROR, "Unknown exception in mod lifecycle callback");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "registry.hpp"
|
||||
#include "slot_map.hpp"
|
||||
|
||||
#include "aurora/dvd.h"
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "mods/svc/overlay.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
@@ -33,15 +35,15 @@ std::unordered_map<uintptr_t, OverlayFileData> s_overlayFiles;
|
||||
uintptr_t s_nextOverlayId = 1;
|
||||
std::mutex s_overlayMutex;
|
||||
|
||||
struct RuntimeOverlayEntry {
|
||||
uint64_t handle = 0;
|
||||
struct RuntimeOverlaySlot {
|
||||
std::string discPath;
|
||||
std::string bundlePath; // bundle-backed if non-empty
|
||||
std::shared_ptr<const std::vector<u8>> buffer; // buffer-backed otherwise
|
||||
size_t size = 0;
|
||||
uint64_t order = 0;
|
||||
};
|
||||
std::unordered_map<const LoadedMod*, std::vector<RuntimeOverlayEntry>> s_runtimeOverlays;
|
||||
uint64_t s_nextRuntimeHandle = 1;
|
||||
SlotMap<RuntimeOverlaySlot> s_runtimeOverlays;
|
||||
uint64_t s_nextRuntimeOrder = 0;
|
||||
bool s_overlaysDirty = false;
|
||||
|
||||
// Aurora matches overlay paths against the disc case-insensitively and later entries win, so
|
||||
@@ -84,20 +86,25 @@ void find_overlay_files(std::vector<AuroraOverlayFile>& files, LoadedMod& mod,
|
||||
|
||||
void append_runtime_overlays(std::vector<AuroraOverlayFile>& files, LoadedMod& mod,
|
||||
std::unordered_map<std::string, const LoadedMod*>& claims) {
|
||||
const auto it = s_runtimeOverlays.find(&mod);
|
||||
if (it == s_runtimeOverlays.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& entry : it->second) {
|
||||
const auto id = s_nextOverlayId++;
|
||||
if (entry.buffer != nullptr) {
|
||||
s_overlayFiles.emplace(id, OverlayFileData{{}, nullptr, entry.buffer});
|
||||
} else {
|
||||
s_overlayFiles.emplace(id, OverlayFileData{entry.bundlePath, mod.bundle, nullptr});
|
||||
// Aurora resolves duplicate paths later-entry-wins, so emit in registration order (SlotMap
|
||||
// iteration is index order, and freed indices are reused).
|
||||
std::vector<const RuntimeOverlaySlot*> slots;
|
||||
s_runtimeOverlays.for_each([&](uint64_t, const auto& entry) {
|
||||
if (entry.owner == &mod) {
|
||||
slots.push_back(&entry.value);
|
||||
}
|
||||
claim_overlay_path(claims, entry.discPath, mod);
|
||||
files.emplace_back(strdup(entry.discPath.c_str()), reinterpret_cast<void*>(id), entry.size);
|
||||
});
|
||||
std::ranges::sort(slots, {}, &RuntimeOverlaySlot::order);
|
||||
|
||||
for (const auto* slot : slots) {
|
||||
const auto id = s_nextOverlayId++;
|
||||
if (slot->buffer != nullptr) {
|
||||
s_overlayFiles.emplace(id, OverlayFileData{{}, nullptr, slot->buffer});
|
||||
} else {
|
||||
s_overlayFiles.emplace(id, OverlayFileData{slot->bundlePath, mod.bundle, nullptr});
|
||||
}
|
||||
claim_overlay_path(claims, slot->discPath, mod);
|
||||
files.emplace_back(strdup(slot->discPath.c_str()), reinterpret_cast<void*>(id), slot->size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,47 +208,39 @@ void overlay_sync_files() {
|
||||
|
||||
uint64_t overlay_add_file(
|
||||
LoadedMod& mod, std::string discPath, std::string bundlePath, size_t size) {
|
||||
const auto handle = s_nextRuntimeHandle++;
|
||||
s_runtimeOverlays[&mod].push_back({
|
||||
.handle = handle,
|
||||
.discPath = std::move(discPath),
|
||||
.bundlePath = std::move(bundlePath),
|
||||
.size = size,
|
||||
});
|
||||
const auto handle = s_runtimeOverlays.emplace(mod, RuntimeOverlaySlot{
|
||||
.discPath = std::move(discPath),
|
||||
.bundlePath = std::move(bundlePath),
|
||||
.size = size,
|
||||
.order = s_nextRuntimeOrder++,
|
||||
});
|
||||
s_overlaysDirty = true;
|
||||
return handle;
|
||||
}
|
||||
|
||||
uint64_t overlay_add_buffer(LoadedMod& mod, std::string discPath, std::vector<u8> data) {
|
||||
const auto handle = s_nextRuntimeHandle++;
|
||||
const auto size = data.size();
|
||||
s_runtimeOverlays[&mod].push_back({
|
||||
.handle = handle,
|
||||
.discPath = std::move(discPath),
|
||||
.buffer = std::make_shared<const std::vector<u8>>(std::move(data)),
|
||||
.size = size,
|
||||
});
|
||||
const auto handle = s_runtimeOverlays.emplace(mod,
|
||||
RuntimeOverlaySlot{
|
||||
.discPath = std::move(discPath),
|
||||
.buffer = std::make_shared<const std::vector<u8>>(std::move(data)),
|
||||
.size = size,
|
||||
.order = s_nextRuntimeOrder++,
|
||||
});
|
||||
s_overlaysDirty = true;
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool overlay_remove(LoadedMod& mod, uint64_t handle) {
|
||||
const auto it = s_runtimeOverlays.find(&mod);
|
||||
if (it == s_runtimeOverlays.end()) {
|
||||
if (!s_runtimeOverlays.erase_owned(handle, mod)) {
|
||||
return false;
|
||||
}
|
||||
if (std::erase_if(it->second, [&](const auto& entry) { return entry.handle == handle; }) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (it->second.empty()) {
|
||||
s_runtimeOverlays.erase(it);
|
||||
}
|
||||
s_overlaysDirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void overlay_remove_mod(LoadedMod& mod) {
|
||||
if (s_runtimeOverlays.erase(&mod) != 0) {
|
||||
if (s_runtimeOverlays.erase_all(mod) != 0) {
|
||||
s_overlaysDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::mods {
|
||||
|
||||
struct LoadedMod;
|
||||
|
||||
namespace svc {
|
||||
|
||||
template <typename T>
|
||||
class SlotMap {
|
||||
public:
|
||||
static_assert(std::is_nothrow_move_constructible_v<T>);
|
||||
|
||||
using Handle = uint64_t;
|
||||
static constexpr Handle InvalidHandle = 0;
|
||||
|
||||
struct Entry {
|
||||
LoadedMod* owner = nullptr;
|
||||
T value;
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
Handle emplace(LoadedMod& owner, Args&&... args) {
|
||||
T value{std::forward<Args>(args)...};
|
||||
const auto index = allocate_index();
|
||||
auto& slot = m_slots[index];
|
||||
slot.entry.emplace(Entry{.owner = &owner, .value = std::move(value)});
|
||||
return make_handle(index, slot.generation);
|
||||
}
|
||||
|
||||
// Returned pointers remain valid only until the next mutating operation.
|
||||
Entry* find(Handle handle) {
|
||||
auto* slot = find_slot(handle);
|
||||
return slot != nullptr ? &*slot->entry : nullptr;
|
||||
}
|
||||
|
||||
const Entry* find(Handle handle) const {
|
||||
const auto* slot = find_slot(handle);
|
||||
return slot != nullptr ? &*slot->entry : nullptr;
|
||||
}
|
||||
|
||||
Entry* find_owned(Handle handle, const LoadedMod& owner) {
|
||||
auto* entry = find(handle);
|
||||
return entry != nullptr && entry->owner == &owner ? entry : nullptr;
|
||||
}
|
||||
|
||||
const Entry* find_owned(Handle handle, const LoadedMod& owner) const {
|
||||
const auto* entry = find(handle);
|
||||
return entry != nullptr && entry->owner == &owner ? entry : nullptr;
|
||||
}
|
||||
|
||||
std::optional<Entry> take(Handle handle) {
|
||||
const auto index = handle_index(handle);
|
||||
auto* slot = find_slot(handle);
|
||||
if (slot == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<Entry> entry{std::move(slot->entry)};
|
||||
release_slot(index);
|
||||
return entry;
|
||||
}
|
||||
|
||||
std::optional<Entry> take_owned(Handle handle, const LoadedMod& owner) {
|
||||
if (find_owned(handle, owner) == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return take(handle);
|
||||
}
|
||||
|
||||
std::vector<Entry> take_all(const LoadedMod& owner) {
|
||||
std::vector<Entry> entries;
|
||||
for (size_t slotIndex = 0; slotIndex < m_slots.size(); ++slotIndex) {
|
||||
const auto index = static_cast<uint32_t>(slotIndex);
|
||||
auto& slot = m_slots[index];
|
||||
if (!slot.entry.has_value() || slot.entry->owner != &owner) {
|
||||
continue;
|
||||
}
|
||||
entries.push_back(std::move(*slot.entry));
|
||||
release_slot(index);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
bool erase(Handle handle) {
|
||||
const auto index = handle_index(handle);
|
||||
if (find_slot(handle) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
release_slot(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool erase_owned(Handle handle, const LoadedMod& owner) {
|
||||
if (find_owned(handle, owner) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return erase(handle);
|
||||
}
|
||||
|
||||
size_t erase_all(const LoadedMod& owner) {
|
||||
return take_all(owner).size();
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void for_each(Fn&& fn) const {
|
||||
// The visitor may inspect entries but must not mutate this SlotMap.
|
||||
for (size_t slotIndex = 0; slotIndex < m_slots.size(); ++slotIndex) {
|
||||
const auto index = static_cast<uint32_t>(slotIndex);
|
||||
const auto& slot = m_slots[index];
|
||||
if (slot.entry.has_value()) {
|
||||
fn(make_handle(index, slot.generation), *slot.entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
uint32_t generation = 1;
|
||||
std::optional<Entry> entry;
|
||||
};
|
||||
|
||||
static constexpr Handle make_handle(uint32_t index, uint32_t generation) {
|
||||
return static_cast<Handle>(generation) << 32 | index;
|
||||
}
|
||||
|
||||
static constexpr uint32_t handle_index(Handle handle) {
|
||||
return static_cast<uint32_t>(handle & std::numeric_limits<uint32_t>::max());
|
||||
}
|
||||
|
||||
static constexpr uint32_t handle_generation(Handle handle) {
|
||||
return static_cast<uint32_t>(handle >> 32);
|
||||
}
|
||||
|
||||
Slot* find_slot(Handle handle) {
|
||||
return const_cast<Slot*>(std::as_const(*this).find_slot(handle));
|
||||
}
|
||||
|
||||
const Slot* find_slot(Handle handle) const {
|
||||
const auto index = handle_index(handle);
|
||||
if (handle == InvalidHandle || index >= m_slots.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto& slot = m_slots[index];
|
||||
if (!slot.entry.has_value() || slot.generation != handle_generation(handle)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &slot;
|
||||
}
|
||||
|
||||
uint32_t allocate_index() {
|
||||
if (!m_freeSlots.empty()) {
|
||||
const auto index = m_freeSlots.back();
|
||||
m_freeSlots.pop_back();
|
||||
return index;
|
||||
}
|
||||
if (m_slots.size() > std::numeric_limits<uint32_t>::max()) {
|
||||
throw std::length_error{"SlotMap handle space exhausted"};
|
||||
}
|
||||
const auto index = static_cast<uint32_t>(m_slots.size());
|
||||
m_slots.emplace_back();
|
||||
return index;
|
||||
}
|
||||
|
||||
void release_slot(uint32_t index) {
|
||||
auto& slot = m_slots[index];
|
||||
slot.entry.reset();
|
||||
if (slot.generation == std::numeric_limits<uint32_t>::max()) {
|
||||
return;
|
||||
}
|
||||
++slot.generation;
|
||||
m_freeSlots.push_back(index);
|
||||
}
|
||||
|
||||
std::vector<Slot> m_slots;
|
||||
std::vector<uint32_t> m_freeSlots;
|
||||
};
|
||||
|
||||
} // namespace svc
|
||||
} // namespace dusk::mods
|
||||
+41
-103
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "config.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "slot_map.hpp"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mod_loader.hpp"
|
||||
@@ -33,7 +34,6 @@ namespace {
|
||||
aurora::Module Log("dusk::mods::ui");
|
||||
|
||||
enum class UiSlotKind : u8 {
|
||||
Free,
|
||||
Window,
|
||||
Dialog,
|
||||
Pane,
|
||||
@@ -63,17 +63,14 @@ const char* slot_kind_name(UiSlotKind kind) {
|
||||
case UiSlotKind::MenuTab:
|
||||
return "menu tab";
|
||||
default:
|
||||
return "free";
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Generation-checked handle slots. Game thread only: all mutations happen in service calls made
|
||||
// from mod code, in UI callbacks (ui::update), or in the loader's deactivate paths.
|
||||
// Game thread only: all mutations happen in service calls made from mod code, in UI callbacks
|
||||
// (ui::update), or in the loader's deactivate paths.
|
||||
struct UiSlot {
|
||||
// Bumped on free, which invalidates every outstanding handle to this slot.
|
||||
uint32_t generation = 1;
|
||||
UiSlotKind kind = UiSlotKind::Free;
|
||||
LoadedMod* owner = nullptr;
|
||||
UiSlotKind kind = UiSlotKind::Window;
|
||||
// Pane/Text/Progress/Control: freed automatically when the element is destroyed
|
||||
Rml::Element* element = nullptr;
|
||||
// Pane payload
|
||||
@@ -93,8 +90,7 @@ struct UiSlot {
|
||||
bool hasElementValue = false;
|
||||
};
|
||||
|
||||
std::vector<UiSlot> s_slots;
|
||||
std::vector<uint32_t> s_freeSlots;
|
||||
SlotMap<UiSlot> s_slots;
|
||||
|
||||
struct ModUiPanel {
|
||||
UiPanelBuildFn build = nullptr;
|
||||
@@ -112,71 +108,26 @@ struct ModMenuTab {
|
||||
std::unordered_map<const LoadedMod*, std::vector<ModMenuTab>> s_modMenuTabs;
|
||||
bool s_menuTabsDirty = false;
|
||||
|
||||
uint64_t handle_for(uint32_t index) {
|
||||
return (uint64_t{s_slots[index].generation} << 32) | index;
|
||||
}
|
||||
|
||||
uint32_t slot_index(const UiSlot& slot) {
|
||||
return static_cast<uint32_t>(&slot - s_slots.data());
|
||||
}
|
||||
|
||||
UiSlot* slot_from_handle(uint64_t handle) {
|
||||
const auto index = static_cast<uint32_t>(handle & 0xFFFFFFFFu);
|
||||
const auto generation = static_cast<uint32_t>(handle >> 32);
|
||||
if (index >= s_slots.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& slot = s_slots[index];
|
||||
if (slot.kind == UiSlotKind::Free || slot.generation != generation) {
|
||||
return nullptr;
|
||||
}
|
||||
return &slot;
|
||||
auto* entry = s_slots.find(handle);
|
||||
return entry != nullptr ? &entry->value : nullptr;
|
||||
}
|
||||
|
||||
// Note: s_slots may reallocate on any later allocation, so callers must not hold the returned
|
||||
// slot reference across calls that can allocate (e.g. mod build callbacks); re-resolve instead.
|
||||
UiSlot& alloc_slot(LoadedMod& mod, UiSlotKind kind, uint64_t& outHandle) {
|
||||
uint32_t index;
|
||||
if (!s_freeSlots.empty()) {
|
||||
index = s_freeSlots.back();
|
||||
s_freeSlots.pop_back();
|
||||
} else {
|
||||
index = static_cast<uint32_t>(s_slots.size());
|
||||
s_slots.emplace_back();
|
||||
}
|
||||
auto& slot = s_slots[index];
|
||||
slot.kind = kind;
|
||||
slot.owner = &mod;
|
||||
outHandle = handle_for(index);
|
||||
return slot;
|
||||
}
|
||||
|
||||
void free_slot(UiSlot& slot) {
|
||||
slot.generation++;
|
||||
slot.kind = UiSlotKind::Free;
|
||||
slot.owner = nullptr;
|
||||
slot.element = nullptr;
|
||||
slot.pane = nullptr;
|
||||
slot.helpPane = nullptr;
|
||||
slot.document = nullptr;
|
||||
slot.onClosed = nullptr;
|
||||
slot.onClosedUserData = nullptr;
|
||||
slot.styleScope = ui::DocumentScope::None;
|
||||
slot.styleId.clear();
|
||||
slot.elementRml.clear();
|
||||
slot.elementFloat = 0.0f;
|
||||
slot.hasElementValue = false;
|
||||
s_freeSlots.push_back(slot_index(slot));
|
||||
outHandle = s_slots.emplace(mod, UiSlot{.kind = kind});
|
||||
return s_slots.find(outHandle)->value;
|
||||
}
|
||||
|
||||
UiSlot* resolve(LoadedMod& mod, uint64_t handle, UiSlotKind kind, const char* what) {
|
||||
auto* slot = slot_from_handle(handle);
|
||||
if (slot == nullptr || slot->owner != &mod || slot->kind != kind) {
|
||||
auto* entry = s_slots.find_owned(handle, mod);
|
||||
if (entry == nullptr || entry->value.kind != kind) {
|
||||
Log.error("[{}] {}: stale or invalid {} handle {:#x}", mod.metadata.id, what,
|
||||
slot_kind_name(kind), handle);
|
||||
return nullptr;
|
||||
}
|
||||
return slot;
|
||||
return &entry->value;
|
||||
}
|
||||
|
||||
// Whether the registration a callback was created under is still live. Callbacks captured by
|
||||
@@ -184,7 +135,7 @@ UiSlot* resolve(LoadedMod& mod, uint64_t handle, UiSlotKind kind, const char* wh
|
||||
// once a reload completes, but captured fn pointers still target the unloaded image. Teardown
|
||||
// frees the slots (ui_remove_mod), which invalidates every callback built under them.
|
||||
bool slot_live(uint64_t handle) {
|
||||
return slot_from_handle(handle) != nullptr;
|
||||
return s_slots.find(handle) != nullptr;
|
||||
}
|
||||
|
||||
bool dialog_open(uint64_t handle) {
|
||||
@@ -197,30 +148,22 @@ bool dialog_open(uint64_t handle) {
|
||||
// The generation check makes a late detach of an already-recycled slot a no-op.
|
||||
class SlotDetachListener final : public Rml::EventListener {
|
||||
public:
|
||||
SlotDetachListener(uint32_t index, uint32_t generation)
|
||||
: m_index{index}, m_generation{generation} {}
|
||||
explicit SlotDetachListener(uint64_t handle) : m_handle{handle} {}
|
||||
|
||||
void ProcessEvent(Rml::Event&) override {}
|
||||
|
||||
void OnDetach(Rml::Element*) override {
|
||||
if (m_index < s_slots.size()) {
|
||||
auto& slot = s_slots[m_index];
|
||||
if (slot.kind != UiSlotKind::Free && slot.generation == m_generation) {
|
||||
free_slot(slot);
|
||||
}
|
||||
}
|
||||
s_slots.erase(m_handle);
|
||||
delete this;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t m_index;
|
||||
uint32_t m_generation;
|
||||
uint64_t m_handle;
|
||||
};
|
||||
|
||||
void track_element(UiSlot& slot, Rml::Element& element) {
|
||||
void track_element(uint64_t handle, UiSlot& slot, Rml::Element& element) {
|
||||
slot.element = &element;
|
||||
element.AddEventListener(
|
||||
Rml::EventId::Click, new SlotDetachListener(slot_index(slot), slot.generation));
|
||||
element.AddEventListener(Rml::EventId::Click, new SlotDetachListener{handle});
|
||||
}
|
||||
|
||||
template <typename T, typename Fn>
|
||||
@@ -269,7 +212,7 @@ uint64_t wrap_pane(LoadedMod& mod, ui::Pane& pane, ui::Pane* helpPane) {
|
||||
auto& slot = alloc_slot(mod, UiSlotKind::Pane, handle);
|
||||
slot.pane = &pane;
|
||||
slot.helpPane = helpPane;
|
||||
track_element(slot, *pane.root());
|
||||
track_element(handle, slot, *pane.root());
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -446,14 +389,14 @@ bool wire_config_var_binding(LoadedMod& mod, const UiControlDesc& desc, ui::ModC
|
||||
}
|
||||
|
||||
void on_mod_window_destroyed(uint64_t handle) {
|
||||
auto* slot = slot_from_handle(handle);
|
||||
if (slot == nullptr || slot->kind != UiSlotKind::Window) {
|
||||
const auto* entry = s_slots.find(handle);
|
||||
if (entry == nullptr || entry->value.kind != UiSlotKind::Window) {
|
||||
return;
|
||||
}
|
||||
LoadedMod* mod = slot->owner;
|
||||
const UiWindowClosedFn onClosed = slot->onClosed;
|
||||
void* userData = slot->onClosedUserData;
|
||||
free_slot(*slot);
|
||||
auto released = s_slots.take(handle);
|
||||
auto* mod = released->owner;
|
||||
const UiWindowClosedFn onClosed = released->value.onClosed;
|
||||
void* userData = released->value.onClosedUserData;
|
||||
if (mod != nullptr && onClosed != nullptr) {
|
||||
guarded_call(*mod, "window on_closed callback", [&] {
|
||||
onClosed(mod->context.get(), handle, userData);
|
||||
@@ -464,7 +407,7 @@ void on_mod_window_destroyed(uint64_t handle) {
|
||||
void on_mod_dialog_destroyed(uint64_t handle) {
|
||||
auto* slot = slot_from_handle(handle);
|
||||
if (slot != nullptr && slot->kind == UiSlotKind::Dialog) {
|
||||
free_slot(*slot);
|
||||
s_slots.erase(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +516,7 @@ ModResult ui_pane_add_text(LoadedMod& mod, uint64_t pane, const char* text, uint
|
||||
auto& elemSlot = alloc_slot(mod, UiSlotKind::Text, *outElem);
|
||||
elemSlot.elementRml = ui::escape(text);
|
||||
elemSlot.hasElementValue = true;
|
||||
track_element(elemSlot, *elem);
|
||||
track_element(*outElem, elemSlot, *elem);
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -588,7 +531,7 @@ ModResult ui_pane_add_rml(LoadedMod& mod, uint64_t pane, const char* rml, uint64
|
||||
auto& elemSlot = alloc_slot(mod, UiSlotKind::Text, *outElem);
|
||||
elemSlot.elementRml = rml;
|
||||
elemSlot.hasElementValue = true;
|
||||
track_element(elemSlot, *elem);
|
||||
track_element(*outElem, elemSlot, *elem);
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -604,7 +547,7 @@ ModResult ui_pane_add_progress(LoadedMod& mod, uint64_t pane, float value, uint6
|
||||
auto& elemSlot = alloc_slot(mod, UiSlotKind::Progress, *outElem);
|
||||
elemSlot.elementFloat = value;
|
||||
elemSlot.hasElementValue = true;
|
||||
track_element(elemSlot, *elem);
|
||||
track_element(*outElem, elemSlot, *elem);
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -691,7 +634,7 @@ ModResult ui_pane_add_control(
|
||||
}
|
||||
if (outElem != nullptr) {
|
||||
auto& elemSlot = alloc_slot(mod, UiSlotKind::Control, *outElem);
|
||||
track_element(elemSlot, *control->root());
|
||||
track_element(*outElem, elemSlot, *control->root());
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -740,13 +683,13 @@ ModResult ui_elem_set_progress(LoadedMod& mod, uint64_t elem, float value) {
|
||||
}
|
||||
|
||||
ModResult ui_elem_set_class(LoadedMod& mod, uint64_t elem, const char* name, bool active) {
|
||||
auto* slot = slot_from_handle(elem);
|
||||
if (slot == nullptr || slot->owner != &mod || slot->element == nullptr) {
|
||||
auto* entry = s_slots.find_owned(elem, mod);
|
||||
if (entry == nullptr || entry->value.element == nullptr) {
|
||||
Log.error(
|
||||
"[{}] elem_set_class: stale or invalid element handle {:#x}", mod.metadata.id, elem);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
slot->element->SetClass(name, active);
|
||||
entry->value.element->SetClass(name, active);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
@@ -945,7 +888,7 @@ ModResult ui_unregister_menu_tab(LoadedMod& mod, uint64_t handle) {
|
||||
s_modMenuTabs.erase(it);
|
||||
}
|
||||
}
|
||||
free_slot(*slot);
|
||||
s_slots.erase_owned(handle, mod);
|
||||
s_menuTabsDirty = true;
|
||||
return MOD_OK;
|
||||
}
|
||||
@@ -1026,7 +969,7 @@ ModResult ui_register_styles(
|
||||
slot.styleId = fmt::format("{}:{:x}", mod.metadata.id, handle);
|
||||
if (!ui::register_scoped_styles(docScope, slot.styleId, rcss)) {
|
||||
Log.error("[{}] register_styles: failed to parse RCSS", mod.metadata.id);
|
||||
free_slot(slot);
|
||||
s_slots.erase(handle);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
outHandle = handle;
|
||||
@@ -1056,8 +999,8 @@ ModResult ui_unregister_styles(LoadedMod& mod, uint64_t handle) {
|
||||
if (slot == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
ui::unregister_scoped_styles(slot->styleScope, slot->styleId);
|
||||
free_slot(*slot);
|
||||
auto released = s_slots.take_owned(handle, mod);
|
||||
ui::unregister_scoped_styles(released->value.styleScope, released->value.styleId);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
@@ -1066,14 +1009,12 @@ void ui_remove_mod(LoadedMod& mod) {
|
||||
if (s_modMenuTabs.erase(&mod) != 0) {
|
||||
s_menuTabsDirty = true;
|
||||
}
|
||||
for (auto& slot : s_slots) {
|
||||
if (slot.kind == UiSlotKind::Free || slot.owner != &mod) {
|
||||
continue;
|
||||
}
|
||||
auto entries = s_slots.take_all(mod);
|
||||
for (auto& entry : entries) {
|
||||
auto& slot = entry.value;
|
||||
switch (slot.kind) {
|
||||
case UiSlotKind::Window: {
|
||||
auto* window = static_cast<ui::ModWindow*>(slot.document);
|
||||
free_slot(slot);
|
||||
if (window != nullptr) {
|
||||
window->force_close();
|
||||
}
|
||||
@@ -1081,7 +1022,6 @@ void ui_remove_mod(LoadedMod& mod) {
|
||||
}
|
||||
case UiSlotKind::Dialog: {
|
||||
auto* dialog = static_cast<ModDialog*>(slot.document);
|
||||
free_slot(slot);
|
||||
if (dialog != nullptr) {
|
||||
dialog->force_close();
|
||||
}
|
||||
@@ -1089,10 +1029,8 @@ void ui_remove_mod(LoadedMod& mod) {
|
||||
}
|
||||
case UiSlotKind::Style:
|
||||
ui::unregister_scoped_styles(slot.styleScope, slot.styleId);
|
||||
free_slot(slot);
|
||||
break;
|
||||
default:
|
||||
free_slot(slot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -73,10 +73,10 @@ std::string format_time(int64_t timeMs) {
|
||||
}
|
||||
|
||||
Rml::Element* append_span(Rml::Element* parent, const char* className, const Rml::String& text) {
|
||||
auto span = parent->GetOwnerDocument()->CreateElement("span");
|
||||
auto* span = append(parent, "span");
|
||||
span->SetClass(className, true);
|
||||
append_text(span.get(), text);
|
||||
return parent->AppendChild(std::move(span));
|
||||
append_text(span, text);
|
||||
return span;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -260,18 +260,18 @@ Rml::Element* LogsWindow::append_log_line(const mods::log::Line& line) {
|
||||
modId = "?";
|
||||
}
|
||||
|
||||
auto elem = mDocument->CreateElement("div");
|
||||
auto* elem = append(mLinesElem, "div");
|
||||
elem->SetClass("log-line", true);
|
||||
elem->SetClass(level_class(line.level), true);
|
||||
|
||||
constexpr const char* kNbsp = "\xc2\xa0";
|
||||
append_span(elem.get(), "log-time", format_time(line.timeMs));
|
||||
append_text(elem.get(), kNbsp);
|
||||
append_span(elem.get(), "log-mod", fmt::format("[{}]", modId));
|
||||
append_text(elem.get(), kNbsp);
|
||||
append_span(elem.get(), "log-msg", line.message);
|
||||
append_span(elem, "log-time", format_time(line.timeMs));
|
||||
append_text(elem, kNbsp);
|
||||
append_span(elem, "log-mod", fmt::format("[{}]", modId));
|
||||
append_text(elem, kNbsp);
|
||||
append_span(elem, "log-msg", line.message);
|
||||
|
||||
return mLinesElem->AppendChild(std::move(elem));
|
||||
return elem;
|
||||
}
|
||||
|
||||
void LogsWindow::copy_to_clipboard() {
|
||||
|
||||
Reference in New Issue
Block a user