Use a single master "clock" to drive timelines

Currently, all timelines install a timeout inside the TimeoutPool
they share. Every time the main loop spins, all the timeouts are
updated. This, in turn, will usually lead to redraws being queued
on the stages.

This behaviour leads to the potential starvation of timelines and
to excessive redraws.

One lesson learned from the games developers is that the scenegraph
should be prepared in its entirety before the GL paint sequence is
initiated. This means making sure that every ::new-frame signal
handler is called before clutter_redraw() is invoked.

In order to do so a TimeoutPool is not enough: we need a master
clock. The clock will be responsible for advancing all the active
timelines created inside a scene, but only when the stage is
being redrawn.

The sequence is:

  + queue_redraw() is invoked on an actor and bubbles up
    to the stage

  + if no redraw() has already been scheduled, install an
    idle handler with a known priority

  + inside the idle handler:

    - advance the master clock, which will in turn advance
      every playing timeline by the amount of milliseconds
      elapsed since the last redraw; this will make every
      playing timeline emit the ::new-frame signal

    - queue a relayout

    - call the redraw() method of the backend

This way we trade multiple timeouts with a single frame source
that only runs if a timeline is playing and queues redraws on
the various stages.
This commit is contained in:
Emmanuele Bassi 2009-03-21 20:39:32 +00:00
parent 51f4e9680b
commit 678f99677f
7 changed files with 539 additions and 189 deletions

View File

@ -169,6 +169,7 @@ source_c = \
$(srcdir)/clutter-list-model.c \ $(srcdir)/clutter-list-model.c \
$(srcdir)/clutter-main.c \ $(srcdir)/clutter-main.c \
clutter-marshal.c \ clutter-marshal.c \
$(srcdir)/clutter-master-clock.c \
$(srcdir)/clutter-media.c \ $(srcdir)/clutter-media.c \
$(srcdir)/clutter-model.c \ $(srcdir)/clutter-model.c \
$(srcdir)/clutter-path.c \ $(srcdir)/clutter-path.c \
@ -195,6 +196,7 @@ source_h_priv = \
$(srcdir)/clutter-bezier.h \ $(srcdir)/clutter-bezier.h \
$(srcdir)/clutter-debug.h \ $(srcdir)/clutter-debug.h \
$(srcdir)/clutter-keysyms-table.h \ $(srcdir)/clutter-keysyms-table.h \
$(srcdir)/clutter-master-clock.h \
$(srcdir)/clutter-model-private.h \ $(srcdir)/clutter-model-private.h \
$(srcdir)/clutter-private.h \ $(srcdir)/clutter-private.h \
$(srcdir)/clutter-id-pool.h \ $(srcdir)/clutter-id-pool.h \

View File

@ -41,6 +41,7 @@
#include "clutter-event.h" #include "clutter-event.h"
#include "clutter-backend.h" #include "clutter-backend.h"
#include "clutter-main.h" #include "clutter-main.h"
#include "clutter-master-clock.h"
#include "clutter-feature.h" #include "clutter-feature.h"
#include "clutter-actor.h" #include "clutter-actor.h"
#include "clutter-stage.h" #include "clutter-stage.h"
@ -181,14 +182,12 @@ void
clutter_redraw (ClutterStage *stage) clutter_redraw (ClutterStage *stage)
{ {
ClutterMainContext *ctx; ClutterMainContext *ctx;
static GTimer *timer = NULL; static GTimer *timer = NULL;
static guint timer_n_frames = 0; static guint timer_n_frames = 0;
ctx = clutter_context_get_default (); 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 (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 */ /* Before we can paint, we have to be sure we have the latest layout */
_clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage)); _clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage));

View File

@ -0,0 +1,364 @@
/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Authored By: Emmanuele Bassi <ebassi@linux.intel.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/*
* 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;
}

View File

@ -0,0 +1,48 @@
/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Authored By: Emmanuele Bassi <ebassi@linux.intel.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __CLUTTER_MASTER_CLOCK_H__
#define __CLUTTER_MASTER_CLOCK_H__
#include <clutter/clutter-timeline.h>
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__ */

View File

@ -47,6 +47,7 @@
#include "clutter-stage-manager.h" #include "clutter-stage-manager.h"
#include "clutter-stage-window.h" #include "clutter-stage-window.h"
#include "clutter-stage.h" #include "clutter-stage.h"
#include "clutter-timeline.h"
G_BEGIN_DECLS G_BEGIN_DECLS
@ -127,6 +128,8 @@ struct _ClutterMainContext
MultiTouch */ MultiTouch */
guint32 last_event_time; guint32 last_event_time;
gulong redraw_count;
}; };
#define CLUTTER_CONTEXT() (clutter_context_get_default ()) #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_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 /* use this function as the accumulator if you have a signal with
* a G_TYPE_BOOLEAN return value; this will stop the emission as * a G_TYPE_BOOLEAN return value; this will stop the emission as

