fix: reset interpolator and initialize it on convert (#11157) 0791ee519d
* fix: reset interpolator and initialize it on convert

Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index c07112e..7c663e5 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-4f005f715eedd281cc911e739b42109fafee0633
+0791ee519da721cd018cf3955906563ddc576117
diff --git a/include/rive/data_bind/converters/data_converter.hpp b/include/rive/data_bind/converters/data_converter.hpp
index 3716221..86e3db8 100644
--- a/include/rive/data_bind/converters/data_converter.hpp
+++ b/include/rive/data_bind/converters/data_converter.hpp
@@ -22,7 +22,6 @@
     };
     virtual DataType outputType() { return DataType::none; };
     virtual void bindFromContext(DataContext* dataContext, DataBind* dataBind);
-    virtual void initialize(DataType inputType) {}
     virtual void unbind();
     StatusCode import(ImportStack& importStack) override;
     void markConverterDirty();
@@ -30,6 +29,7 @@
     void copy(const DataConverter& object);
     virtual bool advance(float elapsedTime);
     void addDirtyDataBind(DataBind*) override;
+    virtual void reset() {};
 
 protected:
     DataBind* m_parentDataBind;
diff --git a/include/rive/data_bind/converters/data_converter_group.hpp b/include/rive/data_bind/converters/data_converter_group.hpp
index 1437c5d..b6829a7 100644
--- a/include/rive/data_bind/converters/data_converter_group.hpp
+++ b/include/rive/data_bind/converters/data_converter_group.hpp
@@ -34,10 +34,10 @@
     const std::vector<DataConverterGroupItem*>& items() { return m_items; }
     Core* clone() const override;
     void bindFromContext(DataContext* dataContext, DataBind* dataBind) override;
-    void initialize(DataType inputType) override;
     void unbind() override;
     void update() override;
     bool advance(float elapsedSeconds) override;
+    void reset() override;
 
 private:
     std::vector<DataConverterGroupItem*> m_items;
diff --git a/include/rive/data_bind/converters/data_converter_interpolator.hpp b/include/rive/data_bind/converters/data_converter_interpolator.hpp
index 3dadfb6..93027ac 100644
--- a/include/rive/data_bind/converters/data_converter_interpolator.hpp
+++ b/include/rive/data_bind/converters/data_converter_interpolator.hpp
@@ -28,6 +28,7 @@
     }
     void dispose()
     {
+        elapsedSeconds = 0;
         if (from != nullptr)
         {
             delete from;
@@ -44,12 +45,14 @@
     template <typename T = DataValue>
     void initialize(DataConverterInterpolator* converter)
     {
+        m_initialized = true;
         m_converter = converter;
 
         m_animationDataA.initialize<T>();
         m_animationDataB.initialize<T>();
         m_currentValue = new T();
     }
+    bool initialized() { return m_initialized; }
     void dispose();
     void resetValues(DataValue* input)
     {
@@ -58,6 +61,12 @@
         input->copyValue(animationData->to);
         input->copyValue(m_currentValue);
     }
+    void reset()
+    {
+        dispose();
+        m_isSmoothingAnimation = false;
+        m_initialized = false;
+    }
     void updateValues(DataValue* input)
     {
         auto animationData = currentAnimationData();
@@ -93,6 +102,7 @@
     InterpolatorAnimationData m_animationDataB;
     InterpolatorAnimationData* currentAnimationData();
     bool m_isSmoothingAnimation = false;
+    bool m_initialized = false;
     DataValue* m_currentValue = nullptr;
     DataConverterInterpolator* m_converter = nullptr;
 };
@@ -110,14 +120,18 @@
     DataValue* convert(DataValue* value, DataBind* dataBind) override;
     DataValue* reverseConvert(DataValue* value, DataBind* dataBind) override;
     bool advance(float elapsedTime) override;
+    void reset() override;
     void copy(const DataConverterInterpolatorBase& object);
     void durationChanged() override;
     template <typename T = DataValue> void startAdvancer()
     {
+        if (m_output != nullptr)
+        {
+            delete m_output;
+        }
         m_output = new T();
         m_advancer.initialize<T>(this);
     }
-    void initialize(DataType inputType) override;
 
 private:
     DataValue* m_output = nullptr;
diff --git a/src/data_bind/converters/data_converter_group.cpp b/src/data_bind/converters/data_converter_group.cpp
index d250c77..904093b 100644
--- a/src/data_bind/converters/data_converter_group.cpp
+++ b/src/data_bind/converters/data_converter_group.cpp
@@ -74,23 +74,6 @@
     }
 }
 
