Move elapsed-time calculations into ClutterTimeline

Instead of calculating a delta in the master clock, and passing that
into each timeline, make each timeline individually responsible for
remembering the last time and computing the delta.

This:

 - Fixes a problem where we could spin infinitely processing
   timeline-only frames with < 1msec differences.
 - Makes timelines consistently start timing on the first frame;
   instead of doing different things for the first started timeline
   and other timelines.
 - Improves accuracy of elapsed time computations by avoiding
   accumulating microsecond => millisecond truncation errors.

http://bugzilla.openedhand.com/show_bug.cgi?id=1637

Signed-off-by: Emmanuele Bassi <ebassi@linux.intel.com>
This commit is contained in:
Owen W. Taylor 2009-06-08 13:00:09 -04:00 committed by Emmanuele Bassi
parent dcd8d28314
commit 6705ce6c6a
6 changed files with 48 additions and 95 deletions

View File

@ -66,8 +66,6 @@ struct _ClutterMasterClock
* a redraw on the stage and drive the animations * a redraw on the stage and drive the animations
*/ */
GSource *source; GSource *source;
guint timelines_running : 1;
}; };
struct _ClutterMasterClockClass struct _ClutterMasterClockClass
@ -364,8 +362,6 @@ _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock,
{ {
master_clock->timelines = g_slist_remove (master_clock->timelines, master_clock->timelines = g_slist_remove (master_clock->timelines,
timeline); timeline);
if (master_clock->timelines == NULL)
master_clock->timelines_running = FALSE;
} }
/* /*
@ -395,39 +391,14 @@ _clutter_master_clock_start_running (ClutterMasterClock *master_clock)
void void
_clutter_master_clock_advance (ClutterMasterClock *master_clock) _clutter_master_clock_advance (ClutterMasterClock *master_clock)
{ {
gulong msecs; GSList *l, *next;
GSList *l;
g_return_if_fail (CLUTTER_IS_MASTER_CLOCK (master_clock)); g_return_if_fail (CLUTTER_IS_MASTER_CLOCK (master_clock));
if (master_clock->timelines == NULL) for (l = master_clock->timelines; l != NULL; l = next)
return;
if (!master_clock->timelines_running)
{ {
/* When we start running, we count the first frame as time 0, next = l->next;
* so we don't need an advance. (And master_clock->prev_tick
* doesn't meaningfully relate to these timelines.)
*/
master_clock->timelines_running = TRUE;
return;
}
msecs = (master_clock->cur_tick.tv_sec - master_clock->prev_tick.tv_sec) * 1000 clutter_timeline_do_tick (l->data, &master_clock->cur_tick);
+ (master_clock->cur_tick.tv_usec - master_clock->prev_tick.tv_usec) / 1000;
if (msecs == 0)
return;
CLUTTER_NOTE (SCHEDULER, "Advancing %d timelines by %lu milliseconds",
g_slist_length (master_clock->timelines),
msecs);
for (l = master_clock->timelines; l != NULL; l = l->next)
{
ClutterTimeline *timeline = l->data;
if (clutter_timeline_is_playing (timeline))
clutter_timeline_advance_delta (timeline, msecs);
} }
} }

View File

