Speed 4793

adds speed on states!

currently just to animation states 👇
<img width="1121" alt="image" src="https://user-images.githubusercontent.com/1216025/217915050-0bea976f-88b1-4aef-aeb0-bed6f36cc577.png">

I've called this 'AdvanceableState', which we could make blendstates/etc inherit from to give em the powers... not sure if there's a better name people can come up with here... also not sure if that empty lookin' class is the right way to do it, so i'd love feedback on that (and if there's a different example somewhere that would be super helpful to see as well)

also fixed up the generator scripts for dart 3, at least the dart ones, the cpp ones were beyond my patience, gotta dart 2.12 for those...

also fixed an issue where we were checking speed against playing backwards, not speed and direction!

@alxgibsn going to bug you for some styling input

works in both editor and viewer!

going to look about adding a test.. or two....

https://user-images.githubusercontent.com/1216025/217915843-6126d3cf-bf19-4a9d-9a95-adb3a498e75d.mov

https://user-images.githubusercontent.com/1216025/217915852-252d4f78-280e-4a63-838c-39d6b27e3e31.mov

Diffs=
ffeb9afaf Speed 4793 (#4806)
diff --git a/.rive_head b/.rive_head
index 30c2abb..7a3ff79 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-c7d125c7d4efe1233c16c2a95fb7b637b746b010
+ffeb9afafb69c3bf34e2df8c4c0c205083f2aabf
diff --git a/dev/defs/animation/advanceable_state.json b/dev/defs/animation/advanceable_state.json
new file mode 100644
index 0000000..05d1d67
--- /dev/null
+++ b/dev/defs/animation/advanceable_state.json
@@ -0,0 +1,19 @@
+{
+  "name": "AdvanceableState",
+  "key": {
+    "int": 145,
+    "string": "advanceablestate"
+  },
+  "abstract": true,
+  "extends": "animation/layer_state.json",
+  "properties": {
+    "speed": {
+      "type": "double",
+      "initialValue": "1",
+      "key": {
+        "int": 292,
+        "string": "speed"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/animation/animation_state.json b/dev/defs/animation/animation_state.json
index adc334a..43d5874 100644
--- a/dev/defs/animation/animation_state.json
+++ b/dev/defs/animation/animation_state.json
@@ -4,7 +4,7 @@
     "int": 61,
     "string": "animationstate"
   },
-  "extends": "animation/layer_state.json",
+  "extends": "animation/advanceable_state.json",
   "properties": {
     "animationId": {
       "type": "Id",
diff --git a/include/rive/animation/advanceable_state.hpp b/include/rive/animation/advanceable_state.hpp
new file mode 100644
index 0000000..58008fe
--- /dev/null
+++ b/include/rive/animation/advanceable_state.hpp
@@ -0,0 +1,13 @@
+#ifndef _RIVE_ADVANCEABLE_STATE_HPP_
+#define _RIVE_ADVANCEABLE_STATE_HPP_
+#include "rive/generated/animation/advanceable_state_base.hpp"
+#include <stdio.h>
+namespace rive
+{
+class AdvanceableState : public AdvanceableStateBase
+{
+public:
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/animation/animation_state.hpp b/include/rive/animation/animation_state.hpp
index 9a2a062..67a60a4 100644
--- a/include/rive/animation/animation_state.hpp
+++ b/include/rive/animation/animation_state.hpp
@@ -18,6 +18,10 @@
 public:
     const LinearAnimation* animation() const { return m_Animation; }
 
+#ifdef TESTING
+    void animation(LinearAnimation* animation);
+#endif
+
     std::unique_ptr<StateInstance> makeInstance(ArtboardInstance*) const override;
 };
 } // namespace rive
diff --git a/include/rive/animation/linear_animation_instance.hpp b/include/rive/animation/linear_animation_instance.hpp
index a88122b..e430995 100644
--- a/include/rive/animation/linear_animation_instance.hpp
+++ b/include/rive/animation/linear_animation_instance.hpp
@@ -16,7 +16,9 @@
     float m_TotalTime;
     float m_LastTotalTime;
     float m_SpilledTime;
-    int m_Direction;
+
+    // float because it gets multiplied with other floats
+    float m_Direction;
     bool m_DidLoop;
     int m_LoopValue = -1;
 
@@ -37,7 +39,7 @@
     float time() const { return m_Time; }
 
     // Returns the direction that we are currently playing in
-    int direction() const { return m_Direction; }
+    float direction() const { return m_Direction; }
 
     // Update the direction of the animation instance, positive value for
     // forwards Negative for backwards
diff --git a/include/rive/generated/animation/advanceable_state_base.hpp b/include/rive/generated/animation/advanceable_state_base.hpp
new file mode 100644
index 0000000..9eb520e
--- /dev/null
+++ b/include/rive/generated/animation/advanceable_state_base.hpp
@@ -0,0 +1,71 @@
+#ifndef _RIVE_ADVANCEABLE_STATE_BASE_HPP_
+#define _RIVE_ADVANCEABLE_STATE_BASE_HPP_
+#include "rive/animation/layer_state.hpp"
+#include "rive/core/field_types/core_double_type.hpp"
+namespace rive
+{
+class AdvanceableStateBase : public LayerState
+{
+protected:
+    typedef LayerState Super;
+
+public:
+    static const uint16_t typeKey = 145;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case AdvanceableStateBase::typeKey:
+            case LayerStateBase::typeKey:
+            case StateMachineLayerComponentBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t speedPropertyKey = 290;
+
+private:
+    float m_Speed = 1.0f;
+
+public:
+    inline float speed() const { return m_Speed; }
+    void speed(float value)
+    {
+        if (m_Speed == value)
+        {
+            return;
+        }
+        m_Speed = value;
+        speedChanged();
+    }
+
+    void copy(const AdvanceableStateBase& object)
+    {
+        m_Speed = object.m_Speed;
+        LayerState::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case speedPropertyKey:
+                m_Speed = CoreDoubleType::deserialize(reader);
+                return true;
+        }
+        return LayerState::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void speedChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/animation/animation_state_base.hpp b/include/rive/generated/animation/animation_state_base.hpp
index ee85b58..dd71a3b 100644
--- a/include/rive/generated/animation/animation_state_base.hpp
+++ b/include/rive/generated/animation/animation_state_base.hpp
@@ -1,13 +1,13 @@
 #ifndef _RIVE_ANIMATION_STATE_BASE_HPP_
 #define _RIVE_ANIMATION_STATE_BASE_HPP_
-#include "rive/animation/layer_state.hpp"
+#include "rive/animation/advanceable_state.hpp"
 #include "rive/core/field_types/core_uint_type.hpp"
 namespace rive
 {
-class AnimationStateBase : public LayerState
+class AnimationStateBase : public AdvanceableState
 {
 protected:
-    typedef LayerState Super;
+    typedef AdvanceableState Super;
 
 public:
     static const uint16_t typeKey = 61;
@@ -19,6 +19,7 @@
         switch (typeKey)
         {
             case AnimationStateBase::typeKey:
+            case AdvanceableStateBase::typeKey:
             case LayerStateBase::typeKey:
             case StateMachineLayerComponentBase::typeKey:
                 return true;
@@ -50,7 +51,7 @@
     void copy(const AnimationStateBase& object)
     {
         m_AnimationId = object.m_AnimationId;
-        LayerState::copy(object);
+        AdvanceableState::copy(object);
     }
 
     bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
@@ -61,7 +62,7 @@
                 m_AnimationId = CoreUintType::deserialize(reader);
                 return true;
         }
-        return LayerState::deserialize(propertyKey, reader);
+        return AdvanceableState::deserialize(propertyKey, reader);
     }
 
 protected:
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index e8147ec..0e16ec4 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -1,5 +1,6 @@
 #ifndef _RIVE_CORE_REGISTRY_HPP_
 #define _RIVE_CORE_REGISTRY_HPP_
+#include "rive/animation/advanceable_state.hpp"
 #include "rive/animation/animation.hpp"
 #include "rive/animation/animation_state.hpp"
 #include "rive/animation/any_state.hpp"
@@ -594,6 +595,9 @@
             case NestedSimpleAnimationBase::speedPropertyKey:
                 object->as<NestedSimpleAnimationBase>()->speed(value);
                 break;
+            case AdvanceableStateBase::speedPropertyKey:
+                object->as<AdvanceableStateBase>()->speed(value);
+                break;
             case StateMachineNumberBase::valuePropertyKey:
                 object->as<StateMachineNumberBase>()->value(value);
                 break;
@@ -1087,6 +1091,8 @@
                 return object->as<NestedLinearAnimationBase>()->mix();
             case NestedSimpleAnimationBase::speedPropertyKey:
                 return object->as<NestedSimpleAnimationBase>()->speed();
+            case AdvanceableStateBase::speedPropertyKey:
+                return object->as<AdvanceableStateBase>()->speed();
             case StateMachineNumberBase::valuePropertyKey:
                 return object->as<StateMachineNumberBase>()->value();
             case CubicInterpolatorBase::x1PropertyKey:
@@ -1388,6 +1394,7 @@
             case NodeBase::yPropertyKey:
             case NestedLinearAnimationBase::mixPropertyKey:
             case NestedSimpleAnimationBase::speedPropertyKey:
+            case AdvanceableStateBase::speedPropertyKey:
             case StateMachineNumberBase::valuePropertyKey:
             case CubicInterpolatorBase::x1PropertyKey:
             case CubicInterpolatorBase::y1PropertyKey:
diff --git a/src/animation/animation_state.cpp b/src/animation/animation_state.cpp
index 4e4dcf9..ac46cf1 100644
--- a/src/animation/animation_state.cpp
+++ b/src/animation/animation_state.cpp
@@ -10,3 +10,7 @@
 {
     return rivestd::make_unique<AnimationStateInstance>(this, instance);
 }
+
+#ifdef TESTING
+void AnimationState::animation(LinearAnimation* animation) { m_Animation = animation; }
+#endif
diff --git a/src/animation/animation_state_instance.cpp b/src/animation/animation_state_instance.cpp
index e3c972d..1c4063e 100644
--- a/src/animation/animation_state_instance.cpp
+++ b/src/animation/animation_state_instance.cpp
@@ -19,9 +19,11 @@
     m_KeepGoing(true)
 {}
 
+// NOTE:: should we return bool here? we are not currently using the output of this, we are instead
+// using m_keepGoing directly.
 void AnimationStateInstance::advance(float seconds, Span<SMIInput*>)
 {
-    m_KeepGoing = m_AnimationInstance.advance(seconds);
+    m_KeepGoing = m_AnimationInstance.advance(seconds * state()->as<AnimationState>()->speed());
 }
 
 void AnimationStateInstance::apply(float mix) { m_AnimationInstance.apply(mix); }
diff --git a/src/animation/linear_animation_instance.cpp b/src/animation/linear_animation_instance.cpp
index 56609d8..2e4b0f1 100644
--- a/src/animation/linear_animation_instance.cpp
+++ b/src/animation/linear_animation_instance.cpp
@@ -50,14 +50,22 @@
 bool LinearAnimationInstance::advance(float elapsedSeconds)
 {
     const LinearAnimation& animation = *m_Animation;
-    float deltaSeconds = elapsedSeconds * animation.speed();
+    float deltaSeconds = elapsedSeconds * animation.speed() * m_Direction;
+    if (deltaSeconds == 0)
+    {
+        // we say keep going, if you advance by 0.
+        // could argue that any further advances by 0 result in nothing so you should not keep going
+        // could argue its saying, we are not at the end of the animation yet, so keep going
+        // our runtimes currently expect the latter, so we say keep going!
+        m_DidLoop = false;
+        return true;
+    }
 
-    m_Time += deltaSeconds * m_Direction;
     m_LastTotalTime = m_TotalTime;
-    m_TotalTime += deltaSeconds;
+    m_TotalTime += std::abs(deltaSeconds);
+    m_Time += deltaSeconds;
 
     int fps = animation.fps();
-
     float frames = m_Time * fps;
 
     int start = animation.enableWorkArea() ? animation.workStart() : 0;
@@ -68,7 +76,10 @@
     bool didLoop = false;
     m_SpilledTime = 0.0f;
 
-    int direction = speed() < 0 ? -m_Direction : m_Direction;
+    // this has some issues when deltaSeconds is 0,
+    // right now we basically assume we default to going forwards in that case
+    //
+    int direction = deltaSeconds < 0 ? -1 : 1;
     switch (loop())
     {
         case Loop::oneShot:
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp
index e0a7132..ef70627 100644
--- a/src/animation/state_machine_instance.cpp
+++ b/src/animation/state_machine_instance.cpp
@@ -84,7 +84,7 @@
         }
     }
 
-    bool advance(/*Artboard* artboard, */ float seconds, Span<SMIInput*> inputs)
+    bool advance(float seconds, Span<SMIInput*> inputs)
     {
         m_StateChangedOnAdvance = false;
 
diff --git a/test/animation_state_instance_test.cpp b/test/animation_state_instance_test.cpp
new file mode 100644
index 0000000..cdced64
--- /dev/null
+++ b/test/animation_state_instance_test.cpp
@@ -0,0 +1,142 @@
+#include <rive/animation/loop.hpp>
+#include <rive/animation/linear_animation.hpp>
+#include <rive/animation/linear_animation_instance.hpp>
+
+#include <rive/animation/animation_state.hpp>
+#include <rive/animation/animation_state_instance.hpp>
+#include "utils/no_op_factory.hpp"
+#include "rive/scene.hpp"
+#include <catch.hpp>
+#include <cstdio>
+
+TEST_CASE("AnimationStateInstance advances in step with animation speed 1", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->loopValue(static_cast<int>(rive::Loop::oneShot));
+
+    rive::AnimationState* animationState = new rive::AnimationState();
+    animationState->animation(linearAnimation);
+
+    rive::AnimationStateInstance* animationStateInstance =
+        new rive::AnimationStateInstance(animationState, abi.get());
+
+    // play from beginning.
+    std::vector<rive::SMIInput*> m_InputInstances;
+    animationStateInstance->advance(2.0, m_InputInstances);
+
+    REQUIRE(animationStateInstance->animationInstance()->time() == 2.0);
+    REQUIRE(animationStateInstance->animationInstance()->totalTime() == 2.0);
+
+    delete animationStateInstance;
+    delete animationState;
+    delete linearAnimation;
+}
+
+TEST_CASE("AnimationStateInstance advances twice as fast when speed is doubled", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->loopValue(static_cast<int>(rive::Loop::oneShot));
+
+    rive::AnimationState* animationState = new rive::AnimationState();
+    animationState->animation(linearAnimation);
+    animationState->speed(2);
+
+    rive::AnimationStateInstance* animationStateInstance =
+        new rive::AnimationStateInstance(animationState, abi.get());
+
+    // play from beginning.
+    std::vector<rive::SMIInput*> m_InputInstances;
+    animationStateInstance->advance(2.0, m_InputInstances);
+
+    REQUIRE(animationStateInstance->animationInstance()->time() == 4.0);
+    REQUIRE(animationStateInstance->animationInstance()->totalTime() == 4.0);
+
+    delete animationStateInstance;
+    delete animationState;
+    delete linearAnimation;
+}
+
+TEST_CASE("AnimationStateInstance advances half as fast when speed is halved", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->loopValue(static_cast<int>(rive::Loop::oneShot));
+
+    rive::AnimationState* animationState = new rive::AnimationState();
+    animationState->animation(linearAnimation);
+    animationState->speed(0.5);
+
+    rive::AnimationStateInstance* animationStateInstance =
+        new rive::AnimationStateInstance(animationState, abi.get());
+
+    // play from beginning.
+    std::vector<rive::SMIInput*> m_InputInstances;
+    animationStateInstance->advance(2.0, m_InputInstances);
+
+    REQUIRE(animationStateInstance->animationInstance()->time() == 1.0);
+    REQUIRE(animationStateInstance->animationInstance()->totalTime() == 1.0);
+
+    delete animationStateInstance;
+    delete animationState;
+    delete linearAnimation;
+}
+
+TEST_CASE("AnimationStateInstance advances backwards when speed is negative", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->loopValue(static_cast<int>(rive::Loop::loop));
+
+    rive::AnimationState* animationState = new rive::AnimationState();
+    animationState->animation(linearAnimation);
+    animationState->speed(-1);
+
+    rive::AnimationStateInstance* animationStateInstance =
+        new rive::AnimationStateInstance(animationState, abi.get());
+
+    // play from beginning.
+    std::vector<rive::SMIInput*> m_InputInstances;
+    animationStateInstance->advance(2.0, m_InputInstances);
+
+    // backwards 2 seconds from 5.
+    REQUIRE(animationStateInstance->animationInstance()->time() == 3.0);
+    REQUIRE(animationStateInstance->animationInstance()->totalTime() == 2.0);
+
+    delete animationStateInstance;
+    delete animationState;
+    delete linearAnimation;
+}
diff --git a/test/linear_animation_instance_test.cpp b/test/linear_animation_instance_test.cpp
index 073cc15..0e8a8a6 100644
--- a/test/linear_animation_instance_test.cpp
+++ b/test/linear_animation_instance_test.cpp
@@ -67,6 +67,35 @@
     delete linearAnimation;
 }
 
+TEST_CASE("LinearAnimationInstance negative advance adds absolute total time ", "[animation]")
+{
+    rive::NoOpFactory emptyFactory;
+    // For each of these tests, we cons up a dummy artboard/instance
+    // just to make the animations happy.
+    rive::Artboard ab(&emptyFactory);
+    auto abi = ab.instance();
+
+    rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+    // duration in seconds is 5
+
+    linearAnimation->duration(10);
+    linearAnimation->fps(2);
+    linearAnimation->loopValue(static_cast<int>(rive::Loop::loop));
+
+    rive::LinearAnimationInstance* linearAnimationInstance =
+        new rive::LinearAnimationInstance(linearAnimation, abi.get());
+
+    // play from beginning.
+    bool continuePlaying = linearAnimationInstance->advance(-2.0);
+    REQUIRE(continuePlaying == true);
+    REQUIRE(linearAnimationInstance->time() == 3.0f);
+    REQUIRE(linearAnimationInstance->totalTime() == 2.0f);
+    REQUIRE(linearAnimationInstance->didLoop() == true);
+
+    delete linearAnimationInstance;
+    delete linearAnimation;
+}
+
 TEST_CASE("LinearAnimationInstance oneShot <-", "[animation]")
 {
     rive::NoOpFactory emptyFactory;
@@ -212,14 +241,15 @@
     linearAnimationInstance->direction(-1);
     REQUIRE(linearAnimationInstance->time() == 2.0);
 
-    // kick off, we're at the lower bound, will move to 5s.
+    // advancing by 0 will not change anything
+    // is expected to return continue playing true regardless
     bool continuePlaying = linearAnimationInstance->advance(0.0);
 
     REQUIRE(continuePlaying == true);
     REQUIRE(linearAnimationInstance->direction() == -1);
-    REQUIRE(linearAnimationInstance->time() == 5.0);
+    REQUIRE(linearAnimationInstance->time() == 2.0);
     REQUIRE(linearAnimationInstance->totalTime() == 0.0);
-    REQUIRE(linearAnimationInstance->didLoop() == true);
+    REQUIRE(linearAnimationInstance->didLoop() == false);
 
     // 2 more secs , 5s -> 3s
     continuePlaying = linearAnimationInstance->advance(2.0);
@@ -227,7 +257,7 @@
     REQUIRE(linearAnimationInstance->direction() == -1);
     REQUIRE(linearAnimationInstance->time() == 3.0);
     REQUIRE(linearAnimationInstance->totalTime() == 2.0);
-    REQUIRE(linearAnimationInstance->didLoop() == false);
+    REQUIRE(linearAnimationInstance->didLoop() == true);
 
     // 2 more secs , 3s -> 1s, thats before start, so loops to 4s
     continuePlaying = linearAnimationInstance->advance(2.0);