From 7136c929cea0ddf955ab69a8ff5a4394bedacea8 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:48:51 +1000 Subject: [PATCH] micro-opt NetworkedAnimator drawables sort before creating drawables --- source/base/StarAnimatedPartSet.cpp | 10 +- source/base/StarAnimatedPartSet.hpp | 109 ++++++++-------- source/game/StarNetworkedAnimator.cpp | 178 +++++++++++++++----------- source/game/StarNetworkedAnimator.hpp | 4 +- source/game/items/StarActiveItem.cpp | 2 +- 5 files changed, 169 insertions(+), 134 deletions(-) diff --git a/source/base/StarAnimatedPartSet.cpp b/source/base/StarAnimatedPartSet.cpp index 9133921..5ffc68f 100644 --- a/source/base/StarAnimatedPartSet.cpp +++ b/source/base/StarAnimatedPartSet.cpp @@ -106,7 +106,7 @@ StringList AnimatedPartSet::states(String const& stateTypeName) const { return m_stateTypes.get(stateTypeName).states.keys(); } -StringList AnimatedPartSet::parts() const { +StringList AnimatedPartSet::partNames() const { return m_parts.keys(); } @@ -148,6 +148,14 @@ AnimatedPartSet::ActivePartInformation const& AnimatedPartSet::activePart(String return part.activePart; } +StringMap const& AnimatedPartSet::constParts() const { + return m_parts; +} + +StringMap& AnimatedPartSet::parts() { + return m_parts; +} + void AnimatedPartSet::forEachActiveState(function callback) const { for (auto const& p : m_stateTypes) { const_cast(this)->freshenActiveState(const_cast(p.second)); diff --git a/source/base/StarAnimatedPartSet.hpp b/source/base/StarAnimatedPartSet.hpp index 8cc2ef3..a608924 100644 --- a/source/base/StarAnimatedPartSet.hpp +++ b/source/base/StarAnimatedPartSet.hpp @@ -56,59 +56,7 @@ public: JsonObject properties; }; - AnimatedPartSet(); - AnimatedPartSet(Json config); - - // Returns the available state types. - StringList stateTypes() const; - - // If a state type is disabled, no parts will match against it even - // if they have entries for that state type. - void setStateTypeEnabled(String const& stateTypeName, bool enabled); - void setEnabledStateTypes(StringList const& stateTypeNames); - bool stateTypeEnabled(String const& stateTypeName) const; - - // Returns the available states for the given state type. - StringList states(String const& stateTypeName) const; - - StringList parts() const; - - // Sets the active state for this state type. If the state is different than - // the previously set state, will start the new states animation off at the - // beginning. If alwaysStart is true, then starts the state animation off at - // the beginning even if no state change has occurred. Returns true if a - // state animation reset was done. - bool setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart = false); - - // Restart this given state type's timer off at the beginning. - void restartState(String const& stateTypeName); - - ActiveStateInformation const& activeState(String const& stateTypeName) const; - ActivePartInformation const& activePart(String const& partName) const; - - // Function will be given the name of each state type, and the - // ActiveStateInformation for the active state for that state type. - void forEachActiveState(function callback) const; - - // Function will be given the name of each part, and the - // ActivePartInformation for the active part. - void forEachActivePart(function callback) const; - - // Useful for serializing state changes. Since each set of states for a - // state type is ordered, it is possible to simply serialize and deserialize - // the state index for that state type. - size_t activeStateIndex(String const& stateTypeName) const; - bool setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart = false); - - // Animate each state type forward 'dt' time, and either change state frames - // or transition to new states, depending on the config. - void update(float dt); - - // Pushes all the animations into their final state - void finishAnimations(); - -private: - enum AnimationMode { + enum AnimationMode { End, Loop, Transition @@ -148,6 +96,61 @@ private: bool activePartDirty; }; + AnimatedPartSet(); + AnimatedPartSet(Json config); + + // Returns the available state types. + StringList stateTypes() const; + + // If a state type is disabled, no parts will match against it even + // if they have entries for that state type. + void setStateTypeEnabled(String const& stateTypeName, bool enabled); + void setEnabledStateTypes(StringList const& stateTypeNames); + bool stateTypeEnabled(String const& stateTypeName) const; + + // Returns the available states for the given state type. + StringList states(String const& stateTypeName) const; + + StringList partNames() const; + + // Sets the active state for this state type. If the state is different than + // the previously set state, will start the new states animation off at the + // beginning. If alwaysStart is true, then starts the state animation off at + // the beginning even if no state change has occurred. Returns true if a + // state animation reset was done. + bool setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart = false); + + // Restart this given state type's timer off at the beginning. + void restartState(String const& stateTypeName); + + ActiveStateInformation const& activeState(String const& stateTypeName) const; + ActivePartInformation const& activePart(String const& partName) const; + + StringMap const& constParts() const; + StringMap& parts(); + + // Function will be given the name of each state type, and the + // ActiveStateInformation for the active state for that state type. + void forEachActiveState(function callback) const; + + // Function will be given the name of each part, and the + // ActivePartInformation for the active part. + void forEachActivePart(function callback) const; + + // Useful for serializing state changes. Since each set of states for a + // state type is ordered, it is possible to simply serialize and deserialize + // the state index for that state type. + size_t activeStateIndex(String const& stateTypeName) const; + bool setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart = false); + + // Animate each state type forward 'dt' time, and either change state frames + // or transition to new states, depending on the config. + void update(float dt); + + // Pushes all the animations into their final state + void finishAnimations(); + +private: static AnimationMode stringToAnimationMode(String const& string); void freshenActiveState(StateType& stateType); diff --git a/source/game/StarNetworkedAnimator.cpp b/source/game/StarNetworkedAnimator.cpp index ccec4fd..a974a3a 100644 --- a/source/game/StarNetworkedAnimator.cpp +++ b/source/game/StarNetworkedAnimator.cpp @@ -307,10 +307,18 @@ String NetworkedAnimator::state(String const& stateType) const { return m_animatedParts.activeState(stateType).stateName; } -StringList NetworkedAnimator::parts() const { +StringMap const& NetworkedAnimator::constParts() const { + return m_animatedParts.constParts(); +} + +StringMap& NetworkedAnimator::parts() { return m_animatedParts.parts(); } +StringList NetworkedAnimator::partNames() const { + return m_animatedParts.partNames(); +} + Json NetworkedAnimator::stateProperty(String const& stateType, String const& propertyName) const { return m_animatedParts.activeState(stateType).properties.value(propertyName); } @@ -573,6 +581,10 @@ List NetworkedAnimator::drawables(Vec2F const& position) const { } List> NetworkedAnimator::drawablesWithZLevel(Vec2F const& position) const { + size_t partCount = m_animatedParts.constParts().size(); + if (!partCount) + return {}; + List baseProcessingDirectives = { m_processingDirectives.get() }; for (auto& pair : m_effects) { auto const& effectState = pair.second; @@ -591,93 +603,103 @@ List> NetworkedAnimator::drawablesWithZLevel(Vec2F const& } } - List> drawables; - + List> parts; + parts.reserve(partCount); m_animatedParts.forEachActivePart([&](String const& partName, AnimatedPartSet::ActivePartInformation const& activePart) { - // Make sure we don't copy the original image - String fallback = ""; - Json jImage = activePart.properties.value("image", {}); - String const& image = jImage.isType(Json::Type::String) ? *jImage.stringPtr() : fallback; + Maybe maybeZLevel; + if (m_flipped.get()) { + if (auto maybeFlipped = activePart.properties.value("flippedZLevel").optFloat()) + maybeZLevel = *maybeFlipped; + } + if (!maybeZLevel) + maybeZLevel = activePart.properties.value("zLevel").optFloat(); - bool centered = activePart.properties.value("centered").optBool().value(true); - bool fullbright = activePart.properties.value("fullbright").optBool().value(false); + parts.append(make_tuple(&activePart, &partName, maybeZLevel.value(0.0f))); + }); - auto maybeZLevel = activePart.properties.value("zLevel").optFloat(); - if (m_flipped.get()) - maybeZLevel = activePart.properties.value("flippedZLevel").optFloat().orMaybe(maybeZLevel); - float zLevel = maybeZLevel.value(0.0f); + sort(parts, [](auto const& a, auto const& b) { return get<2>(a) < get<2>(b); }); - size_t originalDirectivesSize = baseProcessingDirectives.size(); - if (auto directives = activePart.properties.value("processingDirectives").optString()) { + List> drawables; + drawables.reserve(partCount); + for (auto& entry : parts) { + auto& activePart = *get<0>(entry); + auto& partName = *get<1>(entry); + // Make sure we don't copy the original image + String fallback = ""; + Json jImage = activePart.properties.value("image", {}); + String const& image = jImage.isType(Json::Type::String) ? *jImage.stringPtr() : fallback; + + bool centered = activePart.properties.value("centered").optBool().value(true); + bool fullbright = activePart.properties.value("fullbright").optBool().value(false); + + size_t originalDirectivesSize = baseProcessingDirectives.size(); + if (auto directives = activePart.properties.value("processingDirectives").optString()) { + baseProcessingDirectives.append(*directives); + } + + Maybe frame; + String frameStr; + String frameIndexStr; + if (activePart.activeState) { + unsigned stateFrame = activePart.activeState->frame; + frame = stateFrame; + frameStr = static_cast(toString(stateFrame + 1)); + frameIndexStr = static_cast(toString(stateFrame)); + + if (auto directives = activePart.activeState->properties.value("processingDirectives").optString()) { baseProcessingDirectives.append(*directives); } + } - Maybe frame; - String frameStr; - String frameIndexStr; - if (activePart.activeState) { - unsigned stateFrame = activePart.activeState->frame; - frame = stateFrame; - frameStr = static_cast(toString(stateFrame + 1)); - frameIndexStr = static_cast(toString(stateFrame)); - - if (auto directives = activePart.activeState->properties.value("processingDirectives").optString()) { - baseProcessingDirectives.append(*directives); - } + auto const& partTags = m_partTags.get(partName); + Maybe processedImage = image.maybeLookupTagsView([&](StringView tag) -> StringView { + if (tag == "frame") { + if (frame) + return frameStr; + } else if (tag == "frameIndex") { + if (frame) + return frameIndexStr; + } else if (auto p = partTags.ptr(tag)) { + return StringView(*p); + } else if (auto p = m_globalTags.ptr(tag)) { + return StringView(*p); } - auto const& partTags = m_partTags.get(partName); - Maybe processedImage = image.maybeLookupTagsView([&](StringView tag) -> StringView { - if (tag == "frame") { - if (frame) - return frameStr; - } else if (tag == "frameIndex") { - if (frame) - return frameIndexStr; - } else if (auto p = partTags.ptr(tag)) { - return StringView(*p); - } else if (auto p = m_globalTags.ptr(tag)) { - return StringView(*p); - } - - return StringView("default"); - }); - String const& usedImage = processedImage ? processedImage.get() : image; - - if (!usedImage.empty() && usedImage[0] != ':' && usedImage[0] != '?') { - size_t hash = hashOf(usedImage); - auto find = m_cachedPartDrawables.find(partName); - bool fail = find == m_cachedPartDrawables.end() || find->second.first != hash; - if (fail) { - String relativeImage; - if (usedImage[0] != '/') - relativeImage = AssetPath::relativeTo(m_relativePath, usedImage); - - Drawable drawable = Drawable::makeImage(!relativeImage.empty() ? relativeImage : usedImage, 1.0f / TilePixels, centered, Vec2F()); - if (find == m_cachedPartDrawables.end()) - find = m_cachedPartDrawables.emplace(partName, std::pair{ hash, std::move(drawable) }).first; - else { - find->second.first = hash; - find->second.second = std::move(drawable); - } - } - - Drawable drawable = find->second.second; - auto& imagePart = drawable.imagePart(); - for (Directives const& directives : baseProcessingDirectives) - imagePart.addDirectives(directives, centered); - drawable.transform(partTransformation(partName)); - drawable.transform(globalTransformation()); - drawable.fullbright = fullbright; - drawable.translate(position); - - drawables.append({std::move(drawable), zLevel}); - } - - baseProcessingDirectives.resize(originalDirectivesSize); + return StringView("default"); }); + String const& usedImage = processedImage ? processedImage.get() : image; - sort(drawables, [](auto const& a, auto const& b) { return a.second < b.second; }); + if (!usedImage.empty() && usedImage[0] != ':' && usedImage[0] != '?') { + size_t hash = hashOf(usedImage); + auto find = m_cachedPartDrawables.find(partName); + if (find == m_cachedPartDrawables.end() || find->second.first != hash) { + String relativeImage; + if (usedImage[0] != '/') + relativeImage = AssetPath::relativeTo(m_relativePath, usedImage); + + Drawable drawable = Drawable::makeImage(!relativeImage.empty() ? relativeImage : usedImage, 1.0f / TilePixels, centered, Vec2F()); + if (find == m_cachedPartDrawables.end()) + find = m_cachedPartDrawables.emplace(partName, std::pair{ hash, std::move(drawable) }).first; + else { + find->second.first = hash; + find->second.second = std::move(drawable); + } + } + + Drawable drawable = find->second.second; + auto& imagePart = drawable.imagePart(); + for (Directives const& directives : baseProcessingDirectives) + imagePart.addDirectives(directives, centered); + drawable.transform(partTransformation(partName)); + drawable.transform(globalTransformation()); + drawable.fullbright = fullbright; + drawable.translate(position); + + drawables.append({std::move(drawable), get<2>(entry)}); + } + + baseProcessingDirectives.resize(originalDirectivesSize); + } return drawables; } @@ -981,7 +1003,7 @@ void NetworkedAnimator::setupNetStates() { addNetElement(&m_globalTags); - for (auto const& part : sorted(m_animatedParts.parts())) + for (auto const& part : sorted(m_animatedParts.partNames())) addNetElement(&m_partTags[part]); for (auto& pair : m_stateInfo) { diff --git a/source/game/StarNetworkedAnimator.hpp b/source/game/StarNetworkedAnimator.hpp index feee8bf..8265011 100644 --- a/source/game/StarNetworkedAnimator.hpp +++ b/source/game/StarNetworkedAnimator.hpp @@ -82,7 +82,9 @@ public: bool setState(String const& stateType, String const& state, bool startNew = false); String state(String const& stateType) const; - StringList parts() const; + StringMap const& constParts() const; + StringMap& parts(); + StringList partNames() const; // Queries, if it exists, a property value from the underlying // AnimatedPartSet for the given state or part. If the property does not diff --git a/source/game/items/StarActiveItem.cpp b/source/game/items/StarActiveItem.cpp index 102ad16..725ea27 100644 --- a/source/game/items/StarActiveItem.cpp +++ b/source/game/items/StarActiveItem.cpp @@ -251,7 +251,7 @@ Maybe ActiveItem::facingDirection() const { } List ActiveItem::handDrawables() const { - if (m_itemAnimator.parts().empty()) { + if (m_itemAnimator.constParts().empty()) { auto drawables = Item::iconDrawables(); Drawable::scaleAll(drawables, 1.0f / TilePixels); return drawables;