@ -62,8 +62,13 @@ struct _ClutterTimelinePrivate
GHashTable *markers_by_name; GHashTable *markers_by_name;
/* Time we last advanced the elapsed time and showed a frame */
GTimeVal last_frame_time;
guint loop : 1; guint loop : 1;
guint is_playing : 1; guint is_playing : 1;
/* If we've just started playing and haven't yet gotten a tick from the master clock */
guint waiting_first_tick : 1;
}; };
typedef struct { typedef struct {
@ -489,13 +494,18 @@ set_is_playing (ClutterTimeline *timeline,
priv->is_playing = is_playing; priv->is_playing = is_playing;
master_clock = _clutter_master_clock_get_default (); master_clock = _clutter_master_clock_get_default ();
if (priv->is_playing) if (priv->is_playing)
{
_clutter_master_clock_add_timeline (master_clock, timeline); _clutter_master_clock_add_timeline (master_clock, timeline);
priv->waiting_first_tick = TRUE;
}
else else
{
_clutter_master_clock_remove_timeline (master_clock, timeline); _clutter_master_clock_remove_timeline (master_clock, timeline);
} }
}
static gboolean static gboolean
clutter_timeline_advance_internal (ClutterTimeline *timeline) clutter_timeline_do_frame (ClutterTimeline *timeline)
{ {
ClutterTimelinePrivate *priv; ClutterTimelinePrivate *priv;
@ -1135,19 +1145,18 @@ clutter_timeline_get_delta (ClutterTimeline *timeline)
} }
/* /*
* clutter_timeline_advance_delta: * clutter_timeline_do_tick
* @timeline: a #ClutterTimeline * @timeline: a #ClutterTimeline
* @msecs: advance in milliseconds * @tick_time: time of advance
* *
* Advances @timeline by @msecs. This function is called by the master * Advances @timeline based on the time passed in @msecs. This
* clock and it is used to advance a timeline by the amount of milliseconds * function is called by the master clock. The @timeline will use this
* elapsed since the last redraw operation. The @timeline will use this * interval to emit the #ClutterTimeline::new-frame signal and
* interval to emit the #ClutterTimeline::new-frame signal and eventually * eventually skip frames.
* skip frames.
*/ */
void void
clutter_timeline_advance_delta (ClutterTimeline *timeline, clutter_timeline_do_tick (ClutterTimeline *timeline,
guint msecs) GTimeVal *tick_time)
{ {
ClutterTimelinePrivate *priv; ClutterTimelinePrivate *priv;
@ -1155,9 +1164,25 @@ clutter_timeline_advance_delta (ClutterTimeline *timeline,
priv = timeline->priv; priv = timeline->priv;
priv->msecs_delta = msecs; if (priv->waiting_first_tick)
{
priv->last_frame_time = *tick_time;
priv->waiting_first_tick = FALSE;
}
else
{
gint msecs =
(tick_time->tv_sec - priv->last_frame_time.tv_sec) * 1000
+ (tick_time->tv_usec - priv->last_frame_time.tv_usec) / 1000;
clutter_timeline_advance_internal (timeline); if (msecs != 0)
{
/* Avoid accumulating error */
g_time_val_add (&priv->last_frame_time, msecs * 1000L);
priv->msecs_delta = msecs;
clutter_timeline_do_frame (timeline);
}
}
} }
static inline void static inline void

View File

@ -155,8 +155,8 @@ void clutter_timeline_advance_to_marker (ClutterTimeline *timeli
const gchar *marker_name); const gchar *marker_name);
/*< private >*/ /*< private >*/
void clutter_timeline_advance_delta (ClutterTimeline *timeline, void clutter_timeline_do_tick (ClutterTimeline *timeline,
guint msecs); GTimeVal *tick_time);
G_END_DECLS G_END_DECLS

View File

@ -24,8 +24,6 @@ typedef struct _TestState
gint completion_count; gint completion_count;
gboolean passed; gboolean passed;
guint source_id; guint source_id;
GTimeVal prev_tick;
gulong msecs_delta;
} TestState; } TestState;
@ -139,21 +137,11 @@ frame_tick (gpointer data)
{ {
TestState *state = data; TestState *state = data;
GTimeVal cur_tick = { 0, }; GTimeVal cur_tick = { 0, };
gulong msecs;
g_get_current_time (&cur_tick); g_get_current_time (&cur_tick);
if (state->prev_tick.tv_sec == 0)
state->prev_tick = cur_tick;
msecs = (cur_tick.tv_sec - state->prev_tick.tv_sec) * 1000
+ (cur_tick.tv_usec - state->prev_tick.tv_usec) / 1000;
if (clutter_timeline_is_playing (state->timeline)) if (clutter_timeline_is_playing (state->timeline))
clutter_timeline_advance_delta (state->timeline, msecs); clutter_timeline_do_tick (state->timeline, &cur_tick);
state->msecs_delta = msecs;
state->prev_tick = cur_tick;
return TRUE; return TRUE;
} }
@ -180,9 +168,6 @@ test_timeline_interpolate (TestConformSimpleFixture *fixture,
state.new_frame_counter = 0; state.new_frame_counter = 0;
state.passed = TRUE; state.passed = TRUE;
state.expected_frame = 0; state.expected_frame = 0;
state.prev_tick.tv_sec = 0;
state.prev_tick.tv_usec = 0;
state.msecs_delta = 0;
state.source_id = state.source_id =
clutter_threads_add_frame_source (60, frame_tick, &state); clutter_threads_add_frame_source (60, frame_tick, &state);

View File

@ -12,8 +12,6 @@ typedef struct _TestState
ClutterTimeline *timeline; ClutterTimeline *timeline;
gint rewind_count; gint rewind_count;
guint source_id; guint source_id;
GTimeVal prev_tick;
gulong msecs_delta;
} TestState; } TestState;
static gboolean static gboolean
@ -74,21 +72,11 @@ frame_tick (gpointer data)
{ {
TestState *state = data; TestState *state = data;
GTimeVal cur_tick = { 0, }; GTimeVal cur_tick = { 0, };
gulong msecs;
g_get_current_time (&cur_tick); g_get_current_time (&cur_tick);
if (state->prev_tick.tv_sec == 0)
state->prev_tick = cur_tick;
msecs = (cur_tick.tv_sec - state->prev_tick.tv_sec) * 1000
+ (cur_tick.tv_usec - state->prev_tick.tv_usec) / 1000;
if (clutter_timeline_is_playing (state->timeline)) if (clutter_timeline_is_playing (state->timeline))
clutter_timeline_advance_delta (state->timeline, msecs); clutter_timeline_do_tick (state->timeline, &cur_tick);
state->msecs_delta = msecs;
state->prev_tick = cur_tick;
return TRUE; return TRUE;
} }
@ -111,9 +99,6 @@ test_timeline_rewind (TestConformSimpleFixture *fixture,
(GSourceFunc)watchdog_timeout, (GSourceFunc)watchdog_timeout,
&state); &state);
state.rewind_count = 0; state.rewind_count = 0;
state.prev_tick.tv_sec = 0;
state.prev_tick.tv_usec = 0;
state.msecs_delta = 0;
state.source_id = state.source_id =
clutter_threads_add_frame_source (60, frame_tick, &state); clutter_threads_add_frame_source (60, frame_tick, &state);

