Skip to content

Commit

Permalink
Fix event callback crashes (#90)
Browse files Browse the repository at this point in the history
* Fix callbacks

Align with how it is done in the spine extension

* Handle multiple event properties
  • Loading branch information
britzl authored Apr 18, 2024
1 parent 5326371 commit 2edda78
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 149 deletions.
240 changes: 174 additions & 66 deletions defold-rive/src/comp_rive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#include <rive/animation/state_machine_number.hpp>
#include <rive/animation/loop.hpp>
#include <rive/bones/bone.hpp>
#include <rive/custom_property.hpp>
#include <rive/custom_property_boolean.hpp>
#include <rive/custom_property_number.hpp>
#include <rive/custom_property_string.hpp>
#include <rive/file.hpp>
#include <rive/renderer.hpp>

Expand All @@ -42,6 +46,7 @@
#include "renderer.h"

// DMSDK
#include <dmsdk/script.h>
#include <dmsdk/dlib/log.h>
#include <dmsdk/dlib/math.h>
#include <dmsdk/dlib/object_pool.h>
Expand All @@ -60,6 +65,13 @@
#include <dmsdk/gamesys/resources/res_animationset.h>
#include <dmsdk/gamesys/resources/res_textureset.h>

// Not in dmSDK yet
namespace dmScript
{
bool GetURL(lua_State* L, dmMessage::URL* out_url);
void PushURL(lua_State* L, const dmMessage::URL& m);
}

DM_PROPERTY_GROUP(rmtp_Rive, "Rive");
DM_PROPERTY_U32(rmtp_RiveBones, 0, FrameReset, "# rive bones", &rmtp_Rive);
DM_PROPERTY_U32(rmtp_RiveComponents, 0, FrameReset, "# rive components", &rmtp_Rive);
Expand All @@ -86,9 +98,6 @@ namespace dmRive
static void ResourceReloadedCallback(const dmResource::ResourceReloadedParams& params);
static void DestroyComponent(struct RiveWorld* world, uint32_t index);
static void CompRiveAnimationReset(RiveComponent* component);
static bool PlayAnimation(RiveComponent* component, dmRive::RiveSceneData* data, dmhash_t anim_id,
dmGameObject::Playback playback_mode, float offset, float playback_rate);
static bool PlayStateMachine(RiveComponent* component, dmRive::RiveSceneData* data, dmhash_t anim_id, float playback_rate);
static bool CreateBones(struct RiveWorld* world, RiveComponent* component);
static void DeleteBones(RiveComponent* component);
static void UpdateBones(RiveComponent* component);
Expand Down Expand Up @@ -240,14 +249,26 @@ namespace dmRive

if (empty_id != state_machine_id)
{
if (!PlayStateMachine(component, data, state_machine_id, 1.0f))
dmRiveDDF::RivePlayAnimation ddf;
ddf.m_AnimationId = state_machine_id;
ddf.m_Playback = 0;
ddf.m_Offset = 0.0;
ddf.m_PlaybackRate = 1.0;
ddf.m_IsStateMachine = true;
if (!CompRivePlayStateMachine(component, &ddf, 0))
{
dmLogError("Couldn't play state machine named '%s'", dmHashReverseSafe64(state_machine_id));
}
}
else if (empty_id != anim_id)
{
if (!PlayAnimation(component, data, anim_id, dmGameObject::PLAYBACK_LOOP_FORWARD, 0.0f, 1.0f))
dmRiveDDF::RivePlayAnimation ddf;
ddf.m_AnimationId = anim_id;
ddf.m_Playback = dmGameObject::PLAYBACK_LOOP_FORWARD;
ddf.m_Offset = 0.0;
ddf.m_PlaybackRate = 1.0;
ddf.m_IsStateMachine = false;
if (!CompRivePlayAnimation(component, &ddf, 0))
{
dmLogError("Couldn't play animation named '%s'", dmHashReverseSafe64(anim_id));
}
Expand All @@ -259,6 +280,41 @@ namespace dmRive
return dmGameObject::CREATE_RESULT_OK;
}

static void CompRiveRunCallback(RiveComponent* component, const dmDDF::Descriptor* desc, const char* data, const dmMessage::URL* sender)
{
dmScript::LuaCallbackInfo* cbk = component->m_Callback;
if (!dmScript::IsCallbackValid(cbk))
{
dmLogError("Rive callback is invalid.");
return;
}

lua_State* L = dmScript::GetCallbackLuaContext(cbk);
DM_LUA_STACK_CHECK(L, 0);

if (!dmScript::SetupCallback(cbk))
{
dmLogError("Failed to setup Rive callback");
return;
}

dmScript::PushHash(L, desc->m_NameHash);
dmScript::PushDDF(L, desc, data, false);
dmScript::PushURL(L, *sender);
int ret = dmScript::PCall(L, 4, 0);
(void)ret;
dmScript::TeardownCallback(cbk);
}

static void CompRiveClearCallback(RiveComponent* component)
{
if (component->m_Callback)
{
dmScript::DestroyCallback(component->m_Callback);
component->m_Callback = 0x0;
}
}

static void DestroyComponent(RiveWorld* world, uint32_t index)
{
RiveComponent* component = GetComponentFromIndex(world, index);
Expand All @@ -284,6 +340,9 @@ namespace dmRive
if (component->m_Material) {
dmResource::Release(ctx->m_Factory, (void*)component->m_Material);
}
if (component->m_Callback) {
CompRiveClearCallback(component);
}
DestroyComponent(world, index);
return dmGameObject::CREATE_RESULT_OK;
}
Expand Down Expand Up @@ -480,7 +539,6 @@ namespace dmRive
static void CompRiveAnimationReset(RiveComponent* component)
{
component->m_AnimationIndex = 0xff;
component->m_AnimationCallbackRef = 0;
component->m_AnimationPlaybackRate = 1.0f;
component->m_AnimationPlayback = dmGameObject::PLAYBACK_NONE;

Expand All @@ -490,65 +548,108 @@ namespace dmRive
component->m_StateMachineInputs.SetSize(0);
}

static void CompRiveAnimationDoneCallback(RiveComponent& component)
static void CompRiveAnimationDoneCallback(RiveComponent* component)
{
assert(component.m_AnimationInstance);
dmMessage::URL sender;
dmMessage::URL receiver = component.m_Listener;
assert(component->m_AnimationInstance);

if (!GetSender(&component, &sender))
dmMessage::URL sender;
dmMessage::URL receiver = component->m_Listener;
if (!GetSender(component, &sender))
{
dmLogError("Could not send animation_done to listener because of incomplete component.");
return;
}

dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component.m_Resource->m_Scene->m_Scene;
dmhash_t message_id = dmRiveDDF::RiveAnimationDone::m_DDFDescriptor->m_NameHash;
dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene;

dmRiveDDF::RiveAnimationDone message;
message.m_AnimationId = data->m_LinearAnimations[component.m_AnimationIndex];
message.m_Playback = component.m_AnimationPlayback;
message.m_AnimationId = data->m_LinearAnimations[component->m_AnimationIndex];
message.m_Playback = component->m_AnimationPlayback;

uintptr_t descriptor = (uintptr_t)dmRiveDDF::RiveAnimationDone::m_DDFDescriptor;
uint32_t data_size = sizeof(dmRiveDDF::RiveAnimationDone);
if (component->m_Callback)
{
uint32_t id = component->m_CallbackId;
CompRiveRunCallback(component, dmRiveDDF::RiveAnimationDone::m_DDFDescriptor, (const char*)&message, &sender);

dmMessage::Result result = dmMessage::Post(&sender, &receiver, message_id, 0, component.m_AnimationCallbackRef, descriptor, &message, data_size, 0);
dmMessage::ResetURL(&component.m_Listener);
if (result != dmMessage::RESULT_OK)
// Make sure we're clearing the same callback as we ran above and not a new
// callback that was started from the original callback
if (id == component->m_CallbackId)
{
CompRiveClearCallback(component);
}
}
else
{
dmLogError("Could not send animation_done to listener.");
dmGameObject::Result result = dmGameObject::PostDDF(&message, &sender, &receiver, 0, true);
if (result != dmGameObject::RESULT_OK)
{
dmLogError("Could not send animation_done to listener: %d", result);
}
}
}

static void CompRiveEventTriggerCallback(RiveComponent& component, const char* name)
static void CompRiveEventTriggerCallback(RiveComponent* component, rive::Event* event)
{
dmMessage::URL sender;
dmMessage::URL receiver = component.m_Listener;

if (!GetSender(&component, &sender))
dmMessage::URL receiver = component->m_Listener;
if (!GetSender(component, &sender))
{
dmLogError("Could not send event_trigger to listener because of incomplete component.");
return;
}

dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component.m_Resource->m_Scene->m_Scene;
dmhash_t message_id = dmRiveDDF::RiveEventTrigger::m_DDFDescriptor->m_NameHash;

dmRiveDDF::RiveEventTrigger message;
message.m_Name = name;
// message.m_Name = "foobar\0";
// message.m_Trigger = 1;
// message.m_Number = 1234;
// message.m_Text = "Hello";
// trigger event once per event property
for (auto child : event->children())
{
if (child->is<rive::CustomProperty>())
{
if (!child->name().empty())
{
dmRiveDDF::RiveEventTrigger message;
message.m_Name = child->name().c_str();
message.m_Trigger = 0;
message.m_Text = "";
message.m_Number = 0.0f;
switch (child->coreType())
{
case rive::CustomPropertyBoolean::typeKey:
{
bool b = child->as<rive::CustomPropertyBoolean>()->propertyValue();
message.m_Trigger = b;
break;
}
case rive::CustomPropertyString::typeKey:
{
const char* s = child->as<rive::CustomPropertyString>()->propertyValue().c_str();
message.m_Text = s;
break;
}
case rive::CustomPropertyNumber::typeKey:
{
float f = child->as<rive::CustomPropertyNumber>()->propertyValue();
message.m_Number = f;
break;
}
}

uintptr_t descriptor = (uintptr_t)dmRiveDDF::RiveEventTrigger::m_DDFDescriptor;
uint32_t data_size = sizeof(dmRiveDDF::RiveEventTrigger);
if (component->m_Callback)
{
dmScript::LuaCallbackInfo* callback = component->m_Callback;
CompRiveRunCallback(component, dmRiveDDF::RiveEventTrigger::m_DDFDescriptor, (const char*)&message, &sender);

dmMessage::Result result = dmMessage::Post(&sender, &receiver, message_id, 0, component.m_AnimationCallbackRef, descriptor, &message, data_size, 0);
dmMessage::ResetURL(&component.m_Listener);
if (result != dmMessage::RESULT_OK)
{
dmLogError("Could not send event_trigger to listener. %d", result);
// note that we are not clearing the callback here since multiple events can fire at
// different times during the playback
}
else
{
dmGameObject::Result result = dmGameObject::PostDDF(&message, &sender, &receiver, 0, true);
if (result != dmGameObject::RESULT_OK)
{
dmLogError("Could not send event_trigger to listener: %d", result);
}
}
}
}
}
}

Expand Down Expand Up @@ -593,9 +694,8 @@ namespace dmRive
for (size_t i = 0; i < event_count; i++)
{
rive::EventReport reported_event = component.m_StateMachineInstance->reportedEventAt(i);
dmLogInfo("reported event count %zu, %s", event_count, reported_event.event()->name().c_str());
const char* name = reported_event.event()->name().c_str();
CompRiveEventTriggerCallback(component, name);
rive::Event* event = reported_event.event();
CompRiveEventTriggerCallback(&component, event);
}
}
else if (component.m_AnimationInstance)
Expand All @@ -621,7 +721,7 @@ namespace dmRive

if (did_finish)
{
CompRiveAnimationDoneCallback(component);
CompRiveAnimationDoneCallback(&component);
CompRiveAnimationReset(&component);
}
}
Expand Down Expand Up @@ -767,9 +867,14 @@ namespace dmRive
return artboard->stateMachine(index);
}

