fix(runtime): Don't early out when processing KeyedObjects with missing objects (#11856) 3eb4211852 Previously when a LinearAnimation ran onAddedDirty on all of its keyed objects, it would return early if any keyed objects returned a status other than OK (this could happen in cases where the keyed object itself doesn't validate, for example, when a constraint doesn't have a target, and that constraint has a keyed property on the timeline). This PR updates the flow to instead store those keyed objects and after all keyed objects are processed, it removes them from the list, so they will not be processed in future calls to apply(). Co-authored-by: Philip Chung <philterdesign@gmail.com>
diff --git a/.rive_head b/.rive_head index ad5d65c..d6e279c 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -3034940065986aa027b674b1a14fb3e530f7df9e +3eb4211852d4ca66c76c89fada97c9c226fff379
diff --git a/include/rive/animation/linear_animation.hpp b/include/rive/animation/linear_animation.hpp index 5550547..6d4d1dc 100644 --- a/include/rive/animation/linear_animation.hpp +++ b/include/rive/animation/linear_animation.hpp
@@ -43,6 +43,9 @@ /// work area start/end, speed, looping). float globalToLocalSeconds(float seconds) const; + // Returns a list of only the KeyedObjects that were validated during + // onAddedDirty. This is not guaranteed to be the same as the list in the + // exported riv. const KeyedObject* getObject(size_t index) const { if (index < m_KeyedObjects.size())
diff --git a/src/animation/linear_animation.cpp b/src/animation/linear_animation.cpp index 25dd73b..2af3576 100644 --- a/src/animation/linear_animation.cpp +++ b/src/animation/linear_animation.cpp
@@ -24,15 +24,30 @@ StatusCode LinearAnimation::onAddedDirty(CoreContext* context) { - StatusCode code; - for (const auto& object : m_KeyedObjects) + StatusCode status = StatusCode::Ok; + std::vector<size_t> failedKeyedObjects; + for (size_t i = 0; i < m_KeyedObjects.size(); ++i) { - if ((code = object->onAddedDirty(context)) != StatusCode::Ok) + StatusCode code = m_KeyedObjects[i]->onAddedDirty(context); + if (code != StatusCode::Ok) { - return code; + failedKeyedObjects.push_back(i); + if (status == StatusCode::Ok || status == StatusCode::MissingObject) + { + status = code; + } } } - return StatusCode::Ok; + + // Failed keyed objects should not be applied later in animation playback. + for (auto it = failedKeyedObjects.rbegin(); it != failedKeyedObjects.rend(); + ++it) + { + m_KeyedObjects.erase(m_KeyedObjects.begin() + *it); + } + + // Missing keyed objects are non-fatal at artboard init time. + return status == StatusCode::MissingObject ? StatusCode::Ok : status; } StatusCode LinearAnimation::onAddedClean(CoreContext* context)
diff --git a/tests/unit_tests/runtime/linear_animation_test.cpp b/tests/unit_tests/runtime/linear_animation_test.cpp index ee36df0..3fd0de4 100644 --- a/tests/unit_tests/runtime/linear_animation_test.cpp +++ b/tests/unit_tests/runtime/linear_animation_test.cpp
@@ -1,4 +1,5 @@ #include "rive/artboard.hpp" +#include "rive/animation/keyed_object.hpp" #include "rive/animation/linear_animation.hpp" #include "rive/animation/linear_animation_instance.hpp" #include "rive/animation/keyed_callback_reporter.hpp" @@ -8,6 +9,7 @@ #include "rive/shapes/shape.hpp" #include <catch.hpp> #include <cstdio> +#include <memory> TEST_CASE( "LinearAnimation with positive speed have normal start and end seconds", @@ -116,6 +118,43 @@ delete linearAnimation; } +class MockKeyedObject : public rive::KeyedObject +{ +public: + MockKeyedObject(rive::StatusCode status, int* callCount) : + m_status(status), m_callCount(callCount) + {} + + rive::StatusCode onAddedDirty(rive::CoreContext*) override + { + (*m_callCount)++; + return m_status; + } + +private: + rive::StatusCode m_status; + int* m_callCount; +}; + +TEST_CASE("LinearAnimation keeps initializing after missing keyed object", + "[animation]") +{ + rive::LinearAnimation animation; + int missingObjectCalls = 0; + int validObjectCalls = 0; + + animation.addKeyedObject(std::unique_ptr<rive::KeyedObject>( + new MockKeyedObject(rive::StatusCode::MissingObject, + &missingObjectCalls))); + animation.addKeyedObject(std::unique_ptr<rive::KeyedObject>( + new MockKeyedObject(rive::StatusCode::Ok, &validObjectCalls))); + + REQUIRE(animation.onAddedDirty(nullptr) == rive::StatusCode::Ok); + REQUIRE(missingObjectCalls == 1); + REQUIRE(validObjectCalls == 1); + REQUIRE(animation.numKeyedObjects() == 1); +} + class TestReporter : public rive::KeyedCallbackReporter { private: