fix: lua error on in-flight path mutation (#11350) 56f08da6b1

* fix: lua error on in-flight path mutation

* fix: remove DrawOption

* fix: failing tests

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
This commit is contained in:
luigi-rosso
2025-12-23 23:43:07 +00:00
parent 5a94a09dfb
commit c3ff521ac8
8 changed files with 74 additions and 69 deletions

View File

@@ -1 +1 @@
923b32059d2ea012fdb47ae141b304f57095a48b
56f08da6b1998d346fa791770d7a62a50466e0f7

View File

@@ -102,6 +102,7 @@ private:
bool m_didChange = true;
Artboard* parentArtboard() const;
ArtboardHost* m_host = nullptr;
static uint64_t sm_frameId;
bool sharesLayoutWithHost() const;
void cloneObjectDataBinds(const Core* object,
Core* clone,
@@ -128,6 +129,10 @@ private:
void update(ComponentDirt value) override;
public:
static uint64_t frameId() { return sm_frameId; }
#ifdef TESTING
static void incFrameId() { sm_frameId++; }
#endif
void updateDataBinds(bool applyTargetToSource = true) override;
void host(ArtboardHost* artboardHost);
ArtboardHost* host() const;
@@ -229,13 +234,7 @@ public:
Drawable* firstDrawable() { return m_FirstDrawable; };
void addScriptedObject(ScriptedObject* object);
enum class DrawOption
{
kNormal,
kHideBG,
kHideFG,
};
void draw(Renderer* renderer, DrawOption option);
void drawInternal(Renderer* renderer);
void draw(Renderer* renderer) override;
void addToRenderPath(RenderPath* path, const Mat2D& transform);
void addToRawPath(RawPath& path, const Mat2D* transform);

View File

@@ -270,6 +270,7 @@ public:
static constexpr bool hasMetatable = true;
private:
uint64_t m_renderFrameId = 0;
};
class ScriptedGradient

View File

@@ -43,6 +43,8 @@
using namespace rive;
uint64_t Artboard::sm_frameId = 0;
Artboard::Artboard()
{
// Artboards need to override default clip value to true.
@@ -1390,9 +1392,13 @@ bool Artboard::hitTestPoint(const Vec2D& position,
isPrimaryHit);
}
void Artboard::draw(Renderer* renderer) { draw(renderer, DrawOption::kNormal); }
void Artboard::draw(Renderer* renderer)
{
sm_frameId++;
drawInternal(renderer);
}
void Artboard::draw(Renderer* renderer, DrawOption option)
void Artboard::drawInternal(Renderer* renderer)
{
RIVE_PROF_SCOPE()
m_didChange = false;
@@ -1418,69 +1424,61 @@ void Artboard::draw(Renderer* renderer, DrawOption option)
renderer->transform(artboardTransform);
}
if (option != DrawOption::kHideBG)
for (auto shapePaint : m_ShapePaints)
{
for (auto shapePaint : m_ShapePaints)
if (!shapePaint->shouldDraw())
{
if (!shapePaint->shouldDraw())
{
continue;
}
auto shapePaintPath = shapePaint->pickPath(this);
if (shapePaintPath == nullptr)
{
continue;
}
shapePaint->draw(renderer, shapePaintPath, worldTransform());
continue;
}
auto shapePaintPath = shapePaint->pickPath(this);
if (shapePaintPath == nullptr)
{
continue;
}
shapePaint->draw(renderer, shapePaintPath, worldTransform());
}
if (option != DrawOption::kHideFG)
// Empty clips is a counter for clipping shapes that are empty, for
// example because they are hidden in a solo. If emptyClips > 0, the
// drawables should not be drawn.
int emptyClips = 0;
// We stack clip operations to avoid calling a save + clip + restore on
// clipping that don't have any drawables in between. this is a common
// case with drawables in solos where the drawables are not drawn.
std::vector<Drawable*> pendingClipOperations;
for (auto drawable = m_FirstDrawable; drawable != nullptr;
drawable = drawable->prev)
{
// Empty clips is a counter for clipping shapes that are empty, for
// example because they are hidden in a solo. If emptyClips > 0, the
// drawables should not be drawn.
int emptyClips = 0;
// We stack clip operations to avoid calling a save + clip + restore on
// clipping that don't have any drawables in between. this is a common
// case with drawables in solos where the drawables are not drawn.
std::vector<Drawable*> pendingClipOperations;
for (auto drawable = m_FirstDrawable; drawable != nullptr;
drawable = drawable->prev)
auto prevClips = emptyClips;
emptyClips += drawable->emptyClipCount();
if (!drawable->willDraw() || emptyClips != prevClips || emptyClips > 0)
{
auto prevClips = emptyClips;
emptyClips += drawable->emptyClipCount();
if (!drawable->willDraw() || emptyClips != prevClips ||
emptyClips > 0)
{
continue;
}
if (drawable->isClipStart())
{
pendingClipOperations.push_back(drawable);
continue;
}
else if (pendingClipOperations.size() > 0)
{
// If there are clip operations pending and the next drawable is
// a clip end, the clipping operation does not clip anything and
// both can be skipped.
if (drawable->isClipEnd())
{
pendingClipOperations.pop_back();
continue;
}
else
{
for (auto& pendingClip : pendingClipOperations)
{
pendingClip->draw(renderer);
}
pendingClipOperations.clear();
}
}
drawable->draw(renderer);
continue;
}
if (drawable->isClipStart())
{
pendingClipOperations.push_back(drawable);
continue;
}
else if (pendingClipOperations.size() > 0)
{
// If there are clip operations pending and the next drawable is
// a clip end, the clipping operation does not clip anything and
// both can be skipped.
if (drawable->isClipEnd())
{
pendingClipOperations.pop_back();
continue;
}
else
{
for (auto& pendingClip : pendingClipOperations)
{
pendingClip->draw(renderer);
}
pendingClipOperations.clear();
}
}
drawable->draw(renderer);
}
if (save)
{

View File

@@ -468,7 +468,7 @@ void ArtboardComponentList::draw(Renderer* renderer)
renderer->save();
auto transform = m_artboardTransforms[artboard];
renderer->transform(transform);
artboard->draw(renderer);
artboard->drawInternal(renderer);
renderer->restore();
}
if (i == endIndex)
@@ -490,7 +490,7 @@ void ArtboardComponentList::draw(Renderer* renderer)
renderer->save();
auto transform = m_artboardTransforms[artboard];
renderer->transform(transform);
artboard->draw(renderer);
artboard->drawInternal(renderer);
renderer->restore();
}
}

