don’t defer updates when a shape/path is used for hit detect

Fixes issue reported by Duolingo.

https://2dimensions.slack.com/archives/C029X99PETE/p1717700429867579

Diffs=
a10b1e61e don’t defer updates when a shape/path is used for hit detect (#7392)

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index 8bafa52..de25b58 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-8486c344528d5f7f84de1984510064a7d0788e73
+a10b1e61edfd5a5d01801f78d9ee76a064c9c7ce
diff --git a/include/rive/animation/state_machine_listener.hpp b/include/rive/animation/state_machine_listener.hpp
index 244eb27..2867188 100644
--- a/include/rive/animation/state_machine_listener.hpp
+++ b/include/rive/animation/state_machine_listener.hpp
@@ -14,26 +14,23 @@
 {
     friend class StateMachineListenerImporter;
 
-private:
-    std::vector<uint32_t> m_HitShapesIds;
-    std::vector<std::unique_ptr<ListenerAction>> m_Actions;
-    void addAction(std::unique_ptr<ListenerAction>);
-
 public:
     StateMachineListener();
     ~StateMachineListener() override;
 
     ListenerType listenerType() const { return (ListenerType)listenerTypeValue(); }
-    size_t actionCount() const { return m_Actions.size(); }
+    size_t actionCount() const { return m_actions.size(); }
 
     const ListenerAction* action(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,
                         Vec2D position,
                         Vec2D previousPosition) const;
+
+private:
+    void addAction(std::unique_ptr<ListenerAction>);
+    std::vector<std::unique_ptr<ListenerAction>> m_actions;
 };
 } // namespace rive
 
diff --git a/include/rive/component.hpp b/include/rive/component.hpp
index 0aee6ee..7c26099 100644
--- a/include/rive/component.hpp
+++ b/include/rive/component.hpp
@@ -4,6 +4,7 @@
 #include "rive/generated/component_base.hpp"
 
 #include <vector>
+#include <functional>
 
 namespace rive
 {
@@ -55,6 +56,8 @@
     {
         return (m_Dirt & ComponentDirt::Collapsed) == ComponentDirt::Collapsed;
     }
+
+    virtual bool forAll(std::function<bool(Component*)> predicate);
 };
 } // namespace rive
 
diff --git a/include/rive/container_component.hpp b/include/rive/container_component.hpp
index f187926..e340a17 100644
--- a/include/rive/container_component.hpp
+++ b/include/rive/container_component.hpp
@@ -2,17 +2,23 @@
 #define _RIVE_CONTAINER_COMPONENT_HPP_
 #include "rive/generated/container_component_base.hpp"
 #include <vector>
+#include <functional>
+
 namespace rive
 {
 class ContainerComponent : public ContainerComponentBase
 {
-private:
-    std::vector<Component*> m_children;
-
 public:
     const std::vector<Component*>& children() const { return m_children; }
     virtual void addChild(Component* component);
     bool collapse(bool value) override;
+
+    // Returns true if it searched through all of its children. predicate can
+    // return false to stop searching.
+    bool forAll(std::function<bool(Component*)> predicate) override;
+
+private:
+    std::vector<Component*> m_children;
 };
 } // namespace rive
 
diff --git a/include/rive/shapes/paint/fill.hpp b/include/rive/shapes/paint/fill.hpp
index 97291e6..a9a0a64 100644
--- a/include/rive/shapes/paint/fill.hpp
+++ b/include/rive/shapes/paint/fill.hpp
@@ -1,14 +1,14 @@
 #ifndef _RIVE_FILL_HPP_
 #define _RIVE_FILL_HPP_
 #include "rive/generated/shapes/paint/fill_base.hpp"
