From 31fbfd40470aedabec8f6779face91bacc99f7f8 Mon Sep 17 00:00:00 2001 From: Michael James Gratton Date: Thu, 9 Feb 2017 10:54:45 +1100 Subject: [PATCH] Add IdleManager class for sane main loop idle scheduling. --- po/POTFILES.in | 1 + src/CMakeLists.txt | 1 + src/engine/util/util-idle-manager.vala | 102 ++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/engine/util-idle-manager-test.vala | 40 ++++++++++ test/main.vala | 3 +- 6 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/engine/util/util-idle-manager.vala create mode 100644 test/engine/util-idle-manager-test.vala diff --git a/po/POTFILES.in b/po/POTFILES.in index 5ee66101..345dcbff 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -368,6 +368,7 @@ src/engine/util/util-converter.vala src/engine/util/util-files.vala src/engine/util/util-generic-capabilities.vala src/engine/util/util-html.vala +src/engine/util/util-idle-manager.vala src/engine/util/util-imap-utf7.vala src/engine/util/util-inet.vala src/engine/util/util-iterable.vala diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eab8d3d7..4b39a8f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -295,6 +295,7 @@ engine/util/util-converter.vala engine/util/util-files.vala engine/util/util-generic-capabilities.vala engine/util/util-html.vala +engine/util/util-idle-manager.vala engine/util/util-imap-utf7.vala engine/util/util-inet.vala engine/util/util-iterable.vala diff --git a/src/engine/util/util-idle-manager.vala b/src/engine/util/util-idle-manager.vala new file mode 100644 index 00000000..fac46dc7 --- /dev/null +++ b/src/engine/util/util-idle-manager.vala @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Manages execution of a function on the main loop. + * + * This class is a convenience API for the GLib main loop and source + * infrastructure, automatically performing cleanup when destroyed. + * + * Note this class is not thread safe and should only be invoked from + * the main loop. + */ +public class Geary.IdleManager : BaseObject { + + + /** Specifies the priority the idle function should be given. */ + public enum Priority { + HIGH = GLib.Priority.HIGH, + DEFAULT = GLib.Priority.DEFAULT, + HIGH_IDLE = GLib.Priority.HIGH_IDLE, + DEFAULT_IDLE = GLib.Priority.DEFAULT_IDLE, + LOW = GLib.Priority.LOW; + } + + /** Specifies if the idle function should run once or be continuously. */ + public enum Repeat { ONCE, FOREVER; } + + /** The idle callback function prototype. */ + public delegate void IdleFunc(IdleManager manager); + + /** Determines if the function will be re-scheduled after being run. */ + public Repeat repetition = Repeat.ONCE; + + /** Determines the priority the function will receive on the main loop. */ + public Priority priority = Priority.DEFAULT; + + /** Determines if the function is waiting to fire or not. */ + public bool is_running { + get { return this.source_id >= 0; } + } + + private IdleFunc callback; + private int source_id = -1; + + + /** + * Constructs a new idle manager with an interval in seconds. + * + * The idle function will be by default not running, and hence + * needs to be started by a call to {@link start}. + */ + public IdleManager(IdleFunc callback) { + this.callback = callback; + } + + ~IdleManager() { + reset(); + } + + /** + * Schedules the idle function to run on the main loop. + * + * If the function is already waiting to run, it will first be reset. + */ + public void schedule() { + reset(); + this.source_id = (int) GLib.Idle.add_full(this.priority, on_trigger); + } + + /** + * Prevents the idle function from being run. + * + * @return `true` if function was already scheduled, else `false` + */ + public bool reset() { + bool is_running = this.is_running; + if (is_running) { + Source.remove(this.source_id); + this.source_id = -1; + } + return is_running; + } + + private bool on_trigger() { + bool ret = Source.CONTINUE; + // If running only once, reset the source id now in case the + // callback resets the timer while it is executing, so we + // avoid removing the source just before it would be removed + // after this call anyway + if (this.repetition == Repeat.ONCE) { + this.source_id = -1; + ret = Source.REMOVE; + } + callback(this); + return ret; + } + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3c688d44..be65cfcc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,6 +11,7 @@ set(TEST_SRC engine/rfc822-message-data-test.vala engine/rfc822-utils-test.vala engine/util-html-test.vala + engine/util-idle-manager-test.vala engine/util-inet-test.vala engine/util-js-test.vala engine/util-timeout-manager-test.vala diff --git a/test/engine/util-idle-manager-test.vala b/test/engine/util-idle-manager-test.vala new file mode 100644 index 00000000..c0bfa22f --- /dev/null +++ b/test/engine/util-idle-manager-test.vala @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Geary.IdleManagerTest : Gee.TestCase { + + public IdleManagerTest() { + base("Geary.IdleManagerTest"); + add_test("start_reset", start_reset); + add_test("test_run", test_run); + } + + public void start_reset() { + IdleManager test = new IdleManager(() => { /* noop */ }); + assert(!test.is_running); + test.schedule(); + assert(test.is_running); + test.reset(); + assert(!test.is_running); + } + + public void test_run() { + bool did_run = false; + + IdleManager test = new IdleManager(() => { did_run = true; }); + test.schedule(); + + // There should be at least one event pending + assert(Gtk.events_pending()); + + // Execute the idle function + Gtk.main_iteration(); + + assert(did_run); + } + +} diff --git a/test/main.vala b/test/main.vala index 74382e81..8d4dff14 100644 --- a/test/main.vala +++ b/test/main.vala @@ -38,8 +38,9 @@ int main(string[] args) { TestSuite engine = new TestSuite("engine"); engine.add_suite(new Geary.HTML.UtilTest().get_suite()); - engine.add_suite(new Geary.JS.Test().get_suite()); + engine.add_suite(new Geary.IdleManagerTest().get_suite()); engine.add_suite(new Geary.Inet.Test().get_suite()); + engine.add_suite(new Geary.JS.Test().get_suite()); engine.add_suite(new Geary.RFC822.MailboxAddressTest().get_suite()); engine.add_suite(new Geary.RFC822.MessageTest().get_suite()); engine.add_suite(new Geary.RFC822.MessageDataTest().get_suite());