View File

@@ -16,6 +16,11 @@ RenderPath* ScriptedPath::renderPath(lua_State* L)
if (m_isRenderPathDirty)
{
m_isRenderPathDirty = false;
if (m_renderFrameId == Artboard::frameId())
{
luaL_error(L, "Path was modified between draws in the same frame.");
}
m_renderFrameId = Artboard::frameId();
if (!m_renderPath)
{
ScriptingContext* context =

View File

@@ -163,7 +163,7 @@ void NestedArtboard::draw(Renderer* renderer)
renderer->save();
}
renderer->transform(worldTransform());
m_Artboard->draw(renderer);
m_Artboard->drawInternal(renderer);
if (m_needsSaveOperation)
{
renderer->restore();

View File

@@ -1,6 +1,7 @@
#include "utils/serializing_factory.hpp"
#include "rive/decoders/bitmap_decoder.hpp"
#include "rive/core/binary_reader.hpp"
#include "rive/artboard.hpp"
#include <cstring>
#include <stdlib.h>
#include <filesystem>
@@ -623,6 +624,7 @@ std::unique_ptr<Renderer> SerializingFactory::makeRenderer()
void SerializingFactory::addFrame()
{
Artboard::incFrameId();
m_writer.writeVarUint((uint32_t)SerializeOp::frame);
}