diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 4f53a5a2e..fa4d87a06 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -169,6 +169,7 @@ source_c = \ $(srcdir)/clutter-list-model.c \ $(srcdir)/clutter-main.c \ clutter-marshal.c \ + $(srcdir)/clutter-master-clock.c \ $(srcdir)/clutter-media.c \ $(srcdir)/clutter-model.c \ $(srcdir)/clutter-path.c \ @@ -195,6 +196,7 @@ source_h_priv = \ $(srcdir)/clutter-bezier.h \ $(srcdir)/clutter-debug.h \ $(srcdir)/clutter-keysyms-table.h \ + $(srcdir)/clutter-master-clock.h \ $(srcdir)/clutter-model-private.h \ $(srcdir)/clutter-private.h \ $(srcdir)/clutter-id-pool.h \ diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c index 31b24d23f..9e0163a51 100644 --- a/clutter/clutter-main.c +++ b/clutter/clutter-main.c @@ -41,6 +41,7 @@ #include "clutter-event.h" #include "clutter-backend.h" #include "clutter-main.h" +#include "clutter-master-clock.h" #include "clutter-feature.h" #include "clutter-actor.h" #include "clutter-stage.h" @@ -181,14 +182,12 @@ void clutter_redraw (ClutterStage *stage) { ClutterMainContext *ctx; - static GTimer *timer = NULL; - static guint timer_n_frames = 0; + static GTimer *timer = NULL; + static guint timer_n_frames = 0; ctx = clutter_context_get_default (); - CLUTTER_TIMESTAMP (SCHEDULER, "Redraw start for stage:%p", stage); CLUTTER_NOTE (PAINT, " Redraw enter for stage:%p", stage); - CLUTTER_NOTE (MULTISTAGE, "Redraw called for stage:%p", stage); /* Before we can paint, we have to be sure we have the latest layout */ _clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage)); diff --git a/clutter/clutter-master-clock.c b/clutter/clutter-master-clock.c new file mode 100644 index 000000000..2a7e53124 --- /dev/null +++ b/clutter/clutter-master-clock.c @@ -0,0 +1,364 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By: Emmanuele Bassi + * + * Copyright (C) 2009 Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +/* + * SECTION:clutter-master-clock + * @short_description: The master clock for all animations + * + * The #ClutterMasterClock class is responsible for advancing all + * #ClutterTimelines when a stage is being redrawn. The master clock + * makes sure that the scenegraph is always integrally updated before + * painting it. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "clutter-master-clock.h" +#include "clutter-debug.h" +#include "clutter-private.h" + +#define CLUTTER_MASTER_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_MASTER_CLOCK, ClutterMasterClockClass)) +#define CLUTTER_IS_MASTER_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_MASTER_CLOCK)) +#define CLUTTER_MASTER_CLASS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_MASTER_CLOCK, ClutterMasterClockClass)) + +typedef struct _ClutterMasterClockClass ClutterMasterClockClass; + +struct _ClutterMasterClock +{ + GObject parent_instance; + + GSList *timelines; + + GTimeVal prev_tick; + gulong msecs_delta; + + gulong tick_id; +}; + +struct _ClutterMasterClockClass +{ + GObjectClass parent_class; +}; + +static ClutterMasterClock *default_clock = NULL; + +static void on_timeline_start (ClutterTimeline *timeline, + ClutterMasterClock *master_clock); +static void on_timeline_completed (ClutterTimeline *timeline, + ClutterMasterClock *master_clock); +static void on_timeline_paused (ClutterTimeline *timeline, + ClutterMasterClock *master_clock); + +G_DEFINE_TYPE (ClutterMasterClock, clutter_master_clock, G_TYPE_OBJECT); + +static void +timeline_weak_ref (gpointer data, + GObject *object_pointer) +{ + ClutterMasterClock *master_clock = data; + + master_clock->timelines = g_slist_remove (master_clock->timelines, + object_pointer); + + if (master_clock->timelines == NULL) + { + if (master_clock->tick_id != 0) + { + g_source_remove (master_clock->tick_id); + master_clock->tick_id = 0; + } + + master_clock->prev_tick.tv_sec = 0; + } +} + +static void +clutter_master_clock_finalize (GObject *gobject) +{ + ClutterMasterClock *master_clock = CLUTTER_MASTER_CLOCK (gobject); + GSList *l; + + if (master_clock->tick_id != 0) + { + g_source_remove (master_clock->tick_id); + master_clock->tick_id = 0; + } + + for (l = master_clock->timelines; l != NULL; l = l->next) + { + ClutterTimeline *timeline = l->data; + + g_object_weak_unref (G_OBJECT (timeline), + timeline_weak_ref, + master_clock); + + g_signal_handlers_disconnect_by_func (timeline, + G_CALLBACK (on_timeline_start), + master_clock); + g_signal_handlers_disconnect_by_func (timeline, + G_CALLBACK (on_timeline_completed), + master_clock); + g_signal_handlers_disconnect_by_func (timeline, + G_CALLBACK (on_timeline_paused), + master_clock); + } + + g_slist_free (master_clock->timelines); + + G_OBJECT_CLASS (clutter_master_clock_parent_class)->finalize (gobject); +} + +static void +clutter_master_clock_class_init (ClutterMasterClockClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = clutter_master_clock_finalize; +} + +static void +clutter_master_clock_init (ClutterMasterClock *self) +{ +} + +/* + * _clutter_master_clock_get_default: + * + * Retrieves the default master clock. If this function has never + * been called before, the default master clock is created. + * + * Return value: the default master clock. The returned object is + * owned by Clutter and should not be modified or freed + */ +ClutterMasterClock * +_clutter_master_clock_get_default (void) +{ + if (G_LIKELY (default_clock != NULL)) + return default_clock; + + default_clock = g_object_new (CLUTTER_TYPE_MASTER_CLOCK, NULL); + + return default_clock; +} + +static gboolean +master_clock_tick (gpointer data) +{ + ClutterStageManager *stage_manager = clutter_stage_manager_get_default (); + GSList *stages, *l; + + CLUTTER_NOTE (SCHEDULER, "Master clock [tick]"); + + stages = clutter_stage_manager_list_stages (stage_manager); + + for (l = stages; l != NULL; l = l->next) + clutter_actor_queue_redraw (l->data); + + g_slist_free (stages); + + return TRUE; +} + +static gboolean +has_running_timeline (ClutterMasterClock *master_clock) +{ + GSList *l; + + for (l = master_clock->timelines; l != NULL; l = l->next) + { + if (clutter_timeline_is_playing (l->data)) + return TRUE; + } + + return FALSE; +} + +static void +on_timeline_start (ClutterTimeline *timeline, + ClutterMasterClock *master_clock) +{ + if (has_running_timeline (master_clock) && + master_clock->tick_id == 0) + { + master_clock->prev_tick.tv_sec = 0; + + master_clock->tick_id = + clutter_threads_add_frame_source (clutter_get_default_frame_rate (), + master_clock_tick, + master_clock); + } +} + +static void +on_timeline_completed (ClutterTimeline *timeline, + ClutterMasterClock *master_clock) +{ + if (!has_running_timeline (master_clock) && + master_clock->tick_id != 0) + { + master_clock->prev_tick.tv_sec = 0; + + g_source_remove (master_clock->tick_id); + master_clock->tick_id = 0; + } +} + +static void +on_timeline_paused (ClutterTimeline *timeline, + ClutterMasterClock *master_clock) +{ + if (!has_running_timeline (master_clock) && + master_clock->tick_id != 0) + { + master_clock->prev_tick.tv_sec = 0; + + g_source_remove (master_clock->tick_id); + master_clock->tick_id = 0; + } +} + +/* + * _clutter_master_clock_add_timeline: + * @master_clock: a #ClutterMasterClock + * @timeline: a #ClutterTimeline + * + * Adds @timeline to the list of timelines held by the master + * clock. This function should be called during the instance + * creation phase of the timeline. + */ +void +_clutter_master_clock_add_timeline (ClutterMasterClock *master_clock, + ClutterTimeline *timeline) +{ + gboolean is_first = FALSE; + + if (g_slist_find (master_clock->timelines, timeline)) + return; + + is_first = (master_clock->timelines == NULL) ? TRUE : FALSE; + + master_clock->timelines = g_slist_prepend (master_clock->timelines, + timeline); + + g_object_weak_ref (G_OBJECT (timeline), + timeline_weak_ref, + master_clock); + + g_signal_connect (timeline, "started", + G_CALLBACK (on_timeline_start), + master_clock); + g_signal_connect (timeline, "completed", + G_CALLBACK (on_timeline_completed), + master_clock); + g_signal_connect (timeline, "paused", + G_CALLBACK (on_timeline_paused), + master_clock); +} + +/* + * _clutter_master_clock_remove_timeline: + * @master_clock: a #ClutterMasterClock + * @timeline: a #ClutterTimeline + * + * Removes @timeline from the list of timelines held by the + * master clock. This function should be called during the + * #ClutterTimeline finalization. + */ +void +_clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock, + ClutterTimeline *timeline) +{ + if (!g_slist_find (master_clock->timelines, timeline)) + return; + + master_clock->timelines = g_slist_remove (master_clock->timelines, + timeline); + + g_object_weak_unref (G_OBJECT (timeline), + timeline_weak_ref, + master_clock); + + g_signal_handlers_disconnect_by_func (timeline, + G_CALLBACK (on_timeline_start), + master_clock); + g_signal_handlers_disconnect_by_func (timeline, + G_CALLBACK (on_timeline_completed), + master_clock); + g_signal_handlers_disconnect_by_func (timeline, + G_CALLBACK (on_timeline_paused), + master_clock); + + if (master_clock->timelines == NULL) + { + if (master_clock->tick_id != 0) + { + g_source_remove (master_clock->tick_id); + master_clock->tick_id = 0; + } + + master_clock->prev_tick.tv_sec = 0; + } +} + +/* + * _clutter_master_clock_advance: + * @master_clock: a #ClutterMasterClock + * + * Advances all the timelines held by the master clock. This function + * should be called before calling clutter_redraw() to make sure that + * all the timelines are advanced and the scene is updated. + */ +void +_clutter_master_clock_advance (ClutterMasterClock *master_clock) +{ + GTimeVal cur_tick = { 0, }; + gulong msecs; + GSList *l; + + g_return_if_fail (CLUTTER_IS_MASTER_CLOCK (master_clock)); + + g_get_current_time (&cur_tick); + + if (master_clock->prev_tick.tv_sec == 0) + master_clock->prev_tick = cur_tick; + + msecs = (cur_tick.tv_sec - master_clock->prev_tick.tv_sec) * 1000 + + (cur_tick.tv_usec - master_clock->prev_tick.tv_usec) / 1000; + + 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_set_delta (timeline, msecs); + } + + master_clock->msecs_delta = msecs; + master_clock->prev_tick = cur_tick; +} diff --git a/clutter/clutter-master-clock.h b/clutter/clutter-master-clock.h new file mode 100644 index 000000000..5a3f7cd32 --- /dev/null +++ b/clutter/clutter-master-clock.h @@ -0,0 +1,48 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By: Emmanuele Bassi + * + * Copyright (C) 2009 Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __CLUTTER_MASTER_CLOCK_H__ +#define __CLUTTER_MASTER_CLOCK_H__ + +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_MASTER_CLOCK (clutter_master_clock_get_type ()) +#define CLUTTER_MASTER_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_MASTER_CLOCK, ClutterMasterClock)) +#define CLUTTER_IS_MASTER_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_MASTER_CLOCK)) + +typedef struct _ClutterMasterClock ClutterMasterClock; + +GType clutter_master_clock_get_type (void) G_GNUC_CONST; + +ClutterMasterClock *_clutter_master_clock_get_default (void); +void _clutter_master_clock_add_timeline (ClutterMasterClock *master_clock, + ClutterTimeline *timeline); +void _clutter_master_clock_remove_timeline (ClutterMasterClock *master_clock, + ClutterTimeline *timeline); +void _clutter_master_clock_advance (ClutterMasterClock *master_clock); + +G_END_DECLS + +#endif /* __CLUTTER_MASTER_CLOCK_H__ */ diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h index f102a0b90..cb6627cbe 100644 --- a/clutter/clutter-private.h +++ b/clutter/clutter-private.h @@ -47,6 +47,7 @@ #include "clutter-stage-manager.h" #include "clutter-stage-window.h" #include "clutter-stage.h" +#include "clutter-timeline.h" G_BEGIN_DECLS @@ -127,6 +128,8 @@ struct _ClutterMainContext MultiTouch */ guint32 last_event_time; + + gulong redraw_count; }; #define CLUTTER_CONTEXT() (clutter_context_get_default ()) @@ -204,6 +207,8 @@ guint _clutter_pixel_to_id (guchar pixel[4]); void _clutter_id_to_color (guint id, ClutterColor *col); +void _clutter_timeline_set_delta (ClutterTimeline *timeline, + guint msecs); /* use this function as the accumulator if you have a signal with * a G_TYPE_BOOLEAN return value; this will stop the emission as diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c index 9e3576264..2f9320d9d 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -59,6 +59,7 @@ #include "clutter-color.h" #include "clutter-util.h" #include "clutter-marshal.h" +#include "clutter-master-clock.h" #include "clutter-enum-types.h" #include "clutter-private.h" #include "clutter-debug.h" @@ -86,7 +87,7 @@ struct _ClutterStagePrivate gchar *title; ClutterActor *key_focused_actor; - guint update_idle; /* repaint idler id */ + guint update_idle; /* repaint idler id */ guint is_fullscreen : 1; guint is_offscreen : 1; @@ -346,24 +347,42 @@ redraw_update_idle (gpointer user_data) { ClutterStage *stage = user_data; ClutterStagePrivate *priv = stage->priv; + ClutterMasterClock *master_clock; + gboolean retval = FALSE; - /* clutter_redraw() will also call maybe_relayout(), but since a relayout can - * queue a redraw, we want to do the relayout before we clear the update_idle - * to avoid painting the stage twice. Calling maybe_relayout() twice in a row - * is cheap because of caching of requested and allocated size. + /* before we redraw we advance the master clock of one tick; this means + * that all the timelines that need advancing will be advanced by one + * frame. this will cause multiple redraw requests, so we do this before + * we ask for a relayout and before we do the actual redraw. this ensures + * that we paint the most updated scenegraph state and that all animations + * are in sync with the paint process. + */ + CLUTTER_NOTE (PAINT, "Avdancing master clock"); + master_clock = _clutter_master_clock_get_default (); + _clutter_master_clock_advance (master_clock); + + /* clutter_redraw() will also call maybe_relayout(), but since a relayout + * can queue a redraw, we want to do the relayout before we clear the + * update_idle to avoid painting the stage twice. Calling maybe_relayout() + * twice in a row is cheap because of caching of requested and allocated + * size. */ _clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage)); - if (priv->update_idle) - { - g_source_remove (priv->update_idle); - priv->update_idle = 0; - } - - CLUTTER_NOTE (MULTISTAGE, "redrawing via idle for stage:%p", stage); + CLUTTER_NOTE (PAINT, "redrawing via idle for stage[%p]", stage); clutter_redraw (stage); - return FALSE; + if (CLUTTER_CONTEXT ()->redraw_count > 0) + { + CLUTTER_NOTE (PAINT, "Queued %lu redraws during the last cycle", + CLUTTER_CONTEXT ()->redraw_count); + + CLUTTER_CONTEXT ()->redraw_count = 0; + } + + priv->update_idle = 0; + + return retval; } static void @@ -373,17 +392,21 @@ clutter_stage_real_queue_redraw (ClutterActor *actor, ClutterStage *stage = CLUTTER_STAGE (actor); ClutterStagePrivate *priv = stage->priv; + CLUTTER_NOTE (PAINT, "Redraw request number %lu", + CLUTTER_CONTEXT ()->redraw_count + 1); + if (priv->update_idle == 0) { - CLUTTER_TIMESTAMP (SCHEDULER, "Adding idle source for stage: %p", stage); + CLUTTER_NOTE (PAINT, "Adding idle source for stage: %p", stage); - /* FIXME: weak_ref self in case we dissapear before paint? */ priv->update_idle = clutter_threads_add_idle_full (CLUTTER_PRIORITY_REDRAW, redraw_update_idle, stage, NULL); } + else + CLUTTER_CONTEXT ()->redraw_count += 1; } static void diff --git a/clutter/clutter-timeline.c b/clutter/clutter-timeline.c index 72abc0b48..57b517cef 100644 --- a/clutter/clutter-timeline.c +++ b/clutter/clutter-timeline.c @@ -83,13 +83,13 @@ #include "config.h" #endif -#include "clutter-timeout-pool.h" -#include "clutter-timeline.h" -#include "clutter-main.h" -#include "clutter-marshal.h" -#include "clutter-private.h" #include "clutter-debug.h" #include "clutter-enum-types.h" +#include "clutter-main.h" +#include "clutter-marshal.h" +#include "clutter-master-clock.h" +#include "clutter-private.h" +#include "clutter-timeline.h" G_DEFINE_TYPE (ClutterTimeline, clutter_timeline, G_TYPE_OBJECT); @@ -97,7 +97,6 @@ struct _ClutterTimelinePrivate { ClutterTimelineDirection direction; - guint timeout_id; guint delay_id; gint current_frame_num; @@ -108,13 +107,14 @@ struct _ClutterTimelinePrivate gint skipped_frames; - GTimeVal prev_frame_timeval; guint msecs_delta; + guint frame_interval; GHashTable *markers_by_frame; GHashTable *markers_by_name; - guint loop : 1; + guint loop : 1; + guint is_playing : 1; }; typedef struct { @@ -146,67 +146,7 @@ enum LAST_SIGNAL }; -static guint timeline_signals[LAST_SIGNAL] = { 0, }; -static gint timeline_use_pool = -1; -static ClutterTimeoutPool *timeline_pool = NULL; - -static inline void -timeline_pool_init (void) -{ - if (timeline_use_pool == -1) - { - const gchar *timeline_env; - - timeline_env = g_getenv ("CLUTTER_TIMELINE"); - if (timeline_env && timeline_env[0] != '\0' && - strcmp (timeline_env, "no-pool") == 0) - { - timeline_use_pool = FALSE; - } - else - { - timeline_pool = clutter_timeout_pool_new (CLUTTER_PRIORITY_TIMELINE); - timeline_use_pool = TRUE; - } - } -} - -static guint -timeout_add (guint fps, - GSourceFunc func, - gpointer data, - GDestroyNotify notify) -{ - guint res; - - if (G_LIKELY (timeline_use_pool)) - { - g_assert (timeline_pool != NULL); - res = clutter_timeout_pool_add (timeline_pool, - fps, - func, data, notify); - } - else - { - res = clutter_threads_add_frame_source_full (CLUTTER_PRIORITY_TIMELINE, - fps, - func, data, notify); - } - - return res; -} - -static void -timeout_remove (guint tag) -{ - if (G_LIKELY (timeline_use_pool)) - { - g_assert (timeline_pool != NULL); - clutter_timeout_pool_remove (timeline_pool, tag); - } - else - g_source_remove (tag); -} +static guint timeline_signals[LAST_SIGNAL] = { 0, }; static TimelineMarker * timeline_marker_new (const gchar *name, @@ -326,7 +266,9 @@ clutter_timeline_get_property (GObject *object, static void clutter_timeline_finalize (GObject *object) { - ClutterTimelinePrivate *priv = CLUTTER_TIMELINE (object)->priv; + ClutterTimeline *self = CLUTTER_TIMELINE (object); + ClutterTimelinePrivate *priv = self->priv; + ClutterMasterClock *master_clock; if (priv->markers_by_frame) g_hash_table_destroy (priv->markers_by_frame); @@ -334,6 +276,9 @@ clutter_timeline_finalize (GObject *object) if (priv->markers_by_name) g_hash_table_destroy (priv->markers_by_name); + master_clock = _clutter_master_clock_get_default (); + _clutter_master_clock_remove_timeline (master_clock, self); + G_OBJECT_CLASS (clutter_timeline_parent_class)->finalize (object); } @@ -351,12 +296,6 @@ clutter_timeline_dispose (GObject *object) priv->delay_id = 0; } - if (priv->timeout_id) - { - timeout_remove (priv->timeout_id); - priv->timeout_id = 0; - } - G_OBJECT_CLASS (clutter_timeline_parent_class)->dispose (object); } @@ -366,8 +305,6 @@ clutter_timeline_class_init (ClutterTimelineClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; - timeline_pool_init (); - object_class->set_property = clutter_timeline_set_property; object_class->get_property = clutter_timeline_get_property; object_class->finalize = clutter_timeline_finalize; @@ -577,14 +514,19 @@ static void clutter_timeline_init (ClutterTimeline *self) { ClutterTimelinePrivate *priv; + ClutterMasterClock *master_clock; self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_TIMELINE, ClutterTimelinePrivate); priv->fps = clutter_get_default_frame_rate (); + priv->frame_interval = 1000 / priv->fps; priv->n_frames = 0; priv->msecs_delta = 0; + + master_clock = _clutter_master_clock_get_default (); + _clutter_master_clock_add_timeline (master_clock, self); } static void @@ -636,43 +578,23 @@ is_complete (ClutterTimeline *timeline) } static gboolean -timeline_timeout_func (gpointer data) +clutter_timeline_advance_internal (ClutterTimeline *timeline) { - ClutterTimeline *timeline = data; ClutterTimelinePrivate *priv; - GTimeVal timeval; - guint n_frames, speed; - gulong msecs; + guint n_frames, speed; priv = timeline->priv; g_object_ref (timeline); - /* Figure out potential frame skips */ - g_get_current_time (&timeval); - CLUTTER_TIMESTAMP (SCHEDULER, "Timeline [%p] activated (cur: %d)\n", timeline, priv->current_frame_num); - if (!priv->prev_frame_timeval.tv_sec) - { - CLUTTER_NOTE (SCHEDULER, - "Timeline [%p] recieved timeout before being initialised!", - timeline); - priv->prev_frame_timeval = timeval; - } - - /* Interpolate the current frame based on the timeval of the - * previous frame */ - msecs = (timeval.tv_sec - priv->prev_frame_timeval.tv_sec) * 1000; - msecs += (timeval.tv_usec - priv->prev_frame_timeval.tv_usec) / 1000; - priv->msecs_delta = msecs; - /* we need to avoid fps > 1000 */ speed = MAX (1000 / priv->fps, 1); - n_frames = msecs / speed; + n_frames = priv->msecs_delta / speed; if (n_frames == 0) n_frames = 1; @@ -684,8 +606,6 @@ timeline_timeout_func (gpointer data) timeline, priv->skipped_frames); - priv->prev_frame_timeval = timeval; - /* Advance frames */ if (priv->direction == CLUTTER_TIMELINE_FORWARD) priv->current_frame_num += n_frames; @@ -699,7 +619,7 @@ timeline_timeout_func (gpointer data) emit_frame_signal (timeline); /* Signal pauses timeline ? */ - if (!priv->timeout_id) + if (!priv->is_playing) { g_object_unref (timeline); return FALSE; @@ -751,7 +671,7 @@ timeline_timeout_func (gpointer data) priv->n_frames, n_frames - 1); - if (!priv->loop && priv->timeout_id) + if (!priv->loop && priv->is_playing) { /* We remove the timeout now, so that the completed signal handler * may choose to re-start the timeline @@ -759,8 +679,7 @@ timeline_timeout_func (gpointer data) * XXX Perhaps we should remove this earlier, and regardless * of priv->loop. Are we limiting the things that could be done in * the above new-frame signal handler */ - timeout_remove (priv->timeout_id); - priv->timeout_id = 0; + priv->is_playing = FALSE; } g_signal_emit (timeline, timeline_signals[COMPLETED], 0); @@ -769,7 +688,7 @@ timeline_timeout_func (gpointer data) * current_frame_num, before we finally stop or loop the timeline */ if (priv->current_frame_num != end_frame && - !(/* Except allow moving from frame 0 -> n_frame (or vica-versa) + !(/* Except allow moving from frame 0 -> n_frame (or vice-versa) since these are considered equivalent */ (priv->current_frame_num == 0 && end_frame == priv->n_frames) || (priv->current_frame_num == priv->n_frames && end_frame == 0) @@ -791,7 +710,7 @@ timeline_timeout_func (gpointer data) if (priv->direction != saved_direction) { priv->current_frame_num = priv->n_frames - - priv->current_frame_num; + - priv->current_frame_num; } g_object_unref (timeline); @@ -801,39 +720,12 @@ timeline_timeout_func (gpointer data) { clutter_timeline_rewind (timeline); - priv->prev_frame_timeval.tv_sec = 0; - priv->prev_frame_timeval.tv_usec = 0; - g_object_unref (timeline); return FALSE; } } } -static guint -timeline_timeout_add (ClutterTimeline *timeline, - guint fps, - GSourceFunc func, - gpointer data, - GDestroyNotify notify) -{ - ClutterTimelinePrivate *priv; - GTimeVal timeval; - - priv = timeline->priv; - - if (priv->prev_frame_timeval.tv_sec == 0) - { - g_get_current_time (&timeval); - priv->prev_frame_timeval = timeval; - } - - priv->skipped_frames = 0; - priv->msecs_delta = 0; - - return timeout_add (fps, func, data, notify); -} - static gboolean delay_timeout_func (gpointer data) { @@ -841,11 +733,8 @@ delay_timeout_func (gpointer data) ClutterTimelinePrivate *priv = timeline->priv; priv->delay_id = 0; - - priv->timeout_id = timeline_timeout_add (timeline, - priv->fps, - timeline_timeout_func, - timeline, NULL); + priv->msecs_delta = 0; + priv->is_playing = TRUE; g_signal_emit (timeline, timeline_signals[STARTED], 0); @@ -867,24 +756,20 @@ clutter_timeline_start (ClutterTimeline *timeline) priv = timeline->priv; - if (priv->delay_id || priv->timeout_id) + if (priv->delay_id || priv->is_playing) return; if (priv->n_frames == 0) return; if (priv->delay) - { - priv->delay_id = g_timeout_add (priv->delay, - (GSourceFunc)delay_timeout_func, - timeline); - } + priv->delay_id = clutter_threads_add_timeout (priv->delay, + delay_timeout_func, + timeline); else { - priv->timeout_id = timeline_timeout_add (timeline, - priv->fps, - timeline_timeout_func, - timeline, NULL); + priv->msecs_delta = 0; + priv->is_playing = TRUE; g_signal_emit (timeline, timeline_signals[STARTED], 0); } @@ -905,20 +790,17 @@ clutter_timeline_pause (ClutterTimeline *timeline) priv = timeline->priv; + if (priv->delay_id == 0 || !priv->is_playing) + return; + if (priv->delay_id) { g_source_remove (priv->delay_id); priv->delay_id = 0; } - if (priv->timeout_id) - { - timeout_remove (priv->timeout_id); - priv->timeout_id = 0; - } - - priv->prev_frame_timeval.tv_sec = 0; - priv->prev_frame_timeval.tv_usec = 0; + priv->msecs_delta = 0; + priv->is_playing = FALSE; g_signal_emit (timeline, timeline_signals[PAUSED], 0); } @@ -1027,6 +909,8 @@ clutter_timeline_skip (ClutterTimeline *timeline, if (priv->current_frame_num < 1) priv->current_frame_num = priv->n_frames - 1; } + + priv->msecs_delta = 0; } /** @@ -1141,17 +1025,9 @@ clutter_timeline_set_speed (ClutterTimeline *timeline, g_object_ref (timeline); priv->fps = fps; + priv->frame_interval = 1000 / priv->fps; - /* if the timeline is playing restart */ - if (priv->timeout_id) - { - timeout_remove (priv->timeout_id); - - priv->timeout_id = timeline_timeout_add (timeline, - priv->fps, - timeline_timeout_func, - timeline, NULL); - } + /* FIXME if the timeline is playing restart */ g_object_freeze_notify (G_OBJECT (timeline)); @@ -1193,7 +1069,7 @@ clutter_timeline_is_playing (ClutterTimeline *timeline) { g_return_val_if_fail (CLUTTER_IS_TIMELINE (timeline), FALSE); - return (timeline->priv->timeout_id != 0); + return timeline->priv->is_playing; } /** @@ -1495,6 +1371,39 @@ clutter_timeline_get_delta (ClutterTimeline *timeline, return priv->skipped_frames + 1; } +/* + * clutter_timeline_set_delta: + * @timeline: a #ClutterTimeline + * @msecs: advance in milliseconds + * + * Advances @timeline by @msecs. This function is called by the master + * clock and it is used to advance a timeline by the amount of milliseconds + * elapsed since the last redraw operation. The @timeline will use this + * interval to emit the #ClutterTimeline::new-frame signal and eventually + * skip frames. + */ +void +_clutter_timeline_set_delta (ClutterTimeline *timeline, + guint msecs) +{ + ClutterTimelinePrivate *priv; + + g_return_if_fail (CLUTTER_IS_TIMELINE (timeline)); + + priv = timeline->priv; + + priv->msecs_delta += msecs; + + if (priv->msecs_delta < priv->frame_interval) + return; + else + { + clutter_timeline_advance_internal (timeline); + + priv->msecs_delta = 0; + } +} + static inline void clutter_timeline_add_marker_internal (ClutterTimeline *timeline, const gchar *marker_name,