#include "ActionReplayCache.h" #include #include ActionReplayChain ActionReplayCache::FormRefinedReplayChain() noexcept { const bool shouldResetAnimGraph = RefineReplayCache(); ActionReplayChain chain; chain.ResetAnimationGraph = shouldResetAnimGraph; chain.Actions = (shouldResetAnimGraph && !m_actions.empty()) ? Vector{m_actions.begin() + 1, m_actions.end()} // Drop the exit action : m_actions; return chain; } void ActionReplayCache::AppendAll(const Vector& acActions) noexcept { for (const auto& action : acActions) { if (ShouldIgnoreAction(action)) continue; m_actions.push_back(action); } if (m_actions.size() > kReplayCacheMaxSize) m_actions.erase(m_actions.begin(), m_actions.end() - kReplayCacheMaxSize); } bool ActionReplayCache::RefineReplayCache() noexcept { int32_t dropAllUpToIndex = -1; for (int32_t i = m_actions.size() - 1; i >= 0; --i) { ActionEvent& action = m_actions[i]; // Instant counterparts of actions are highly preferred for animation replay if (auto instantAnimName = FindInstantCounterpartForAction(action.EventName)) { action.EventName = String{*instantAnimName}; action.TargetEventName = String{*instantAnimName}; action.ActionId = 0; action.IdleId = 0; } if (IsExitAction(action)) { // An "exit" action is found, meaning the actor has likely returned to its root/normal // state, and the clients should reset the animation state before running replay dropAllUpToIndex = i; break; } } if (dropAllUpToIndex == -1) return false; m_actions.erase(m_actions.begin(), m_actions.begin() + dropAllUpToIndex); return true; } bool ActionReplayCache::IsExitAction(const ActionEvent& acAction) noexcept { if (AnimationEventLists::kExitSpecial.contains(acAction.EventName)) return true; // Not found in special exit actions, let's check the suffix const String& originalName = acAction.EventName; String lowercaseName{acAction.EventName}; std::transform(originalName.begin(), originalName.end(), lowercaseName.begin(), [](unsigned char c) { return std::tolower(c); }); return lowercaseName.starts_with("idle") && (lowercaseName.ends_with("exit") || lowercaseName.ends_with("exitstart")); } bool ActionReplayCache::ShouldIgnoreAction(const ActionEvent& acAction) noexcept { return AnimationEventLists::kIgnore.contains(acAction.EventName); } std::optional ActionReplayCache::FindInstantCounterpartForAction(std::string_view aAction) noexcept { auto it = AnimationEventLists::kIdleToInstant.find(aAction); if (it == AnimationEventLists::kIdleToInstant.end()) return std::nullopt; auto& [_, instantAnimationName] = *it; if (!instantAnimationName.empty()) { return {instantAnimationName}; } return std::nullopt; } void ActionReplayCache::Clear() noexcept { m_actions.clear(); }