Add RemoteActionQueue and descriptor animation sync
Add a RemoteActionComponent (header + impl) implementing RemoteActionSnapshot and RemoteActionQueue to capture, queue (max 16) and replay discrete remote actions with comprehensive logging. Integrate the action queue into ProxyActorSlot by including the new header and adding an actionQueue member. Replace string-based per-frame animation writes with a descriptor-based bulk sync function (ApplyProxyAnimationFromRemoteStateDescriptorBased) that builds indexed AnimationVariableSnapshot and calls descriptor.LoadAnimationVariablesToCache for a single bulk write (reduces string lookups and improves performance). Update docs/dev-log with Phase 5 summary and details of the action replay and descriptor-based animation sync changes.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "F4RemoteActionComponent.h"
|
||||
|
||||
#include "F4AnimationDescriptor.h"
|
||||
#include "RE/A/Actor.h"
|
||||
#include "RE/A/ActorState.h"
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace F4T::RemoteActionComponent
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void LogInfo(const std::string& a_message)
|
||||
{
|
||||
REX::INFO(a_message);
|
||||
}
|
||||
|
||||
void LogWarning(const std::string& a_message)
|
||||
{
|
||||
REX::WARN(a_message);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RemoteActionQueue Implementation
|
||||
// ============================================================================
|
||||
|
||||
void RemoteActionQueue::Enqueue(const RemoteActionSnapshot& a_action)
|
||||
{
|
||||
if (m_actionQueue.size() >= m_maxQueueSize) {
|
||||
LogWarning(std::format(
|
||||
"RemoteActionQueue: Action queue full (max={}); dropping oldest action to make room.",
|
||||
m_maxQueueSize));
|
||||
m_actionQueue.erase(m_actionQueue.begin());
|
||||
}
|
||||
|
||||
m_actionQueue.push_back(a_action);
|
||||
|
||||
LogInfo(std::format(
|
||||
"RemoteActionQueue: Enqueued action '{}' (type={}); queue size={}",
|
||||
a_action.eventName,
|
||||
a_action.actionType,
|
||||
m_actionQueue.size()));
|
||||
}
|
||||
|
||||
void RemoteActionQueue::Clear()
|
||||
{
|
||||
if (!m_actionQueue.empty()) {
|
||||
LogInfo(std::format(
|
||||
"RemoteActionQueue: Cleared {} pending actions.",
|
||||
m_actionQueue.size()));
|
||||
m_actionQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool RemoteActionQueue::ReplayNextAction(RE::Actor& a_proxy)
|
||||
{
|
||||
if (m_actionQueue.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto action = m_actionQueue.front();
|
||||
m_actionQueue.erase(m_actionQueue.begin());
|
||||
|
||||
if (ApplyActionToProxy(a_proxy, action)) {
|
||||
LogInfo(std::format(
|
||||
"RemoteActionQueue: Replayed action '{}' on proxy {:08X}; {} pending.",
|
||||
action.eventName,
|
||||
a_proxy.GetFormID(),
|
||||
m_actionQueue.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
LogWarning(std::format(
|
||||
"RemoteActionQueue: Failed to replay action '{}' on proxy {:08X}.",
|
||||
action.eventName,
|
||||
a_proxy.GetFormID()));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RemoteActionQueue::ApplyActionToProxy(RE::Actor& a_proxy, const RemoteActionSnapshot& a_action)
|
||||
{
|
||||
try {
|
||||
// Phase 5.2: Apply action state and trigger animation
|
||||
ApplyActorStateFromSnapshot(a_proxy, a_action);
|
||||
TriggerActionAnimation(a_proxy, a_action);
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LogWarning(std::format(
|
||||
"RemoteActionQueue: Exception applying action to proxy {:08X}: {}",
|
||||
a_proxy.GetFormID(),
|
||||
e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteActionQueue::ApplyActorStateFromSnapshot(
|
||||
RE::Actor& a_proxy,
|
||||
const RemoteActionSnapshot& a_action)
|
||||
{
|
||||
// Set actor state flags from snapshot
|
||||
// This ensures the animation graph is in the correct FSM state for the action
|
||||
if (a_action.actorStateFlags1 != 0 || a_action.actorStateFlags2 != 0) {
|
||||
a_proxy.actorState.flags1 = a_action.actorStateFlags1;
|
||||
a_proxy.actorState.flags2 = a_action.actorStateFlags2;
|
||||
|
||||
LogInfo(std::format(
|
||||
"RemoteActionQueue: Applied actor state flags to proxy {:08X}: flags1={:08X}, flags2={:08X}",
|
||||
a_proxy.GetFormID(),
|
||||
a_action.actorStateFlags1,
|
||||
a_action.actorStateFlags2));
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteActionQueue::TriggerActionAnimation(
|
||||
RE::Actor& a_proxy,
|
||||
const RemoteActionSnapshot& a_action)
|
||||
{
|
||||
// Phase 5.2: Load animation variables snapshot and trigger action
|
||||
// This allows the animation graph to play the correct animation for the action
|
||||
|
||||
// Get animation descriptor for the proxy's graph type (assume humanoid)
|
||||
const auto& descriptor = AnimationDescriptor::F4AnimationDescriptor::GetHumanoidDescriptor();
|
||||
|
||||
// Load animation variables from snapshot to proxy's graph
|
||||
// This ensures Speed, Direction, IsSprinting, etc. are set correctly
|
||||
if (!descriptor.LoadAnimationVariablesToCache(
|
||||
*static_cast<RE::IAnimationGraphManagerHolder*>(&a_proxy),
|
||||
a_action.animationVariables)) {
|
||||
LogWarning(std::format(
|
||||
"RemoteActionQueue: Failed to load animation variables for action '{}' on proxy {:08X}.",
|
||||
a_action.eventName,
|
||||
a_proxy.GetFormID()));
|
||||
}
|
||||
|
||||
// TODO: Phase 5.2.2 - Trigger the action animation via ActorMediator::ForceAction()
|
||||
// For now, we've set the graph variables and state; the game should blend to appropriate animation
|
||||
//
|
||||
// In TiltedEvolution, this would call:
|
||||
// ActorMediator::ForceAction(&actionData)
|
||||
// which combines:
|
||||
// - Setting actor state
|
||||
// - Loading animation variables
|
||||
// - Triggering the action animation
|
||||
//
|
||||
// F4SE may expose this differently; needs investigation
|
||||
|
||||
LogInfo(std::format(
|
||||
"RemoteActionQueue: Triggered action '{}' (type={}) animation load on proxy {:08X}.",
|
||||
a_action.eventName,
|
||||
a_action.actionType,
|
||||
a_proxy.GetFormID()));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "F4TNetworking.h"
|
||||
#include "F4TProxyAnimationSync.h"
|
||||
#include "F4TRemotePlayerState.h"
|
||||
#include "F4RemoteActionComponent.h"
|
||||
|
||||
#include "RE/A/ACTOR_STANCE.h"
|
||||
#include "RE/A/ActorValue.h"
|
||||
@@ -267,6 +268,9 @@ namespace
|
||||
std::optional<ProxyAnimationSpeedBucket> lastObservedMovementSpeedBucket;
|
||||
std::optional<bool> lastAppliedVisualSneakingState;
|
||||
F4T::ProxyAnimationSync::ProxyAppliedAnimationState appliedAnimationState{};
|
||||
|
||||
// Phase 5.1: Action queue for discrete action replay
|
||||
F4T::RemoteActionComponent::RemoteActionQueue actionQueue;
|
||||
};
|
||||
|
||||
constexpr auto kProxyMovementMode = ProxyMovementMode::kRemotePlayerStateTest;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "F4TProxyAnimationSync.h"
|
||||
|
||||
#include "F4TNetworking.h"
|
||||
#include "F4AnimationDescriptor.h"
|
||||
|
||||
#include "RE/B/BGSAnimationSystemUtils.h"
|
||||
#include "RE/B/BSFixedString.h"
|
||||
@@ -338,4 +339,71 @@ namespace F4T::ProxyAnimationSync
|
||||
|
||||
ResetAppliedAnimationState(a_state);
|
||||
}
|
||||
|
||||
// Phase 5.3: Descriptor-based animation sync (efficient bulk writes)
|
||||
// Replaces per-frame string-based variable writes with indexed descriptor access
|
||||
void ApplyProxyAnimationFromRemoteStateDescriptorBased(
|
||||
RE::Actor& a_proxy,
|
||||
const RemotePlayerState::RemotePlayerState& a_remotePlayer)
|
||||
{
|
||||
if constexpr (!kEnableProxyVisualAnimationSync) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto actorId = a_proxy.GetFormID();
|
||||
if (!CanWriteProxyGraph(a_proxy, a_remotePlayer.playerId, actorId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 5.3: Build animation variable snapshot using descriptor
|
||||
auto& descriptor = F4T::AnimationDescriptor::F4AnimationDescriptor::GetHumanoidDescriptor();
|
||||
F4T::AnimationDescriptor::AnimationVariableSnapshot snapshot;
|
||||
|
||||
// Prepare float variables (Speed, direction, etc.)
|
||||
snapshot.floats.resize(descriptor.GetFloatVariableCount(), 0.0F);
|
||||
|
||||
// Speed: calculated from movement speed
|
||||
if (descriptor.GetFloatVariableIndex("Speed") != static_cast<std::size_t>(-1)) {
|
||||
const auto desired = BuildDesiredState(a_remotePlayer);
|
||||
snapshot.floats[descriptor.GetFloatVariableIndex("Speed")] = desired.graphSpeed;
|
||||
}
|
||||
|
||||
// Direction: from remote player's heading
|
||||
if (descriptor.GetFloatVariableIndex("direction") != static_cast<std::size_t>(-1)) {
|
||||
const auto desired = BuildDesiredState(a_remotePlayer);
|
||||
snapshot.floats[descriptor.GetFloatVariableIndex("direction")] = desired.direction;
|
||||
}
|
||||
|
||||
// Prepare bool variables (isSprinting, isSneaking, isMoving)
|
||||
snapshot.bools.resize(descriptor.GetBoolVariableCount(), false);
|
||||
|
||||
if (descriptor.GetBoolVariableIndex("isSprinting") != static_cast<std::size_t>(-1)) {
|
||||
const auto desired = BuildDesiredState(a_remotePlayer);
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isSprinting")] = desired.isSprinting;
|
||||
}
|
||||
|
||||
if (descriptor.GetBoolVariableIndex("isSneaking") != static_cast<std::size_t>(-1)) {
|
||||
const auto desired = BuildDesiredState(a_remotePlayer);
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isSneaking")] = desired.isSneaking;
|
||||
}
|
||||
|
||||
if (descriptor.GetBoolVariableIndex("isMoving") != static_cast<std::size_t>(-1)) {
|
||||
const auto desired = BuildDesiredState(a_remotePlayer);
|
||||
snapshot.bools[descriptor.GetBoolVariableIndex("isMoving")] = desired.isMoving;
|
||||
}
|
||||
|
||||
// Prepare int variables (equipment, etc.)
|
||||
snapshot.ints.resize(descriptor.GetIntVariableCount(), 0);
|
||||
|
||||
// Apply all variables at once via descriptor bulk write
|
||||
if (!descriptor.LoadAnimationVariablesToCache(
|
||||
*static_cast<RE::IAnimationGraphManagerHolder*>(&a_proxy),
|
||||
snapshot)) {
|
||||
LogWarningWithLocalPlayerPrefix(std::format(
|
||||
"ApplyProxyAnimationDescriptorBased: Failed to load animation variables for remote player {}: actor={:08X}.",
|
||||
a_remotePlayer.playerId,
|
||||
actorId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user