/* * 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 #include #include /////////////////////////////////////////////////////////// // 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); }