Merge clutter.git/threading branch

This commit is contained in:
Emmanuele Bassi 2007-08-08 10:20:14 +00:00
parent f5b4b96394
commit 4befcd0a64
16 changed files with 543 additions and 62 deletions

View File

@ -1,3 +1,40 @@
2007-08-08 Emmanuele Bassi <ebassi@openedhand.com>
Merge the clutter.git/threading branch.
* clutter/clutter-main.c:
* clutter/clutter-main.h:
* clutter/clutter-private.h: Add threading locking and unlocking
functions, to mark a critical section and access the Clutter API
from differen threads. Add an initialisation function and a function
to override the default lock aquisition and release functions, for
bindings and application-specific locking handling. Add MT-safe
versions of g_idle_add() and g_timeout_add() which will call the
functions under the main Clutter lock and without races. The
Clutter thread-safe implementation is basically the same used by
GDK, so the same caveats apply.
* clutter/clutter-actor.c:
* clutter/clutter-timeline.c:
* clutter/clutter-timeout-pool.c: Use the new threading API when
invoking idle and timeouts.
* clutter/eglnative/clutter-event-egl.c:
* clutter/eglx/clutter-event-egl.c:
* clutter/glx/clutter-event-glx.c:
* clutter/sdl/clutter-event-sdl.c: Acquire and release the main
Clutter lock when preparing, checking and dispatching the events
on the queue in every backend.
* tests/Makefile.am:
* tests/test-threads.c: Add a test case, showing how to use the
threading API and write thread-safe Clutter applications.
2007-08-08 Emmanuele Bassi <ebassi@openedhand.com>
* configure.ac: Bump up to 0.5.0 and start the new development
branch.
2007-08-07 Emmanuele Bassi <ebassi@openedhand.com>
* configure.ac: Bump up to 0.4.0.

View File

@ -1210,11 +1210,12 @@ clutter_actor_queue_redraw (ClutterActor *self)
if (!ctx->update_idle)
{
CLUTTER_TIMESTAMP (SCHEDULER,
"Adding ideler for actor: %p", self);
ctx->update_idle = g_idle_add_full (G_PRIORITY_DEFAULT + 10,
redraw_update_idle,
NULL, NULL);
CLUTTER_TIMESTAMP (SCHEDULER, "Adding idle source for actor: %p", self);
ctx->update_idle =
clutter_threads_add_idle_full (G_PRIORITY_DEFAULT + 10,
redraw_update_idle,
NULL, NULL);
}
}

View File

