Artboard object validation

This introduces an iterative validation cycle which allows for multi-level (parent->parent) validation by re-running the check whenever an object's validation state (valid/invalid) changes in the cycle.

Based on discussion here: https://2dimensions.slack.com/archives/CLLCU09T6/p1730395496000459

This makes it so an object which doesn't find a valid parent can itself be removed from the artboard and other objects which then depend on it can also invalidate if necessary.

Diffs=
ce39f8be9f Artboard object validation (#8464)

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
This commit is contained in:
luigi-rosso
2024-11-02 00:52:00 +00:00
parent 81de6c7333
commit 94d57165ab
11 changed files with 97 additions and 23 deletions

View File

@@ -1 +1 @@
bdb9eb01db0e9aac7a22708e72576c301cc071dc
ce39f8be9f60fd42581b088d1b523c1bb1a01139

View File

@@ -108,6 +108,7 @@ public:
public:
Artboard();
~Artboard() override;
bool validateObjects();
StatusCode initialize();
Core* resolve(uint32_t id) const override;

View File

@@ -29,6 +29,7 @@ public:
DependencyHelper<Artboard, Component> m_DependencyHelper;
virtual bool collapse(bool value);
inline Artboard* artboard() const { return m_Artboard; }
bool validate(CoreContext* context) override;
StatusCode onAddedDirty(CoreContext* context) override;
inline ContainerComponent* parent() const { return m_Parent; }
const std::vector<Component*>& dependents() const

View File

@@ -12,6 +12,8 @@ protected:
public:
void buildDependencies() override;
bool validate(CoreContext* context) override;
StatusCode onAddedDirty(CoreContext* context) override;
};
} // namespace rive

View File

@@ -46,6 +46,9 @@ public:
return static_cast<const T*>(this);
}
/// Called to validate the object can be used at runtime.
virtual bool validate(CoreContext* context) { return true; }
/// Called when the object is first added to the context, other objects
/// may not have resolved their dependencies yet. This is an opportunity
/// to look up objects referenced by id, but not assume that they in

View File

@@ -61,6 +61,7 @@ private:
class NestedAnimation : public NestedAnimationBase
{
public:
bool validate(CoreContext* context) override;
StatusCode onAddedDirty(CoreContext* context) override;
// Advance animations and apply them to the artboard.

View File

@@ -1,18 +1,27 @@
#include "rive/nested_animation.hpp"
#include "rive/container_component.hpp"
#include "rive/nested_artboard.hpp"
#include "rive/core_context.hpp"
using namespace rive;
bool NestedAnimation::validate(CoreContext* context)
{
if (!Super::validate(context))
{
return false;
}
auto parentObject = context->resolve(parentId());
// We know parentObject is not null from Super::validate().
return parentObject->is<NestedArtboard>();
}
StatusCode NestedAnimation::onAddedDirty(CoreContext* context)
{
StatusCode code = Super::onAddedDirty(context);
if (code == StatusCode::Ok)
{
if (!parent()->is<NestedArtboard>())
{
return StatusCode::InvalidObject;
}
auto nestedArtboard = parent()->as<NestedArtboard>();
nestedArtboard->addNestedAnimation(this);
}

View File

@@ -96,6 +96,54 @@ static bool canContinue(StatusCode code)
return code != StatusCode::InvalidObject;
}
bool Artboard::validateObjects()
{
auto size = m_Objects.size();
std::vector<bool> valid(size);
// Max iterations..
for (int cycle = 0; cycle < 100; cycle++)
{
bool changed = false;
for (size_t i = 1; i < size; i++)
{
auto object = m_Objects[i];
if (object == nullptr)
{
// objects can be null if they were not understood by this
// runtime.
continue;
}
bool wasValid = valid[i];
bool isValid = object->validate(this);
if (wasValid != isValid)
{
changed = true;
valid[i] = isValid;
}
}
if (changed)
{
// Delete invalid objects.
for (size_t i = 1; i < size; i++)
{
if (valid[i])
{
continue;
}
delete m_Objects[i];
m_Objects[i] = nullptr;
}
}
else
{
break;
}
}
return true;
}
StatusCode Artboard::initialize()
{
StatusCode code;

View File

@@ -9,6 +9,12 @@
using namespace rive;
bool Component::validate(CoreContext* context)
{
auto coreObject = context->resolve(parentId());
return coreObject != nullptr && coreObject->is<ContainerComponent>();
}
StatusCode Component::onAddedDirty(CoreContext* context)
{
m_Artboard = static_cast<Artboard*>(context);
@@ -18,12 +24,7 @@ StatusCode Component::onAddedDirty(CoreContext* context)
// We're the artboard, don't parent to ourselves.
return StatusCode::Ok;
}
auto coreObject = context->resolve(parentId());
if (coreObject == nullptr || !coreObject->is<ContainerComponent>())
{
return StatusCode::MissingObject;
}
m_Parent = static_cast<ContainerComponent*>(coreObject);
m_Parent = context->resolve(parentId())->as<ContainerComponent>();
m_Parent->addChild(this);
return StatusCode::Ok;
}

View File

@@ -4,6 +4,16 @@
using namespace rive;
bool TargetedConstraint::validate(CoreContext* context)
{
if (!Super::validate(context))
{
return false;
}
auto coreObject = context->resolve(targetId());
return coreObject != nullptr && coreObject->is<TransformComponent>();
}
StatusCode TargetedConstraint::onAddedDirty(CoreContext* context)
{
StatusCode code = Super::onAddedDirty(context);
@@ -11,13 +21,7 @@ StatusCode TargetedConstraint::onAddedDirty(CoreContext* context)
{
return code;
}
auto coreObject = context->resolve(targetId());
if (coreObject == nullptr || !coreObject->is<TransformComponent>())
{
return StatusCode::MissingObject;
}
m_Target = static_cast<TransformComponent*>(coreObject);
m_Target = context->resolve(targetId())->as<TransformComponent>();
return StatusCode::Ok;
}
@@ -26,8 +30,5 @@ void TargetedConstraint::buildDependencies()
{
// Targeted constraints must have their constrained component (parent)
// update after the target.
if (m_Target != nullptr)
{
m_Target->addDependent(parent());
}
m_Target->addDependent(parent());
}

View File

@@ -31,7 +31,14 @@ void ArtboardImporter::addDataBind(DataBind* dataBind)
m_Artboard->addDataBind(dataBind);
}
StatusCode ArtboardImporter::resolve() { return m_Artboard->initialize(); }
StatusCode ArtboardImporter::resolve()
{
if (!m_Artboard->validateObjects())
{
return StatusCode::InvalidObject;
}
return m_Artboard->initialize();
}
bool ArtboardImporter::readNullObject()
{