#define CLUTTER_DISABLE_DEPRECATION_WARNINGS #include #include #include #include "tests/clutter-test-utils.h" /* We ask for 1 frame per millisecond. * Whenever this rate can't be achieved then the timeline * will interpolate the number frames that should have * passed between timeouts. */ #define TEST_TIMELINE_FPS 1000 #define TEST_TIMELINE_DURATION 5000 /* We are at the mercy of the system scheduler so this * may not be a very reliable tolerance. * * It's set as very tolerable (1 ms shorter than the frame interval) as * otherwise CI, which are very prone to not get CPU time scheduled, tend to * often fail. */ #define TEST_ERROR_TOLERANCE ((TEST_TIMELINE_FPS / 4) - 1) typedef struct _TestState { ClutterTimeline *timeline; int64_t start_time; guint new_frame_counter; gint expected_frame; gint completion_count; gboolean passed; } TestState; static void new_frame_cb (ClutterTimeline *timeline, gint frame_num, TestState *state) { int64_t current_time; gint current_frame; glong msec_diff; gint loop_overflow = 0; static gint step = 1; current_time = g_get_real_time (); current_frame = clutter_timeline_get_elapsed_time (state->timeline); msec_diff = (current_time - state->start_time) / G_TIME_SPAN_MILLISECOND; /* If we expect to have interpolated past the end of the timeline * we keep track of the overflow so we can determine when * the next timeout will happen. We then clip expected_frames * to TEST_TIMELINE_DURATION since clutter-timeline * semantics guaranty this frame is always signaled before * looping */ if (state->expected_frame > TEST_TIMELINE_DURATION) { loop_overflow = state->expected_frame - TEST_TIMELINE_DURATION; state->expected_frame = TEST_TIMELINE_DURATION; } if (current_frame >= (state->expected_frame-TEST_ERROR_TOLERANCE) && current_frame <= (state->expected_frame+TEST_ERROR_TOLERANCE)) { g_test_message ("elapsed milliseconds=%-5li " "expected frame=%-4i actual frame=%-4i (OK)", msec_diff, state->expected_frame, current_frame); } else { g_test_message ("elapsed milliseconds=%-5li " "expected frame=%-4i actual frame=%-4i (FAILED)", msec_diff, state->expected_frame, current_frame); state->passed = FALSE; } if (step>0) { state->expected_frame = current_frame + (TEST_TIMELINE_FPS / 4); g_test_message ("Sleeping for 250ms " "so next frame should be (%i + %i) = %i", current_frame, (TEST_TIMELINE_FPS / 4), state->expected_frame); g_usleep (250000); } else { state->expected_frame = current_frame + TEST_TIMELINE_FPS; g_test_message ("Sleeping for 1sec " "so next frame should be (%i + %i) = %i", current_frame, TEST_TIMELINE_FPS, state->expected_frame); g_usleep (1000000); } if (current_frame >= TEST_TIMELINE_DURATION) { state->expected_frame += loop_overflow; state->expected_frame -= TEST_TIMELINE_DURATION; g_test_message ("End of timeline reached: " "Wrapping expected frame too %i", state->expected_frame); } state->new_frame_counter++; step = -step; } static void completed_cb (ClutterTimeline *timeline, TestState *state) { state->completion_count++; if (state->completion_count == 2) { if (state->passed) clutter_main_quit (); else g_assert_not_reached (); } } static void timeline_interpolation (void) { ClutterActor *stage; TestState state; stage = clutter_test_get_stage (); state.timeline = clutter_timeline_new_for_actor (stage, TEST_TIMELINE_DURATION); clutter_timeline_set_repeat_count (state.timeline, -1); g_signal_connect (G_OBJECT(state.timeline), "new-frame", G_CALLBACK(new_frame_cb), &state); g_signal_connect (G_OBJECT(state.timeline), "completed", G_CALLBACK(completed_cb), &state); state.completion_count = 0; state.new_frame_counter = 0; state.passed = TRUE; state.expected_frame = 0; clutter_actor_show (stage); state.start_time = g_get_real_time (); clutter_timeline_start (state.timeline); clutter_main(); g_object_unref (state.timeline); } CLUTTER_TEST_SUITE ( CLUTTER_TEST_UNIT ("/timeline/interpolate", timeline_interpolation) )