static bool PlayAnimation(RiveComponent* component, dmRive::RiveSceneData* data, dmhash_t anim_id,
dmGameObject::Playback playback_mode, float offset, float playback_rate)
bool CompRivePlayAnimation(RiveComponent* component, dmRiveDDF::RivePlayAnimation* ddf, dmScript::LuaCallbackInfo* callback_info)
{
dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene;
dmhash_t anim_id = ddf->m_AnimationId;
dmGameObject::Playback playback_mode = (dmGameObject::Playback)ddf->m_Playback;
float offset = ddf->m_Offset;
float playback_rate = ddf->m_PlaybackRate;

int animation_index;
rive::LinearAnimation* animation = FindAnimation(component->m_ArtboardInstance.get(), data, &animation_index, anim_id);

Expand All @@ -778,6 +883,7 @@ namespace dmRive
}

CompRiveAnimationReset(component);
CompRiveClearCallback(component);

rive::Loop loop_value = rive::Loop::oneShot;
int play_direction = 1;
Expand Down Expand Up @@ -813,6 +919,8 @@ namespace dmRive
default:break;
}

component->m_Callback = callback_info;
component->m_CallbackId++;
component->m_AnimationIndex = animation_index;
component->m_AnimationPlaybackRate = playback_rate;
component->m_AnimationPlayback = playback_mode;
Expand All @@ -825,8 +933,12 @@ namespace dmRive
return true;
}

static bool PlayStateMachine(RiveComponent* component, dmRive::RiveSceneData* data, dmhash_t anim_id, float playback_rate)
bool CompRivePlayStateMachine(RiveComponent* component, dmRiveDDF::RivePlayAnimation* ddf, dmScript::LuaCallbackInfo* callback_info)
{
dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene;
dmhash_t anim_id = ddf->m_AnimationId;
float playback_rate = ddf->m_PlaybackRate;

int state_machine_index;
rive::StateMachine* state_machine = FindStateMachine(component->m_ArtboardInstance.get(), data, &state_machine_index, anim_id);

Expand All @@ -835,7 +947,10 @@ namespace dmRive
}

CompRiveAnimationReset(component);
CompRiveClearCallback(component);

component->m_Callback = callback_info;
component->m_CallbackId++;
component->m_AnimationInstance = nullptr;
component->m_StateMachineInstance = component->m_ArtboardInstance->stateMachineAt(state_machine_index);
component->m_AnimationPlaybackRate = playback_rate;
Expand Down Expand Up @@ -873,27 +988,20 @@ namespace dmRive
if (params.m_Message->m_Id == dmRiveDDF::RivePlayAnimation::m_DDFDescriptor->m_NameHash)
{
dmRiveDDF::RivePlayAnimation* ddf = (dmRiveDDF::RivePlayAnimation*)params.m_Message->m_Data;
dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene;

dmhash_t anim_id = ddf->m_AnimationId;

if (ddf->m_IsStateMachine)
{
bool result = PlayStateMachine(component, data, anim_id, ddf->m_PlaybackRate);
if (result) {
component->m_AnimationCallbackRef = params.m_Message->m_UserData2;
component->m_Listener = params.m_Message->m_Sender;
} else {
dmLogError("Couldn't play state machine named '%s'", dmHashReverseSafe64(anim_id));
bool result = CompRivePlayStateMachine(component, ddf, 0);
if (!result)
{
dmLogError("Couldn't play state machine named '%s'", dmHashReverseSafe64(ddf->m_AnimationId));
}

} else {
bool result = PlayAnimation(component, data, anim_id, (dmGameObject::Playback)ddf->m_Playback, ddf->m_Offset, ddf->m_PlaybackRate);
if (result) {
component->m_AnimationCallbackRef = params.m_Message->m_UserData2;
component->m_Listener = params.m_Message->m_Sender;
} else {
dmLogError("Couldn't play animation named '%s'", dmHashReverseSafe64(anim_id));
}
else
{
bool result = CompRivePlayAnimation(component, ddf, 0);
if (!result)
{
dmLogError("Couldn't play animation named '%s'", dmHashReverseSafe64(ddf->m_AnimationId));
}
}
}
Expand Down
Loading

0 comments on commit 2edda78

Please sign in to comment.