View File

@ -59,6 +59,7 @@
#include "clutter-color.h" #include "clutter-color.h"
#include "clutter-util.h" #include "clutter-util.h"
#include "clutter-marshal.h" #include "clutter-marshal.h"
#include "clutter-master-clock.h"
#include "clutter-enum-types.h" #include "clutter-enum-types.h"
#include "clutter-private.h" #include "clutter-private.h"
#include "clutter-debug.h" #include "clutter-debug.h"
@ -86,7 +87,7 @@ struct _ClutterStagePrivate
gchar *title; gchar *title;
ClutterActor *key_focused_actor; ClutterActor *key_focused_actor;
guint update_idle; /* repaint idler id */ guint update_idle; /* repaint idler id */
guint is_fullscreen : 1; guint is_fullscreen : 1;
guint is_offscreen : 1; guint is_offscreen : 1;
@ -346,24 +347,42 @@ redraw_update_idle (gpointer user_data)
{ {
ClutterStage *stage = user_data; ClutterStage *stage = user_data;
ClutterStagePrivate *priv = stage->priv; ClutterStagePrivate *priv = stage->priv;
ClutterMasterClock *master_clock;
gboolean retval = FALSE;
/* clutter_redraw() will also call maybe_relayout(), but since a relayout can /* before we redraw we advance the master clock of one tick; this means
* queue a redraw, we want to do the relayout before we clear the update_idle * that all the timelines that need advancing will be advanced by one
* to avoid painting the stage twice. Calling maybe_relayout() twice in a row * frame. this will cause multiple redraw requests, so we do this before
* is cheap because of caching of requested and allocated size. * 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)); _clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage));
if (priv->update_idle) CLUTTER_NOTE (PAINT, "redrawing via idle for stage[%p]", stage);
{
g_source_remove (priv->update_idle);
priv->update_idle = 0;
}
CLUTTER_NOTE (MULTISTAGE, "redrawing via idle for stage:%p", stage);
clutter_redraw (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 static void
@ -373,17 +392,21 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
ClutterStage *stage = CLUTTER_STAGE (actor); ClutterStage *stage = CLUTTER_STAGE (actor);
ClutterStagePrivate *priv = stage->priv; ClutterStagePrivate *priv = stage->priv;
CLUTTER_NOTE (PAINT, "Redraw request number %lu",
CLUTTER_CONTEXT ()->redraw_count + 1);
if (priv->update_idle == 0) 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 = priv->update_idle =
clutter_threads_add_idle_full (CLUTTER_PRIORITY_REDRAW, clutter_threads_add_idle_full (CLUTTER_PRIORITY_REDRAW,
redraw_update_idle, redraw_update_idle,
stage, stage,
NULL); NULL);
} }
else
CLUTTER_CONTEXT ()->redraw_count += 1;
} }
static void static void

View File

@ -83,13 +83,13 @@
#include "config.h" #include "config.h"
#endif #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-debug.h"
#include "clutter-enum-types.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); G_DEFINE_TYPE (ClutterTimeline, clutter_timeline, G_TYPE_OBJECT);
@ -97,7 +97,6 @@ struct _ClutterTimelinePrivate
{ {
ClutterTimelineDirection direction; ClutterTimelineDirection direction;
guint timeout_id;
guint delay_id; guint delay_id;
gint current_frame_num; gint current_frame_num;
@ -108,13 +107,14 @@ struct _ClutterTimelinePrivate
gint skipped_frames; gint skipped_frames;
GTimeVal prev_frame_timeval;
guint msecs_delta; guint msecs_delta;
guint frame_interval;
GHashTable *markers_by_frame; GHashTable *markers_by_frame;
GHashTable *markers_by_name; GHashTable *markers_by_name;
guint loop : 1; guint loop : 1;
guint is_playing : 1;
}; };
typedef struct { typedef struct {
@ -146,67 +146,7 @@ enum
LAST_SIGNAL LAST_SIGNAL
}; };
static guint timeline_signals[LAST_SIGNAL] = { 0, }; 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 TimelineMarker * static TimelineMarker *
timeline_marker_new (const gchar *name, timeline_marker_new (const gchar *name,
@ -326,7 +266,9 @@ clutter_timeline_get_property (GObject *object,
static void static void
clutter_timeline_finalize (GObject *object) 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) if (priv->markers_by_frame)
g_hash_table_destroy (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) if (priv->markers_by_name)
g_hash_table_destroy (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); G_OBJECT_CLASS (clutter_timeline_parent_class)->finalize (object);
} }
@ -351,12 +296,6 @@ clutter_timeline_dispose (GObject *object)
priv->delay_id = 0; 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); 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); GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec; GParamSpec *pspec;
timeline_pool_init ();
object_class->set_property = clutter_timeline_set_property; object_class->set_property = clutter_timeline_set_property;
object_class->get_property = clutter_timeline_get_property; object_class->get_property = clutter_timeline_get_property;
object_class->finalize = clutter_timeline_finalize; object_class->finalize = clutter_timeline_finalize;
@ -577,14 +514,19 @@ static void
clutter_timeline_init (ClutterTimeline *self) clutter_timeline_init (ClutterTimeline *self)
{ {
ClutterTimelinePrivate *priv; ClutterTimelinePrivate *priv;
ClutterMasterClock *master_clock;
self->priv = priv = self->priv = priv =
G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_TIMELINE, G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_TIMELINE,
ClutterTimelinePrivate); ClutterTimelinePrivate);
priv->fps = clutter_get_default_frame_rate (); priv->fps = clutter_get_default_frame_rate ();
priv->frame_interval = 1000 / priv->fps;
priv->n_frames = 0; priv->n_frames = 0;
priv->msecs_delta = 0; priv->msecs_delta = 0;
master_clock = _clutter_master_clock_get_default ();
_clutter_master_clock_add_timeline (master_clock, self);
} }
static void static void
@ -636,43 +578,23 @@ is_complete (ClutterTimeline *timeline)
} }
static gboolean static gboolean
timeline_timeout_func (gpointer data) clutter_timeline_advance_internal (ClutterTimeline *timeline)
{ {
ClutterTimeline *timeline = data;
ClutterTimelinePrivate *priv; ClutterTimelinePrivate *priv;
GTimeVal timeval; guint n_frames, speed;
guint n_frames, speed;
gulong msecs;
priv = timeline->priv; priv = timeline->priv;
g_object_ref (timeline); g_object_ref (timeline);
/* Figure out potential frame skips */
g_get_current_time (&timeval);
CLUTTER_TIMESTAMP (SCHEDULER, "Timeline [%p] activated (cur: %d)\n", CLUTTER_TIMESTAMP (SCHEDULER, "Timeline [%p] activated (cur: %d)\n",
timeline, timeline,
priv->current_frame_num); 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 */ /* we need to avoid fps > 1000 */
speed = MAX (1000 / priv->fps, 1); speed = MAX (1000 / priv->fps, 1);
n_frames = msecs / speed; n_frames = priv->msecs_delta / speed;
if (n_frames == 0) if (n_frames == 0)
n_frames = 1; n_frames = 1;
@ -684,8 +606,6 @@ timeline_timeout_func (gpointer data)
timeline, timeline,
priv->skipped_frames); priv->skipped_frames);
priv->prev_frame_timeval = timeval;
/* Advance frames */ /* Advance frames */
if (priv->direction == CLUTTER_TIMELINE_FORWARD) if (priv->direction == CLUTTER_TIMELINE_FORWARD)
priv->current_frame_num += n_frames; priv->current_frame_num += n_frames;
@ -699,7 +619,7 @@ timeline_timeout_func (gpointer data)
emit_frame_signal (timeline); emit_frame_signal (timeline);
/* Signal pauses timeline ? */ /* Signal pauses timeline ? */
if (!priv->timeout_id) if (!priv->is_playing)
{ {
g_object_unref (timeline); g_object_unref (timeline);
return FALSE; return FALSE;
@ -751,7 +671,7 @@ timeline_timeout_func (gpointer data)
priv->n_frames, priv->n_frames,
n_frames - 1); 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 /* We remove the timeout now, so that the completed signal handler
* may choose to re-start the timeline * 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 * XXX Perhaps we should remove this earlier, and regardless
* of priv->loop. Are we limiting the things that could be done in * of priv->loop. Are we limiting the things that could be done in
* the above new-frame signal handler */ * the above new-frame signal handler */
timeout_remove (priv->timeout_id); priv->is_playing = FALSE;
priv->timeout_id = 0;
} }
g_signal_emit (timeline, timeline_signals[COMPLETED], 0); 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 */ * current_frame_num, before we finally stop or loop the timeline */
if (priv->current_frame_num != end_frame && 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 */ since these are considered equivalent */
(priv->current_frame_num == 0 && end_frame == priv->n_frames) || (priv->current_frame_num == 0 && end_frame == priv->n_frames) ||
(priv->current_frame_num == priv->n_frames && end_frame == 0) (priv->current_frame_num == priv->n_frames && end_frame == 0)
@ -791,7 +710,7 @@ timeline_timeout_func (gpointer data)
if (priv->direction != saved_direction) if (priv->direction != saved_direction)
{ {
priv->current_frame_num = priv->n_frames priv->current_frame_num = priv->n_frames
- priv->current_frame_num; - priv->current_frame_num;
} }
g_object_unref (timeline); g_object_unref (timeline);
@ -801,39 +720,12 @@ timeline_timeout_func (gpointer data)
{ {
clutter_timeline_rewind (timeline); clutter_timeline_rewind (timeline);
priv->prev_frame_timeval.tv_sec = 0;
priv->prev_frame_timeval.tv_usec = 0;
g_object_unref (timeline); g_object_unref (timeline);
return FALSE; 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 static gboolean
delay_timeout_func (gpointer data) delay_timeout_func (gpointer data)
{ {
@ -841,11 +733,8 @@ delay_timeout_func (gpointer data)
ClutterTimelinePrivate *priv = timeline->priv; ClutterTimelinePrivate *priv = timeline->priv;
priv->delay_id = 0; priv->delay_id = 0;
priv->msecs_delta = 0;
priv->timeout_id = timeline_timeout_add (timeline, priv->is_playing = TRUE;
priv->fps,
timeline_timeout_func,
timeline, NULL);
g_signal_emit (timeline, timeline_signals[STARTED], 0); g_signal_emit (timeline, timeline_signals[STARTED], 0);
@ -867,24 +756,20 @@ clutter_timeline_start (ClutterTimeline *timeline)
priv = timeline->priv; priv = timeline->priv;
if (priv->delay_id || priv->timeout_id) if (priv->delay_id || priv->is_playing)
return; return;
if (priv->n_frames == 0) if (priv->n_frames == 0)
return; return;
if (priv->delay) if (priv->delay)
{ priv->delay_id = clutter_threads_add_timeout (priv->delay,
priv->delay_id = g_timeout_add (priv->delay, delay_timeout_func,
(GSourceFunc)delay_timeout_func, timeline);
timeline);
}
else else
{ {
priv->timeout_id = timeline_timeout_add (timeline, priv->msecs_delta = 0;
priv->fps, priv->is_playing = TRUE;
timeline_timeout_func,
timeline, NULL);
g_signal_emit (timeline, timeline_signals[STARTED], 0); g_signal_emit (timeline, timeline_signals[STARTED], 0);
} }
@ -905,20 +790,17 @@ clutter_timeline_pause (ClutterTimeline *timeline)
priv = timeline->priv; priv = timeline->priv;
if (priv->delay_id == 0 || !priv->is_playing)
return;
if (priv->delay_id) if (priv->delay_id)
{ {
g_source_remove (priv->delay_id); g_source_remove (priv->delay_id);
priv->delay_id = 0; priv->delay_id = 0;
} }
if (priv->timeout_id) priv->msecs_delta = 0;
{ priv->is_playing = FALSE;
timeout_remove (priv->timeout_id);
priv->timeout_id = 0;
}
priv->prev_frame_timeval.tv_sec = 0;
priv->prev_frame_timeval.tv_usec = 0;
g_signal_emit (timeline, timeline_signals[PAUSED], 0); g_signal_emit (timeline, timeline_signals[PAUSED], 0);
} }
@ -1027,6 +909,8 @@ clutter_timeline_skip (ClutterTimeline *timeline,
if (priv->current_frame_num < 1) if (priv->current_frame_num < 1)
priv->current_frame_num = priv->n_frames - 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); g_object_ref (timeline);
priv->fps = fps; priv->fps = fps;
priv->frame_interval = 1000 / priv->fps;
/* if the timeline is playing restart */ /* FIXME 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);
}
g_object_freeze_notify (G_OBJECT (timeline)); 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); 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; 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 static inline void
clutter_timeline_add_marker_internal (ClutterTimeline *timeline, clutter_timeline_add_marker_internal (ClutterTimeline *timeline,
const gchar *marker_name, const gchar *marker_name,