Handle events
React to dotLottie playback events from C by draining the event queue each frame, and read the frame number and loop count each event carries.
The runtime reports what the player is doing — loaded, started playing, completed a loop — through a queue you drain, rather than through callbacks you register.
Why polling instead of callbacks
Callbacks across an FFI boundary mean handing a function pointer to the library and having it invoked from whatever thread and stack the library happens to be on. That constrains what you can safely do inside the handler and complicates lifetime management on both sides.
Queued events avoid all of it. Events accumulate as playback progresses, and you consume them at a point you choose, on your own thread, where your application state is already accessible.
Draining the queue
dotlottie_poll_event() writes one event into a struct you provide and tells you whether it wrote anything:
| Return | Meaning |
1 | An event was written |
0 | The queue is empty |
-1 | A null player or event pointer |
Drain it fully with a while loop, since more than one event can be queued per tick:
dotlottieDotLottiePlayerEvent event;
while (dotlottie_poll_event(player, &event) == 1) {
switch (event.event_type) {
case Load:
printf("animation loaded\n");
break;
case LoadError:
fprintf(stderr, "animation failed to load\n");
break;
case Loop:
printf("completed loop %u\n", event.data.loop_count);
break;
case Complete:
printf("playback finished\n");
break;
default:
break;
}
}Poll once per iteration of your render loop, after dotlottie_tick(). Events that are never polled stay queued and accumulate, so drain the queue even if you only care about a couple of event types.
Event types
| Type | Numeric | Fires when | Payload |
Load | 0 | An animation finished loading | — |
LoadError | 1 | An animation failed to load | — |
Play | 2 | Playback started | — |
Pause | 3 | Playback paused | — |
Stop | 4 | Playback stopped and reset | — |
Frame | 5 | The current frame advanced | frame_no |
Render | 6 | A frame was drawn | frame_no |
Loop | 7 | A loop iteration completed | loop_count |
Complete | 8 | Playback finished and will not continue | — |
The values are emitted bare — Load, Frame, Loop, Complete — not DotLottiePlayerEventType_Load. Some doc
comments inside the generated header show the prefixed spelling, but those identifiers do not exist and will not
compile. Because names this common share the global namespace, watch for collisions with your own macros and enums.
Reading the payload
Two event types carry data, in a union whose active member is determined by the event type:
switch (event.event_type) {
case Frame:
case Render:
printf("frame %.2f\n", event.data.frame_no); /* float */
break;
case Loop:
printf("loop %u\n", event.data.loop_count); /* uint32_t */
break;
default:
break; /* no payload */
}Reading frame_no on a Loop event, or loop_count on a Frame event, reinterprets unrelated bytes and yields nonsense. Always branch on event_type first.
Frame and Render fire every time the animation advances or draws — many times per second. Logging from those handlers will flood your output.
Where events fit in the loop
double last = now_seconds();
for (;;) {
double current = now_seconds();
float dt = (float)(current - last);
last = current;
bool rendered = false;
dotlottie_tick(player, dt, &rendered);
dotlottieDotLottiePlayerEvent event;
while (dotlottie_poll_event(player, &event) == 1) {
handle_event(&event);
}
if (rendered) {
present(buffer, width, height);
}
}For the common cases you don't need events at all — dotlottie_is_complete(), dotlottie_status(), and dotlottie_get_current_loop_count() answer the same questions by direct query. Events are most useful when you want to act at the exact moment something changes, such as starting a sound on Loop or advancing UI state on Complete.
State machine events
State machines have their own separate queues, polled with dotlottie_state_machine_poll_event() and dotlottie_state_machine_poll_internal_event(). Their events report transitions and input changes rather than playback progress, and unlike player events they contain string pointers with a limited lifetime. See State machines for details.
Learn more
Control playback — the tick loop events hang off
Memory and safety rules — event string lifetimes
C API reference — full signatures