Add live spatial weight tuning

This commit is contained in:
2026-06-26 21:34:17 -05:00
parent c525c1edf9
commit 9966cf8272
4 changed files with 218 additions and 8 deletions
+142 -8
View File
@@ -10,6 +10,7 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstring>
@@ -211,6 +212,7 @@ constexpr float kGpsSpatialRoadMultiplier = 1.0f;
constexpr float kGpsSpatialGpsOnlyMultiplier = 1.20f;
constexpr float kGpsSpatialPavementMultiplier = 1.35f;
constexpr float kGpsSpatialUnknownMultiplier = 1.05f;
constexpr uint64_t kGpsSpatialWeightsReloadIntervalMs = 500;
struct GpsProviderClassOverride
{
@@ -530,17 +532,38 @@ GpsProviderFilterFunc gOriginalGpsFilteredProviderFilter = nullptr;
GpsProviderFilterFunc gOriginalGpsBaseProviderFilter = nullptr;
GpsMultiplierFunc gOriginalGpsAuxMultiplier = nullptr;
GpsMultiplierFunc gOriginalGpsNodeMultiplier = nullptr;
std::array<std::atomic<float>, 5> gGpsSpatialMultipliers = {
kGpsSpatialHighwayMultiplier,
kGpsSpatialRoadMultiplier,
kGpsSpatialGpsOnlyMultiplier,
kGpsSpatialPavementMultiplier,
kGpsSpatialUnknownMultiplier,
};
std::mutex gGpsSpatialWeightsMutex;
std::atomic<uint64_t> gGpsSpatialWeightsNextReloadCheckMs = 0;
std::filesystem::file_time_type gGpsSpatialWeightsLastWriteTime{};
bool gGpsSpatialWeightsFileKnown = false;
std::filesystem::path GetLogPath()
std::filesystem::path GetPluginDirectory()
{
wchar_t modulePath[MAX_PATH]{};
if (gModule && GetModuleFileNameW(gModule, modulePath, MAX_PATH) > 0)
{
auto path = std::filesystem::path(modulePath);
return path.parent_path() / L"EdgeWeightGPS.log";
return path.parent_path();
}
return std::filesystem::path(L"red4ext") / L"plugins" / L"EdgeWeightGPS" / L"EdgeWeightGPS.log";
return std::filesystem::path(L"red4ext") / L"plugins" / L"EdgeWeightGPS";
}
std::filesystem::path GetLogPath()
{
return GetPluginDirectory() / L"EdgeWeightGPS.log";
}
std::filesystem::path GetSpatialWeightsPath()
{
return GetPluginDirectory() / L"spatial_weights.bin";
}
uint64_t GetElapsedMs()
@@ -587,6 +610,100 @@ void LogRed4ext(std::string_view aMessage)
}
}
bool IsUsableSpatialMultiplier(float aValue)
{
return std::isfinite(aValue) && aValue > 0.0f && aValue < 20.0f;
}
void StoreGpsSpatialMultipliers(const std::array<float, 5>& aValues)
{
for (size_t index = 0; index < aValues.size(); ++index)
{
gGpsSpatialMultipliers[index].store(aValues[index], std::memory_order_relaxed);
}
}
void LogGpsSpatialMultipliers(std::string_view aPrefix, const std::array<float, 5>& aValues)
{
std::ostringstream line;
line << aPrefix << " highway=" << aValues[0] << " road=" << aValues[1] << " gpsonly=" << aValues[2]
<< " pavement=" << aValues[3] << " unknown=" << aValues[4];
LogRed4ext(line.str());
}
void MaybeReloadGpsSpatialWeights()
{
if (!kEnableGpsSpatialEdgeWeightPatch)
{
return;
}
const auto nowMs = GetElapsedMs();
if (nowMs < gGpsSpatialWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
{
return;
}
std::lock_guard lock(gGpsSpatialWeightsMutex);
if (nowMs < gGpsSpatialWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
{
return;
}
gGpsSpatialWeightsNextReloadCheckMs.store(nowMs + kGpsSpatialWeightsReloadIntervalMs, std::memory_order_relaxed);
const auto path = GetSpatialWeightsPath();
std::error_code error;
const auto writeTime = std::filesystem::last_write_time(path, error);
if (error)
{
if (gGpsSpatialWeightsFileKnown)
{
gGpsSpatialWeightsFileKnown = false;
std::ostringstream line;
line << "spatial weights file unavailable path=" << path.string() << " using current/default values";
LogRed4ext(line.str());
}
return;
}
if (gGpsSpatialWeightsFileKnown && writeTime == gGpsSpatialWeightsLastWriteTime)
{
return;
}
std::array<float, 5> values{};
std::ifstream input(path, std::ios::binary);
input.read(reinterpret_cast<char*>(values.data()), static_cast<std::streamsize>(values.size() * sizeof(float)));
if (input.gcount() != static_cast<std::streamsize>(values.size() * sizeof(float)))
{
std::ostringstream line;
line << "spatial weights reload skipped path=" << path.string() << " bytes=" << input.gcount()
<< " expected=" << values.size() * sizeof(float);
LogRed4ext(line.str());
gGpsSpatialWeightsFileKnown = true;
gGpsSpatialWeightsLastWriteTime = writeTime;
return;
}
for (const auto value : values)
{
if (!IsUsableSpatialMultiplier(value))
{
std::ostringstream line;
line << "spatial weights reload skipped path=" << path.string() << " invalidValue=" << value;
LogRed4ext(line.str());
gGpsSpatialWeightsFileKnown = true;
gGpsSpatialWeightsLastWriteTime = writeTime;
return;
}
}
StoreGpsSpatialMultipliers(values);
gGpsSpatialWeightsFileKnown = true;
gGpsSpatialWeightsLastWriteTime = writeTime;
LogGpsSpatialMultipliers("spatial weights reloaded", values);
}
uintptr_t GetImageBase()
{
return reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
@@ -1035,15 +1152,15 @@ float GpsSpatialRoadClassMultiplier(char aCode)
switch (aCode)
{
case 'H':
return kGpsSpatialHighwayMultiplier;
return gGpsSpatialMultipliers[0].load(std::memory_order_relaxed);
case 'R':
return kGpsSpatialRoadMultiplier;
return gGpsSpatialMultipliers[1].load(std::memory_order_relaxed);
case 'G':
return kGpsSpatialGpsOnlyMultiplier;
return gGpsSpatialMultipliers[2].load(std::memory_order_relaxed);
case 'P':
return kGpsSpatialPavementMultiplier;
return gGpsSpatialMultipliers[3].load(std::memory_order_relaxed);
default:
return kGpsSpatialUnknownMultiplier;
return gGpsSpatialMultipliers[4].load(std::memory_order_relaxed);
}
}
@@ -4213,6 +4330,8 @@ int32_t DetourGpsRouteProducer(void* aGraph, void* aQuery, void* aOutResult)
LogRed4ext(line.str());
}
MaybeReloadGpsSpatialWeights();
GpsEdgeCostStats stats{};
auto* previousStats = gActiveGpsEdgeCostStats;
const auto previousRouteProducerCall = gActiveGpsRouteProducerCall;
@@ -4905,6 +5024,21 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
const auto added = aSdk->gameStates->Add(aHandle, kGameStateRunning, &gRunningState);
LogRed4ext(added ? "registered Running game-state callbacks" : "failed to register Running game-state callbacks");
if (kEnableGpsSpatialEdgeWeightPatch)
{
std::array<float, 5> values = {
gGpsSpatialMultipliers[0].load(std::memory_order_relaxed),
gGpsSpatialMultipliers[1].load(std::memory_order_relaxed),
gGpsSpatialMultipliers[2].load(std::memory_order_relaxed),
gGpsSpatialMultipliers[3].load(std::memory_order_relaxed),
gGpsSpatialMultipliers[4].load(std::memory_order_relaxed),
};
std::ostringstream line;
line << "spatial weights path=" << GetSpatialWeightsPath().string();
LogRed4ext(line.str());
LogGpsSpatialMultipliers("spatial weights defaults", values);
}
if (kEnableGpsQueryLifecycleHooks)
{
AttachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva,