Starting to process events in state machine instance.
diff --git a/include/rive/animation/event_bool_change.hpp b/include/rive/animation/event_bool_change.hpp index 79d3933..61f79a2 100644 --- a/include/rive/animation/event_bool_change.hpp +++ b/include/rive/animation/event_bool_change.hpp
@@ -1,10 +1,12 @@ #ifndef _RIVE_EVENT_BOOL_CHANGE_HPP_ #define _RIVE_EVENT_BOOL_CHANGE_HPP_ #include "rive/generated/animation/event_bool_change_base.hpp" -#include <stdio.h> + namespace rive { class EventBoolChange : public EventBoolChangeBase { public: + bool validateInputType(const StateMachineInput* input) const override; + void perform(StateMachineInstance* stateMachineInstance) const override; }; } // namespace rive
diff --git a/include/rive/animation/event_input_change.hpp b/include/rive/animation/event_input_change.hpp index ee3e724..b3ae0ce 100644 --- a/include/rive/animation/event_input_change.hpp +++ b/include/rive/animation/event_input_change.hpp
@@ -3,9 +3,13 @@ #include "rive/generated/animation/event_input_change_base.hpp" namespace rive { + class StateMachineInstance; + class StateMachineInput; class EventInputChange : public EventInputChangeBase { public: StatusCode import(ImportStack& importStack) override; + virtual void perform(StateMachineInstance* stateMachineInstance) const = 0; + virtual bool validateInputType(const StateMachineInput* input) const { return true; } }; } // namespace rive
diff --git a/include/rive/animation/event_number_change.hpp b/include/rive/animation/event_number_change.hpp index 27ac05f..6e123d0 100644 --- a/include/rive/animation/event_number_change.hpp +++ b/include/rive/animation/event_number_change.hpp
@@ -1,10 +1,12 @@ #ifndef _RIVE_EVENT_NUMBER_CHANGE_HPP_ #define _RIVE_EVENT_NUMBER_CHANGE_HPP_ #include "rive/generated/animation/event_number_change_base.hpp" -#include <stdio.h> + namespace rive { class EventNumberChange : public EventNumberChangeBase { public: + bool validateInputType(const StateMachineInput* input) const override; + void perform(StateMachineInstance* stateMachineInstance) const override; }; } // namespace rive
diff --git a/include/rive/animation/event_trigger_change.hpp b/include/rive/animation/event_trigger_change.hpp index b6245af..20200b5 100644 --- a/include/rive/animation/event_trigger_change.hpp +++ b/include/rive/animation/event_trigger_change.hpp
@@ -1,10 +1,12 @@ #ifndef _RIVE_EVENT_TRIGGER_CHANGE_HPP_ #define _RIVE_EVENT_TRIGGER_CHANGE_HPP_ #include "rive/generated/animation/event_trigger_change_base.hpp" -#include <stdio.h> + namespace rive { class EventTriggerChange : public EventTriggerChangeBase { public: + bool validateInputType(const StateMachineInput* input) const override; + void perform(StateMachineInstance* stateMachineInstance) const override; }; } // namespace rive
diff --git a/include/rive/animation/state_machine_event.hpp b/include/rive/animation/state_machine_event.hpp index b3d7606..ea6cb5a 100644 --- a/include/rive/animation/state_machine_event.hpp +++ b/include/rive/animation/state_machine_event.hpp
@@ -1,21 +1,30 @@ #ifndef _RIVE_STATE_MACHINE_EVENT_HPP_ #define _RIVE_STATE_MACHINE_EVENT_HPP_ #include "rive/generated/animation/state_machine_event_base.hpp" +#include "rive/event_type.hpp" namespace rive { + class Shape; class StateMachineEventImporter; class EventInputChange; + class StateMachineInstance; class StateMachineEvent : public StateMachineEventBase { friend class StateMachineEventImporter; private: + std::vector<uint32_t> m_HitShapesIds; std::vector<EventInputChange*> m_InputChanges; void addInputChange(EventInputChange* inputChange); public: + EventType eventType() const { return (EventType)eventTypeValue(); } size_t inputChangeCount() const { return m_InputChanges.size(); } const EventInputChange* inputChange(size_t index) const; StatusCode import(ImportStack& importStack) override; + StatusCode onAddedClean(CoreContext* context) override; + + const std::vector<uint32_t>& hitShapeIds() const { return m_HitShapesIds; } + void performChanges(StateMachineInstance* stateMachineInstance) const; }; } // namespace rive
diff --git a/include/rive/animation/state_machine_instance.hpp b/include/rive/animation/state_machine_instance.hpp index 5b1629e..56109e0 100644 --- a/include/rive/animation/state_machine_instance.hpp +++ b/include/rive/animation/state_machine_instance.hpp
@@ -3,7 +3,9 @@ #include <string> #include <stddef.h> +#include <vector> #include "rive/animation/linear_animation_instance.hpp" +#include "rive/event_type.hpp" namespace rive { class StateMachine; @@ -13,14 +15,18 @@ class SMIBool; class SMINumber; class SMITrigger; - + class Shape; class StateMachineLayerInstance; + class HitShape; class StateMachineInstance { friend class SMIInput; private: const StateMachine* m_Machine; + /// The artboard the changes will be applied to. + Artboard* m_Artboard; + bool m_NeedsAdvance = false; size_t m_InputCount; @@ -30,13 +36,16 @@ void markNeedsAdvance(); + std::vector<HitShape*> m_HitShapes; + void processEvent(Vec2D position, EventType hitEvent); + public: - StateMachineInstance(const StateMachine* machine); + StateMachineInstance(const StateMachine* machine, Artboard* artboard); ~StateMachineInstance(); // Advance the state machine by the specified time. Returns true if the // state machine will continue to animate after this advance. - bool advance(Artboard* artboard, float seconds); + bool advance(float seconds); // Returns true when the StateMachineInstance has more data to process. bool needsAdvance() const;
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp index e9a32d8..ef0f192 100644 --- a/include/rive/artboard.hpp +++ b/include/rive/artboard.hpp
@@ -22,6 +22,7 @@ class DrawTarget; class ArtboardImporter; class NestedArtboard; + class StateMachineInstance; class Artboard : public ArtboardBase, public CoreContext, public ShapePaintContainer { friend class File; @@ -63,7 +64,8 @@ ~Artboard(); StatusCode initialize(); - Core* resolve(int id) const override; + Core* resolve(uint32_t id) const override; + uint32_t idOf(Core* object) const; // EXPERIMENTAL -- for internal testing only for now. // DO NOT RELY ON THIS as it may change/disappear in the future. @@ -128,6 +130,15 @@ StateMachine* firstStateMachine() const; StateMachine* stateMachine(std::string name) const; StateMachine* stateMachine(size_t index) const; + + /// Make an instance of a state machine given the name of the source state machine in this + /// artboard. The StateMachineInstance must be explictly deleted when no longer needed. + StateMachineInstance* stateMachineInstance(std::string name); + + /// Make an instance of a state machine given the index of the source state machine in this + /// artboard. The StateMachineInstance must be explictly deleted when no longer needed. + StateMachineInstance* stateMachineInstance(size_t index); + size_t stateMachineCount() const { return m_StateMachines.size(); } /// Make an instance of this artboard, must be explictly deleted when no
diff --git a/include/rive/core_context.hpp b/include/rive/core_context.hpp index 1711f38..435c9f7 100644 --- a/include/rive/core_context.hpp +++ b/include/rive/core_context.hpp
@@ -1,11 +1,13 @@ #ifndef _RIVE_CORE_CONTEXT_HPP_ #define _RIVE_CORE_CONTEXT_HPP_ +#include "rive/rive_types.hpp" + namespace rive { class Core; class CoreContext { public: - virtual Core* resolve(int id) const = 0; + virtual Core* resolve(uint32_t id) const = 0; }; } // namespace rive #endif \ No newline at end of file
diff --git a/include/rive/event_type.hpp b/include/rive/event_type.hpp new file mode 100644 index 0000000..d50df00 --- /dev/null +++ b/include/rive/event_type.hpp
@@ -0,0 +1,11 @@ +#ifndef _RIVE_EVENT_TYPE_HPP_ +#define _RIVE_EVENT_TYPE_HPP_ +namespace rive { + enum class EventType : unsigned int { + enter = 0, + exit = 1, + down = 2, + up = 3, + }; +} +#endif \ No newline at end of file
diff --git a/include/rive/generated/animation/event_input_change_base.hpp b/include/rive/generated/animation/event_input_change_base.hpp index 61f30d1..7212efb 100644 --- a/include/rive/generated/animation/event_input_change_base.hpp +++ b/include/rive/generated/animation/event_input_change_base.hpp
@@ -26,11 +26,11 @@ static const uint16_t inputIdPropertyKey = 227; private: - int m_InputId = -1; + uint32_t m_InputId = -1; public: - inline int inputId() const { return m_InputId; } - void inputId(int value) { + inline uint32_t inputId() const { return m_InputId; } + void inputId(uint32_t value) { if (m_InputId == value) { return; }
diff --git a/include/rive/generated/animation/state_machine_event_base.hpp b/include/rive/generated/animation/state_machine_event_base.hpp index 2aa9f01..a67e39c 100644 --- a/include/rive/generated/animation/state_machine_event_base.hpp +++ b/include/rive/generated/animation/state_machine_event_base.hpp
@@ -28,12 +28,12 @@ static const uint16_t eventTypeValuePropertyKey = 225; private: - int m_TargetId = 0; - int m_EventTypeValue = 0; + uint32_t m_TargetId = 0; + uint32_t m_EventTypeValue = 0; public: - inline int targetId() const { return m_TargetId; } - void targetId(int value) { + inline uint32_t targetId() const { return m_TargetId; } + void targetId(uint32_t value) { if (m_TargetId == value) { return; } @@ -41,8 +41,8 @@ targetIdChanged(); } - inline int eventTypeValue() const { return m_EventTypeValue; } - void eventTypeValue(int value) { + inline uint32_t eventTypeValue() const { return m_EventTypeValue; } + void eventTypeValue(uint32_t value) { if (m_EventTypeValue == value) { return; }
diff --git a/src/animation/event_bool_change.cpp b/src/animation/event_bool_change.cpp new file mode 100644 index 0000000..946f15a --- /dev/null +++ b/src/animation/event_bool_change.cpp
@@ -0,0 +1,22 @@ +#include "rive/animation/event_bool_change.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/state_machine_bool.hpp" +#include "rive/animation/state_machine_input_instance.hpp" +using namespace rive; + +bool EventBoolChange::validateInputType(const StateMachineInput* input) const { + // A null input is valid as the StateMachine can attempt to limp along if we + // introduce new input types that old conditions are expected to handle in + // newer runtimes. The older runtimes will just evaluate them to true. + return input == nullptr || input->is<StateMachineBool>(); +} + +void EventBoolChange::perform(StateMachineInstance* stateMachineInstance) const { + auto inputInstance = stateMachineInstance->input(inputId()); + if (inputInstance == nullptr) { + return; + } + // If it's not null, it must be our correct type (why we validate at load time). + auto boolInput = reinterpret_cast<SMIBool*>(inputInstance); + boolInput->value(value()); +} \ No newline at end of file
diff --git a/src/animation/event_input_change.cpp b/src/animation/event_input_change.cpp index d319855..7914ae3 100644 --- a/src/animation/event_input_change.cpp +++ b/src/animation/event_input_change.cpp
@@ -1,17 +1,27 @@ #include "rive/animation/state_machine_event.hpp" #include "rive/importers/import_stack.hpp" #include "rive/importers/state_machine_event_importer.hpp" +#include "rive/importers/state_machine_importer.hpp" #include "rive/animation/event_input_change.hpp" -#include "rive/generated/animation/state_machine_base.hpp" +#include "rive/animation/state_machine.hpp" using namespace rive; StatusCode EventInputChange::import(ImportStack& importStack) { + auto stateMachineImporter = importStack.latest<StateMachineImporter>(StateMachine::typeKey); + if (stateMachineImporter == nullptr) { + return StatusCode::MissingObject; + } + auto stateMachineEventImporter = importStack.latest<StateMachineEventImporter>(StateMachineEventBase::typeKey); if (stateMachineEventImporter == nullptr) { return StatusCode::MissingObject; } + + if (!validateInputType(stateMachineImporter->stateMachine()->input((size_t)inputId()))) { + return StatusCode::InvalidObject; + } stateMachineEventImporter->addInputChange(this); return Super::import(importStack); }
diff --git a/src/animation/event_number_change.cpp b/src/animation/event_number_change.cpp new file mode 100644 index 0000000..a97d306 --- /dev/null +++ b/src/animation/event_number_change.cpp
@@ -0,0 +1,23 @@ +#include "rive/animation/event_number_change.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/state_machine_number.hpp" +#include "rive/animation/state_machine_input_instance.hpp" + +using namespace rive; + +bool EventNumberChange::validateInputType(const StateMachineInput* input) const { + // A null input is valid as the StateMachine can attempt to limp along if we + // introduce new input types that old conditions are expected to handle in + // newer runtimes. The older runtimes will just evaluate them to true. + return input == nullptr || input->is<StateMachineNumber>(); +} + +void EventNumberChange::perform(StateMachineInstance* stateMachineInstance) const { + auto inputInstance = stateMachineInstance->input(inputId()); + if (inputInstance == nullptr) { + return; + } + // If it's not null, it must be our correct type (why we validate at load time). + auto numberInput = reinterpret_cast<SMINumber*>(inputInstance); + numberInput->value(value()); +}
diff --git a/src/animation/event_trigger_change.cpp b/src/animation/event_trigger_change.cpp new file mode 100644 index 0000000..e2b8d9d --- /dev/null +++ b/src/animation/event_trigger_change.cpp
@@ -0,0 +1,23 @@ +#include "rive/animation/event_trigger_change.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/state_machine_trigger.hpp" +#include "rive/animation/state_machine_input_instance.hpp" + +using namespace rive; + +bool EventTriggerChange::validateInputType(const StateMachineInput* input) const { + // A null input is valid as the StateMachine can attempt to limp along if we + // introduce new input types that old conditions are expected to handle in + // newer runtimes. The older runtimes will just evaluate them to true. + return input == nullptr || input->is<StateMachineTrigger>(); +} + +void EventTriggerChange::perform(StateMachineInstance* stateMachineInstance) const { + auto inputInstance = stateMachineInstance->input(inputId()); + if (inputInstance == nullptr) { + return; + } + // If it's not null, it must be our correct type (why we validate at load time). + auto triggerInput = reinterpret_cast<SMITrigger*>(inputInstance); + triggerInput->fire(); +} \ No newline at end of file
diff --git a/src/animation/state_machine_event.cpp b/src/animation/state_machine_event.cpp index d6791b3..fd367ed 100644 --- a/src/animation/state_machine_event.cpp +++ b/src/animation/state_machine_event.cpp
@@ -2,6 +2,10 @@ #include "rive/importers/import_stack.hpp" #include "rive/importers/state_machine_importer.hpp" #include "rive/generated/animation/state_machine_base.hpp" +#include "rive/artboard.hpp" +#include "rive/shapes/shape.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/event_input_change.hpp" using namespace rive; @@ -23,4 +27,34 @@ return m_InputChanges[index]; } return nullptr; +} + +StatusCode StateMachineEvent::onAddedClean(CoreContext* context) { + auto artboard = static_cast<Artboard*>(context); + for (auto core : artboard->objects()) { + if (core == nullptr) { + continue; + } + auto target = artboard->resolve(targetId()); + + // Iterate artboard to find Shapes that are parented to the target + if (core->is<Shape>()) { + auto shape = core->as<Shape>(); + for (ContainerComponent* component = shape; component != nullptr; + component = component->parent()) { + if (component == target) { + m_HitShapesIds.push_back(artboard->idOf(shape)); + break; + } + } + } + } + + return Super::onAddedClean(context); +} + +void StateMachineEvent::performChanges(StateMachineInstance* stateMachineInstance) const { + for (auto inputChange : m_InputChanges) { + inputChange->perform(stateMachineInstance); + } } \ No newline at end of file
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp index e37dad3..bbc923f 100644 --- a/src/animation/state_machine_instance.cpp +++ b/src/animation/state_machine_instance.cpp
@@ -13,6 +13,10 @@ #include "rive/animation/animation_state.hpp" #include "rive/animation/state_instance.hpp" #include "rive/animation/animation_state_instance.hpp" +#include "rive/animation/state_machine_event.hpp" +#include "rive/shapes/shape.hpp" +#include "rive/math/aabb.hpp" +#include <unordered_map> using namespace rive; namespace rive { @@ -211,9 +215,61 @@ return static_cast<AnimationStateInstance*>(m_CurrentState)->animationInstance(); } }; + + /// Representation of a Shape from the Artboard Instance and all the events it + /// triggers. Allows tracking hover and performing hit detection only once on + /// shapes that trigger multiple events. + class HitShape { + private: + Shape* m_Shape; + + public: + Shape* shape() const { return m_Shape; } + HitShape(Shape* shape) : m_Shape(shape) {} + bool isHovered = false; + std::vector<const StateMachineEvent*> events; + }; } // namespace rive -StateMachineInstance::StateMachineInstance(const StateMachine* machine) : m_Machine(machine) { +void StateMachineInstance::processEvent(Vec2D position, EventType hitEvent) { + const int hitRadius = 2; + auto hitArea = AABB(position.x() - hitRadius, + position.y() - hitRadius, + position.x() + hitRadius, + position.y() + hitRadius) + .round(); + for (auto hitShape : m_HitShapes) { + + // TODO: quick reject. + + // TODO: hit test + bool isOver = false; + + bool hoverChange = hitShape->isHovered != isOver; + hitShape->isHovered = isOver; + // iterate all events associated with this hit shape + for (auto event : hitShape->events) { + // Always update hover states regardless of which specific event type + // we're trying to trigger. + if (hoverChange) { + if (isOver && event->eventType() == EventType::enter) { + event->performChanges(this); + markNeedsAdvance(); + } else if (!isOver && event->eventType() == EventType::exit) { + event->performChanges(this); + markNeedsAdvance(); + } + } + if (isOver && hitEvent == event->eventType()) { + event->performChanges(this); + markNeedsAdvance(); + } + } + } +} + +StateMachineInstance::StateMachineInstance(const StateMachine* machine, Artboard* artboard) : + m_Machine(machine), m_Artboard(artboard) { m_InputCount = machine->inputCount(); m_InputInstances = new SMIInput*[m_InputCount]; for (size_t i = 0; i < m_InputCount; i++) { @@ -244,9 +300,40 @@ for (size_t i = 0; i < m_LayerCount; i++) { m_Layers[i].init(machine->layer(i)); } + + // Initialize events. Store a lookup table of shape id to hit shape + // representation (an object that stores all the events triggered by the + // shape producing an event). + std::unordered_map<uint32_t, HitShape*> hitShapeLookup; + for (std::size_t i = 0; i < machine->eventCount(); i++) { + auto event = machine->event(i); + + // Iterate actual leaf hittable shapes tied to this event and resolve + // corresponding ones in the artboard instance. + for (auto id : event->hitShapeIds()) { + HitShape* hitShape; + auto itr = hitShapeLookup.find(id); + if (itr == hitShapeLookup.end()) { + auto shape = artboard->resolve(id); + if (shape != nullptr && shape->is<Shape>()) { + hitShapeLookup[id] = hitShape = new HitShape(shape->as<Shape>()); + m_HitShapes.push_back(hitShape); + } else { + // No object or not a shape... + continue; + } + } else { + hitShape = itr->second; + } + hitShape->events.push_back(event); + } + } } StateMachineInstance::~StateMachineInstance() { + for (auto hitShape : m_HitShapes) { + delete hitShape; + } // TODO: can this and others be rewritten as for (auto inst : m_InputInstances) ? for (size_t i = 0; i < m_InputCount; i++) { delete m_InputInstances[i]; @@ -255,10 +342,10 @@ delete[] m_Layers; } -bool StateMachineInstance::advance(Artboard* artboard, float seconds) { +bool StateMachineInstance::advance(float seconds) { m_NeedsAdvance = false; for (size_t i = 0; i < m_LayerCount; i++) { - if (m_Layers[i].advance(artboard, seconds, m_InputInstances, m_InputCount)) { + if (m_Layers[i].advance(m_Artboard, seconds, m_InputInstances, m_InputCount)) { m_NeedsAdvance = true; } }
diff --git a/src/artboard.cpp b/src/artboard.cpp index d9cb7cb..98c038e 100644 --- a/src/artboard.cpp +++ b/src/artboard.cpp
@@ -13,6 +13,7 @@ #include "rive/importers/import_stack.hpp" #include "rive/importers/backboard_importer.hpp" #include "rive/nested_artboard.hpp" +#include "rive/animation/state_machine_instance.hpp" #include <stack> #include <unordered_map> @@ -278,13 +279,15 @@ m_NestedArtboards.push_back(artboard); } -Core* Artboard::resolve(int id) const { +Core* Artboard::resolve(uint32_t id) const { if (id < 0 || id >= static_cast<int>(m_Objects.size())) { return nullptr; } return m_Objects[id]; } +uint32_t Artboard::idOf(Core* object) const { return 0; } + void Artboard::onComponentDirty(Component* component) { m_Dirt |= ComponentDirt::Components; @@ -490,6 +493,22 @@ return m_StateMachines[index]; } +StateMachineInstance* Artboard::stateMachineInstance(std::string name) { + StateMachine* machine = stateMachine(name); + if (machine != nullptr) { + return new StateMachineInstance(machine, this); + } + return nullptr; +} + +StateMachineInstance* Artboard::stateMachineInstance(size_t index) { + StateMachine* machine = stateMachine(index); + if (machine != nullptr) { + return new StateMachineInstance(machine, this); + } + return nullptr; +} + Artboard* Artboard::instance() const { auto artboardClone = clone()->as<Artboard>(); artboardClone->m_FrameOrigin = m_FrameOrigin;
diff --git a/test/state_machine_test.cpp b/test/state_machine_test.cpp index 92cc576..3a5f464 100644 --- a/test/state_machine_test.cpp +++ b/test/state_machine_test.cpp
@@ -72,7 +72,7 @@ } } - rive::StateMachineInstance smi(artboard->stateMachine("Button")); + rive::StateMachineInstance smi(artboard->stateMachine("Button"), artboard); REQUIRE(smi.getBool("Hover")->name() == "Hover"); REQUIRE(smi.getBool("Press")->name() == "Press"); REQUIRE(smi.getBool("Hover") != nullptr); @@ -160,6 +160,6 @@ auto animationState = layer->state(3)->as<rive::AnimationState>(); REQUIRE(animationState->animation() == nullptr); - rive::StateMachineInstance smi(stateMachine); - smi.advance(artboard, 0.0f); + rive::StateMachineInstance smi(stateMachine, artboard); + smi.advance(0.0f); }