@ -48,12 +48,21 @@
#include "cogl.h"
/* main context */
static ClutterMainContext *ClutterCntx = NULL;
/* main lock and locking/unlocking functions */
static GMutex *clutter_threads_mutex = NULL;
static GCallback clutter_threads_lock = NULL;
static GCallback clutter_threads_unlock = NULL;
static gboolean clutter_is_initialized = FALSE;
static gboolean clutter_show_fps = FALSE;
static gboolean clutter_fatal_warnings = FALSE;
static guint clutter_main_loop_level = 0;
static GSList *main_loops = NULL;
guint clutter_debug_flags = 0; /* global clutter debug flag */
#ifdef CLUTTER_ENABLE_DEBUG
@ -75,9 +84,12 @@ static const GDebugKey clutter_debug_keys[] = {
/**
* clutter_get_show_fps:
*
* FIXME
* Returns whether Clutter should print out the frames per second on the
* console. You can enable this setting either using the
* <literal>CLUTTER_SHOW_FPS</literal> environment variable or passing
* the <literal>--clutter-show-fps</literal> command line argument. *
*
* Return value: FIXME
* Return value: %TRUE if Clutter should show the FPS.
*
* Since: 0.4
*/
@ -91,7 +103,8 @@ clutter_get_show_fps (void)
/**
* clutter_redraw:
*
* FIXME
* Forces a redraw of the entire stage. Applications should never use this
* function, but queue a redraw using clutter_actor_queue_redraw().
*/
void
clutter_redraw (void)
@ -161,8 +174,11 @@ clutter_redraw (void)
/**
* clutter_do_event
* @event: a #ClutterEvent.
*
* This function should never be called by applications.
* Processes an event. This function should never be called by applications.
*
* Since: 0.4
*/
void
clutter_do_event (ClutterEvent *event)
@ -215,12 +231,9 @@ clutter_do_event (ClutterEvent *event)
void
clutter_main_quit (void)
{
ClutterMainContext *context = CLUTTER_CONTEXT ();
g_return_if_fail (main_loops != NULL);
g_return_if_fail (context->main_loops != NULL);
if (g_main_loop_is_running (context->main_loops->data))
g_main_loop_quit (context->main_loops->data);
g_main_loop_quit (main_loops->data);
}
/**
@ -233,9 +246,7 @@ clutter_main_quit (void)
gint
clutter_main_level (void)
{
ClutterMainContext *context = CLUTTER_CONTEXT ();
return context->main_loop_level;
return clutter_main_loop_level;
}
/**
@ -258,24 +269,25 @@ clutter_main (void)
CLUTTER_MARK ();
context->main_loop_level++;
clutter_main_loop_level++;
loop = g_main_loop_new (NULL, TRUE);
context->main_loops = g_slist_prepend (context->main_loops, loop);
main_loops = g_slist_prepend (main_loops, loop);
if (g_main_loop_is_running (context->main_loops->data))
if (g_main_loop_is_running (main_loops->data))
{
/* FIXME - add thread locking around this call */
clutter_threads_leave ();
g_main_loop_run (loop);
clutter_threads_enter ();
}
context->main_loops = g_slist_remove (context->main_loops, loop);
main_loops = g_slist_remove (main_loops, loop);
g_main_loop_unref (loop);
context->main_loop_level--;
clutter_main_loop_level--;
if (context->main_loop_level == 0)
if (clutter_main_loop_level == 0)
{
/* this will take care of destroying the stage */
g_object_unref (context->backend);
@ -287,6 +299,326 @@ clutter_main (void)
CLUTTER_MARK ();
}
static void
clutter_threads_impl_lock (void)
{
if (clutter_threads_mutex)
g_mutex_lock (clutter_threads_mutex);
}
static void
clutter_threads_impl_unlock (void)
{
if (clutter_threads_mutex)
g_mutex_unlock (clutter_threads_mutex);
}
/**
* clutter_threads_init:
*
* Initialises the Clutter threading mechanism, so that Clutter API can be
* called by multiple threads, using clutter_threads_enter() and
* clutter_threads_leave() to mark the critical sections.
*
* You must call g_thread_init() before this function.
*
* This function must be called before clutter_init().
*
* Since: 0.6
*/
void
clutter_threads_init (void)
{
if (!g_thread_supported ())
g_error ("g_thread_init() must be called before clutter_threads_init()");
clutter_threads_mutex = g_mutex_new ();
if (!clutter_threads_lock)
clutter_threads_lock = clutter_threads_impl_lock;
if (!clutter_threads_unlock)
clutter_threads_unlock = clutter_threads_impl_unlock;
}
/**
* clutter_threads_set_lock_functions:
* @enter_fn: function called when aquiring the Clutter main lock
* @leave_fn: function called when releasing the Clutter main lock
*
* Allows the application to replace the standard method that
* Clutter uses to protect its data structures. Normally, Clutter
* creates a single #GMutex that is locked by clutter_threads_enter(),
* and released by clutter_threads_leave(); using this function an
* application provides, instead, a function @enter_fn that is
* called by clutter_threads_enter() and a function @leave_fn that is
* called by clutter_threads_leave().
*
* The functions must provide at least same locking functionality
* as the default implementation, but can also do extra application
* specific processing.
*
* As an example, consider an application that has its own recursive
* lock that when held, holds the Clutter lock as well. When Clutter
* unlocks the Clutter lock when entering a recursive main loop, the
* application must temporarily release its lock as well.
*
* Most threaded Clutter apps won't need to use this method.
*
* This method must be called before clutter_threads_init(), and cannot
* be called multiple times.
*
* Since: 0.6
*/
void
clutter_threads_set_lock_functions (GCallback enter_fn,
GCallback leave_fn)
{
g_return_if_fail (clutter_threads_lock == NULL &&
clutter_threads_unlock == NULL);
clutter_threads_lock = enter_fn;
clutter_threads_unlock = leave_fn;
}
typedef struct
{
GSourceFunc func;
gpointer data;
GDestroyNotify notify;
} ClutterThreadsDispatch;
static gboolean
clutter_threads_dispatch (gpointer data)
{
ClutterThreadsDispatch *dispatch = data;
gboolean ret = FALSE;
clutter_threads_enter ();
if (!g_source_is_destroyed (g_main_current_source ()))
ret = dispatch->func (dispatch->data);
clutter_threads_leave ();
return ret;
}
static void
clutter_threads_dispatch_free (gpointer data)
{
ClutterThreadsDispatch *dispatch = data;
clutter_threads_enter ();
if (dispatch->notify)
dispatch->notify (dispatch->data);
clutter_threads_leave ();
g_slice_free (ClutterThreadsDispatch, dispatch);
}
/**
* clutter_threads_add_idle_full:
* @priority: the priority of the timeout source. Typically this will be in the
* range between #G_PRIORITY_DEFAULT_IDLE and #G_PRIORITY_HIGH_IDLE
* @func: function to call
* @data: data to pass to the function
* @notify: functio to call when the idle source is removed
*
* Adds a function to be called whenever there are no higher priority
* events pending. If the function returns %FALSE it is automatically
* removed from the list of event sources and will not be called again.
*
* This variant of g_idle_add_full() calls @function with the Clutter lock
* held. It can be thought of a MT-safe version for Clutter actors for the
* following use case, where you have to worry about idle_callback()
* running in thread A and accessing @self after it has been finalized
* in thread B:
*
* <informalexample><programlisting>
* static gboolean
* idle_callback (gpointer data)
* {
* // clutter_threads_enter(); would be needed for g_idle_add()
*
* SomeActor *self = data;
* /<!-- -->* do stuff with self *<!-- -->/
*
* self->idle_id = 0;
*
* // clutter_threads_leave(); would be needed for g_idle_add()
* return FALSE;
* }
* static void
* some_actor_do_stuff_later (SomeActor *self)
* {
* self->idle_id = clutter_threads_add_idle (idle_callback, self)
* // using g_idle_add() here would require thread protection in the callback
* }
*
* static void
* some_actor_finalize (GObject *object)
* {
* SomeActor *self = SOME_ACTOR (object);
* if (self->idle_id)
* g_source_remove (self->idle_id);
* G_OBJECT_CLASS (parent_class)->finalize (object);
* }
* </programlisting></informalexample>
*
* Return value: the ID (greater than 0) of the event source.
*
* Since: 0.6
*/
guint
clutter_threads_add_idle_full (gint priority,
GSourceFunc func,
gpointer data,
GDestroyNotify notify)
{
ClutterThreadsDispatch *dispatch;
g_return_val_if_fail (func != NULL, 0);
dispatch = g_slice_new (ClutterThreadsDispatch);
dispatch->func = func;
dispatch->data = data;
dispatch->notify = notify;
return g_idle_add_full (priority,
clutter_threads_dispatch, dispatch,
clutter_threads_dispatch_free);
}
/**
* clutter_threads_add_idle:
* @func: function to call
* @data: data to pass to the function
*
* Simple wrapper around clutter_threads_add_idle_full()
*
* Return value: the ID (greater than 0) of the event source.
*
* Since: 0.6
*/
guint
clutter_threads_add_idle (GSourceFunc func,
gpointer data)
{
g_return_val_if_fail (func != NULL, 0);
return clutter_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
func, data,
NULL);
}
/**
* clutter_threads_add_timeout_full:
* @priority: the priority of the timeout source. Typically this will be in the
* range between #G_PRIORITY_DEFAULT and #G_PRIORITY_HIGH.
* @interval: the time between calls to the function, in milliseconds
* @func: function to call
* @data: data to pass to the function
* @notify: function to call when the timeout source is removed
*
* Sets a function to be called at regular intervals holding the Clutter lock,
* with the given priority. The function is called repeatedly until it
* returns %FALSE, at which point the timeout is automatically destroyed
* and the function will not be called again. The @notify function is
* called when the timeout is destroyed. The first call to the
* function will be at the end of the first @interval.
*
* Note that timeout functions may be delayed, due to the processing of other
* event sources. Thus they should not be relied on for precise timing.
* After each call to the timeout function, the time of the next
* timeout is recalculated based on the current time and the given interval
* (it does not try to 'catch up' time lost in delays).
*
* This variant of g_timeout_add_full() can be thought of a MT-safe version
* for Clutter actors. See also clutter_threads_add_idle_full().
*
* Return value: the ID (greater than 0) of the event source.
*
* Since: 0.6
*/
guint
clutter_threads_add_timeout_full (gint priority,
guint interval,
GSourceFunc func,
gpointer data,
GDestroyNotify notify)
{
ClutterThreadsDispatch *dispatch;
g_return_val_if_fail (func != NULL, 0);
dispatch = g_slice_new (ClutterThreadsDispatch);
dispatch->func = func;
dispatch->data = data;
dispatch->notify = notify;
return g_timeout_add_full (priority,
interval,
clutter_threads_dispatch, dispatch,
clutter_threads_dispatch_free);
}
/**
* clutter_threads_add_timeout:
* @interval: the time between calls to the function, in milliseconds
* @func: function to call
* @data: data to pass to the function
*
* Simple wrapper around clutter_threads_add_timeout_full().
*
* Return value: the ID (greater than 0) of the event source.
*
* Since: 0.6
*/
guint
clutter_threads_add_timeout (guint interval,
GSourceFunc func,
gpointer data)
{
g_return_val_if_fail (func != NULL, 0);
return clutter_threads_add_timeout_full (G_PRIORITY_DEFAULT,
interval,
func, data,
NULL);
}
/**
* clutter_threads_enter:
*
* Locks the Clutter thread lock.
*
* Since: 0.6
*/
void
clutter_threads_enter (void)
{
if (clutter_threads_lock)
(* clutter_threads_lock) ();
}
/**
* clutter_threads_leave:
*
* Unlocks the Clutter thread lock.
*
* Since: 0.6
*/
void
clutter_threads_leave (void)
{
if (clutter_threads_unlock)
(* clutter_threads_unlock) ();
}
/**
* clutter_get_debug_enabled:
*
@ -410,8 +742,6 @@ pre_parse_hook (GOptionContext *context,
return TRUE;
clutter_context = clutter_context_get_default ();
clutter_context->main_loops = NULL;
clutter_context->main_loop_level = 0;
clutter_context->font_map = PANGO_FT2_FONT_MAP (pango_ft2_font_map_new ());
pango_ft2_font_map_set_resolution (clutter_context->font_map, 96.0, 96.0);
@ -568,11 +898,11 @@ clutter_init_with_args (int *argc,
if (clutter_is_initialized)
return CLUTTER_INIT_SUCCESS;
clutter_base_init ();
if (argc && *argc > 0 && *argv)
g_set_prgname ((*argv)[0]);
clutter_base_init ();
group = clutter_get_option_group ();
context = g_option_context_new (parameter_string);
@ -664,10 +994,11 @@ clutter_init (int *argc,
if (clutter_is_initialized)
return CLUTTER_INIT_SUCCESS;
clutter_base_init ();
if (argc && *argc > 0 && *argv)
g_set_prgname ((*argv)[0]);
clutter_base_init ();
/* parse_args will trigger backend creation and things like
* DISPLAY connection etc.
@ -734,6 +1065,8 @@ clutter_base_init (void)
/* initialise GLib type system */
g_type_init ();
/* CLUTTER_TYPE_ACTOR */
foo = clutter_actor_get_type ();
}
}

