Add local animation graph read-only debug
Introduce a game-thread-only, read-only diagnostic helper to probe local player animation graph variables and (optionally) numeric cache IDs. Adds F4TLocalAnimationGraphDebug.h/cpp with UpdateLocalPlayerAnimationGraphDebug(...) and wires a call into plugin/src/main.cpp immediately after GetPlayerMovementState. The helper probes curated variable names via CommonLibF4 read-only APIs, logs availability/changes, and includes a disabled-by-default numeric cache ID scan (kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug = false) due to a reported crash during testing. Update also adds extensive dev-log documentation (docs/dev-log.md) describing behavior, testing notes, and rollback instructions. Build succeeded; manual F4SE in-game verification remains pending.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace RE
|
||||
{
|
||||
class PlayerCharacter;
|
||||
}
|
||||
|
||||
namespace F4T::LocalAnimationGraphDebug
|
||||
{
|
||||
// Game-thread-only, read-only diagnostics for discovering local player
|
||||
// animation graph variables before any proxy visual animation writes.
|
||||
void UpdateLocalPlayerAnimationGraphDebug(
|
||||
RE::PlayerCharacter& a_player,
|
||||
bool a_isMoving,
|
||||
bool a_isSprinting,
|
||||
bool a_isSneaking,
|
||||
bool a_isJumping,
|
||||
bool a_weaponDrawn,
|
||||
float a_movementSpeed,
|
||||
std::chrono::steady_clock::time_point a_currentTime);
|
||||
}
|
||||
@@ -0,0 +1,804 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "F4TLocalAnimationGraphDebug.h"
|
||||
|
||||
#include "F4TNetworking.h"
|
||||
|
||||
#include "RE/A/ACTOR_STANCE.h"
|
||||
#include "RE/B/BGSAnimationSystemUtils.h"
|
||||
#include "RE/B/BSFixedString.h"
|
||||
#include "RE/I/IAnimationGraphManagerHolder.h"
|
||||
#include "RE/P/PlayerCharacter.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr bool kEnableLocalPlayerAnimationGraphReadOnlyDebug = true;
|
||||
// Numeric graph cache ID probing caused a Fallout 4 crash during testing. Keep disabled by default.
|
||||
// Future investigation must isolate single IDs/types manually instead of scanning the full cache.
|
||||
constexpr bool kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug = false;
|
||||
constexpr bool kEnableProxyAnimationGraphReadOnlyDebug = false;
|
||||
|
||||
constexpr auto kCandidateGraphVariables = std::to_array<std::string_view>({
|
||||
"IsSneaking",
|
||||
"bIsSneaking",
|
||||
"IsCrouching",
|
||||
"bIsCrouching",
|
||||
"Sneaking",
|
||||
"bSneaking",
|
||||
"IsMoving",
|
||||
"bIsMoving",
|
||||
"Speed",
|
||||
"speed",
|
||||
"Direction",
|
||||
"fSpeed",
|
||||
"bSprint",
|
||||
"IsSprinting",
|
||||
"IsJumping",
|
||||
"bInJumpState",
|
||||
"WeaponDrawn",
|
||||
"bWeaponDrawn",
|
||||
});
|
||||
|
||||
struct LocalAnimationGraphState
|
||||
{
|
||||
bool isMoving{ false };
|
||||
bool isSprinting{ false };
|
||||
bool isSneaking{ false };
|
||||
bool isJumping{ false };
|
||||
bool weaponDrawn{ false };
|
||||
bool playerCharacterIsSneaking{ false };
|
||||
bool actorIsSneaking{ false };
|
||||
bool apiJumping{ false };
|
||||
bool apiSprinting{ false };
|
||||
bool apiWeaponDrawn{ false };
|
||||
bool actorStateForceSneak{ false };
|
||||
std::uint32_t actorStateStance{ 0 };
|
||||
std::int32_t weaponState{ 0 };
|
||||
std::int32_t gunState{ 0 };
|
||||
float movementSpeed{ 0.0F };
|
||||
bool graphManagerAvailable{ false };
|
||||
bool graphInTransition{ false };
|
||||
std::uint32_t graphVariableCacheSize{ 0 };
|
||||
};
|
||||
|
||||
struct CandidateReadState
|
||||
{
|
||||
bool boolUnavailableLogged{ false };
|
||||
bool intUnavailableLogged{ false };
|
||||
bool floatUnavailableLogged{ false };
|
||||
|
||||
bool hasBoolValue{ false };
|
||||
bool lastBoolValue{ false };
|
||||
|
||||
bool hasIntValue{ false };
|
||||
std::int32_t lastIntValue{ 0 };
|
||||
|
||||
bool hasFloatValue{ false };
|
||||
float lastFloatValue{ 0.0F };
|
||||
};
|
||||
|
||||
struct CacheIdReadState
|
||||
{
|
||||
bool boolUnavailableObserved{ false };
|
||||
bool hasBoolValue{ false };
|
||||
bool lastBoolValue{ false };
|
||||
|
||||
bool intUnavailableObserved{ false };
|
||||
bool hasIntValue{ false };
|
||||
std::int32_t lastIntValue{ 0 };
|
||||
|
||||
bool floatUnavailableObserved{ false };
|
||||
bool hasFloatValue{ false };
|
||||
float lastFloatValue{ 0.0F };
|
||||
};
|
||||
|
||||
struct CacheIdDebugState
|
||||
{
|
||||
bool hasCacheSize{ false };
|
||||
std::uint32_t cacheSize{ 0 };
|
||||
std::vector<CacheIdReadState> idStates{};
|
||||
std::vector<std::uint32_t> candidateSneakBoolIds{};
|
||||
std::vector<std::uint32_t> candidateSneakIntIds{};
|
||||
std::vector<std::uint32_t> candidateMovementFloatIds{};
|
||||
std::vector<std::uint32_t> candidateWeaponBoolIds{};
|
||||
std::vector<std::uint32_t> candidateWeaponIntIds{};
|
||||
std::vector<std::uint32_t> candidateJumpBoolIds{};
|
||||
std::vector<std::uint32_t> candidateJumpIntIds{};
|
||||
std::vector<std::uint32_t> candidateSprintBoolIds{};
|
||||
std::vector<std::uint32_t> candidateSprintIntIds{};
|
||||
};
|
||||
|
||||
struct CacheIdProbeUpdate
|
||||
{
|
||||
std::vector<std::uint32_t> changedBoolIds{};
|
||||
std::vector<std::uint32_t> changedIntIds{};
|
||||
std::vector<std::uint32_t> changedFloatIds{};
|
||||
std::uint32_t readableBoolIds{ 0 };
|
||||
std::uint32_t readableIntIds{ 0 };
|
||||
std::uint32_t readableFloatIds{ 0 };
|
||||
bool candidateSummaryChanged{ false };
|
||||
};
|
||||
|
||||
std::string GetLocalPlayerLogMessage(std::string_view a_message)
|
||||
{
|
||||
return F4T::Networking::GetLocalPlayerLogPrefix() + " " + std::string(a_message);
|
||||
}
|
||||
|
||||
void LogInfoWithLocalPlayerPrefix(std::string_view a_message)
|
||||
{
|
||||
const auto message = GetLocalPlayerLogMessage(a_message);
|
||||
REX::INFO(std::string_view{ message });
|
||||
}
|
||||
|
||||
bool AreFloatsDifferent(float a_lhs, float a_rhs)
|
||||
{
|
||||
if (!std::isfinite(a_lhs) || !std::isfinite(a_rhs)) {
|
||||
return a_lhs != a_rhs;
|
||||
}
|
||||
|
||||
return std::fabs(a_lhs - a_rhs) > 0.001F;
|
||||
}
|
||||
|
||||
std::string FormatIdList(const std::vector<std::uint32_t>& a_ids)
|
||||
{
|
||||
std::string result{ "[" };
|
||||
for (std::size_t index = 0; index < a_ids.size(); ++index) {
|
||||
if (index != 0) {
|
||||
result += ", ";
|
||||
}
|
||||
result += std::format("{}", a_ids[index]);
|
||||
}
|
||||
result += "]";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FormatLocalStateContext(const LocalAnimationGraphState& a_state)
|
||||
{
|
||||
return std::format(
|
||||
"moving={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, movementSpeed={:.1f}, actorStateStance={}, actorStateForceSneak={}",
|
||||
a_state.isMoving,
|
||||
a_state.isSprinting,
|
||||
a_state.isSneaking,
|
||||
a_state.isJumping,
|
||||
a_state.weaponDrawn,
|
||||
a_state.movementSpeed,
|
||||
a_state.actorStateStance,
|
||||
a_state.actorStateForceSneak);
|
||||
}
|
||||
|
||||
bool AddUniqueId(std::vector<std::uint32_t>& a_ids, std::uint32_t a_id)
|
||||
{
|
||||
for (const auto existingId : a_ids) {
|
||||
if (existingId == a_id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
a_ids.push_back(a_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MatchesStateBool(bool a_value, bool a_stateValue)
|
||||
{
|
||||
return a_value == a_stateValue;
|
||||
}
|
||||
|
||||
bool MatchesStateInt(std::int32_t a_value, bool a_stateValue)
|
||||
{
|
||||
if (a_value != 0 && a_value != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (a_value != 0) == a_stateValue;
|
||||
}
|
||||
|
||||
bool HasRelevantStateChange(
|
||||
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||
const LocalAnimationGraphState& a_currentState)
|
||||
{
|
||||
if (!a_lastState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& lastState = *a_lastState;
|
||||
return lastState.isMoving != a_currentState.isMoving ||
|
||||
lastState.isSprinting != a_currentState.isSprinting ||
|
||||
lastState.isSneaking != a_currentState.isSneaking ||
|
||||
lastState.isJumping != a_currentState.isJumping ||
|
||||
lastState.weaponDrawn != a_currentState.weaponDrawn ||
|
||||
lastState.playerCharacterIsSneaking != a_currentState.playerCharacterIsSneaking ||
|
||||
lastState.actorIsSneaking != a_currentState.actorIsSneaking ||
|
||||
lastState.apiJumping != a_currentState.apiJumping ||
|
||||
lastState.apiSprinting != a_currentState.apiSprinting ||
|
||||
lastState.apiWeaponDrawn != a_currentState.apiWeaponDrawn ||
|
||||
lastState.actorStateForceSneak != a_currentState.actorStateForceSneak ||
|
||||
lastState.actorStateStance != a_currentState.actorStateStance ||
|
||||
lastState.weaponState != a_currentState.weaponState ||
|
||||
lastState.gunState != a_currentState.gunState;
|
||||
}
|
||||
|
||||
bool HasGraphMetadataChange(
|
||||
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||
const LocalAnimationGraphState& a_currentState)
|
||||
{
|
||||
if (!a_lastState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& lastState = *a_lastState;
|
||||
return lastState.graphManagerAvailable != a_currentState.graphManagerAvailable ||
|
||||
lastState.graphInTransition != a_currentState.graphInTransition ||
|
||||
lastState.graphVariableCacheSize != a_currentState.graphVariableCacheSize;
|
||||
}
|
||||
|
||||
bool HasSneakTransition(
|
||||
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||
const LocalAnimationGraphState& a_currentState)
|
||||
{
|
||||
return a_lastState && a_lastState->isSneaking != a_currentState.isSneaking;
|
||||
}
|
||||
|
||||
bool HasMovementTransition(
|
||||
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||
const LocalAnimationGraphState& a_currentState)
|
||||
{
|
||||
if (!a_lastState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a_lastState->isMoving != a_currentState.isMoving ||
|
||||
AreFloatsDifferent(a_lastState->movementSpeed, a_currentState.movementSpeed);
|
||||
}
|
||||
|
||||
bool HasJumpTransition(
|
||||
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||
const LocalAnimationGraphState& a_currentState)
|
||||
{
|
||||
return a_lastState && a_lastState->isJumping != a_currentState.isJumping;
|
||||
}
|
||||
|
||||
bool HasWeaponTransition(
|
||||
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||
const LocalAnimationGraphState& a_currentState)
|
||||
{
|
||||
return a_lastState && a_lastState->weaponDrawn != a_currentState.weaponDrawn;
|
||||
}
|
||||
|
||||
bool HasSprintTransition(
|
||||
const std::optional<LocalAnimationGraphState>& a_lastState,
|
||||
const LocalAnimationGraphState& a_currentState)
|
||||
{
|
||||
return a_lastState && a_lastState->isSprinting != a_currentState.isSprinting;
|
||||
}
|
||||
|
||||
void LogStateSnapshot(
|
||||
std::string_view a_reason,
|
||||
const LocalAnimationGraphState& a_state)
|
||||
{
|
||||
const auto actorStateSneaking =
|
||||
a_state.actorStateStance == static_cast<std::uint32_t>(RE::ACTOR_STANCE::kSneaking);
|
||||
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph debug: {} moving={}, sprinting={}, sneaking={}, jumping={}, weaponDrawn={}, movementSpeed={:.1f}, playerCharacterIsSneaking={}, actorIsSneaking={}, actorStateSneaking={}, actorStateStance={}, actorStateForceSneak={}, apiJumping={}, apiSprinting={}, apiWeaponDrawn={}, weaponState={}, gunState={}, graphManagerAvailable={}, graphVariableCacheSize={}, graphInTransition={}.",
|
||||
a_reason,
|
||||
a_state.isMoving,
|
||||
a_state.isSprinting,
|
||||
a_state.isSneaking,
|
||||
a_state.isJumping,
|
||||
a_state.weaponDrawn,
|
||||
a_state.movementSpeed,
|
||||
a_state.playerCharacterIsSneaking,
|
||||
a_state.actorIsSneaking,
|
||||
actorStateSneaking,
|
||||
a_state.actorStateStance,
|
||||
a_state.actorStateForceSneak,
|
||||
a_state.apiJumping,
|
||||
a_state.apiSprinting,
|
||||
a_state.apiWeaponDrawn,
|
||||
a_state.weaponState,
|
||||
a_state.gunState,
|
||||
a_state.graphManagerAvailable,
|
||||
a_state.graphVariableCacheSize,
|
||||
a_state.graphInTransition));
|
||||
}
|
||||
|
||||
void ProbeBoolCandidate(
|
||||
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||
std::string_view a_candidate,
|
||||
CandidateReadState& a_state,
|
||||
bool a_shouldLogSuccessfulValues)
|
||||
{
|
||||
const RE::BSFixedString variableName{ a_candidate.data() };
|
||||
bool value = false;
|
||||
if (!a_holder.GetGraphVariableImplBool(variableName, value)) {
|
||||
if (!a_state.boolUnavailableLogged) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph debug: candidate bool variable \"{}\" unavailable.",
|
||||
a_candidate));
|
||||
a_state.boolUnavailableLogged = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto changed = !a_state.hasBoolValue || a_state.lastBoolValue != value;
|
||||
if (changed || a_shouldLogSuccessfulValues) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph debug: candidate bool variable \"{}\"={}.",
|
||||
a_candidate,
|
||||
value));
|
||||
}
|
||||
|
||||
a_state.hasBoolValue = true;
|
||||
a_state.lastBoolValue = value;
|
||||
}
|
||||
|
||||
void ProbeIntCandidate(
|
||||
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||
std::string_view a_candidate,
|
||||
CandidateReadState& a_state,
|
||||
bool a_shouldLogSuccessfulValues)
|
||||
{
|
||||
const RE::BSFixedString variableName{ a_candidate.data() };
|
||||
std::int32_t value = 0;
|
||||
if (!a_holder.GetGraphVariableImplInt(variableName, value)) {
|
||||
if (!a_state.intUnavailableLogged) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph debug: candidate int variable \"{}\" unavailable.",
|
||||
a_candidate));
|
||||
a_state.intUnavailableLogged = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto changed = !a_state.hasIntValue || a_state.lastIntValue != value;
|
||||
if (changed || a_shouldLogSuccessfulValues) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph debug: candidate int variable \"{}\"={}.",
|
||||
a_candidate,
|
||||
value));
|
||||
}
|
||||
|
||||
a_state.hasIntValue = true;
|
||||
a_state.lastIntValue = value;
|
||||
}
|
||||
|
||||
void ProbeFloatCandidate(
|
||||
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||
std::string_view a_candidate,
|
||||
CandidateReadState& a_state,
|
||||
bool a_shouldLogSuccessfulValues)
|
||||
{
|
||||
const RE::BSFixedString variableName{ a_candidate.data() };
|
||||
float value = 0.0F;
|
||||
if (!a_holder.GetGraphVariableImplFloat(variableName, value)) {
|
||||
if (!a_state.floatUnavailableLogged) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph debug: candidate float variable \"{}\" unavailable.",
|
||||
a_candidate));
|
||||
a_state.floatUnavailableLogged = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto changed = !a_state.hasFloatValue || AreFloatsDifferent(a_state.lastFloatValue, value);
|
||||
if (changed || a_shouldLogSuccessfulValues) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph debug: candidate float variable \"{}\"={:.3f}.",
|
||||
a_candidate,
|
||||
value));
|
||||
}
|
||||
|
||||
a_state.hasFloatValue = true;
|
||||
a_state.lastFloatValue = value;
|
||||
}
|
||||
|
||||
void ProbeCandidateGraphVariables(
|
||||
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||
bool a_shouldLogSuccessfulValues)
|
||||
{
|
||||
static std::array<CandidateReadState, kCandidateGraphVariables.size()> candidateStates{};
|
||||
|
||||
for (std::size_t index = 0; index < kCandidateGraphVariables.size(); ++index) {
|
||||
const auto candidate = kCandidateGraphVariables[index];
|
||||
auto& state = candidateStates[index];
|
||||
|
||||
ProbeBoolCandidate(a_holder, candidate, state, a_shouldLogSuccessfulValues);
|
||||
ProbeIntCandidate(a_holder, candidate, state, a_shouldLogSuccessfulValues);
|
||||
ProbeFloatCandidate(a_holder, candidate, state, a_shouldLogSuccessfulValues);
|
||||
}
|
||||
}
|
||||
|
||||
void ResetCacheIdDebugState(CacheIdDebugState& a_debugState, std::uint32_t a_cacheSize)
|
||||
{
|
||||
a_debugState.hasCacheSize = true;
|
||||
a_debugState.cacheSize = a_cacheSize;
|
||||
a_debugState.idStates.clear();
|
||||
a_debugState.idStates.resize(a_cacheSize);
|
||||
a_debugState.candidateSneakBoolIds.clear();
|
||||
a_debugState.candidateSneakIntIds.clear();
|
||||
a_debugState.candidateMovementFloatIds.clear();
|
||||
a_debugState.candidateWeaponBoolIds.clear();
|
||||
a_debugState.candidateWeaponIntIds.clear();
|
||||
a_debugState.candidateJumpBoolIds.clear();
|
||||
a_debugState.candidateJumpIntIds.clear();
|
||||
a_debugState.candidateSprintBoolIds.clear();
|
||||
a_debugState.candidateSprintIntIds.clear();
|
||||
}
|
||||
|
||||
void MaybeLogBoolCandidate(
|
||||
CacheIdDebugState& a_debugState,
|
||||
CacheIdProbeUpdate& a_update,
|
||||
std::uint32_t a_id,
|
||||
bool a_value,
|
||||
const LocalAnimationGraphState& a_currentState,
|
||||
bool a_sneakTransition,
|
||||
bool a_weaponTransition,
|
||||
bool a_jumpTransition,
|
||||
bool a_sprintTransition)
|
||||
{
|
||||
if (a_sneakTransition && MatchesStateBool(a_value, a_currentState.isSneaking) &&
|
||||
AddUniqueId(a_debugState.candidateSneakBoolIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: candidate sneak bool id {} mirrors local sneaking transition.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
|
||||
if (a_weaponTransition && MatchesStateBool(a_value, a_currentState.weaponDrawn) &&
|
||||
AddUniqueId(a_debugState.candidateWeaponBoolIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: candidate weapon bool id {} mirrors local weapon drawn transition.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
|
||||
if (a_jumpTransition && MatchesStateBool(a_value, a_currentState.isJumping) &&
|
||||
AddUniqueId(a_debugState.candidateJumpBoolIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: candidate jump bool id {} mirrors local jump transition.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
|
||||
if (a_sprintTransition && MatchesStateBool(a_value, a_currentState.isSprinting) &&
|
||||
AddUniqueId(a_debugState.candidateSprintBoolIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: candidate sprint bool id {} mirrors local sprint transition.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MaybeLogIntCandidate(
|
||||
CacheIdDebugState& a_debugState,
|
||||
CacheIdProbeUpdate& a_update,
|
||||
std::uint32_t a_id,
|
||||
std::int32_t a_value,
|
||||
const LocalAnimationGraphState& a_currentState,
|
||||
bool a_sneakTransition,
|
||||
bool a_weaponTransition,
|
||||
bool a_jumpTransition,
|
||||
bool a_sprintTransition)
|
||||
{
|
||||
if (a_sneakTransition && MatchesStateInt(a_value, a_currentState.isSneaking) &&
|
||||
AddUniqueId(a_debugState.candidateSneakIntIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: candidate sneak int id {} mirrors local sneaking transition.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
|
||||
if (a_weaponTransition && MatchesStateInt(a_value, a_currentState.weaponDrawn) &&
|
||||
AddUniqueId(a_debugState.candidateWeaponIntIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: candidate weapon int id {} mirrors local weapon drawn transition.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
|
||||
if (a_jumpTransition && MatchesStateInt(a_value, a_currentState.isJumping) &&
|
||||
AddUniqueId(a_debugState.candidateJumpIntIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: candidate jump int id {} mirrors local jump transition.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
|
||||
if (a_sprintTransition && MatchesStateInt(a_value, a_currentState.isSprinting) &&
|
||||
AddUniqueId(a_debugState.candidateSprintIntIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: candidate sprint int id {} mirrors local sprint transition.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MaybeLogFloatCandidate(
|
||||
CacheIdDebugState& a_debugState,
|
||||
CacheIdProbeUpdate& a_update,
|
||||
std::uint32_t a_id,
|
||||
bool a_movementTransition)
|
||||
{
|
||||
if (a_movementTransition && AddUniqueId(a_debugState.candidateMovementFloatIds, a_id)) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: float id {} appears movement-speed-related.",
|
||||
a_id));
|
||||
a_update.candidateSummaryChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void LogTransitionChangedIds(
|
||||
std::string_view a_transition,
|
||||
const CacheIdProbeUpdate& a_update,
|
||||
const LocalAnimationGraphState& a_currentState)
|
||||
{
|
||||
if (a_update.changedBoolIds.empty() && a_update.changedIntIds.empty() && a_update.changedFloatIds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: {} transition changed boolIds={}, intIds={}, floatIds={} with {}.",
|
||||
a_transition,
|
||||
FormatIdList(a_update.changedBoolIds),
|
||||
FormatIdList(a_update.changedIntIds),
|
||||
FormatIdList(a_update.changedFloatIds),
|
||||
FormatLocalStateContext(a_currentState)));
|
||||
}
|
||||
|
||||
void LogCandidateSummary(const CacheIdDebugState& a_debugState)
|
||||
{
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug summary: candidateSneakBoolIds={}, candidateSneakIntIds={}, candidateMovementFloatIds={}, candidateWeaponBoolIds={}, candidateWeaponIntIds={}, candidateJumpBoolIds={}, candidateJumpIntIds={}, candidateSprintBoolIds={}, candidateSprintIntIds={}.",
|
||||
FormatIdList(a_debugState.candidateSneakBoolIds),
|
||||
FormatIdList(a_debugState.candidateSneakIntIds),
|
||||
FormatIdList(a_debugState.candidateMovementFloatIds),
|
||||
FormatIdList(a_debugState.candidateWeaponBoolIds),
|
||||
FormatIdList(a_debugState.candidateWeaponIntIds),
|
||||
FormatIdList(a_debugState.candidateJumpBoolIds),
|
||||
FormatIdList(a_debugState.candidateJumpIntIds),
|
||||
FormatIdList(a_debugState.candidateSprintBoolIds),
|
||||
FormatIdList(a_debugState.candidateSprintIntIds)));
|
||||
}
|
||||
|
||||
void ProbeCacheIdGraphVariables(
|
||||
const RE::IAnimationGraphManagerHolder& a_holder,
|
||||
const LocalAnimationGraphState& a_currentState,
|
||||
const std::optional<LocalAnimationGraphState>& a_lastState)
|
||||
{
|
||||
if constexpr (!kEnableLocalPlayerAnimationGraphCacheIdReadOnlyDebug) {
|
||||
return;
|
||||
}
|
||||
|
||||
static CacheIdDebugState debugState{};
|
||||
|
||||
const auto cacheSize = a_currentState.graphVariableCacheSize;
|
||||
const auto cacheSizeChanged = !debugState.hasCacheSize || debugState.cacheSize != cacheSize;
|
||||
if (cacheSizeChanged) {
|
||||
ResetCacheIdDebugState(debugState, cacheSize);
|
||||
}
|
||||
|
||||
CacheIdProbeUpdate update{};
|
||||
const auto stateContext = FormatLocalStateContext(a_currentState);
|
||||
const auto sneakTransition = HasSneakTransition(a_lastState, a_currentState);
|
||||
const auto movementTransition = HasMovementTransition(a_lastState, a_currentState);
|
||||
const auto jumpTransition = HasJumpTransition(a_lastState, a_currentState);
|
||||
const auto weaponTransition = HasWeaponTransition(a_lastState, a_currentState);
|
||||
const auto sprintTransition = HasSprintTransition(a_lastState, a_currentState);
|
||||
|
||||
for (std::uint32_t id = 0; id < cacheSize; ++id) {
|
||||
auto& idState = debugState.idStates[id];
|
||||
|
||||
bool boolValue = false;
|
||||
if (a_holder.GetGraphVariableImpl(id, boolValue)) {
|
||||
++update.readableBoolIds;
|
||||
const auto changed = !idState.hasBoolValue || idState.lastBoolValue != boolValue;
|
||||
if (changed) {
|
||||
if (idState.hasBoolValue) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: bool id {} changed {} -> {} with {}.",
|
||||
id,
|
||||
idState.lastBoolValue,
|
||||
boolValue,
|
||||
stateContext));
|
||||
} else {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: bool id {} initial {} with {}.",
|
||||
id,
|
||||
boolValue,
|
||||
stateContext));
|
||||
}
|
||||
|
||||
update.changedBoolIds.push_back(id);
|
||||
MaybeLogBoolCandidate(
|
||||
debugState,
|
||||
update,
|
||||
id,
|
||||
boolValue,
|
||||
a_currentState,
|
||||
sneakTransition,
|
||||
weaponTransition,
|
||||
jumpTransition,
|
||||
sprintTransition);
|
||||
}
|
||||
idState.hasBoolValue = true;
|
||||
idState.lastBoolValue = boolValue;
|
||||
} else {
|
||||
idState.boolUnavailableObserved = true;
|
||||
}
|
||||
|
||||
std::int32_t intValue = 0;
|
||||
if (a_holder.GetGraphVariableImpl(id, intValue)) {
|
||||
++update.readableIntIds;
|
||||
const auto changed = !idState.hasIntValue || idState.lastIntValue != intValue;
|
||||
if (changed) {
|
||||
if (idState.hasIntValue) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: int id {} changed {} -> {} with {}.",
|
||||
id,
|
||||
idState.lastIntValue,
|
||||
intValue,
|
||||
stateContext));
|
||||
} else {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: int id {} initial {} with {}.",
|
||||
id,
|
||||
intValue,
|
||||
stateContext));
|
||||
}
|
||||
|
||||
update.changedIntIds.push_back(id);
|
||||
MaybeLogIntCandidate(
|
||||
debugState,
|
||||
update,
|
||||
id,
|
||||
intValue,
|
||||
a_currentState,
|
||||
sneakTransition,
|
||||
weaponTransition,
|
||||
jumpTransition,
|
||||
sprintTransition);
|
||||
}
|
||||
idState.hasIntValue = true;
|
||||
idState.lastIntValue = intValue;
|
||||
} else {
|
||||
idState.intUnavailableObserved = true;
|
||||
}
|
||||
|
||||
float floatValue = 0.0F;
|
||||
if (a_holder.GetGraphVariableImpl(id, floatValue)) {
|
||||
++update.readableFloatIds;
|
||||
const auto changed = !idState.hasFloatValue || AreFloatsDifferent(idState.lastFloatValue, floatValue);
|
||||
if (changed) {
|
||||
if (idState.hasFloatValue) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: float id {} changed {:.3f} -> {:.3f} with {}.",
|
||||
id,
|
||||
idState.lastFloatValue,
|
||||
floatValue,
|
||||
stateContext));
|
||||
} else {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: float id {} initial {:.3f} with {}.",
|
||||
id,
|
||||
floatValue,
|
||||
stateContext));
|
||||
}
|
||||
|
||||
update.changedFloatIds.push_back(id);
|
||||
MaybeLogFloatCandidate(debugState, update, id, movementTransition);
|
||||
}
|
||||
idState.hasFloatValue = true;
|
||||
idState.lastFloatValue = floatValue;
|
||||
} else {
|
||||
idState.floatUnavailableObserved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheSizeChanged) {
|
||||
const auto allUnavailable =
|
||||
update.readableBoolIds == 0 && update.readableIntIds == 0 && update.readableFloatIds == 0;
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Local animation graph cache debug: cacheSize={}, readableBoolIds={}, readableIntIds={}, readableFloatIds={}{}.",
|
||||
cacheSize,
|
||||
update.readableBoolIds,
|
||||
update.readableIntIds,
|
||||
update.readableFloatIds,
|
||||
allUnavailable ? ", all numeric cache ID reads unavailable" : ""));
|
||||
}
|
||||
|
||||
if (sneakTransition) {
|
||||
LogTransitionChangedIds("sneak", update, a_currentState);
|
||||
}
|
||||
if (movementTransition) {
|
||||
LogTransitionChangedIds("movement", update, a_currentState);
|
||||
}
|
||||
if (jumpTransition) {
|
||||
LogTransitionChangedIds("jump", update, a_currentState);
|
||||
}
|
||||
if (weaponTransition) {
|
||||
LogTransitionChangedIds("weapon", update, a_currentState);
|
||||
}
|
||||
if (sprintTransition) {
|
||||
LogTransitionChangedIds("sprint", update, a_currentState);
|
||||
}
|
||||
if (update.candidateSummaryChanged) {
|
||||
LogCandidateSummary(debugState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace F4T::LocalAnimationGraphDebug
|
||||
{
|
||||
void UpdateLocalPlayerAnimationGraphDebug(
|
||||
RE::PlayerCharacter& a_player,
|
||||
bool a_isMoving,
|
||||
bool a_isSprinting,
|
||||
bool a_isSneaking,
|
||||
bool a_isJumping,
|
||||
bool a_weaponDrawn,
|
||||
float a_movementSpeed,
|
||||
std::chrono::steady_clock::time_point a_currentTime)
|
||||
{
|
||||
(void)a_currentTime;
|
||||
|
||||
if constexpr (!kEnableLocalPlayerAnimationGraphReadOnlyDebug) {
|
||||
return;
|
||||
}
|
||||
|
||||
static_assert(!kEnableProxyAnimationGraphReadOnlyDebug);
|
||||
static std::optional<LocalAnimationGraphState> lastState{};
|
||||
|
||||
auto& actor = static_cast<RE::Actor&>(a_player);
|
||||
auto* graphHolder = static_cast<RE::IAnimationGraphManagerHolder*>(&a_player);
|
||||
RE::BSTSmartPointer<RE::BSAnimationGraphManager> graphManager;
|
||||
|
||||
const auto graphManagerAvailable =
|
||||
graphHolder->GetAnimationGraphManagerImpl(graphManager) && static_cast<bool>(graphManager);
|
||||
const auto graphVariableCacheSize = graphHolder->GetGraphVariableCacheSize();
|
||||
const auto graphInTransition =
|
||||
RE::BGSAnimationSystemUtils::IsActiveGraphInTransition(static_cast<RE::TESObjectREFR*>(&a_player));
|
||||
|
||||
const LocalAnimationGraphState currentState{
|
||||
a_isMoving,
|
||||
a_isSprinting,
|
||||
a_isSneaking,
|
||||
a_isJumping,
|
||||
a_weaponDrawn,
|
||||
a_player.IsSneaking(),
|
||||
actor.IsSneaking(),
|
||||
a_player.IsJumping(),
|
||||
a_player.DoGetSprinting(),
|
||||
a_player.GetWeaponMagicDrawn(),
|
||||
a_player.forceSneak != 0,
|
||||
static_cast<std::uint32_t>(a_player.stance),
|
||||
static_cast<std::int32_t>(a_player.weaponState),
|
||||
static_cast<std::int32_t>(a_player.gunState),
|
||||
a_movementSpeed,
|
||||
graphManagerAvailable,
|
||||
graphInTransition,
|
||||
graphVariableCacheSize
|
||||
};
|
||||
|
||||
const auto relevantStateChanged = HasRelevantStateChange(lastState, currentState);
|
||||
const auto graphMetadataChanged = HasGraphMetadataChange(lastState, currentState);
|
||||
if (relevantStateChanged || graphMetadataChanged) {
|
||||
LogStateSnapshot(
|
||||
relevantStateChanged ? "state transition" : "graph metadata changed",
|
||||
currentState);
|
||||
}
|
||||
|
||||
ProbeCandidateGraphVariables(*graphHolder, relevantStateChanged || graphMetadataChanged);
|
||||
ProbeCacheIdGraphVariables(*graphHolder, currentState, lastState);
|
||||
|
||||
lastState = currentState;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "F4TNetworking.h"
|
||||
#include "F4TLocalAnimationGraphDebug.h"
|
||||
#include "F4TProxyActorController.h"
|
||||
|
||||
#include "RE/A/ACTOR_STANCE.h"
|
||||
@@ -399,6 +400,15 @@ namespace
|
||||
const auto currentTime = std::chrono::steady_clock::now();
|
||||
const auto currentMovementState =
|
||||
GetPlayerMovementState(*player, hasLastTransform ? &lastSampleTransform : nullptr, lastSampleTime, currentTransform, currentTime);
|
||||
F4T::LocalAnimationGraphDebug::UpdateLocalPlayerAnimationGraphDebug(
|
||||
*player,
|
||||
currentMovementState.isMoving,
|
||||
currentMovementState.isSprinting,
|
||||
currentMovementState.isSneaking,
|
||||
currentMovementState.isJumping,
|
||||
currentMovementState.weaponDrawn,
|
||||
currentMovementState.movementSpeed,
|
||||
currentTime);
|
||||
if (!hasLastTransform) {
|
||||
lastSentTransform = currentTransform;
|
||||
lastSampleTransform = currentTransform;
|
||||
|
||||
Reference in New Issue
Block a user