View File

@ -185,9 +185,6 @@ typedef struct _FrameCounter FrameCounter;
struct _FrameCounter struct _FrameCounter
{ {
GTimeVal prev_tick;
gulong msecs_delta;
GSList *timelines; GSList *timelines;
}; };
@ -197,27 +194,17 @@ frame_tick (gpointer data)
FrameCounter *counter = data; FrameCounter *counter = data;
GTimeVal cur_tick = { 0, }; GTimeVal cur_tick = { 0, };
GSList *l; GSList *l;
gulong msecs;
g_get_current_time (&cur_tick); g_get_current_time (&cur_tick);
if (counter->prev_tick.tv_sec == 0)
counter->prev_tick = cur_tick;
msecs = (cur_tick.tv_sec - counter->prev_tick.tv_sec) * 1000
+ (cur_tick.tv_usec - counter->prev_tick.tv_usec) / 1000;
for (l = counter->timelines; l != NULL; l = l->next) for (l = counter->timelines; l != NULL; l = l->next)
{ {
ClutterTimeline *timeline = l->data; ClutterTimeline *timeline = l->data;
if (clutter_timeline_is_playing (timeline)) if (clutter_timeline_is_playing (timeline))
clutter_timeline_advance_delta (timeline, msecs); clutter_timeline_do_tick (timeline, &cur_tick);
} }
counter->msecs_delta = msecs;
counter->prev_tick = cur_tick;
return TRUE; return TRUE;
} }