Add "leisure function" capability

To support scheduling performance-measurement scripts that want to run
a number of actions in series, add shell_global_run_at_leisure() to run
a callback when all work is finished.

The initial implementation of this is not that accurate: we track
business in Tweener.js via new shell_global_begin_work(),
shell_global_end_work() functions, and we also handle the case
where the main loop is continually busy.

https://bugzilla.gnome.org/show_bug.cgi?id=618189
This commit is contained in:
Owen W. Taylor 2010-05-09 00:34:15 -04:00
parent 2ce746e7dd
commit a9a513c621
3 changed files with 147 additions and 0 deletions

View File

@ -243,12 +243,14 @@ ClutterFrameTicker.prototype = {
start : function() { start : function() {
this._timeline.start(); this._timeline.start();
global.begin_work();
}, },
stop : function() { stop : function() {
this._timeline.stop(); this._timeline.stop();
this._startTime = -1; this._startTime = -1;
this._currentTime = -1; this._currentTime = -1;
global.end_work();
} }
}; };

View File

@ -57,6 +57,10 @@ struct _ShellGlobal {
ClutterActor *root_pixmap; ClutterActor *root_pixmap;
gint last_change_screen_width, last_change_screen_height; gint last_change_screen_width, last_change_screen_height;
guint work_count;
GSList *leisure_closures;
guint leisure_function_id;
}; };
enum { enum {
@ -1392,3 +1396,134 @@ shell_global_set_property_mutable (ShellGlobal *global,
JS_RemoveRoot (context, &val); JS_RemoveRoot (context, &val);
return !gjs_log_exception (context, NULL); return !gjs_log_exception (context, NULL);
} }
typedef struct
{
ShellLeisureFunction func;
gpointer user_data;
GDestroyNotify notify;
} LeisureClosure;
static gboolean
run_leisure_functions (gpointer data)
{
ShellGlobal *global = data;
GSList *closures;
GSList *iter;
global->leisure_function_id = 0;
/* We started more work since we scheduled the idle */
if (global->work_count > 0)
return FALSE;
closures = global->leisure_closures;
global->leisure_closures = NULL;
for (iter = closures; iter; iter = iter->next)
{
LeisureClosure *closure = closures->data;
closure->func (closure->user_data);
if (closure->notify)
closure->notify (closure->user_data);
g_slice_free (LeisureClosure, closure);
}
g_slist_free (closures);
return FALSE;
}
static void
schedule_leisure_functions (ShellGlobal *global)
{
/* This is called when we think we are ready to run leisure functions
* by our own accounting. We try to handle other types of business
* (like ClutterAnimation) by adding a low priority idle function.
*
* This won't work properly if the mainloop goes idle waiting for
* the vertical blanking interval or waiting for work being done
* in another thread.
*/
if (!global->leisure_function_id)
global->leisure_function_id = g_idle_add_full (G_PRIORITY_LOW,
run_leisure_functions,
global, NULL);
}
/**
* shell_global_begin_work:
* @global: the #ShellGlobal
*
* Marks that we are currently doing work. This is used to to track
* whether we are busy for the purposes of shell_global_run_at_leisure().
* A count is kept and shell_global_end_work() must be called exactly
* as many times as shell_global_begin_work().
*/
void
shell_global_begin_work (ShellGlobal *global)
{
global->work_count++;
}
/**
* shell_global_end_work:
* @global: the #ShellGlobal
*
* Marks the end of work that we started with shell_global_begin_work().
* If no other work is ongoing and functions have been added with
* shell_global_run_at_leisure(), they will be run at the next
* opportunity.
*/
void
shell_global_end_work (ShellGlobal *global)
{
g_return_if_fail (global->work_count > 0);
global->work_count--;
if (global->work_count == 0 && global->leisure_closures != NULL)
schedule_leisure_functions (global);
}
/**
* shell_global_run_at_leisure:
* @global: the #ShellGlobal
* @func: function to call at leisure
* @user_data: data to pass to @func
* @notify: function to call to free @user_data
*
* Schedules a function to be called the next time the shell is idle.
* Idle means here no animations, no redrawing, and no ongoing background
* work. Since there is currently no way to hook into the Clutter master
* clock and know when is running, the implementation here is somewhat
* approximation. Animations done through the shell's Tweener module will
* be handled properly, but other animations may be detected as terminating
* early if they can be drawn fast enough so that the event loop goes idle
* between frames.
*
* The intent of this function is for performance measurement runs
* where a number of actions should be run serially and each action is
* timed individually. Using this function for other purposes will
* interfere with the ability to use it for performance measurement so
* should be avoided.
*/
void
shell_global_run_at_leisure (ShellGlobal *global,
ShellLeisureFunction func,
gpointer user_data,
GDestroyNotify notify)
{
LeisureClosure *closure = g_slice_new (LeisureClosure);
closure->func = func;
closure->user_data = user_data;
closure->notify = notify;
global->leisure_closures = g_slist_append (global->leisure_closures,
closure);
if (global->work_count == 0)
schedule_leisure_functions (global);
}

View File

@ -101,6 +101,16 @@ gboolean shell_global_set_property_mutable (ShellGlobal *global,
const char *property, const char *property,
gboolean mutable); gboolean mutable);
void shell_global_begin_work (ShellGlobal *global);
void shell_global_end_work (ShellGlobal *global);
typedef void (*ShellLeisureFunction) (gpointer data);
void shell_global_run_at_leisure (ShellGlobal *global,
ShellLeisureFunction func,
gpointer user_data,
GDestroyNotify notify);
G_END_DECLS G_END_DECLS
#endif /* __SHELL_GLOBAL_H__ */ #endif /* __SHELL_GLOBAL_H__ */