-#include "rive/shapes/path_space.hpp"
+#include "rive/shapes/path_flags.hpp"
 namespace rive
 {
 class Fill : public FillBase
 {
 public:
     RenderPaint* initRenderPaint(ShapePaintMutator* mutator) override;
-    PathSpace pathSpace() const override;
+    PathFlags pathFlags() const override;
     void draw(Renderer* renderer,
               CommandPath* path,
               const RawPath* rawPath,
diff --git a/include/rive/shapes/paint/shape_paint.hpp b/include/rive/shapes/paint/shape_paint.hpp
index 388fb13..67e9660 100644
--- a/include/rive/shapes/paint/shape_paint.hpp
+++ b/include/rive/shapes/paint/shape_paint.hpp
@@ -4,7 +4,7 @@
 #include "rive/renderer.hpp"
 #include "rive/shapes/paint/blend_mode.hpp"
 #include "rive/shapes/paint/shape_paint_mutator.hpp"
-#include "rive/shapes/path_space.hpp"
+#include "rive/shapes/path_flags.hpp"
 #include "rive/math/raw_path.hpp"
 
 namespace rive
@@ -30,7 +30,8 @@
     /// lifecycle of the RenderPaint.
     virtual RenderPaint* initRenderPaint(ShapePaintMutator* mutator);
 
-    virtual PathSpace pathSpace() const = 0;
+    virtual PathFlags pathFlags() const = 0;
+    bool isFlagged(PathFlags flags) const { return (int)(pathFlags() & flags) != 0x00; }
 
     void draw(Renderer* renderer, CommandPath* path, const RawPath* rawPath = nullptr)
     {
diff --git a/include/rive/shapes/paint/stroke.hpp b/include/rive/shapes/paint/stroke.hpp
index 7663669..99a7a0a 100644
--- a/include/rive/shapes/paint/stroke.hpp
+++ b/include/rive/shapes/paint/stroke.hpp
@@ -1,7 +1,7 @@
 #ifndef _RIVE_STROKE_HPP_
 #define _RIVE_STROKE_HPP_
 #include "rive/generated/shapes/paint/stroke_base.hpp"
-#include "rive/shapes/path_space.hpp"
+#include "rive/shapes/path_flags.hpp"
 namespace rive
 {
 class StrokeEffect;
@@ -12,7 +12,7 @@
 
 public:
     RenderPaint* initRenderPaint(ShapePaintMutator* mutator) override;
-    PathSpace pathSpace() const override;
+    PathFlags pathFlags() const override;
     void draw(Renderer* renderer,
               CommandPath* path,
               const RawPath* rawPath,
diff --git a/include/rive/shapes/path.hpp b/include/rive/shapes/path.hpp
index d5ba979..59cc59b 100644
--- a/include/rive/shapes/path.hpp
+++ b/include/rive/shapes/path.hpp
@@ -40,7 +40,7 @@
     Shape* m_Shape = nullptr;
     std::vector<PathVertex*> m_Vertices;
     bool m_deferredPathDirt = false;
-    PathSpace m_DefaultPathSpace = PathSpace::Neither;
+    PathFlags m_pathFlags = PathFlags::none;
     RawPath m_rawPath;
 
 public:
@@ -52,7 +52,9 @@
     const RawPath& rawPath() const { return m_rawPath; }
     void update(ComponentDirt value) override;
 
-    void addDefaultPathSpace(PathSpace space);
+    void addFlags(PathFlags flags);
+    bool isFlagged(PathFlags flags) const;
+
     bool canDeferPathUpdate();
     void addVertex(PathVertex* vertex);
 
diff --git a/include/rive/shapes/path_flags.hpp b/include/rive/shapes/path_flags.hpp
new file mode 100644
index 0000000..172ed42
--- /dev/null
+++ b/include/rive/shapes/path_flags.hpp
@@ -0,0 +1,66 @@
+#ifndef _RIVE_PATH_FLAGS_HPP_
+#define _RIVE_PATH_FLAGS_HPP_
+
+#include "rive/rive_types.hpp"
+
+namespace rive
+{
+enum class PathFlags : uint8_t
+{
+    none = 0,
+    local = 1 << 1,
+    world = 1 << 2,
+    clipping = 1 << 3,
+    followPath = 1 << 4,
+    neverDeferUpdate = 1 << 5,
+};
+
+inline constexpr PathFlags operator&(PathFlags lhs, PathFlags rhs)
+{
+    return static_cast<PathFlags>(static_cast<std::underlying_type<PathFlags>::type>(lhs) &
+                                  static_cast<std::underlying_type<PathFlags>::type>(rhs));
+}
+
+inline constexpr PathFlags operator^(PathFlags lhs, PathFlags rhs)
+{
+    return static_cast<PathFlags>(static_cast<std::underlying_type<PathFlags>::type>(lhs) ^
+                                  static_cast<std::underlying_type<PathFlags>::type>(rhs));
+}
+
+inline constexpr PathFlags operator|(PathFlags lhs, PathFlags rhs)
+{
+    return static_cast<PathFlags>(static_cast<std::underlying_type<PathFlags>::type>(lhs) |
+                                  static_cast<std::underlying_type<PathFlags>::type>(rhs));
+}
+
+inline constexpr PathFlags operator~(PathFlags rhs)
+{
+    return static_cast<PathFlags>(~static_cast<std::underlying_type<PathFlags>::type>(rhs));
+}
+
+inline PathFlags& operator|=(PathFlags& lhs, PathFlags rhs)
+{
+    lhs = static_cast<PathFlags>(static_cast<std::underlying_type<PathFlags>::type>(lhs) |
+                                 static_cast<std::underlying_type<PathFlags>::type>(rhs));
+
+    return lhs;
+}
+
+inline PathFlags& operator&=(PathFlags& lhs, PathFlags rhs)
+{
+    lhs = static_cast<PathFlags>(static_cast<std::underlying_type<PathFlags>::type>(lhs) &
+                                 static_cast<std::underlying_type<PathFlags>::type>(rhs));
+
+    return lhs;
+}
+
+inline PathFlags& operator^=(PathFlags& lhs, PathFlags rhs)
+{
+    lhs = static_cast<PathFlags>(static_cast<std::underlying_type<PathFlags>::type>(lhs) ^
+                                 static_cast<std::underlying_type<PathFlags>::type>(rhs));
+
+    return lhs;
+}
+} // namespace rive
+
+#endif
diff --git a/include/rive/shapes/path_space.hpp b/include/rive/shapes/path_space.hpp
deleted file mode 100644
index d44e5c7..0000000
--- a/include/rive/shapes/path_space.hpp
+++ /dev/null
@@ -1,65 +0,0 @@
-#ifndef _RIVE_PATH_SPACE_HPP_
-#define _RIVE_PATH_SPACE_HPP_
-
-#include "rive/rive_types.hpp"
-
-namespace rive
-{
-enum class PathSpace : unsigned char
-{
-    Neither = 0,
-    Local = 1 << 1,
-    World = 1 << 2,
-    Clipping = 1 << 3,
-    FollowPath = 1 << 4
-};
-
-inline constexpr PathSpace operator&(PathSpace lhs, PathSpace rhs)
-{
-    return static_cast<PathSpace>(static_cast<std::underlying_type<PathSpace>::type>(lhs) &
-                                  static_cast<std::underlying_type<PathSpace>::type>(rhs));
-}
-
-inline constexpr PathSpace operator^(PathSpace lhs, PathSpace rhs)
-{
-    return static_cast<PathSpace>(static_cast<std::underlying_type<PathSpace>::type>(lhs) ^
-                                  static_cast<std::underlying_type<PathSpace>::type>(rhs));
-}
-
-inline constexpr PathSpace operator|(PathSpace lhs, PathSpace rhs)
-{
-    return static_cast<PathSpace>(static_cast<std::underlying_type<PathSpace>::type>(lhs) |
-                                  static_cast<std::underlying_type<PathSpace>::type>(rhs));
-}
-
-inline constexpr PathSpace operator~(PathSpace rhs)
-{
-    return static_cast<PathSpace>(~static_cast<std::underlying_type<PathSpace>::type>(rhs));
-}
-
-inline PathSpace& operator|=(PathSpace& lhs, PathSpace rhs)
-{
-    lhs = static_cast<PathSpace>(static_cast<std::underlying_type<PathSpace>::type>(lhs) |
-                                 static_cast<std::underlying_type<PathSpace>::type>(rhs));
-
-    return lhs;
-}
-
-inline PathSpace& operator&=(PathSpace& lhs, PathSpace rhs)
-{
-    lhs = static_cast<PathSpace>(static_cast<std::underlying_type<PathSpace>::type>(lhs) &
-                                 static_cast<std::underlying_type<PathSpace>::type>(rhs));
-
-    return lhs;
-}
-
-inline PathSpace& operator^=(PathSpace& lhs, PathSpace rhs)
-{
-    lhs = static_cast<PathSpace>(static_cast<std::underlying_type<PathSpace>::type>(lhs) ^
-                                 static_cast<std::underlying_type<PathSpace>::type>(rhs));
-
-    return lhs;
-}
-} // namespace rive
-
-#endif
diff --git a/include/rive/shapes/shape.hpp b/include/rive/shapes/shape.hpp
index 587d4aa..e55f794 100644
--- a/include/rive/shapes/shape.hpp
+++ b/include/rive/shapes/shape.hpp
@@ -44,7 +44,8 @@
     PathComposer* pathComposer() { return &m_PathComposer; }
 
     void pathChanged();
-    void addDefaultPathSpace(PathSpace space);
+    void addFlags(PathFlags flags);
+    bool isFlagged(PathFlags flags) const;
     StatusCode onAddedDirty(CoreContext* context) override;
     bool isEmpty();
     void pathCollapseChanged();
diff --git a/include/rive/shapes/shape_paint_container.hpp b/include/rive/shapes/shape_paint_container.hpp
index 7ba4d76..8d394ce 100644
--- a/include/rive/shapes/shape_paint_container.hpp
+++ b/include/rive/shapes/shape_paint_container.hpp
@@ -1,7 +1,7 @@
 #ifndef _RIVE_SHAPE_PAINT_CONTAINER_HPP_
 #define _RIVE_SHAPE_PAINT_CONTAINER_HPP_
 #include "rive/refcnt.hpp"
-#include "rive/shapes/path_space.hpp"
+#include "rive/shapes/path_flags.hpp"
 #include <vector>
 
 namespace rive
@@ -21,7 +21,7 @@
     // as a Shape or Artboard, so both of those will override this.
     virtual Artboard* getArtboard() = 0;
 
-    PathSpace m_DefaultPathSpace = PathSpace::Neither;
+    PathFlags m_pathFlags = PathFlags::none;
     std::vector<ShapePaint*> m_ShapePaints;
     void addPaint(ShapePaint* paint);
 
@@ -31,7 +31,7 @@
 
     virtual ~ShapePaintContainer() {}
 
-    PathSpace pathSpace() const;
+    PathFlags pathFlags() const;
 
     void invalidateStrokeEffects();
 
diff --git a/src/animation/state_machine_instance.cpp b/src/animation/state_machine_instance.cpp
index dd3f785..11225a1 100644
--- a/src/animation/state_machine_instance.cpp
+++ b/src/animation/state_machine_instance.cpp
@@ -631,37 +631,33 @@
     // Initialize listeners. Store a lookup table of shape id to hit shape
     // representation (an object that stores all the listeners triggered by the
     // shape producing a listener).
-    std::unordered_map<uint32_t, HitShape*> hitShapeLookup;
+    std::unordered_map<Component*, HitShape*> hitShapeLookup;
     for (std::size_t i = 0; i < machine->listenerCount(); i++)
     {
         auto listener = machine->listener(i);
-
-        // Iterate actual leaf hittable shapes tied to this listener and resolve
-        // corresponding ones in the artboard instance.
-        for (auto id : listener->hitShapeIds())
+        auto target = m_artboardInstance->resolve(listener->targetId());
+        if (target != nullptr && target->is<Component>())
         {
-            HitShape* hitShape;
-            auto itr = hitShapeLookup.find(id);
-            if (itr == hitShapeLookup.end())
-            {
-                auto shape = m_artboardInstance->resolve(id);
-                if (shape != nullptr && shape->is<Shape>())
+            target->as<Component>()->forAll([&](Component* component) {
+                if (component->is<Shape>())
                 {
-                    auto hs = rivestd::make_unique<HitShape>(shape->as<Component>(), this);
-                    hitShapeLookup[id] = hitShape = hs.get();
-                    m_hitComponents.push_back(std::move(hs));
+                    HitShape* hitShape;
+                    auto itr = hitShapeLookup.find(component);
+                    if (itr == hitShapeLookup.end())
+                    {
+                        component->as<Shape>()->addFlags(PathFlags::neverDeferUpdate);
+                        auto hs = rivestd::make_unique<HitShape>(component, this);
+                        hitShapeLookup[component] = hitShape = hs.get();
+                        m_hitComponents.push_back(std::move(hs));
+                    }
+                    else
+                    {
+                        hitShape = itr->second;
+                    }
+                    hitShape->listeners.push_back(listener);
                 }
-                else
-                {
-                    // No object or not a shape...
-                    continue;
-                }
-            }
-            else
-            {
-                hitShape = itr->second;
-            }
-            hitShape->listeners.push_back(listener);
+                return true;
+            });
         }
     }
 
@@ -669,7 +665,6 @@
     {
         if (nestedArtboard->hasNestedStateMachines())
         {
-
             auto hn =
                 rivestd::make_unique<HitNestedArtboard>(nestedArtboard->as<Component>(), this);
             m_hitComponents.push_back(std::move(hn));
diff --git a/src/animation/state_machine_listener.cpp b/src/animation/state_machine_listener.cpp
index f943f82..7abf521 100644
--- a/src/animation/state_machine_listener.cpp
+++ b/src/animation/state_machine_listener.cpp
@@ -14,7 +14,7 @@
 
 void StateMachineListener::addAction(std::unique_ptr<ListenerAction> action)
 {
-    m_Actions.push_back(std::move(action));
+    m_actions.push_back(std::move(action));
 }
 
 StatusCode StateMachineListener::import(ImportStack& importStack)
@@ -31,54 +31,18 @@
 
 const ListenerAction* StateMachineListener::action(size_t index) const
 {
-    if (index < m_Actions.size())
+    if (index < m_actions.size())
     {
-        return m_Actions[index].get();
+        return m_actions[index].get();
     }
     return nullptr;
 }
 
-StatusCode StateMachineListener::onAddedClean(CoreContext* context)
-{
-    auto artboard = static_cast<Artboard*>(context);
-    auto target = artboard->resolve(targetId());
-
-    for (auto core : artboard->objects())
-    {
-        if (core == nullptr)
-        {
-            continue;
-        }
-
-        // 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)
-                {
-                    auto index = artboard->idOf(shape);
-                    if (index != 0)
-                    {
-                        m_HitShapesIds.push_back(index);
-                    }
-                    break;
-                }
-            }
-        }
-    }
-
-    return Super::onAddedClean(context);
-}
-
 void StateMachineListener::performChanges(StateMachineInstance* stateMachineInstance,
                                           Vec2D position,
                                           Vec2D previousPosition) const
 {
-    for (auto& action : m_Actions)
+    for (auto& action : m_actions)
     {
         action->perform(stateMachineInstance, position, previousPosition);
     }
diff --git a/src/component.cpp b/src/component.cpp
index b68ffdf..eaf7003 100644
--- a/src/component.cpp
+++ b/src/component.cpp
@@ -99,4 +99,6 @@
     onDirty(m_Dirt);
     m_Artboard->onComponentDirty(this);
     return true;
-}
\ No newline at end of file
+}
+
+bool Component::forAll(std::function<bool(Component*)> predicate) { return predicate(this); }
\ No newline at end of file
diff --git a/src/constraints/follow_path_constraint.cpp b/src/constraints/follow_path_constraint.cpp
index 199a0db..8c74d9a 100644
--- a/src/constraints/follow_path_constraint.cpp
+++ b/src/constraints/follow_path_constraint.cpp
@@ -167,12 +167,12 @@
         if (m_Target->is<Shape>())
         {
             Shape* shape = static_cast<Shape*>(m_Target);
-            shape->addDefaultPathSpace(PathSpace::FollowPath);
+            shape->addFlags(PathFlags::followPath);
         }
         else if (m_Target->is<Path>())
         {
             Path* path = static_cast<Path*>(m_Target);
-            path->addDefaultPathSpace(PathSpace::FollowPath);
+            path->addFlags(PathFlags::followPath);
         }
     }
     return Super::onAddedClean(context);
diff --git a/src/container_component.cpp b/src/container_component.cpp
index 0a25260..af03348 100644
--- a/src/container_component.cpp
+++ b/src/container_component.cpp
@@ -14,4 +14,24 @@
         child->collapse(value);
     }
     return true;
+}
+
+bool ContainerComponent::forAll(std::function<bool(Component*)> predicate)
+{
+    if (!Super::forAll(predicate))
+    {
+        return false;
+    }
+    for (Component* child : m_children)
+    {
+        if (!predicate(child))
+        {
+            return false;
+        }
+        if (child->is<ContainerComponent>() && !child->as<ContainerComponent>()->forAll(predicate))
+        {
+            return false;
+        }
+    }
+    return true;
 }
\ No newline at end of file
diff --git a/src/shapes/clipping_shape.cpp b/src/shapes/clipping_shape.cpp
index cfa7f90..e795a6c 100644
--- a/src/shapes/clipping_shape.cpp
+++ b/src/shapes/clipping_shape.cpp
@@ -47,7 +47,7 @@
                 if (component == m_Source)
                 {
                     auto shape = core->as<Shape>();
-                    shape->addDefaultPathSpace(PathSpace::World | PathSpace::Clipping);
+                    shape->addFlags(PathFlags::world | PathFlags::clipping);
                     m_Shapes.push_back(shape);
                     break;
                 }
diff --git a/src/shapes/paint/fill.cpp b/src/shapes/paint/fill.cpp
index 466b61a..d0af7ef 100644
--- a/src/shapes/paint/fill.cpp
+++ b/src/shapes/paint/fill.cpp
@@ -2,7 +2,7 @@
 
 using namespace rive;
 
-PathSpace Fill::pathSpace() const { return PathSpace::Local; }
+PathFlags Fill::pathFlags() const { return PathFlags::local; }
 
 RenderPaint* Fill::initRenderPaint(ShapePaintMutator* mutator)
 {
diff --git a/src/shapes/paint/linear_gradient.cpp b/src/shapes/paint/linear_gradient.cpp
index aa49c94..0bcc1c6 100644
--- a/src/shapes/paint/linear_gradient.cpp
+++ b/src/shapes/paint/linear_gradient.cpp
@@ -84,7 +84,7 @@
                 ComponentDirt::Paint | ComponentDirt::RenderOpacity | ComponentDirt::Transform) ||
         (
             // paints in world space
-            parent()->as<ShapePaint>()->pathSpace() == PathSpace::World &&
+            parent()->as<ShapePaint>()->isFlagged(PathFlags::world) &&
             // and had a world transform change
             hasDirt(value, ComponentDirt::WorldTransform));
     if (rebuildGradient)
@@ -95,7 +95,7 @@
 
 void LinearGradient::applyTo(RenderPaint* renderPaint, float opacityModifier) const
 {
-    bool paintsInWorldSpace = parent()->as<ShapePaint>()->pathSpace() == PathSpace::World;
+    bool paintsInWorldSpace = parent()->as<ShapePaint>()->isFlagged(PathFlags::world);
     Vec2D start(startX(), startY());
     Vec2D end(endX(), endY());
     // Check if we need to update the world space gradient (if there's no
diff --git a/src/shapes/paint/stroke.cpp b/src/shapes/paint/stroke.cpp
index b0d7348..a76adae 100644
--- a/src/shapes/paint/stroke.cpp
+++ b/src/shapes/paint/stroke.cpp
@@ -6,11 +6,10 @@
 
 using namespace rive;
 
-PathSpace Stroke::pathSpace() const
+PathFlags Stroke::pathFlags() const
 {
-    return transformAffectsStroke() ? PathSpace::Local : PathSpace::World;
+    return transformAffectsStroke() ? PathFlags::local : PathFlags::world;
 }
-
 RenderPaint* Stroke::initRenderPaint(ShapePaintMutator* mutator)
 {
     auto renderPaint = Super::initRenderPaint(mutator);
diff --git a/src/shapes/path.cpp b/src/shapes/path.cpp
index cdf45aa..fd5af98 100644
--- a/src/shapes/path.cpp
+++ b/src/shapes/path.cpp
@@ -51,7 +51,8 @@
 
 void Path::addVertex(PathVertex* vertex) { m_Vertices.push_back(vertex); }
 
-void Path::addDefaultPathSpace(PathSpace space) { m_DefaultPathSpace |= space; }
+void Path::addFlags(PathFlags flags) { m_pathFlags |= flags; }
+bool Path::isFlagged(PathFlags flags) const { return (int)(m_pathFlags & flags) != 0x00; }
 
 bool Path::canDeferPathUpdate()
 {
@@ -60,10 +61,9 @@
     // (meaning all child paths need to follow path). This doesn't mean the
     // Shape is necessarily forced to update put the paths are, which is why we
     // explicitly also check the shape's path space.
-    return m_Shape->canDeferPathUpdate() &&
-           (m_Shape->pathSpace() & PathSpace::FollowPath) != PathSpace::FollowPath &&
-           ((m_DefaultPathSpace & PathSpace::Clipping) != PathSpace::Clipping) &&
-           ((m_DefaultPathSpace & PathSpace::FollowPath) != PathSpace::FollowPath);
+
+    return m_Shape->canDeferPathUpdate() && !m_Shape->isFlagged(PathFlags::followPath) &&
+           !isFlagged(PathFlags::followPath | PathFlags::clipping);
 }
 
 const Mat2D& Path::pathTransform() const { return worldTransform(); }
diff --git a/src/shapes/path_composer.cpp b/src/shapes/path_composer.cpp
index 71a4c00..5efda24 100644
--- a/src/shapes/path_composer.cpp
+++ b/src/shapes/path_composer.cpp
@@ -41,8 +41,7 @@
         }
         m_deferredPathDirt = false;
 
-        auto space = m_shape->pathSpace();
-        if ((space & PathSpace::Local) == PathSpace::Local)
+        if (m_shape->isFlagged(PathFlags::local))
         {
             if (m_localPath == nullptr)
             {
@@ -68,7 +67,7 @@
             // TODO: add a CommandPath::copy(RawPath)
             m_localRawPath.addTo(m_localPath.get());
         }
-        if ((space & PathSpace::World) == PathSpace::World)
+        if (m_shape->isFlagged(PathFlags::world))
         {
             if (m_worldPath == nullptr)
             {
diff --git a/src/shapes/shape.cpp b/src/shapes/shape.cpp
index 01310ed..6a22160 100644
--- a/src/shapes/shape.cpp
+++ b/src/shapes/shape.cpp
@@ -22,10 +22,13 @@
     m_Paths.push_back(path);
 }
 
+void Shape::addFlags(PathFlags flags) { m_pathFlags |= flags; }
+bool Shape::isFlagged(PathFlags flags) const { return (int)(pathFlags() & flags) != 0x00; }
+
 bool Shape::canDeferPathUpdate()
 {
     auto canDefer =
-        renderOpacity() == 0 && (pathSpace() & PathSpace::Clipping) != PathSpace::Clipping;
+        renderOpacity() == 0 && !isFlagged(PathFlags::clipping | PathFlags::neverDeferUpdate);
     if (canDefer)
     {
         // If we have a dependent Skin, don't defer the update
@@ -72,8 +75,7 @@
 
 void Shape::addToRenderPath(RenderPath* path, const Mat2D& transform)
 {
-    auto space = pathSpace();
-    if ((space & PathSpace::Local) == PathSpace::Local)
+    if (isFlagged(PathFlags::local))
     {
         path->addPath(m_PathComposer.localPath(), transform * worldTransform());
     }
@@ -100,7 +102,7 @@
                 continue;
             }
             renderer->save();
-            bool paintsInLocal = (shapePaint->pathSpace() & PathSpace::Local) == PathSpace::Local;
+            bool paintsInLocal = shapePaint->isFlagged(PathFlags::local);
             if (paintsInLocal)
             {
                 renderer->transform(worldTransform());
@@ -143,7 +145,7 @@
 
     // TODO: clip:
 
-    const bool shapeIsLocal = (pathSpace() & PathSpace::Local) == PathSpace::Local;
+    const bool shapeIsLocal = isFlagged(PathFlags::local);
 
     for (auto rit = m_ShapePaints.rbegin(); rit != m_ShapePaints.rend(); ++rit)
     {
@@ -157,7 +159,7 @@
             continue;
         }
 
-        auto paintIsLocal = (shapePaint->pathSpace() & PathSpace::Local) == PathSpace::Local;
+        auto paintIsLocal = shapePaint->isFlagged(PathFlags::local);
 
         auto mx = xform;
         if (paintIsLocal)
@@ -204,8 +206,6 @@
     }
 }
 
-void Shape::addDefaultPathSpace(PathSpace space) { m_DefaultPathSpace |= space; }
-
 StatusCode Shape::onAddedDirty(CoreContext* context)
 {
     auto code = Super::onAddedDirty(context);
diff --git a/src/shapes/shape_paint_container.cpp b/src/shapes/shape_paint_container.cpp
index ab9057a..37e5853 100644
--- a/src/shapes/shape_paint_container.cpp
+++ b/src/shapes/shape_paint_container.cpp
@@ -24,12 +24,12 @@
 
 void ShapePaintContainer::addPaint(ShapePaint* paint) { m_ShapePaints.push_back(paint); }
 
-PathSpace ShapePaintContainer::pathSpace() const
+PathFlags ShapePaintContainer::pathFlags() const
 {
-    PathSpace space = m_DefaultPathSpace;
+    PathFlags space = m_pathFlags;
     for (auto paint : m_ShapePaints)
     {
-        space |= paint->pathSpace();
+        space |= paint->pathFlags();
     }
     return space;
 }