pebble/tests/fw/ui/test_animation.c

3754 lines
138 KiB
C
Raw Permalink Normal View History

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "clar.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/gtransform.h"
#include "applib/ui/layer.h"
#include "applib/ui/property_animation.h"
#include "applib/ui/property_animation_private.h"
#include "applib/ui/animation.h"
#include "applib/ui/animation_private.h"
#include "applib/legacy2/ui/animation_private_legacy2.h"
#include "util/math.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
///////////////////////////////////////////////////////////
// Stubs
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_app_state.h"
#include "stubs_app_timer.h"
#include "stubs_freertos.h"
#include "stubs_heap.h"
#include "stubs_mutex.h"
#include "stubs_resources.h"
#include "stubs_serial.h"
#include "stubs_syscalls.h"
#include "stubs_unobstructed_area.h"
// Fakes
#include "fake_new_timer.h"
#include "fake_pebble_tasks.h"
#include "fake_pbl_malloc.h"
#include "fake_rtc.h"
#include "fake_events.h"
#define TEST_INCLUDE_BASIC
#define TEST_INCLUDE_COMPLEX
#define DEBUG_TEST
#ifdef DEBUG_TEST
#define DPRINTF(fmt, ...) \
do { printf("%s: " fmt , __func__, ## __VA_ARGS__); \
} while (0)
#else
#define DPRINTF(fmt, ...)
#endif
// Use our own macro instead of the abs function to avoid issues with non-int values.
#define ABSOLUTE_VALUE(x) (((x) > 0) ? (x) : -(x))
#define MIN_FRAME_INTERVAL_MS 33
#define TEST_ANIMATION_NORMALIZED_HIGH 50000
#define TEST_ANIMATION_NORMALIZED_LOW 5000
static PebbleEvent s_last_event;
bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent* e) {
s_last_event = *e;
return true;
}
bool process_manager_compiled_with_legacy2_sdk(void) {
return false;
}
GDrawState graphics_context_get_drawing_state(GContext *ctx) {
return (GDrawState) { };
}
bool graphics_release_frame_buffer(GContext *ctx, GBitmap *buffer) {
return false;
}
void graphics_context_set_drawing_state(GContext *ctx, GDrawState draw_state) {
}
void window_schedule_render(struct Window *window) {
}
TimerID animation_service_test_get_timer_id(void);
// --------------------------------------------------------------------------------------
static void cl_assert_equal_point(const GPoint a, const GPoint b) {
cl_assert_equal_i(a.x, b.x);
cl_assert_equal_i(a.y, b.y);
}
// --------------------------------------------------------------------------------------
static void cl_assert_equal_rect(const GRect a, const GRect b) {
cl_assert_equal_point(a.origin, b.origin);
cl_assert_equal_i(a.size.w, b.size.w);
cl_assert_equal_i(a.size.h, b.size.h);
}
// --------------------------------------------------------------------------------------
static void cl_assert_equal_gtransform(const GTransform a, const GTransform b) {
cl_assert_equal_i(a.a.raw_value, b.a.raw_value);
cl_assert_equal_i(a.b.raw_value, b.b.raw_value);
cl_assert_equal_i(a.c.raw_value, b.c.raw_value);
cl_assert_equal_i(a.d.raw_value, b.d.raw_value);
cl_assert_equal_i(a.tx.raw_value, b.tx.raw_value);
cl_assert_equal_i(a.ty.raw_value, b.ty.raw_value);
}
static void cl_assert_close_gtransform(const GTransform a, const GTransform b) {
cl_assert(abs(a.a.integer - b.a.integer) < 10);
cl_assert(abs(a.b.integer - b.b.integer) < 10);
cl_assert(abs(a.c.integer - b.c.integer) < 10);
cl_assert(abs(a.d.integer - b.d.integer) < 10);
cl_assert(abs(a.tx.integer - b.tx.integer) < 10);
cl_assert(abs(a.ty.integer - b.ty.integer) < 10);
}
// --------------------------------------------------------------------------------------
static void cl_assert_equal_fixed_s32_16(const Fixed_S32_16 a, const Fixed_S32_16 b) {
cl_assert_equal_i(a.raw_value, b.raw_value);
}
static void cl_assert_close_fixed_s32_16(const Fixed_S32_16 a, const Fixed_S32_16 b) {
cl_assert(abs(a.integer - b.integer) < 10);
}
// --------------------------------------------------------------------------------------
// Get current time in ms.
static uint64_t prv_now_ms(void) {
RtcTicks ticks = rtc_get_ticks();
return (ticks * 1000 + RTC_TICKS_HZ / 2) / RTC_TICKS_HZ;
}
// --------------------------------------------------------------------------------------
// Advance current time by N ms. This does NOT check to see if the timer should fire
// If you want to advance time and fire all timers that would have fired during that time
// call prv_advance_to_ms_with_timers()
static void prv_advance_by_ms_no_timers(uint64_t ms_delta) {
uint64_t target_ms = prv_now_ms() + ms_delta;
// Comppensate for rounding errors
uint64_t new_ticks = rtc_get_ticks() + (ms_delta * RTC_TICKS_HZ + 500 ) / 1000;
uint64_t new_ms = (new_ticks * 1000 + RTC_TICKS_HZ / 2) / RTC_TICKS_HZ;
if (new_ms == target_ms - 1) {
new_ticks++;
} else if (new_ms == target_ms + 1) {
new_ticks--;
}
new_ms = (new_ticks * 1000 + RTC_TICKS_HZ / 2) / RTC_TICKS_HZ;
cl_assert(new_ms == target_ms);
fake_rtc_set_ticks(new_ticks);
}
// --------------------------------------------------------------------------------------
// Fire the timer used by the animation service. Before doing so, advance the time to when
// the timer would fire next.
static void prv_fire_animation_timer(void) {
TimerID sys_timer_id = animation_service_test_get_timer_id();
if (!sys_timer_id) {
DPRINTF("timer not scheduled\n");
return;
}
if (!stub_new_timer_is_scheduled(sys_timer_id)) {
DPRINTF("timer not scheduled\n");
return;
}
// Advance time
uint32_t ms_delta = stub_new_timer_timeout(sys_timer_id);
prv_advance_by_ms_no_timers(ms_delta);
// This posts a callback event to the KernelMain event queue
stub_new_timer_fire(sys_timer_id);
// Get the callback event and process it
PebbleEvent evt = fake_event_get_last();
cl_assert_equal_i(evt.type, PEBBLE_CALLBACK_EVENT);
evt.callback.callback(evt.callback.data);
}
// --------------------------------------------------------------------------------------
// Advance to the given time, firing all timers that are scheduled along the way
static void prv_advance_to_ms_with_timers(uint64_t dst_time) {
uint64_t now = prv_now_ms();
while (now < dst_time) {
TimerID sys_timer_id = animation_service_test_get_timer_id();
if (!sys_timer_id) {
DPRINTF("timer not created\n");
prv_advance_by_ms_no_timers(dst_time - now);
return;
}
if (!stub_new_timer_is_scheduled(sys_timer_id)) {
DPRINTF("timer not scheduled\n");
prv_advance_by_ms_no_timers(dst_time - now);
return;
}
// Advance time to when timer would fire and fire it
uint32_t ms_delta = stub_new_timer_timeout(sys_timer_id);
if (ms_delta < dst_time - now) {
prv_fire_animation_timer();
} else {
prv_advance_by_ms_no_timers(dst_time - now);
return;
}
now = prv_now_ms();
}
}
// =============================================================================================
// Started. stopped, setup, and teardown handler call histories. Every time a handler runs, we
// append the time animation handle and timestamp to the history list.
typedef struct {
uint64_t fired_time_ms;
uint32_t fire_order;
bool finished; // only applicable for stopped handlers
void *context; // For update handler, this is the distance arg
Animation *animation; // which animation
} AnimTestHandlerEntry;
#define MAX_HANDLER_CALLS 500
typedef struct {
uint32_t num_calls;
AnimTestHandlerEntry entries[MAX_HANDLER_CALLS];
} AnimTestHandlerHistory;
static AnimTestHandlerHistory s_started_handler_calls;
static AnimTestHandlerHistory s_stopped_handler_calls;
static AnimTestHandlerHistory s_setup_handler_calls;
static AnimTestHandlerHistory s_teardown_handler_calls;
static AnimTestHandlerHistory s_update_handler_calls;
static uint32_t s_fire_order_index;
// -------------------------------------------------------------------------
// Clear all handler history
static void prv_clear_handler_histories(void) {
memset(&s_started_handler_calls, 0, sizeof(AnimTestHandlerHistory));
memset(&s_stopped_handler_calls, 0, sizeof(AnimTestHandlerHistory));
memset(&s_setup_handler_calls, 0, sizeof(AnimTestHandlerHistory));
memset(&s_teardown_handler_calls, 0, sizeof(AnimTestHandlerHistory));
memset(&s_update_handler_calls, 0, sizeof(AnimTestHandlerHistory));
}
// -------------------------------------------------------------------------
// Add an entry to the history
static void prv_add_handler_entry(AnimTestHandlerHistory *history, Animation *animation,
bool finished, void *context) {
cl_assert(history->num_calls < MAX_HANDLER_CALLS);
history->entries[history->num_calls++] = (AnimTestHandlerEntry) {
.fired_time_ms = prv_now_ms(),
.fire_order = s_fire_order_index++,
.finished = finished,
.context = context,
.animation = animation
};
}
// -------------------------------------------------------------------------
// Count how many entries were entered for the given animation
static uint32_t prv_count_handler_entries(AnimTestHandlerHistory *history, Animation *animation) {
uint32_t count = 0;
for (int i = 0; i < history->num_calls; i++) {
if (!animation || history->entries[i].animation == animation) {
count++;
}
}
return count;
}
// -------------------------------------------------------------------------
// Get the last entry for the given handle
static AnimTestHandlerEntry *prv_last_handler_entry(AnimTestHandlerHistory *history,
Animation *animation) {
int last_entry=-1;
for (int i = 0; i < history->num_calls; i++) {
if (!animation || history->entries[i].animation == animation) {
last_entry = i;
}
}
if (last_entry == -1) {
return NULL;
} else {
return &history->entries[last_entry];
}
}
// -------------------------------------------------------------------------
// Get the last distance from an update handler
static uint32_t prv_last_update_distance(Animation *animation) {
AnimTestHandlerEntry *entry = prv_last_handler_entry(&s_update_handler_calls, animation);
if (entry) {
return (uint32_t)entry->context;
} else {
return 0;
}
}
// =============================================================================================
// Handlers
// --------------------------------------------------------------------------------------
// Started handler
static void prv_started_handler(Animation *animation, void *context) {
prv_add_handler_entry(&s_started_handler_calls, animation, false, context);
DPRINTF("%"PRIu64" ms: Executing started handler for %d\n", prv_now_ms(), (int)animation);
}
// --------------------------------------------------------------------------------------
// Stopped handler
static void prv_stopped_handler(Animation *animation, bool finished, void *context) {
prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
DPRINTF("%"PRIu64" ms: Executing stopped handler for %d\n", prv_now_ms(), (int)animation);
}
// --------------------------------------------------------------------------------------
// Stopped handler with check for finish
static void prv_stopped_handler_check_finished(Animation *animation, bool finished, void *context) {
prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
DPRINTF("%"PRIu64" ms: Executing stopped handler for %d\n", prv_now_ms(), (int)animation);
cl_assert(finished);
AnimationPrivate *animation_private = animation_private_animation_find(animation);
if (animation_private) {
// Flag should now get reset to false before entering stopped handler
cl_assert(animation_private->is_completed == false);
}
}
// --------------------------------------------------------------------------------------
// Stopped handler that calls reschedule the first time it is called
static void prv_stopped_handler_reschedule(Animation *animation, bool finished, void *context) {
prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
if (s_stopped_handler_calls.num_calls == 1) {
DPRINTF("%"PRIu64" ms: Rescheduling from stopped handler for %d\n", prv_now_ms(),
(int)animation);
animation_schedule(animation);
} else {
DPRINTF("%"PRIu64" ms: NOT rescheduling from stopped handler for %d\n", prv_now_ms(),
(int)animation);
}
}
// --------------------------------------------------------------------------------------
// setup handler
void prv_setup_handler(Animation *animation) {
prv_add_handler_entry(&s_setup_handler_calls, animation, false, NULL);
DPRINTF("%"PRIu64" ms: Executing setup handler for %d\n", prv_now_ms(), (int)animation);
}
// --------------------------------------------------------------------------------------
// teardown handler
void prv_teardown_handler(Animation *animation) {
prv_add_handler_entry(&s_teardown_handler_calls, animation, false, NULL);
DPRINTF("%"PRIu64" ms: Executing teardown handler for %d\n", prv_now_ms(), (int)animation);
}
// --------------------------------------------------------------------------------------
// update handler
// implemented in Animation.c
AnimationPrivate *prv_animation_get_current(void);
void prv_update_handler(Animation *animation, const AnimationProgress distance) {
// always ensure that animation state gives access to the current animation
cl_assert_equal_p(animation_private_animation_find(animation), prv_animation_get_current());
prv_add_handler_entry(&s_update_handler_calls, animation, false,
(void *)(uintptr_t)distance /*context*/);
DPRINTF("%"PRIu64" ms: Executing update handler for %d, distance: %d\n", prv_now_ms(),
(int)animation, (int)distance);
}
// --------------------------------------------------------------------------------------
static int s_custom_curve_call_count;
static AnimationProgress prv_custom_curve(AnimationProgress distance) {
// Input is a value from 0 to 65535 (ANIMATION_NORMALIZED_MAX)
// Output is a value from 0 to 65535
s_custom_curve_call_count++;
return distance;
}
// --------------------------------------------------------------------------------------
// Count how many animations have been allocated
static uint32_t prv_count_animations(void) {
AnimationState *state = kernel_applib_get_animation_state();
return list_count(state->unscheduled_head) + list_count(state->scheduled_head);
}
// --------------------------------------------------------------------------------------
// Count how many animations have been scheduled
static uint32_t prv_count_scheduled_animations(void) {
AnimationState *state = kernel_applib_get_animation_state();
return list_count(state->scheduled_head);
}
// --------------------------------------------------------------------------------------
static void prv_int16_setter(int16_t *p, int16_t value) {
*p = value;
}
static int16_t prv_int16_getter(int16_t *p) {
return *p;
}
// --------------------------------------------------------------------------------------
static void prv_gpoint_setter(GPoint *p, GPoint value) {
*p = value;
}
static GPoint prv_gpoint_getter(GPoint *p) {
return *p;
}
// --------------------------------------------------------------------------------------
static void prv_gtransform_setter(GTransform *p, GTransform value) {
*p = value;
}
static GTransform prv_gtransform_getter(GTransform *p) {
return *p;
}
// --------------------------------------------------------------------------------------
static void prv_gcolor8_setter(GColor8 *p, GColor8 value) {
*p = value;
}
static GColor8 prv_gcolor8_getter(GColor8 *p) {
return *p;
}
// --------------------------------------------------------------------------------------
static void prv_fixed_s32_16_setter(Fixed_S32_16 *p, Fixed_S32_16 value) {
*p = value;
}
static Fixed_S32_16 prv_fixed_s32_16_getter(Fixed_S32_16 *p) {
return *p;
}
// --------------------------------------------------------------------------------------
static void prv_uint32_setter(int32_t *p, uint32_t value) {
*p = value;
}
static uint32_t prv_uint32_getter(uint32_t *p) {
return *p;
}
// --------------------------------------------------------------------------------------
// Helper function for creating a int16 property animation
static Animation *prv_create_test_animation(void) {
Animation *h;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
static const AnimationImplementation implementation = {
.setup = prv_setup_handler,
.update = prv_update_handler,
.teardown = prv_teardown_handler
};
h = animation_create();
cl_assert(h != NULL);
void *context = h;
animation_set_handlers(h, handlers, context);
animation_set_implementation(h, &implementation);
return h;
}
// --------------------------------------------------------------------------------------
// Setup. Called before each of the tests execute
void test_animation__initialize(void) {
fake_rtc_init(1024*200 /*ticks */, 200 /*seconds */);
AnimationState *state = kernel_applib_get_animation_state();
animation_private_state_init(state);
// Insure that at least some time elapsed after init so that state->last_frame_time is
// in the past.
prv_advance_by_ms_no_timers(10);
// Clear handler histories
prv_clear_handler_histories();
}
// -------------------------------------------------------------------------------------
// Cleanup, called after each test executes
void test_animation__cleanup(void) {
// Make sure no animations were left over
cl_assert_equal_i(prv_count_animations(), 0);
}
// --------------------------------------------------------------------------------------
// Test a basic layer_frame property animation
// Test that the started and stopped handlers get called at the right time
void test_animation__property_layer_frame(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
Layer layer;
GRect from_r;
GRect to_r;
const int duration = 100;
GRect r;
void *subject;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
memset(&layer, 0, sizeof(layer));
from_r = GRect(0, 0, 100, 200); // x, y, width, height
to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
Animation *h = property_animation_get_animation(prop_h);
void *context = &layer;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
animation_set_auto_destroy(h, false);
// Clone it and make sure the clone is correct
PropertyAnimation *clone_h = (PropertyAnimation *)animation_clone((Animation *)prop_h);
property_animation_get_from_grect(clone_h, &r);
cl_assert_equal_rect(from_r, r);
property_animation_get_to_grect(clone_h, &r);
cl_assert_equal_rect(to_r, r);
property_animation_get_subject(prop_h, &subject);
cl_assert(subject == &layer);
property_animation_destroy(clone_h);
prv_clear_handler_histories();
animation_schedule(h);
int max_loops = 20;
uint64_t start_ms = prv_now_ms();
uint64_t time_ms;
while (s_stopped_handler_calls.num_calls == 0) {
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
layer.frame.origin.x, layer.frame.origin.y,
layer.frame.size.w, layer.frame.size.h);
cl_assert(max_loops > 0);
max_loops--;
}
// Make sure the frame reached the "to" state
cl_assert_equal_point(layer.frame.origin, to_r.origin);
// Make sure our started and stopped handlers got called
cl_assert_equal_i(s_started_handler_calls.num_calls, 1);;
cl_assert(s_started_handler_calls.entries[0].fired_time_ms - start_ms <= 1);
cl_assert(s_started_handler_calls.entries[0].context == context);
cl_assert_equal_i(s_stopped_handler_calls.num_calls, 1);;
cl_assert(s_stopped_handler_calls.entries[0].fired_time_ms - start_ms >= duration);
cl_assert(s_stopped_handler_calls.entries[0].context == context);
cl_assert(s_stopped_handler_calls.entries[0].finished);
// -------------------------------------------------------------------------------------------
// Test the accessor functions
property_animation_get_from_grect(prop_h, &r);
cl_assert_equal_rect(from_r, r);
property_animation_get_to_grect(prop_h, &r);
cl_assert_equal_rect(to_r, r);
property_animation_get_subject(prop_h, &subject);
cl_assert(subject == &layer);
GRect set_r = GRect(1, 2, 3, 4);
property_animation_set_from_grect(prop_h, &set_r);
r = GRect(0, 0, 0, 0);
property_animation_get_from_grect(prop_h, &r);
cl_assert_equal_rect(set_r, r);
set_r = GRect(5, 6, 7, 8);
property_animation_set_to_grect(prop_h, &set_r);
r = GRect(0, 0, 0, 0);
property_animation_get_to_grect(prop_h, &r);
cl_assert_equal_rect(set_r, r);
subject = (void *)0x11223344;
property_animation_set_subject(prop_h, &subject);
subject = NULL;
property_animation_get_subject(prop_h, &subject);
cl_assert(subject == (void *)0x11223344);
// Destroy it
animation_destroy(h);
#endif
}
// --------------------------------------------------------------------------------------
// Test a basic int16 property animation
// Test that the started and stopped handlers get called at the right time
// Test that the setup and teardown handlers get called at the right time
// Test that delay works
// Test that duration works
// Test that custom curve works
void test_animation__property_int16(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
int16_t value = 0;
int16_t start_value = 0, end_value = 100;
const int duration = 200;
const int delay = 25;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
static const PropertyAnimationImplementation implementation = {
.base = {
.setup = prv_setup_handler,
.update = (AnimationUpdateImplementation) property_animation_update_int16,
.teardown = prv_teardown_handler
},
.accessors = {
.setter = { .int16 = (const Int16Setter) prv_int16_setter, },
.getter = { .int16 = (const Int16Getter) prv_int16_getter },
},
};
prop_h = property_animation_create(&implementation, &value, &start_value, &end_value);
Animation *h = property_animation_get_animation(prop_h);
void *context = &value;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
animation_set_delay(h, delay);
animation_set_custom_curve(h, prv_custom_curve);
animation_set_auto_destroy(h, false);
prv_clear_handler_histories();
s_custom_curve_call_count = 0;
animation_schedule(h);
int max_loops = 20;
int num_loops = 0;
uint64_t start_ms = prv_now_ms();
uint64_t time_ms;
while (s_stopped_handler_calls.num_calls == 0) {
num_loops++;
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("%"PRIu64": value at: %d\n", time_ms - start_ms, value);
cl_assert(max_loops > 0);
max_loops--;
}
// Make sure the frame reached the "to" state
cl_assert_equal_i(value, 100);
// Make sure our started and stopped handlers got called
cl_assert_equal_i(s_started_handler_calls.num_calls, 1);;
cl_assert(ABSOLUTE_VALUE(s_started_handler_calls.entries[0].fired_time_ms - start_ms - delay) <= 1);
cl_assert_equal_i(s_setup_handler_calls.num_calls, 1);;
cl_assert(s_setup_handler_calls.entries[0].fired_time_ms - start_ms <= 1);
cl_assert_equal_i(s_stopped_handler_calls.num_calls, 1);;
cl_assert(s_stopped_handler_calls.entries[0].fired_time_ms - start_ms >= duration);
cl_assert(s_stopped_handler_calls.entries[0].finished);
cl_assert_equal_i(s_teardown_handler_calls.num_calls, 1);;
cl_assert(s_teardown_handler_calls.entries[0].fired_time_ms - start_ms >= duration);
// Make sure the custom curve function got called
cl_assert_equal_i(num_loops, s_custom_curve_call_count);
// -------------------------------------------------------------------------------------------
// Test the int16 accessor functions
int16_t test_value;
property_animation_get_from_int16(prop_h, &test_value);
cl_assert_equal_i(test_value, start_value);
property_animation_get_to_int16(prop_h, &test_value);
cl_assert_equal_i(test_value, end_value);
int16_t set_value;
set_value = 42;
property_animation_set_from_int16(prop_h, &set_value);
property_animation_get_from_int16(prop_h, &test_value);
cl_assert_equal_i(test_value, set_value);
set_value = 43;
property_animation_set_to_int16(prop_h, &set_value);
property_animation_get_to_int16(prop_h, &test_value);
cl_assert_equal_i(test_value, set_value);
// Destroy it
animation_destroy(h);
#endif
}
// --------------------------------------------------------------------------------------
// Test a basic gpoint property animation
void test_animation__property_gpoint(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
GPoint value;
GPoint start_value, end_value;
const int duration = 200;
const int delay = 25;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
static const PropertyAnimationImplementation implementation = {
.base = {
.update = (AnimationUpdateImplementation) property_animation_update_gpoint,
},
.accessors = {
.setter = { .gpoint = (const GPointSetter) prv_gpoint_setter, },
.getter = { .gpoint = (const GPointGetter) prv_gpoint_getter },
},
};
start_value = GPoint(0, 0);
end_value = GPoint(100, 200);
prop_h = property_animation_create(&implementation, &value, &start_value, &end_value);
Animation *h = property_animation_get_animation(prop_h);
void *context = &value;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
animation_set_delay(h, delay);
animation_set_auto_destroy(h, false);
prv_clear_handler_histories();
animation_schedule(h);
int max_loops = 20;
int num_loops = 0;
uint64_t start_ms = prv_now_ms();
uint64_t time_ms;
while (s_stopped_handler_calls.num_calls == 0) {
num_loops++;
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("%"PRIu64": value at: (%d, %d)\n", time_ms - start_ms, value.x, value.y);
cl_assert(max_loops > 0);
max_loops--;
}
// Make sure the frame reached the "to" state
cl_assert_equal_point(value, end_value);
// -------------------------------------------------------------------------------------------
// Test the GPoint accessor functions
GPoint test_value;
property_animation_get_from_gpoint(prop_h, &test_value);
cl_assert_equal_point(test_value, start_value);
property_animation_get_to_gpoint(prop_h, &test_value);
cl_assert_equal_point(test_value, end_value);
GPoint set_value;
set_value = GPoint(42, 43);
property_animation_set_from_gpoint(prop_h, &set_value);
property_animation_get_from_gpoint(prop_h, &test_value);
cl_assert_equal_point(test_value, set_value);
set_value = GPoint(44, 45);
property_animation_set_to_gpoint(prop_h, &set_value);
property_animation_get_to_gpoint(prop_h, &test_value);
cl_assert_equal_point(test_value, set_value);
// Destroy it
animation_destroy(h);
#endif
}
// --------------------------------------------------------------------------------------
// Test a basic gtransform property animation
void test_animation__property_gtransform(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
GTransform value;
GTransform start_value, end_value, mid_value;
const int duration = 1000;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
// NOTE: We are not exposing the GTransform in the public SDK, so the setter and getter
// must be typecast
static const PropertyAnimationImplementation implementation = {
.base = {
.update = (AnimationUpdateImplementation) property_animation_update_gtransform,
},
.accessors = {
.setter = { .int16 = (const Int16Setter) prv_gtransform_setter, },
.getter = { .int16 = (const Int16Getter) prv_gtransform_getter },
},
};
start_value = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
end_value = GTransformFromNumbers(100, 200, 300, 400, 500, 600);
mid_value = GTransformFromNumbers(50, 101, 151, 202, 252, 303);
value = end_value;
prop_h = property_animation_create(&implementation, &value, &start_value, NULL);
Animation *h = property_animation_get_animation(prop_h);
void *context = &value;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(h);
// Test the accessor functions
GTransform test_value;
property_animation_get_from_gtransform(prop_h, &test_value);
cl_assert_equal_gtransform(test_value, start_value);
property_animation_get_to_gtransform(prop_h, &test_value);
cl_assert_equal_gtransform(test_value, end_value);
GTransform set_value;
set_value = GTransformIdentity();
property_animation_set_from_gtransform(prop_h, &set_value);
property_animation_get_from_gtransform(prop_h, &test_value);
cl_assert_equal_gtransform(test_value, GTransformIdentity());
property_animation_set_from_gtransform(prop_h, &start_value);
// Start, we should start at the start values
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_gtransform(value, start_value);
// Halfway through
prv_advance_to_ms_with_timers(start_ms + duration/2);
cl_assert_close_gtransform(value, mid_value);
// End
prv_advance_to_ms_with_timers(start_ms + duration + MIN_FRAME_INTERVAL_MS*2);
cl_assert_equal_gtransform(value, end_value);
#endif
}
// --------------------------------------------------------------------------------------
// Test a basic Fixed_S32_16 property animation
void test_animation__property_fixed_s32_16(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
Fixed_S32_16 value;
Fixed_S32_16 start_value, end_value, mid_value;
const int duration = 1000;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
// NOTE: We are not exposing the GTransform in the public SDK, so the setter and getter
// must be typecast
static const PropertyAnimationImplementation implementation = {
.base = {
.update = (AnimationUpdateImplementation) property_animation_update_fixed_s32_16,
},
.accessors = {
.setter = { .int16 = (const Int16Setter) prv_fixed_s32_16_setter },
.getter = { .int16 = (const Int16Getter) prv_fixed_s32_16_getter },
},
};
start_value = ((Fixed_S32_16){ .integer = 1, .fraction = 0 });
end_value = ((Fixed_S32_16){ .integer = 100, .fraction = 0 });
mid_value = ((Fixed_S32_16){ .integer = 50, .fraction = 0 });
value = end_value;
prop_h = property_animation_create(&implementation, &value, &start_value, NULL);
Animation *h = property_animation_get_animation(prop_h);
void *context = &value;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(h);
// Test the accessor functions
Fixed_S32_16 test_value;
property_animation_get_from_fixed_s32_16(prop_h, &test_value);
cl_assert_equal_fixed_s32_16(test_value, start_value);
property_animation_get_to_fixed_s32_16(prop_h, &test_value);
cl_assert_equal_fixed_s32_16(test_value, end_value);
Fixed_S32_16 set_value;
set_value = FIXED_S32_16_ONE;
property_animation_set_from_fixed_s32_16(prop_h, &set_value);
property_animation_get_from_fixed_s32_16(prop_h, &test_value);
cl_assert_equal_fixed_s32_16(test_value, FIXED_S32_16_ONE);
property_animation_set_from_fixed_s32_16(prop_h, &start_value);
// Start, we should start at the start values
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_fixed_s32_16(value, start_value);
// Halfway through
prv_advance_to_ms_with_timers(start_ms + duration/2);
cl_assert_close_fixed_s32_16(value, mid_value);
// End
prv_advance_to_ms_with_timers(start_ms + duration + MIN_FRAME_INTERVAL_MS*2);
cl_assert_equal_fixed_s32_16(value, end_value);
#endif
}
// --------------------------------------------------------------------------------------
// Test a basic uint32_t property animation
void test_animation__property_uint32(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
uint32_t value;
uint32_t start_value, end_value, mid_value;
const int duration = 1000;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
// NOTE: We are not exposing the GTransform in the public SDK, so the setter and getter
// must be typecast
static const PropertyAnimationImplementation implementation = {
.base = {
.update = (AnimationUpdateImplementation) property_animation_update_uint32,
},
.accessors = {
.setter = { .uint32 = (const UInt32Setter) prv_uint32_setter },
.getter = { .uint32 = (const UInt32Getter) prv_uint32_getter },
},
};
start_value = 1;
end_value = 100;
mid_value = 50;
value = end_value;
prop_h = property_animation_create(&implementation, &value, &start_value, NULL);
Animation *h = property_animation_get_animation(prop_h);
void *context = &value;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(h);
// Test the accessor functions
uint32_t test_value;
property_animation_get_from_uint32(prop_h, &test_value);
cl_assert_equal_i(test_value, start_value);
property_animation_get_to_uint32(prop_h, &test_value);
cl_assert_equal_i(test_value, end_value);
uint32_t set_value = 1;
property_animation_set_from_uint32(prop_h, &set_value);
property_animation_get_from_uint32(prop_h, &test_value);
cl_assert_equal_i(test_value, 1);
property_animation_set_from_uint32(prop_h, &start_value);
// Start, we should start at the start values
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(value, start_value);
// Halfway through
prv_advance_to_ms_with_timers(start_ms + duration/2);
cl_assert(abs((int32_t)value - (int32_t)mid_value) < 10);
// End
prv_advance_to_ms_with_timers(start_ms + duration + MIN_FRAME_INTERVAL_MS*2);
cl_assert_equal_i(value, end_value);
#endif
}
// --------------------------------------------------------------------------------------
// Test a basic gcolor8 property animation
void test_animation__property_gcolor8(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
GColor8 value;
GColor8 start_value, end_value, mid_value;
const int duration = 1000;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
static const PropertyAnimationImplementation implementation = {
.base = {
.update = (AnimationUpdateImplementation) property_animation_update_gcolor8,
},
.accessors = {
.setter = { .gcolor8 = (const GColor8Setter) prv_gcolor8_setter, },
.getter = { .gcolor8 = (const GColor8Getter) prv_gcolor8_getter },
},
};
start_value = (GColor8) {.a=0, .r=0, .g=0, .b=0};
end_value = (GColor8) {.a=3, .r=3, .g=3, .b=3};
mid_value = (GColor8) {.a=1, .r=1, .g=1, .b=1};
value = end_value;
prop_h = property_animation_create(&implementation, &value, &start_value, NULL);
Animation *h = property_animation_get_animation(prop_h);
void *context = &value;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(h);
// Test the accessor functions
GColor8 test_value;
property_animation_get_from_gcolor8(prop_h, &test_value);
cl_assert(gcolor_equal(test_value, start_value));
property_animation_get_to_gcolor8(prop_h, &test_value);
cl_assert(gcolor_equal(test_value, end_value));
GColor8 set_value;
set_value = (GColor8) {.a=0, .r=1, .g=2, .b=3};
property_animation_set_from_gcolor8(prop_h, &set_value);
property_animation_get_from_gcolor8(prop_h, &test_value);
cl_assert(gcolor_equal(test_value, set_value));
property_animation_set_from_gcolor8(prop_h, &start_value);
// Start, we should start at the start values
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert(gcolor_equal(value, start_value));
// Halfway through
prv_advance_to_ms_with_timers(start_ms + duration/2);
cl_assert(gcolor_equal(value, mid_value));
// End
prv_advance_to_ms_with_timers(start_ms + duration + MIN_FRAME_INTERVAL_MS*2);
cl_assert(gcolor_equal(value, end_value));
#endif
}
// --------------------------------------------------------------------------------------
// Test that the schedule/unschedule calls work correctly.
// We should be able to unschedule an amimation parthway through
void test_animation__unschedule(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
Layer layer;
GRect from_r;
GRect to_r;
GRect stopped_at_r;
const int duration = 500;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
memset(&layer, 0, sizeof(layer));
from_r = GRect(0, 0, 100, 200); // x, y, width, height
to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
Animation *h = property_animation_get_animation(prop_h);
void *context = &layer;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
animation_set_auto_destroy(h, false);
prv_clear_handler_histories();
animation_schedule(h);
uint64_t start_ms = prv_now_ms();
uint64_t unschedule_time = 0;
uint64_t time_ms;
for (int num_loops = 0; num_loops < 10; num_loops++) {
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
layer.frame.origin.x, layer.frame.origin.y,
layer.frame.size.w, layer.frame.size.h);
// Unschedule after 2 iterations
if (num_loops == 2) {
DPRINTF("%"PRIu64": Unscheduling now\n", prv_now_ms());
animation_unschedule(h);
stopped_at_r = layer.frame;
unschedule_time = prv_now_ms();
}
}
// Make sure the frame stopped at the state it was in when we unscheduled it
cl_assert_equal_point(layer.frame.origin, stopped_at_r.origin);
// Make sure our started and stopped handlers got called at
cl_assert_equal_i(s_started_handler_calls.num_calls, 1);;
cl_assert(s_started_handler_calls.entries[0].fired_time_ms - start_ms <= 1);
cl_assert(s_started_handler_calls.entries[0].context == context);
cl_assert_equal_i(s_stopped_handler_calls.num_calls, 1);;
cl_assert(s_stopped_handler_calls.entries[0].fired_time_ms - start_ms < duration);
cl_assert(ABSOLUTE_VALUE(s_stopped_handler_calls.entries[0].fired_time_ms - unschedule_time) < 1);
cl_assert(!s_stopped_handler_calls.entries[0].finished);
// Destroy it
animation_destroy(h);
#endif
}
// --------------------------------------------------------------------------------------
// Test that we can reschedule an animation after it completes and have it run again
void test_animation__reschedule(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
Layer layer;
GRect from_r;
GRect to_r;
const int duration = 100;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
memset(&layer, 0, sizeof(layer));
from_r = GRect(0, 0, 100, 200); // x, y, width, height
to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
Animation *h = property_animation_get_animation(prop_h);
void *context = &layer;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
animation_set_auto_destroy(h, false);
prv_clear_handler_histories();
animation_schedule(h);
int max_loops = 20;
uint64_t start_ms = prv_now_ms();
uint64_t time_ms;
while (s_stopped_handler_calls.num_calls == 0) {
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
layer.frame.origin.x, layer.frame.origin.y,
layer.frame.size.w, layer.frame.size.h);
cl_assert(max_loops > 0);
max_loops--;
}
// Make sure the frame reached the "to" state
cl_assert_equal_point(layer.frame.origin, to_r.origin);
// -------------------------------------------------------------------------------
// Now, reschedule it
prv_advance_by_ms_no_timers(10);
prv_clear_handler_histories();
animation_schedule(h);
max_loops = 20;
start_ms = prv_now_ms();
while (s_stopped_handler_calls.num_calls == 0) {
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
layer.frame.origin.x, layer.frame.origin.y,
layer.frame.size.w, layer.frame.size.h);
cl_assert(max_loops > 0);
max_loops--;
}
// Make sure our started and stopped handlers got called
cl_assert_equal_i(s_started_handler_calls.num_calls, 1);;
cl_assert(s_started_handler_calls.entries[0].fired_time_ms - start_ms <= 1);
cl_assert_equal_i(s_stopped_handler_calls.num_calls, 1);;
cl_assert(s_stopped_handler_calls.entries[0].fired_time_ms - start_ms >= duration);
cl_assert(s_stopped_handler_calls.entries[0].finished);
// Destroy it
animation_destroy(h);
#endif
}
// --------------------------------------------------------------------------------------
// Test that we can reschedule an animation from the stopped handler
void test_animation__reschedule_from_stopped_handler(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
Layer layer;
GRect from_r;
GRect to_r;
const int duration = 100;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler_reschedule
};
memset(&layer, 0, sizeof(layer));
from_r = GRect(0, 0, 100, 200); // x, y, width, height
to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
Animation *h = property_animation_get_animation(prop_h);
void *context = &layer;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
prv_clear_handler_histories();
animation_schedule(h);
int max_loops = 20;
uint64_t start_ms = prv_now_ms();
bool detected_reset_of_elapsed = false;
bool reached_end_elapsed = false;
uint64_t time_ms;
while (s_stopped_handler_calls.num_calls < 2) {
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("rescheduled count: %d\n", s_stopped_handler_calls.num_calls);
DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
layer.frame.origin.x, layer.frame.origin.y,
layer.frame.size.w, layer.frame.size.h);
if (layer.frame.origin.x == to_r.origin.x && layer.frame.origin.y == to_r.origin.y) {
reached_end_elapsed = true;
}
if (reached_end_elapsed && s_stopped_handler_calls.num_calls == 1
&& layer.frame.origin.x < to_r.origin.x) {
detected_reset_of_elapsed = true;
}
cl_assert(max_loops > 0);
max_loops--;
}
// Make sure we detected a reset of the elapsed after rescheduling
cl_assert(s_stopped_handler_calls.num_calls == 2);
cl_assert(detected_reset_of_elapsed);
// Make sure the frame reached the "to" state
cl_assert_equal_point(layer.frame.origin, to_r.origin);
#endif
}
// --------------------------------------------------------------------------------------
// Test that auto-destroy works correctly
void test_animation__auto_destroy(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
Layer layer;
GRect from_r;
GRect to_r;
const int duration = 100;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
memset(&layer, 0, sizeof(layer));
from_r = GRect(0, 0, 100, 200); // x, y, width, height
to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
Animation *h = property_animation_get_animation(prop_h);
void *context = &layer;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
prv_clear_handler_histories();
// Before we run, the context should be non NULL
cl_assert(animation_get_context(h) == context);
animation_schedule(h);
int max_loops = 20;
uint64_t start_ms = prv_now_ms();
uint64_t time_ms;
while (s_stopped_handler_calls.num_calls == 0) {
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
layer.frame.origin.x, layer.frame.origin.y,
layer.frame.size.w, layer.frame.size.h);
cl_assert(max_loops > 0);
max_loops--;
}
// This should return a NULL context now if the animation got destroyed
cl_assert(animation_get_context(h) == NULL);
// Make sure no animations exist
cl_assert_equal_i(prv_count_animations(), 0);
#endif
}
// --------------------------------------------------------------------------------------
// Test that we can reschedule an animation from the stopped handler that has auto-destroy on
void test_animation__auto_destroy_reschedule(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
Layer layer;
GRect from_r;
GRect to_r;
const int duration = 100;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler_reschedule
};
memset(&layer, 0, sizeof(layer));
from_r = GRect(0, 0, 100, 200); // x, y, width, height
to_r = GRect(1000, 2000, 100, 200); // x, y, width, height
prop_h = property_animation_create_layer_frame(&layer, &from_r, &to_r);
Animation *h = property_animation_get_animation(prop_h);
void *context = &layer;
animation_set_handlers(h, handlers, context);
animation_set_duration(h, duration);
prv_clear_handler_histories();
animation_schedule(h);
int max_loops = 20;
uint64_t start_ms = prv_now_ms();
bool detected_reset_of_elapsed = false;
bool reached_end_elapsed = false;
uint64_t time_ms;
while (s_stopped_handler_calls.num_calls < 2) {
prv_fire_animation_timer();
time_ms = prv_now_ms();
DPRINTF("rescheduled count: %d\n", s_stopped_handler_calls.num_calls);
DPRINTF("%"PRIu64": frame at: %d, %d, %d %d\n", time_ms - start_ms,
layer.frame.origin.x, layer.frame.origin.y,
layer.frame.size.w, layer.frame.size.h);
if (layer.frame.origin.x == to_r.origin.x && layer.frame.origin.y == to_r.origin.y) {
reached_end_elapsed = true;
}
if (reached_end_elapsed && s_stopped_handler_calls.num_calls == 1
&& layer.frame.origin.x < to_r.origin.x) {
detected_reset_of_elapsed = true;
}
cl_assert(max_loops > 0);
max_loops--;
}
// Make sure we detected a reset of the elapsed after rescheduling
cl_assert(s_stopped_handler_calls.num_calls == 2);
cl_assert(detected_reset_of_elapsed);
// Make sure the frame reached the "to" state
cl_assert_equal_point(layer.frame.origin, to_r.origin);
// This should return a NULL context now if the animation got destroyed
cl_assert(animation_get_context(h) == NULL);
// Make sure no animations exist
cl_assert_equal_i(prv_count_animations(), 0);
#endif
}
// --------------------------------------------------------------------------------------
// Stopped handler that calls destroy
static void prv_stopped_handler_destroy(Animation *animation, bool finished, void *context) {
prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
animation_destroy(animation);
}
// --------------------------------------------------------------------------------------
// Test that animation_destroy can be called from the stopped handler
static void prv_test_destroy_from_stopped_handler(bool auto_destroy) {
Animation *h;
const int duration = 100;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler_destroy
};
static const AnimationImplementation implementation = {
.setup = prv_setup_handler,
.update = prv_update_handler,
.teardown = prv_teardown_handler
};
h = animation_create();
cl_assert(h != NULL);
void *context = h;
animation_set_handlers(h, handlers, context);
animation_set_implementation(h, &implementation);
animation_set_duration(h, duration);
animation_set_auto_destroy(h, auto_destroy);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(h);
prv_advance_to_ms_with_timers(start_ms + duration + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, h), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, h), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, h), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, h), 1);
// Make sure the frame reached the "to" state
cl_assert_equal_i(prv_last_update_distance(h), ANIMATION_NORMALIZED_MAX);
// This should return NULL now if the animation got destroyed
cl_assert(animation_private_animation_find(h) == NULL);
// Make sure no animations exist
cl_assert_equal_i(prv_count_animations(), 0);
}
// --------------------------------------------------------------------------------------
// Test that animation_destroy can be called from the stopped handler
void test_animation__destroy_from_stopped_handler_with_auto_destroy(void) {
#ifdef TEST_INCLUDE_BASIC
prv_test_destroy_from_stopped_handler(true);
#endif
}
// --------------------------------------------------------------------------------------
// Test that animation_destroy can be called from the stopped handler
void test_animation__destroy_from_stopped_handler_without_auto_destroy(void) {
#ifdef TEST_INCLUDE_BASIC
prv_test_destroy_from_stopped_handler(false);
#endif
}
// --------------------------------------------------------------------------------------
// Stopped handler that calls unschedule
static void prv_stopped_handler_unschedule(Animation *animation, bool finished, void *context) {
prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
animation_unschedule(animation);
}
// --------------------------------------------------------------------------------------
// Test that animation_unschedule can be called from the stopped handler
void test_animation__unschedule_from_stopped_handler(void) {
Animation *h;
const int duration = 100;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler_unschedule
};
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration);
animation_set_handlers(a, handlers, a);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(a);
prv_advance_to_ms_with_timers(start_ms + duration + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
// Make sure no animations exist
cl_assert_equal_i(prv_count_animations(), 0);
}
// --------------------------------------------------------------------------------------
// Test setting a play count of 0
void test_animation__basic_play_count_0(void) {
#ifdef TEST_INCLUDE_BASIC
const int duration_a = 300;
Animation *a = prv_create_test_animation();
animation_set_play_count(a, 0);
animation_set_duration(a, duration_a);
prv_clear_handler_histories();
animation_schedule(a);
uint64_t start_ms = prv_now_ms();
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
// Should not have run at all
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);
// Should have been deleted automatically
cl_assert_equal_i(prv_count_animations(), 0);
#endif
}
// --------------------------------------------------------------------------------------
// Test setting a duration of infinite duration
void test_animation__basic_infinite_duration(void) {
#ifdef TEST_INCLUDE_BASIC
// A long time, but not so long as to use up our 500 capacity callback history limit
const uint32_t test_duration = 10000;
const uint32_t duration_a = ANIMATION_DURATION_INFINITE;
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
prv_clear_handler_histories();
animation_schedule(a);
uint64_t start_ms = prv_now_ms();
prv_advance_to_ms_with_timers(start_ms + test_duration);
// Should still be running
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert(prv_count_handler_entries(&s_update_handler_calls, a)
>= test_duration/MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);
// The distance should always be at 0
cl_assert_equal_i(prv_last_update_distance(a), 0);
// Destroy it
animation_destroy(a);
#endif
}
// --------------------------------------------------------------------------------------
// Test a simple sequence animation
void test_animation__simple_sequence(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int play_count_b = 2;
int duration_total = duration_a + play_count_b * duration_b;
// Create 2 animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_play_count(b, play_count_b);
// Create a sequence
Animation *seq = animation_sequence_create(a, b, NULL);
cl_assert(seq != NULL);
// Setup started/stopped handlers for the sequence
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
animation_set_handlers(seq, handlers, seq);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
// Start A
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);
// Just before A completes
prv_advance_to_ms_with_timers(start_ms + duration_a - 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);
// Complete A and start B
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);
// The stopped handler for A should fire before the started handler for B
cl_assert(prv_last_handler_entry(&s_stopped_handler_calls, a)->fire_order
< prv_last_handler_entry(&s_started_handler_calls, b)->fire_order);
// Just before B completes the 2nd play
prv_advance_to_ms_with_timers(start_ms + duration_total - 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);
// Complete B
prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);
#endif
}
static Animation *s_parent_for_sequence_unschedule_from_child;
static void prv_unschedule_parent(Animation *animation, bool finished, void *context) {
DPRINTF("%"PRIu64" ms: Executing prv_unschedule_parent handler for %d\n", prv_now_ms(),
(int)animation);
animation_unschedule(s_parent_for_sequence_unschedule_from_child);
}
// --------------------------------------------------------------------------------------
// Test calling unschedule on the sequence from the stopped handler of one of its children
void test_animation__sequence_unschedule_from_child(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int play_count_b = 2;
int duration_total = duration_a + play_count_b * duration_b;
// Create 2 animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
// Setup stopped handler for the first child that unschedules the parent
const AnimationHandlers special_handlers = {
.started = NULL,
.stopped = prv_unschedule_parent
};
animation_set_handlers(a, special_handlers, NULL);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_play_count(b, play_count_b);
// Create a sequence
Animation *seq = animation_sequence_create(a, b, NULL);
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
animation_set_handlers(seq, handlers, seq);
s_parent_for_sequence_unschedule_from_child = seq;
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
// Complete A and start B. This should unschedule the parent
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);
// Everything should have been freed
cl_assert_equal_i(prv_count_animations(), 0);
#endif
}
// --------------------------------------------------------------------------------------
// Test a seeking in a basic sequence animation
void test_animation__simple_sequence_set_elapsed(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int duration_c = 400;
const int play_count_b = 2;
int duration_total = duration_a + play_count_b * duration_b;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
// Create 2 property animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_play_count(b, play_count_b);
// Create a sequence out of them.
Animation *seq = animation_sequence_create(a, b, NULL);
cl_assert(seq != NULL);
// Create a shorter animation to play in parallel
Animation *c = prv_create_test_animation();
cl_assert(c != NULL);
animation_set_duration(c, duration_c);
Animation *complex = animation_spawn_create(seq, c, NULL);
cl_assert(complex != NULL);
animation_set_handlers(complex, handlers, complex);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(complex);
// -------------------------------------------------------------------------------------
// Start A
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 0);
// -------------------------------------------------------------------------------------
// Execute about half of A
prv_advance_to_ms_with_timers(start_ms + duration_a/2);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 0);
// -------------------------------------------------------------------------------------
// Seek to about the middle of B
// Save the current update elapsed
animation_set_elapsed(complex, duration_a + duration_b/2);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);
// A should be at the end
int32_t update_a_after = prv_last_update_distance(a);
cl_assert_equal_i(update_a_after, ANIMATION_NORMALIZED_MAX);
// B should be in the middle
int32_t update_b_after = prv_last_update_distance(b);
cl_assert(abs(update_b_after - ANIMATION_NORMALIZED_MAX/2) < 5000);
// C should be at the end
int32_t update_c_after = prv_last_update_distance(c);
cl_assert_equal_i(update_c_after, ANIMATION_NORMALIZED_MAX);
// -------------------------------------------------------------------------------------
// Seek to just before the end of the second B
// Save the current update elapsed
animation_set_elapsed(complex, duration_total - 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);
// -------------------------------------------------------------------------------------
// animation a has completed, but it shouldn't be deleted yet until the top-level
// animation is done.
uint32_t duration = animation_get_duration(a, false, false);
cl_assert_equal_i(duration, duration_a);
// -------------------------------------------------------------------------------------
// Advance to the end
animation_set_elapsed(complex, duration_total);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, complex), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_last_update_distance(c), ANIMATION_NORMALIZED_MAX);
// Make sure each animation got to the end
AnimTestHandlerEntry *entry;
entry = prv_last_handler_entry(&s_update_handler_calls, c);
cl_assert_equal_i((uint32_t)entry->context, ANIMATION_NORMALIZED_MAX);
#endif
}
// --------------------------------------------------------------------------------------
// Test unscheduling a complex animation
void test_animation__sequence_unschedule(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 200;
const int duration_c = 500;
const int repeat_count = 5;
int duration_total = duration_a + MAX(duration_b, duration_c);
// Create 3 animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
Animation *c = prv_create_test_animation();
animation_set_duration(c, duration_c);
// Create a spawn out of b and c
Animation *spawn = animation_spawn_create(b, c, NULL);
cl_assert(spawn != NULL);
// Create a sequence by putting a in front
// We now have a -> (b | c)
Animation *seq = animation_sequence_create(a, spawn, NULL);
// Make it repeat
animation_set_play_count(seq, repeat_count);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
// Start A
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, c), 0);
// Execute to the start of B and C
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 0);
// Execute to the end of B & C
prv_advance_to_ms_with_timers(start_ms + duration_total + 1 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);
// If we keep going, we should repeat the whole sequence
prv_advance_to_ms_with_timers(start_ms + 2 * (duration_total + 4 * MIN_FRAME_INTERVAL_MS));
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 3);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 2);
// Unschedule the top-level
animation_unschedule(seq);
// Keep going, nothing new should happen except the stop handler for a (which we started)
prv_advance_to_ms_with_timers(start_ms + 5 * (duration_total + 3 * MIN_FRAME_INTERVAL_MS));
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 3);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 3);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, c), 1);
#endif
}
// --------------------------------------------------------------------------------------
// Test using clone and reverse in a complex animation
void test_animation__complex_reverse(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_total = 2 * duration_a;
const int repeat_count = 2;
uint32_t distance;
// Create animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = animation_clone(a);
animation_set_reverse(b, true);
// Create a sequence
Animation *seq = animation_sequence_create(a, b, NULL);
animation_set_play_count(seq, repeat_count);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
// Start A
prv_advance_to_ms_with_timers(start_ms + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// A should start out low
distance = prv_last_update_distance(a);
cl_assert(distance < TEST_ANIMATION_NORMALIZED_LOW);
// Execute to the start of B
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
// A should end high
distance = prv_last_update_distance(a);
cl_assert(distance > TEST_ANIMATION_NORMALIZED_HIGH);
// B should start high
distance = prv_last_update_distance(b);
cl_assert(distance > TEST_ANIMATION_NORMALIZED_HIGH);
// Execute to the end of B
prv_advance_to_ms_with_timers(start_ms + duration_total + 1 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
// B should end low
distance = prv_last_update_distance(b);
cl_assert(distance < TEST_ANIMATION_NORMALIZED_LOW);
// If we keep going, we should repeat the whole sequence
prv_advance_to_ms_with_timers(start_ms + 2 * (duration_total + 10 * MIN_FRAME_INTERVAL_MS));
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 1);
// A should end up at max
// A should end high
distance = prv_last_update_distance(a);
cl_assert_equal_i(distance, ANIMATION_NORMALIZED_MAX);
// B should start high
distance = prv_last_update_distance(b);
cl_assert_equal_i(distance, ANIMATION_NORMALIZED_MIN);
#endif
}
// --------------------------------------------------------------------------------------
// Test cloning complex animation
void test_animation__complex_clone(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 200;
const int duration_c = 500;
const int repeat_count = 5;
int duration_total = duration_a + MAX(duration_b, duration_c);
// Create 3 animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
Animation *c = prv_create_test_animation();
animation_set_duration(c, duration_c);
// Create a spawn out of b and c
Animation *spawn = animation_spawn_create(b, c, NULL);
cl_assert(spawn != NULL);
// Create a sequence by putting a in front and repeat it 5 times
// We now have a -> (b | c)
Animation *seq = animation_sequence_create(a, spawn, NULL);
animation_set_play_count(seq, repeat_count);
// Now, clone it
Animation *clone = animation_clone(seq);
// Destroy the original
animation_destroy(seq);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(clone);
// Start A
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 0);
// Execute to the start of B and C
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 3);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 1);
// Execute to the end of B & C
prv_advance_to_ms_with_timers(start_ms + duration_total + 1 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 3);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 3);
// If we keep going, we should repeat the whole sequence another time
prv_advance_to_ms_with_timers(start_ms + 2 * (duration_total + 4 * MIN_FRAME_INTERVAL_MS));
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 7);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 6);
// Unschedule the top-level
animation_unschedule(clone);
// Keep going, nothing new should happen except stop handlers for each component
prv_advance_to_ms_with_timers(start_ms + 5 * (duration_total + 3 * MIN_FRAME_INTERVAL_MS));
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, NULL), 7);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, NULL), 7);
#endif
}
// --------------------------------------------------------------------------------------
// Test scheduling a sequence of 2 spawns. Insure that ALL of the primitives in the first spawn,
// finish before the primitives from the 2nd spawn start.
static void prv_test_sequence_of_spawns(int create_order[4]) {
const int duration_a = 150;
const int duration_total = 2 * duration_a;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
// Simulate some delay incurred on every call to rtc_get_ticks()
fake_rtc_auto_increment_ticks(3);
Animation *a0, *a1, *a2, *a3;
Animation *b0, *b1, *b2, *b3;
Animation *spawn_a, *spawn_b;
for (int i=0; i<4; i++) {
switch(create_order[i]) {
case 0:
a0 = prv_create_test_animation();
animation_set_duration(a0, duration_a);
a1 = animation_clone(a0);
a2 = animation_clone(a0);
a3 = animation_clone(a0);
break;
case 1:
b0 = prv_create_test_animation();
animation_set_duration(b0, duration_a);
b1 = animation_clone(b0);
b2 = animation_clone(b0);
b3 = animation_clone(b0);
break;
case 2:
spawn_a = animation_spawn_create(a0, a1, a2, a3, NULL);
animation_set_handlers(spawn_a, handlers, (void *)spawn_a);
break;
case 3:
spawn_b = animation_spawn_create(b0, b1, b2, b3, NULL);
animation_set_handlers(spawn_b, handlers, (void *)spawn_a);
break;
}
}
// Create the sequence
Animation *seq = animation_sequence_create(spawn_a, spawn_b, NULL);
// Schedule it
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
fake_rtc_auto_increment_ticks(0);
// Let the first spawn finish
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a0), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a0), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a1), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a1), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a2), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a2), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a3), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a3), 1);
// None of the b's should finish yet
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b0), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b1), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b2), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b3), 0);
// Let it finish completely
prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b0), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b1), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b2), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b3), 1);
// Make sure the all the spawn a stopped handlers got called before any of the spawn b
// started handlers
uint32_t last_fire_a = 0;
last_fire_a = MAX(last_fire_a,
prv_last_handler_entry(&s_stopped_handler_calls, a0)->fire_order);
last_fire_a = MAX(last_fire_a,
prv_last_handler_entry(&s_stopped_handler_calls, a1)->fire_order);
last_fire_a = MAX(last_fire_a,
prv_last_handler_entry(&s_stopped_handler_calls, a2)->fire_order);
last_fire_a = MAX(last_fire_a,
prv_last_handler_entry(&s_stopped_handler_calls, a3)->fire_order);
last_fire_a = MAX(last_fire_a,
prv_last_handler_entry(&s_stopped_handler_calls, spawn_a)->fire_order);
uint32_t first_fire_b = prv_last_handler_entry(&s_started_handler_calls, b0)->fire_order;
first_fire_b = MIN(first_fire_b,
prv_last_handler_entry(&s_started_handler_calls, b1)->fire_order);
first_fire_b = MIN(first_fire_b,
prv_last_handler_entry(&s_started_handler_calls, b2)->fire_order);
first_fire_b = MIN(first_fire_b,
prv_last_handler_entry(&s_started_handler_calls, b3)->fire_order);
first_fire_b = MIN(first_fire_b,
prv_last_handler_entry(&s_started_handler_calls, spawn_b)->fire_order);
cl_assert(last_fire_a < first_fire_b);
cl_assert_equal_i(prv_count_animations(), 0);
}
// --------------------------------------------------------------------------------------
// Test scheduling a sequence of 2 spawns. Insure that ALL of the primitives in the first spawn,
// finish before the primitives from the 2nd spawn start.
void test_animation__sequence_of_spawns(void) {
#ifdef TEST_INCLUDE_COMPLEX
int order_a[4] = {0, 1, 2, 3};
prv_test_sequence_of_spawns(order_a);
int order_b[4] = {1, 0, 2, 3};
prv_test_sequence_of_spawns(order_b);
int order_c[4] = {1, 0, 3, 2};
prv_test_sequence_of_spawns(order_c);
#endif
}
// --------------------------------------------------------------------------------------
// Test delays in sequence animation
void test_animation__sequence_delay(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int delay_a = 100;
const int duration_b = 500;
const int delay_b = 200;
const int delay_seq = 150;
int duration_total = duration_a + duration_b + delay_a + delay_b + delay_seq;
// Create 2 test animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_delay(b, delay_b);
// Create a sequence
Animation *seq = animation_sequence_create(a, b, NULL);
cl_assert(seq != NULL);
animation_set_delay(seq, delay_seq);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
// Test the elapsed
int32_t elapsed_ms;
animation_get_elapsed(seq, &elapsed_ms);
cl_assert_equal_i(elapsed_ms, -1 * (delay_seq));
animation_get_elapsed(a, &elapsed_ms);
cl_assert_equal_i(elapsed_ms, -1 * (delay_seq + delay_a));
animation_get_elapsed(b, &elapsed_ms);
cl_assert_equal_i(elapsed_ms, -1 * (delay_seq + delay_a + duration_a + delay_b));
// Start
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Start A after delay
prv_advance_to_ms_with_timers(start_ms + delay_seq + delay_a + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Just before A completes
prv_advance_to_ms_with_timers(start_ms + delay_seq + delay_a + duration_a - 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Complete A and start B
prv_advance_to_ms_with_timers(start_ms + duration_a + delay_seq + delay_a + delay_b
+ 2 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
// Complete B
prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);
#endif
}
// --------------------------------------------------------------------------------------
// Test delays in spawn animation
void test_animation__spawn_delay(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int delay_a = 100;
const int duration_b = 500;
const int delay_b = 200;
const int delay_spawn = 150;
int duration_total = MAX(duration_a + delay_a, duration_b + delay_b) + delay_spawn;
// Create 2 test animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_delay(b, delay_b);
// Create a spawn
Animation *spawn = animation_spawn_create(a, b, NULL);
cl_assert(spawn != NULL);
animation_set_delay(spawn, delay_spawn);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(spawn);
// Test the elapsed
int32_t elapsed_ms;
animation_get_elapsed(spawn, &elapsed_ms);
cl_assert_equal_i(elapsed_ms, -1 * (delay_spawn));
animation_get_elapsed(a, &elapsed_ms);
cl_assert_equal_i(elapsed_ms, -1 * (delay_spawn + delay_a));
animation_get_elapsed(b, &elapsed_ms);
cl_assert_equal_i(elapsed_ms, -1 * (delay_spawn + delay_b));
// Start
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Start A
prv_advance_to_ms_with_timers(start_ms + delay_spawn + delay_a + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
// Start B
prv_advance_to_ms_with_timers(start_ms + delay_spawn + MAX(delay_a, delay_b)
+ 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
// Complete A and start B
prv_advance_to_ms_with_timers(start_ms + delay_spawn + duration_a + delay_a
+ 2 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
// Complete B
prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);
#endif
}
// --------------------------------------------------------------------------------------
// Test a sequence animation with a component that has a play count of 0
void test_animation__sequence_with_0_component(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int play_count_seq = 2;
int duration_total = play_count_seq * duration_a;
// Create 2 animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_play_count(b, 0);
// Create a sequence
Animation *seq = animation_sequence_create(a, b, NULL);
cl_assert(seq != NULL);
animation_set_play_count(seq, play_count_seq);
// Check the duration
cl_assert_equal_i(animation_get_duration(seq, true, true), play_count_seq * duration_a);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
// Start A
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Just before A completes
prv_advance_to_ms_with_timers(start_ms + duration_a - 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Complete A the first time
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
// Complete sequence
prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
#endif
}
// --------------------------------------------------------------------------------------
// Test a spawn animation with a component that has a play count of 0
void test_animation__spawn_with_0_component(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int play_count_spawn = 2;
int duration_total = play_count_spawn * duration_a;
// Create 2 animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_play_count(b, 0);
// Create a spawn that repeats
Animation *spawn = animation_spawn_create(a, b, NULL);
cl_assert(spawn != NULL);
animation_set_play_count(spawn, play_count_spawn);
// Check the duration
cl_assert_equal_i(animation_get_duration(spawn, true, true), play_count_spawn * duration_a);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(spawn);
// Start A
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Just before A completes
prv_advance_to_ms_with_timers(start_ms + duration_a - 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Complete A the first time
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
// Complete spawn
prv_advance_to_ms_with_timers(start_ms + duration_total + 5 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 2);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
#endif
}
// --------------------------------------------------------------------------------------
// Test a sequence animation with a play count of 0
void test_animation__sequence_with_0_play_count(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int play_count_seq = 0;
// Create 2 animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
// Create a sequence
Animation *seq = animation_sequence_create(a, b, NULL);
cl_assert(seq != NULL);
animation_set_play_count(seq, play_count_seq);
// Check the duration
cl_assert_equal_i(animation_get_duration(seq, true, true), 0);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
// Start
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Complete sequence
prv_advance_to_ms_with_timers(start_ms + duration_a + duration_b + 5 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MIN);
cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MIN);
#endif
}
// --------------------------------------------------------------------------------------
// Test a sequence within a sequence where the imbedded one has a play count of 0
void test_animation__nested_sequence_with_0_play_count(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int duration_c = 200;
const int duration_d = 400;
const int total_duration = duration_c + duration_d;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
// Create the inner sequence with a play count of 0
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
Animation *inner_seq = animation_sequence_create(a, b, NULL);
animation_set_play_count(inner_seq, 0);
animation_set_handlers(inner_seq, handlers, inner_seq);
// Create the outer sequence
Animation *c = prv_create_test_animation();
animation_set_duration(c, duration_c);
Animation *d = prv_create_test_animation();
animation_set_duration(d, duration_d);
Animation *seq = animation_sequence_create(inner_seq, c, d, NULL);
animation_set_handlers(seq, handlers, seq);
// Play it
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(seq);
prv_advance_to_ms_with_timers(start_ms + total_duration + 5 * MIN_FRAME_INTERVAL_MS);
// Make sure neither inner_seq, a, nor b played
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, inner_seq), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, inner_seq), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, b), 0);
// Make sure seq, c, and d completed
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, d), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, d), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, d), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, d), 1);
#endif
}
// --------------------------------------------------------------------------------------
// Test a spawn animation with a play count of 0
void test_animation__spawn_with_0_play_count(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int play_count_spawn = 0;
// Create 2 animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
// Create a spawn
Animation *spawn = animation_sequence_create(a, b, NULL);
cl_assert(spawn != NULL);
animation_set_play_count(spawn, play_count_spawn);
// Check the duration
cl_assert_equal_i(animation_get_duration(spawn, true, true), 0);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(spawn);
// Start
prv_advance_to_ms_with_timers(start_ms + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_update_handler_calls, b), 0);
// Complete sequence
prv_advance_to_ms_with_timers(start_ms + duration_a + duration_b + 5 * MIN_FRAME_INTERVAL_MS + 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MIN);
cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MIN);
#endif
}
// --------------------------------------------------------------------------------------
// Test the get_duration call on a sequence animation
void test_animation__sequence_get_duration(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int delay_a = 100;
const int play_count_a = 1;
const int total_duration_a = play_count_a * (delay_a + duration_a);
const int duration_b = 500;
const int delay_b = 200;
const int play_count_b = 3;
const int total_duration_b = play_count_b * (delay_b + duration_b);
const int delay_seq = 150;
const int play_count_seq = 2;
int duration_total = play_count_seq * (total_duration_a + total_duration_b + delay_seq);
// Create 2 test animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
animation_set_play_count(a, play_count_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_delay(b, delay_b);
animation_set_play_count(b, play_count_b);
// Create a sequence
Animation *seq = animation_sequence_create(a, b, NULL);
cl_assert(seq != NULL);
animation_set_delay(seq, delay_seq);
animation_set_play_count(seq, play_count_seq);
// Check durations
cl_assert_equal_i(animation_get_duration(a, false, false), duration_a);
cl_assert_equal_i(animation_get_duration(a, false, true), play_count_a * duration_a);
cl_assert_equal_i(animation_get_duration(a, true, false), delay_a + duration_a);
cl_assert_equal_i(animation_get_duration(a, true, true), total_duration_a);
cl_assert_equal_i(animation_get_duration(b, false, false), duration_b);
cl_assert_equal_i(animation_get_duration(b, false, true), play_count_b * duration_b);
cl_assert_equal_i(animation_get_duration(b, true, false), delay_b + duration_b);
cl_assert_equal_i(animation_get_duration(b, true, true), total_duration_b);
cl_assert_equal_i(animation_get_duration(seq, false, false), total_duration_a + total_duration_b);
cl_assert_equal_i(animation_get_duration(seq, false, true),
play_count_seq * (total_duration_a + total_duration_b));
cl_assert_equal_i(animation_get_duration(seq, true, false),
delay_seq + total_duration_a + total_duration_b);
cl_assert_equal_i(animation_get_duration(seq, true, true), duration_total);
animation_destroy(seq);
#endif
}
// --------------------------------------------------------------------------------------
// Test the get_duration call on a spawn animation
void test_animation__spawn_get_duration(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int delay_a = 100;
const int play_count_a = 1;
const int total_duration_a = play_count_a * (delay_a + duration_a);
const int duration_b = 500;
const int delay_b = 200;
const int play_count_b = 3;
const int total_duration_b = play_count_b * (delay_b + duration_b);
const int delay_spawn = 150;
const int play_count_spawn = 2;
int duration_total = play_count_spawn * (MAX(total_duration_a, total_duration_b) + delay_spawn);
// Create 2 test animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
animation_set_play_count(a, play_count_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_delay(b, delay_b);
animation_set_play_count(b, play_count_b);
// Create a spawn
Animation *spawn = animation_spawn_create(a, b, NULL);
cl_assert(spawn != NULL);
animation_set_delay(spawn, delay_spawn);
animation_set_play_count(spawn, play_count_spawn);
// Check durations
cl_assert_equal_i(animation_get_duration(a, false, false), duration_a);
cl_assert_equal_i(animation_get_duration(a, false, true), play_count_a * duration_a);
cl_assert_equal_i(animation_get_duration(a, true, false), delay_a + duration_a);
cl_assert_equal_i(animation_get_duration(a, true, true), total_duration_a);
cl_assert_equal_i(animation_get_duration(b, false, false), duration_b);
cl_assert_equal_i(animation_get_duration(b, false, true), play_count_b * duration_b);
cl_assert_equal_i(animation_get_duration(b, true, false), delay_b + duration_b);
cl_assert_equal_i(animation_get_duration(b, true, true), total_duration_b);
cl_assert_equal_i(animation_get_duration(spawn, false, false),
MAX(total_duration_a, total_duration_b));
cl_assert_equal_i(animation_get_duration(spawn, false, true),
play_count_spawn * (MAX(total_duration_a, total_duration_b)));
cl_assert_equal_i(animation_get_duration(spawn, true, false),
delay_spawn + MAX(total_duration_a, total_duration_b));
cl_assert_equal_i(animation_get_duration(spawn, true, true), duration_total);
animation_destroy(spawn);
#endif
}
// --------------------------------------------------------------------------------------
// Test unschedule all when we have multiple animations, some complex
void test_animation__unschedule_all(void) {
#ifdef TEST_INCLUDE_COMPLEX
// Create animations
// Create a sequence
Animation *a = prv_create_test_animation();
Animation *b = prv_create_test_animation();
Animation *seq = animation_sequence_create(a, b, NULL);
cl_assert(seq != NULL);
// Create a spawn
Animation *c = prv_create_test_animation();
Animation *d = prv_create_test_animation();
Animation *spawn = animation_spawn_create(c, d, NULL);
cl_assert(spawn != NULL);
// Create a primitive one
Animation *e = prv_create_test_animation();
// Schedule them all
animation_schedule(seq);
animation_schedule(spawn);
animation_schedule(e);
// Verify count
cl_assert_equal_i(prv_count_scheduled_animations(), 7);
// Unschedule all
animation_unschedule_all();
cl_assert_equal_i(prv_count_scheduled_animations(), 0);
// Make sure just the setup and teardown handlers were called
cl_assert_equal_i(prv_count_handler_entries(&s_setup_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_teardown_handler_calls, a), 1);
#endif
}
// --------------------------------------------------------------------------------------
// Test that we fail if we try and put a component in more than 1 complex animation
void test_animation__reuse_components(void) {
#ifdef TEST_INCLUDE_COMPLEX
// Create animations
// Create a sequence out of a and b
Animation *a = prv_create_test_animation();
Animation *b = prv_create_test_animation();
Animation *seq = animation_sequence_create(a, b, NULL);
cl_assert(seq != NULL);
// Try to create a spawn out of b and c
Animation *c = prv_create_test_animation();
Animation *spawn = animation_spawn_create(c, b, NULL);
cl_assert(spawn == NULL);
// We should be able to create one out of c and d
Animation *d = prv_create_test_animation();
spawn = animation_spawn_create(c, d, NULL);
cl_assert(spawn != NULL);
animation_destroy(seq);
animation_destroy(spawn);
#endif
}
// Test all the accessors
void test_animation__accessors(void) {
#ifdef TEST_INCLUDE_BASIC
PropertyAnimation *prop_h;
int16_t value = 0;
int16_t start_value = 0, end_value = 100;
const int duration = 200;
const int delay = 25;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
static const PropertyAnimationImplementation implementation = {
.base = {
.setup = prv_setup_handler,
.update = (AnimationUpdateImplementation) property_animation_update_int16,
.teardown = prv_teardown_handler
},
.accessors = {
.setter = { .int16 = (const Int16Setter) prv_int16_setter, },
.getter = { .int16 = (const Int16Getter) prv_int16_getter },
},
};
prop_h = property_animation_create(&implementation, &value, &start_value, &end_value);
Animation *h = property_animation_get_animation(prop_h);
cl_assert(animation_set_auto_destroy(h, false) == true);
// Handlers
void *context = &value;
animation_set_handlers(h, handlers, context);
AnimationHandlers c_handlers = animation_get_handlers(h);
cl_assert(memcmp(&c_handlers, &handlers, sizeof(AnimationHandlers)) == 0);
// Context
cl_assert(animation_get_context(h) == context);
// Duration
cl_assert(animation_get_duration(h, true, true) == 250); // default value
animation_set_duration(h, duration);
cl_assert(animation_get_duration(h, true, true) == duration);
// Delay
cl_assert(animation_get_delay(h) == 0);
animation_set_delay(h, delay);
cl_assert(animation_get_delay(h) == delay);
cl_assert(animation_get_duration(h, true, true) == (duration + delay));
// Play count
cl_assert_equal_i(animation_get_play_count(h), 1);
animation_set_play_count(h, 2);
cl_assert_equal_i(animation_get_play_count(h), 2);
cl_assert(animation_get_duration(h, true, true) == 2*(duration + delay));
// Curve
cl_assert(animation_get_curve(h) == AnimationCurveDefault);
animation_set_curve(h, AnimationCurveEaseOut);
cl_assert(animation_get_curve(h) == AnimationCurveEaseOut);
static const PropertyAnimationImplementation implementation2 = {
.base = {
.setup = prv_setup_handler,
.update = (AnimationUpdateImplementation) property_animation_update_gpoint,
.teardown = prv_teardown_handler
},
.accessors = {
.setter = { .int16 = (const Int16Setter) prv_gpoint_setter, },
.getter = { .int16 = (const Int16Getter) prv_gpoint_getter },
},
};
// Implementation
cl_assert(animation_get_implementation(h) == &implementation.base);
animation_set_implementation(h, &implementation2.base);
cl_assert(animation_get_implementation(h) != &implementation.base);
cl_assert(animation_get_implementation(h) == &implementation2.base);
// Custom Curve
cl_assert(animation_get_custom_curve(h) != prv_custom_curve);
animation_set_custom_curve(h, prv_custom_curve);
cl_assert(animation_get_custom_curve(h) == prv_custom_curve);
// Reverse
cl_assert(animation_get_reverse(h) == false);
animation_set_reverse(h, true);
cl_assert(animation_get_reverse(h) == true);
animation_set_reverse(h, false);
// Position
int32_t elapsed_ms = 0;
AnimationProgress progress = 0;
animation_schedule(h);
cl_must_pass(animation_get_elapsed(h, &elapsed_ms));
cl_assert_equal_i(elapsed_ms, -delay);
cl_assert_passert(animation_get_progress(h, &progress));
animation_set_elapsed(h, 0);
cl_must_pass(animation_get_elapsed(h, &elapsed_ms));
cl_assert_equal_i(elapsed_ms, 0);
cl_must_pass(animation_get_progress(h, &progress));
cl_assert_equal_i(progress, 0);
animation_set_elapsed(h, duration / 2);
cl_must_pass(animation_get_elapsed(h, &elapsed_ms));
cl_assert_equal_i(elapsed_ms, duration / 2);
cl_must_pass(animation_get_progress(h, &progress));
cl_assert_equal_i(progress, 32768); // Rounding occurs within, this is close to MAX / 2
animation_destroy(h);
#endif
}
void test_animation__completed(void) {
#ifdef TEST_INCLUDE_BASIC
const int duration_a = 300;
// Create 1 property animations
Animation *a = prv_create_test_animation();
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler_check_finished
};
animation_set_handlers(a, handlers, animation_get_context(a));
animation_set_duration(a, duration_a);
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
animation_schedule(a);
// Seek to just after the end of the second A
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
#endif
}
// --------------------------------------------------------------------------------------
// Test creating a sequence where the first argument is already scheduled and started
//
// Here's a graph of what we are doing
//
// 0 60 310 360 380 880
// | | | | | |
// -----------------------------------------------------------------------------------
// delay_a | duration_a |
// | delay_b | duration_b |
// | seq scheduled
//
void test_animation__sequence_of_already_scheduled_started(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int delay_a = 60;
const int leftover_a = 50;
const int duration_b = 500;
const int delay_b = 20;
const int leftover_seq = 40;
const int delay_seq = 30;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
// Create a property animation and advance it
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
animation_schedule(a);
// -------------------------------------------------------------------------------------
// Start A and advance it
prv_advance_to_ms_with_timers(start_ms + delay_a + MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
animation_set_elapsed(a, duration_a - leftover_a);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
// -------------------------------------------------------------------------------------
// Build up a sequence out of the leftover a + b
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_delay(b, delay_b);
// Should be an error trying to use a scheduled animation not in the first position
Animation *seq = animation_sequence_create(b, a, NULL);
cl_assert(seq == NULL);
seq = animation_sequence_create(a, b, NULL);
animation_set_delay(seq, delay_seq); // This delay not applicable since a was already scheduled
animation_set_handlers(seq, handlers, seq);
animation_schedule(seq);
// The duration of seq should include all of a and b
uint32_t duration = animation_get_duration(seq, true, true);
cl_assert_equal_i(duration, duration_a + delay_a + duration_b + delay_b);
// The position of seq should be the amount we already played of a, including the 'a' delay
// since a is embedded within seq
int32_t position;
animation_get_elapsed(seq, &position);
cl_assert_equal_i(position, duration_a + delay_a - leftover_a);
// Now, advance sequence to almost the end of seq. Positions don't include the delay, so
// pass false for 'include_delay'
animation_set_elapsed(seq, animation_get_duration(seq, false /*delay*/, true /*play_count*/)
- leftover_seq);
// Verify that a finished and that a's stop handler got called before B's start handler
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);
cl_assert(prv_last_handler_entry(&s_stopped_handler_calls, a)->fire_order
< prv_last_handler_entry(&s_started_handler_calls, b)->fire_order);
// Finish the sequence
prv_advance_to_ms_with_timers(prv_now_ms() + leftover_seq + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);
#endif
}
// --------------------------------------------------------------------------------------
// Test creating a sequence where the first argument is already scheduled, but not started
// yet (still in the delay portion).
//
// Here's a graph of what we are doing
//
// 0 100 200 360 380 880
// | | | | | |
// -----------------------------------------------------------------------------------
// delay_a | duration_a |
// | delay_b | duration_b |
// | seq scheduled
//
void test_animation__sequence_of_already_scheduled_not_started(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 160;
const int delay_a = 200;
const int duration_b = 500;
const int delay_b = 20;
const int leftover_seq = 50;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
prv_clear_handler_histories();
// Create a property animation and advance it
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
uint64_t start_ms = prv_now_ms();
animation_schedule(a);
// -------------------------------------------------------------------------------------
// Got partway through the delayof a
prv_advance_to_ms_with_timers(start_ms + 100);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 0);
// -------------------------------------------------------------------------------------
// Build up a sequence out of the leftover a + b
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_delay(b, delay_b);
Animation *seq = animation_sequence_create(a, b, NULL);
animation_set_handlers(seq, handlers, seq);
animation_schedule(seq);
// The duration of seq should include all of a and b
uint32_t duration = animation_get_duration(seq, true, true);
cl_assert_equal_i(duration, duration_a + delay_a + duration_b + delay_b);
// The position of seq should be the amount we already played of a, including the 'a' delay
// since a is embedded within seq
int32_t position;
animation_get_elapsed(seq, &position);
cl_assert_equal_i(position, 100);
// Now, advance sequence to almost the end of seq. Positions don't include the delay, so
// pass false for 'include_delay'
animation_set_elapsed(seq, animation_get_duration(seq, false /*delay*/, true /*play_count*/)
- leftover_seq);
// Verify that a finished and that a's stop handler got called before B's start handler
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 0);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, seq), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 0);
cl_assert(prv_last_handler_entry(&s_stopped_handler_calls, a)->fire_order
< prv_last_handler_entry(&s_started_handler_calls, b)->fire_order);
// Finish the sequence
prv_advance_to_ms_with_timers(prv_now_ms() + leftover_seq + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);
#endif
}
// --------------------------------------------------------------------------------------
// Test creating a sequence where the first argument is already completed.
// We will first create animation 'a' and run it to the end
// We will then create a sequence out of 'a' + 'b' and verify that if we advance that 'b' runs
// correctly.
void test_animation__sequence_of_already_completed(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300;
const int duration_b = 500;
const int delay_b = 20;
const int delay_seq = 30;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
// Create a property animation and play it to the end
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_schedule(a);
// -------------------------------------------------------------------------------------
// Start A and play to the end
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_last_update_distance(a), ANIMATION_NORMALIZED_MAX);
// -------------------------------------------------------------------------------------
// Build up a sequence out of a + b
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_delay(b, delay_b);
Animation *seq = animation_sequence_create(a, b, NULL);
animation_set_delay(seq, delay_seq);
animation_set_handlers(seq, handlers, seq);
animation_schedule(seq);
// The duration of seq should include all of b
uint32_t duration = animation_get_duration(seq, true, true);
cl_assert_equal_i(duration, duration_b + delay_b + delay_seq);
// The position of seq should be at -delay_seq
int32_t position;
animation_get_elapsed(seq, &position);
cl_assert_equal_i(position, -delay_seq);
// Finish the sequence
prv_advance_to_ms_with_timers(prv_now_ms() + delay_b + duration_b + delay_seq
+ 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_last_update_distance(b), ANIMATION_NORMALIZED_MAX);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, seq), 1);
#endif
}
// --------------------------------------------------------------------------------------
// Test creating a spawn where where some children are already scheduled and some have
// already completed.
//
// Here's a graph of what we are doing
//
// 0 10 310 320 330 500 680 730 850 950 1080 1300
// | | | | | | | | | | | |
// -----------------------------------------------------------------------------------
// delay_a | duration a | |
// | delay_b | duration_b |
// | delay_c | duration_c |
// | delay_s | delay_d | duration_d |
// -----------------------------------------------------------------------------------
// | spawn scheduled here
void test_animation__spawn_of_already_scheduled(void) {
#ifdef TEST_INCLUDE_COMPLEX
const int duration_a = 300; // This one will complete
const int delay_a = 10;
const int duration_b = 400; // This one will have 50 ms left on it
const int delay_b = 20;
const int duration_c = 350;
const int delay_c = 230;
const int duration_d = 220; // This one won't be scheduled yet
const int delay_d = 230;
const int delay_spawn = 170;
const AnimationHandlers handlers = {
.started = prv_started_handler,
.stopped = prv_stopped_handler
};
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
// Create the animations
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_b);
animation_set_delay(b, delay_b);
Animation *c = prv_create_test_animation();
animation_set_duration(c, duration_c);
animation_set_delay(c, delay_c);
Animation *d = prv_create_test_animation();
animation_set_duration(d, duration_d);
animation_set_delay(d, delay_d);
// -------------------------------------------------------------------------------------
// Run A to completion
animation_schedule(a);
prv_advance_to_ms_with_timers(start_ms + 20);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, a), 1);
prv_advance_to_ms_with_timers(start_ms + 310);
// Schedule B now and run for a little
animation_schedule(b);
prv_advance_to_ms_with_timers(start_ms + 330);
// Schedule C now and run for a while
prv_advance_to_ms_with_timers(start_ms + 500);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, a), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, b), 1);
animation_schedule(c);
// Create the spawn using a, b, c, and d
prv_advance_to_ms_with_timers(start_ms + 680);
Animation *spawn = animation_spawn_create(a, b, c, d, NULL);
animation_set_delay(spawn, delay_spawn);
animation_set_handlers(spawn, handlers, spawn);
animation_schedule(spawn);
// Check the duration and position of the spawn
uint32_t duration = animation_get_duration(spawn, true /*delay*/, true /*play_count*/);
cl_assert_equal_i(duration, 1300 - 310);
int32_t position;
animation_get_elapsed(spawn, &position);
cl_assert_equal_i(position, (680 - 310));
// Run to the completion of B, start of C
prv_advance_to_ms_with_timers(start_ms + 730 + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, b), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, spawn), 1);
// Run to the completion of C, start of D
prv_advance_to_ms_with_timers(start_ms + 1080 + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, c), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_started_handler_calls, d), 1);
// Run to the completion of D
prv_advance_to_ms_with_timers(start_ms + 1300 + 2 * MIN_FRAME_INTERVAL_MS);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, d), 1);
cl_assert_equal_i(prv_count_handler_entries(&s_stopped_handler_calls, spawn), 1);
#endif
}
void prv_update_unschedule_all_handler(Animation *animation, const AnimationProgress distance) {
prv_add_handler_entry(&s_update_handler_calls, animation, false,
(void *)(uintptr_t)distance /*context*/);
DPRINTF("%"PRIu64" ms: Executing update handler for %d, distance: %d\n", prv_now_ms(),
(int)animation, (int)distance);
if (distance > ANIMATION_NORMALIZED_MAX / 2) {
animation_unschedule_all();
}
}
// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in an update handler.
static void prv_unschedule_all_in_update_handler(bool auto_destroy) {
const int duration_a = 300; // This one will complete
const int delay_a = 10;
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
static const AnimationImplementation implementation = {
.setup = prv_setup_handler,
.update = prv_update_unschedule_all_handler,
.teardown = prv_teardown_handler
};
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
animation_set_auto_destroy(a, auto_destroy);
animation_schedule(a);
Animation *b = prv_create_test_animation();
animation_set_implementation(b, &implementation);
animation_set_duration(b, duration_a);
animation_set_delay(b, delay_a);
animation_set_auto_destroy(b, auto_destroy);
animation_schedule(b);
Animation *c = prv_create_test_animation();
animation_set_duration(c, duration_a);
animation_set_delay(c, delay_a);
animation_set_auto_destroy(c, auto_destroy);
animation_schedule(c);
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
animation_destroy(a);
animation_destroy(b);
animation_destroy(c);
}
// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in an update handler with auto destroy.
void test_animation__unschedule_all_in_update_handler_with_auto_destroy(void) {
prv_unschedule_all_in_update_handler(true);
}
// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in an update handler without auto destroy.
void test_animation__unschedule_all_in_update_handler_without_auto_destroy(void) {
prv_unschedule_all_in_update_handler(false);
}
static void prv_stopped_unschedule_all_handler(Animation *animation, bool finished, void *context) {
prv_add_handler_entry(&s_stopped_handler_calls, animation, finished, context);
DPRINTF("%"PRIu64" ms: Executing stopped handler for %d\n", prv_now_ms(), (int)animation);
animation_unschedule_all();
}
// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in a stopped handler with/without auto destroy.
void prv_test_unschedule_all_in_stopped_handler(bool auto_destroy) {
const int duration_a = 300; // This one will complete
const int delay_a = 10;
prv_clear_handler_histories();
uint64_t start_ms = prv_now_ms();
const AnimationHandlers handlers = {
.stopped = prv_stopped_unschedule_all_handler,
};
void *context = NULL;
Animation *a = prv_create_test_animation();
animation_set_duration(a, duration_a);
animation_set_delay(a, delay_a);
animation_set_handlers(a, handlers, context);
animation_set_auto_destroy(a, auto_destroy);
animation_schedule(a);
Animation *b = prv_create_test_animation();
animation_set_duration(b, duration_a);
animation_set_delay(b, delay_a);
animation_set_handlers(b, handlers, context);
animation_set_auto_destroy(b, auto_destroy);
animation_schedule(b);
Animation *c = prv_create_test_animation();
animation_set_duration(c, duration_a);
animation_set_delay(c, delay_a);
animation_set_handlers(c, handlers, context);
animation_set_auto_destroy(c, auto_destroy);
animation_schedule(c);
prv_advance_to_ms_with_timers(start_ms + duration_a + 2 * MIN_FRAME_INTERVAL_MS);
animation_destroy(a);
animation_destroy(b);
animation_destroy(c);
}
// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in a stopped handler with auto destroy.
void test_animation__unschedule_all_in_stopped_handler_with_auto_destroy(void) {
prv_test_unschedule_all_in_stopped_handler(true);
}
// --------------------------------------------------------------------------------------
// Test unscheduling animations arbitrarily in a stopped handler without auto destroy.
void test_animation__unschedule_all_in_stopped_handler_without_auto_destroy(void) {
prv_test_unschedule_all_in_stopped_handler(false);
}
void test_animation__custom_functions(void) {
// just some pointer to compare against
AnimationCurveFunction curve = (void*)1;
InterpolateInt64Function interpolation = (void*)2;
Animation *a = prv_create_test_animation();
cl_assert_equal_p(animation_get_custom_curve(a), NULL);
cl_assert_equal_p(animation_get_custom_interpolation(a), NULL);
cl_assert_equal_i(animation_get_curve(a), AnimationCurveDefault);
animation_set_custom_curve(a, curve);
cl_assert_equal_p(animation_get_custom_curve(a), curve);
cl_assert_equal_p(animation_get_custom_interpolation(a), NULL);
cl_assert_equal_i(animation_get_curve(a), AnimationCurveCustomFunction);
animation_set_custom_interpolation(a, interpolation);
cl_assert_equal_p(animation_get_custom_curve(a), NULL);
cl_assert_equal_p(animation_get_custom_interpolation(a), interpolation);
cl_assert_equal_i(animation_get_curve(a), AnimationCurveCustomInterpolationFunction);
animation_set_curve(a, AnimationCurveDefault);
cl_assert_equal_p(animation_get_custom_curve(a), NULL);
cl_assert_equal_p(animation_get_custom_interpolation(a), NULL);
cl_assert_equal_i(animation_get_curve(a), AnimationCurveDefault);
animation_destroy(a);
}
void test_animation__current_interpolate_override(void) {
// just some pointer to compare against
AnimationCurveFunction curve = (void*)1;
InterpolateInt64Function interpolation = (void*)2;
AnimationState *state = kernel_applib_get_animation_state();
cl_assert_equal_p(state->aux->current_animation, NULL);
cl_assert_equal_p(animation_private_current_interpolate_override(), NULL);
Animation *a = prv_create_test_animation();
AnimationPrivate *a_p = animation_private_animation_find(a);
state->aux->current_animation = a_p;
cl_assert_equal_p(animation_private_current_interpolate_override(), NULL);
animation_set_custom_interpolation(a, interpolation);
cl_assert_equal_p(animation_private_current_interpolate_override(), interpolation);
animation_set_custom_curve(a, curve);
cl_assert_equal_p(animation_private_current_interpolate_override(), NULL);
animation_destroy(a);
}