-void DataConverterGroup::initialize(DataType inputType)
-{
-    auto currentInputType = inputType;
-    for (auto& item : m_items)
-    {
-        if (item->converter() != nullptr)
-        {
-            item->converter()->initialize(currentInputType);
-            if (item->converter()->outputType() != DataType::input &&
-                item->converter()->outputType() != DataType::none)
-            {
-                currentInputType = item->converter()->outputType();
-            }
-        }
-    }
-}
-
 void DataConverterGroup::unbind()
 {
     for (auto& item : m_items)
@@ -115,6 +98,18 @@
     }
 }
 
+void DataConverterGroup::reset()
+{
+    for (auto& item : m_items)
+    {
+        auto converter = item->converter();
+        if (converter != nullptr)
+        {
+            converter->reset();
+        }
+    }
+}
+
 bool DataConverterGroup::advance(float elapsedSeconds)
 {
     bool didUpdate = false;
diff --git a/src/data_bind/converters/data_converter_interpolator.cpp b/src/data_bind/converters/data_converter_interpolator.cpp
index bede410..f351f65 100644
--- a/src/data_bind/converters/data_converter_interpolator.cpp
+++ b/src/data_bind/converters/data_converter_interpolator.cpp
@@ -92,6 +92,7 @@
     }
     auto prevTime = animationData->elapsedSeconds;
     advanceAnimationData(elapsedTime);
+
     if (prevTime < m_converter->duration())
     {
         m_converter->markConverterDirty();
@@ -103,21 +104,6 @@
     return false;
 }
 
-void DataConverterInterpolator::initialize(DataType inputType)
-{
-    switch (inputType)
-    {
-        case DataType::number:
-            startAdvancer<DataValueNumber>();
-            break;
-        case DataType::color:
-            startAdvancer<DataValueColor>();
-            break;
-        default:
-            break;
-    }
-}
-
 DataConverterInterpolator::~DataConverterInterpolator()
 {
     if (m_output != nullptr)
@@ -141,23 +127,38 @@
 
 bool DataConverterInterpolator::advance(float elapsedTime)
 {
-    if (m_output == nullptr)
-    {
-        return false;
-    }
     // Advance can be called multiple times in a single frame.
     // We want to make sure that two advances with time > 0 have elapsed before
     // considering the state as second frame.
-    if (m_advanceCount < 2)
+    if (m_advanceCount < 2 && elapsedTime > 0)
     {
         m_advanceCount++;
     }
+    if (!m_advancer.initialized())
+    {
+        return true;
+    }
     return m_advancer.advance(elapsedTime);
 }
 
 DataValue* DataConverterInterpolator::convert(DataValue* input,
                                               DataBind* dataBind)
 {
+    if (!m_advancer.initialized())
+    {
+        if (input->is<DataValueNumber>())
+        {
+            startAdvancer<DataValueNumber>();
+        }
+        else if (input->is<DataValueColor>())
+        {
+            startAdvancer<DataValueColor>();
+        }
+        else
+        {
+            return input;
+        }
+    }
     if (m_output != nullptr &&
         (input->is<DataValueNumber>() || input->is<DataValueColor>()))
     {
@@ -175,6 +176,12 @@
     return input;
 }
 
+void DataConverterInterpolator::reset()
+{
+    m_advanceCount = 0;
+    m_advancer.reset();
+}
+
 DataValue* DataConverterInterpolator::reverseConvert(DataValue* input,
                                                      DataBind* dataBind)
 {
diff --git a/src/data_bind/data_bind.cpp b/src/data_bind/data_bind.cpp
index 4c95b3a..4c92711 100644
--- a/src/data_bind/data_bind.cpp
+++ b/src/data_bind/data_bind.cpp
@@ -259,6 +259,10 @@
         default:
             break;
     }
+    if (m_dataConverter)
+    {
+        m_dataConverter->reset();
+    }
     addDirt(ComponentDirt::Bindings, true);
 }
 
diff --git a/src/data_bind/data_bind_context.cpp b/src/data_bind/data_bind_context.cpp
index bded564..b8867a8 100644
--- a/src/data_bind/data_bind_context.cpp
+++ b/src/data_bind/data_bind_context.cpp
@@ -53,7 +53,6 @@
         if (m_dataConverter != nullptr)
         {
             m_dataConverter->bindFromContext(dataContext, this);
-            m_dataConverter->initialize(sourceOutputType());
         }
     }
 }