View File

@ -55,6 +55,8 @@ typedef enum {
GQuark clutter_init_error_quark (void);
/* Initialisation */
void clutter_base_init (void);
ClutterInitError clutter_init (int *argc,
char ***argv);
ClutterInitError clutter_init_with_args (int *argc,
@ -63,22 +65,41 @@ ClutterInitError clutter_init_with_args (int *argc,
GOptionEntry *entries,
char *translation_domain,
GError **error);
GOptionGroup * clutter_get_option_group (void);
void clutter_main (void);
void clutter_main_quit (void);
gint clutter_main_level (void);
/* Mainloop */
void clutter_main (void);
void clutter_main_quit (void);
gint clutter_main_level (void);
void clutter_redraw (void);
void clutter_redraw (void);
gboolean clutter_get_debug_enabled (void);
gboolean clutter_get_show_fps (void);
/* Debug utility functions */
gboolean clutter_get_debug_enabled (void);
gboolean clutter_get_show_fps (void);
gulong clutter_get_timestamp (void);
void clutter_base_init (void);
gulong clutter_get_timestamp (void);
/* Threading functions */
void clutter_threads_init (void);
void clutter_threads_enter (void);
void clutter_threads_leave (void);
void clutter_threads_set_lock_functions (GCallback enter_fn,
GCallback leave_fn);
guint clutter_threads_add_idle (GSourceFunc func,
gpointer data);
guint clutter_threads_add_idle_full (gint priority,
GSourceFunc func,
gpointer data,
GDestroyNotify notify);
guint clutter_threads_add_timeout (guint interval,
GSourceFunc func,
gpointer data);
guint clutter_threads_add_timeout_full (gint priority,
guint interval,
GSourceFunc func,
gpointer data,
GDestroyNotify notify);
G_END_DECLS
#endif
#endif /* _HAVE_CLUTTER_MAIN_H */

View File

@ -47,15 +47,19 @@ typedef struct _ClutterMainContext ClutterMainContext;
struct _ClutterMainContext
{
/* holds a pointer to the stage */
/* holds a pointer to the backend, which controls the stage */
ClutterBackend *backend;
/* the main event queue */
GQueue *events_queue;
PangoFT2FontMap *font_map;
guint update_idle;
guint main_loop_level;
GSList *main_loops;
guint is_initialized : 1;
guint pick_mode :1; /* Indicates pick render mode */
guint pick_mode : 1; /* Indicates pick render mode */
GTimer *timer; /* Used for debugging scheduler */
};

View File

@ -133,9 +133,9 @@ timeout_add (guint interval,
}
else
{
res = g_timeout_add_full (CLUTTER_TIMELINE_PRIORITY,
interval,
func, data, notify);
res = clutter_threads_add_timeout_full (CLUTTER_TIMELINE_PRIORITY,
interval,
func, data, notify);
}
return res;

View File

@ -183,12 +183,16 @@ static gboolean
clutter_timeout_dispatch (GSource *source,
ClutterTimeout *timeout)
{
gboolean retval = FALSE;
if (G_UNLIKELY (!timeout->func))
{
g_warning ("Timeout dispatched without a callback.");
return FALSE;
}
clutter_threads_enter ();
if (timeout->func (timeout->data))
{
GTimeVal current_time;
@ -196,10 +200,12 @@ clutter_timeout_dispatch (GSource *source,
g_source_get_current_time (source, &current_time);
clutter_timeout_set_expiration (timeout, &current_time);
return TRUE;
retval = TRUE;
}
else
return FALSE;
clutter_threads_leave ();
return retval;
}
static ClutterTimeout *

View File

@ -150,10 +150,16 @@ clutter_event_prepare (GSource *source,
gint *timeout)
{
ClutterBackend *backend = ((ClutterEventSource *) source)->backend;
gboolean retval;
clutter_threads_enter ();
*timeout = -1;
retval = clutter_events_pending ();
return clutter_events_pending ();
clutter_threads_leave ();
return retval;
}
static gboolean
@ -161,9 +167,16 @@ clutter_event_check (GSource *source)
{
ClutterEventSource *event_source = (ClutterEventSource *) source;
ClutterBackend *backend = event_source->backend;
gboolean retval;
return ((event_source->event_poll_fd.revents & G_IO_IN)
|| clutter_events_pending ());
clutter_threads_enter ();
retval = ((event_source->event_poll_fd.revents & G_IO_IN) ||
clutter_events_pending ());
clutter_threads_leave ();
return retval;
}
static gboolean
@ -178,6 +191,8 @@ clutter_event_dispatch (GSource *source,
ClutterMainContext *clutter_context;
static gint last_x, last_y;
clutter_threads_enter ();
clutter_context = clutter_context_get_default ();
#ifdef HAVE_TSLIB
/* FIXME while would be better here but need to deal with lockups */
@ -189,8 +204,8 @@ clutter_event_dispatch (GSource *source,
* event_button_generate gets confused generating lots of double
* and triple clicks.
*/
if (tsevent.pressure && last_x == tsevent.x && last_y == tsevent.y)
return;
if (tsevent.pressure && last_x == tsevent.x && last_y == tsevent.y)
goto out;
event = clutter_event_new (CLUTTER_NOTHING);
@ -223,5 +238,8 @@ clutter_event_dispatch (GSource *source,
clutter_event_free (event);
}
out:
clutter_threads_leave ();
return TRUE;
}

View File

@ -326,9 +326,13 @@ clutter_event_prepare (GSource *source,
ClutterBackend *backend = ((ClutterEventSource *) source)->backend;
gboolean retval;
clutter_threads_enter ();
*timeout = -1;
retval = (clutter_events_pending () || clutter_check_xpending (backend));
clutter_threads_leave ();
return retval;
}
@ -339,10 +343,14 @@ clutter_event_check (GSource *source)
ClutterBackend *backend = event_source->backend;
gboolean retval;
clutter_threads_enter ();
if (event_source->event_poll_fd.revents & G_IO_IN)
retval = (clutter_events_pending () || clutter_check_xpending (backend));
else
retval = FALSE;
clutter_threads_leave ();
return retval;
}
@ -355,6 +363,8 @@ clutter_event_dispatch (GSource *source,
ClutterBackend *backend = ((ClutterEventSource *) source)->backend;
ClutterEvent *event;
clutter_thread_enter ();
events_queue (backend);
event = clutter_event_get ();
@ -365,5 +375,7 @@ clutter_event_dispatch (GSource *source,
clutter_event_free (event);
}
clutter_threads_leave ();
return TRUE;
}

View File

@ -549,9 +549,13 @@ clutter_event_prepare (GSource *source,
ClutterBackend *backend = ((ClutterEventSource *) source)->backend;
gboolean retval;
clutter_threads_enter ();
*timeout = -1;
retval = (clutter_events_pending () || check_xpending (backend));
clutter_threads_leave ();
return retval;
}
@ -562,11 +566,15 @@ clutter_event_check (GSource *source)
ClutterBackend *backend = event_source->backend;
gboolean retval;
clutter_threads_enter ();
if (event_source->event_poll_fd.revents & G_IO_IN)
retval = (clutter_events_pending () || check_xpending (backend));
else
retval = FALSE;
clutter_threads_leave ();
return retval;
}
@ -578,6 +586,8 @@ clutter_event_dispatch (GSource *source,
ClutterBackend *backend = ((ClutterEventSource *) source)->backend;
ClutterEvent *event;
clutter_threads_enter ();
/* Grab the event(s), translate and figure out double click.
* The push onto queue (stack) if valid.
*/
@ -593,5 +603,7 @@ clutter_event_dispatch (GSource *source,
clutter_event_free (event);
}
clutter_threads_leave ();
return TRUE;
}

View File

@ -112,11 +112,16 @@ clutter_event_prepare (GSource *source,
{
SDL_Event events;
int num_events;
gboolean retval;
clutter_threads_enter ();
num_events = SDL_PeepEvents(&events, 1, SDL_PEEKEVENT, SDL_ALLEVENTS);
if (num_events == 1)
{
clutter_threads_leave ();
*timeout = 0;
return TRUE;
}
@ -126,7 +131,11 @@ clutter_event_prepare (GSource *source,
*timeout = 50;
return clutter_events_pending ();
retval = clutter_events_pending ();
clutter_threads_leave ();
return retval;
}
static gboolean
@ -134,6 +143,9 @@ clutter_event_check (GSource *source)
{
SDL_Event events;
int num_events;
gboolean retval;
clutter_threads_enter ();
/* Pump SDL */
SDL_PumpEvents();
@ -143,7 +155,11 @@ clutter_event_check (GSource *source)
if (num_events == -1)
g_warning("Error polling SDL: %s", SDL_GetError());
return (num_events == 1 || clutter_events_pending ());
retval = (num_events == 1 || clutter_events_pending ());
clutter_threads_leave ();
return retval;
}
static void
@ -292,6 +308,8 @@ clutter_event_dispatch (GSource *source,
ClutterBackend *backend = ((ClutterEventSource *) source)->backend;
ClutterMainContext *clutter_context;
clutter_threads_enter ();
clutter_context = clutter_context_get_default ();
while (SDL_PollEvent(&sdl_event))
@ -328,5 +346,7 @@ clutter_event_dispatch (GSource *source,
clutter_event_free (event);
}
clutter_threads_leave ();
return TRUE;
}

View File

@ -2,7 +2,7 @@
# An odd micro number indicates in-progress development, (eg. from CVS)
# An even micro number indicates a released version.
m4_define([clutter_major_version], [0])
m4_define([clutter_minor_version], [4])
m4_define([clutter_minor_version], [5])
m4_define([clutter_micro_version], [0])
m4_define([clutter_version],

View File

@ -1,3 +1,7 @@
2007-08-08 Emmanuele Bassi <ebassi@openedhand.com>
* clutter-sections.txt: Add the new clutter_threads_* API.
2007-08-07 Emmanuele Bassi <ebassi@openedhand.com>
* clutter-sections.txt: Shuffle around a bit the symbols.

View File

@ -828,12 +828,23 @@ ClutterInitError
clutter_init
clutter_init_with_args
clutter_get_option_group
clutter_get_debug_enabled
clutter_get_show_fps
<SUBSECTION>
clutter_main
clutter_main_quit
clutter_main_level
<SUBSECTION>
clutter_get_debug_enabled
clutter_get_show_fps
clutter_get_timestamp
<SUBSECTION>
clutter_threads_set_lock_functions
clutter_threads_init
clutter_threads_enter
clutter_threads_leave
clutter_threads_add_idle
clutter_threads_add_idle_full
clutter_threads_add_timeout
clutter_threads_add_timeout_full
<SUBSECTION Standard>
CLUTTER_INIT_ERROR
<SUBSECTION Private>

View File

@ -1,6 +1,7 @@
noinst_PROGRAMS = test-textures test-events test-offscreen test-scale \
test-actors test-behave test-text test-entry test-project \
test-boxes test-perspective test-rotate test-depth
test-boxes test-perspective test-rotate test-depth \
test-threads
INCLUDES = -I$(top_srcdir)/
LDADD = $(top_builddir)/clutter/libclutter-@CLUTTER_FLAVOUR@-@CLUTTER_MAJORMINOR@.la
@ -20,5 +21,6 @@ test_boxes_SOURCES = test-boxes.c
test_perspective_SOURCES = test-perspective.c
test_rotate_SOURCES = test-rotate.c
test_depth_SOURCES = test-depth.c
test_threads_SOURCES = test-threads.c
EXTRA_DIST = redhand.png

View File

@ -234,7 +234,7 @@ main (int argc, char *argv[])
/* and start it */
clutter_timeline_start (timeline);
clutter_main();
clutter_main ();
g_free (oh->hand);
g_free (oh);