diff --git a/tests/unit_tests/assets/data_converter_interpolator_reset.riv b/tests/unit_tests/assets/data_converter_interpolator_reset.riv
new file mode 100644
index 0000000..35a771b
--- /dev/null
+++ b/tests/unit_tests/assets/data_converter_interpolator_reset.riv
Binary files differ
diff --git a/tests/unit_tests/runtime/data_binding_converters_test.cpp b/tests/unit_tests/runtime/data_binding_converters_test.cpp
index 7861040..428791e 100644
--- a/tests/unit_tests/runtime/data_binding_converters_test.cpp
+++ b/tests/unit_tests/runtime/data_binding_converters_test.cpp
@@ -3,6 +3,7 @@
 #include "rive/animation/linear_animation_instance.hpp"
 #include "rive/animation/state_machine_instance.hpp"
 #include "rive/viewmodel/viewmodel.hpp"
+#include "rive/viewmodel/viewmodel_instance_color.hpp"
 #include "rive/viewmodel/viewmodel_instance_number.hpp"
 #include "rive/viewmodel/viewmodel_instance_trigger.hpp"
 #include "rive/viewmodel/viewmodel_instance_list.hpp"
@@ -59,4 +60,80 @@
     }
 
     CHECK(silver.matches("list_to_length_test"));
+}
+
+TEST_CASE("data converter interpolator resets on binding", "[silver]")
+{
+    SerializingFactory silver;
+    auto file =
+        ReadRiveFile("assets/data_converter_interpolator_reset.riv", &silver);
+
+    auto artboard = file->artboardDefault();
+
+    silver.frameSize(artboard->width(), artboard->height());
+
+    REQUIRE(artboard != nullptr);
+    auto stateMachine = artboard->stateMachineAt(0);
+    auto renderer = silver.makeRenderer();
+    int viewModelId = artboard.get()->viewModelId();
+    {
+        auto vmi = viewModelId == -1
+                       ? file->createViewModelInstance(artboard.get())
+                       : file->createViewModelInstance(viewModelId, 0);
+        auto numProp =
+            vmi->propertyValue("xPos")->as<ViewModelInstanceNumber>();
+        numProp->propertyValue(250);
+        auto colProp = vmi->propertyValue("col")->as<ViewModelInstanceColor>();
+        auto redColor = (255 << 24) | (255 << 16);
+        colProp->propertyValue(redColor);
+
+        stateMachine->bindViewModelInstance(vmi);
+        stateMachine->advanceAndApply(0.1f);
+
+        artboard->draw(renderer.get());
+
+        auto greenColor = (255 << 24) | (255 << 8);
+        colProp->propertyValue(greenColor);
+        numProp->propertyValue(500);
+
+        int frames = (int)(1.0f / 0.016f);
+        for (int i = 0; i < frames; i++)
+        {
+            silver.addFrame();
+            stateMachine->advanceAndApply(0.016f);
+            artboard->draw(renderer.get());
+        }
+    }
+    // When a new binding is applied, interpolators are reset and the initial
+    // value is not interpolated
+    {
+        silver.addFrame();
+        auto vmi = viewModelId == -1
+                       ? file->createViewModelInstance(artboard.get())
+                       : file->createViewModelInstance(viewModelId, 0);
+        auto numProp =
+            vmi->propertyValue("xPos")->as<ViewModelInstanceNumber>();
+        numProp->propertyValue(250);
+        auto colProp = vmi->propertyValue("col")->as<ViewModelInstanceColor>();
+        auto redColor = (255 << 24) | (255 << 16);
+        colProp->propertyValue(redColor);
+        stateMachine->bindViewModelInstance(vmi);
+        stateMachine->advanceAndApply(0.1f);
+
+        artboard->draw(renderer.get());
+
+        auto blueColor = (255 << 24) | 255;
+        colProp->propertyValue(blueColor);
+        numProp->propertyValue(0);
+
+        int frames = (int)(1.0f / 0.016f);
+        for (int i = 0; i < frames; i++)
+        {
+            silver.addFrame();
+            stateMachine->advanceAndApply(0.016f);
+            artboard->draw(renderer.get());
+        }
+    }
+
+    CHECK(silver.matches("data_converter_interpolator_reset"));
 }
\ No newline at end of file
diff --git a/tests/unit_tests/silvers/data_converter_interpolator_reset.sriv b/tests/unit_tests/silvers/data_converter_interpolator_reset.sriv
new file mode 100644
index 0000000..1f8092d
--- /dev/null
+++ b/tests/unit_tests/silvers/data_converter_interpolator_reset.sriv
Binary files differ