]> git.neil.brown.name Git - edlib.git/commitdiff
event: add on-idle support
authorNeilBrown <neil@brown.name>
Fri, 9 Jun 2023 02:12:36 +0000 (12:12 +1000)
committerNeilBrown <neil@brown.name>
Wed, 28 Jun 2023 07:51:39 +0000 (17:51 +1000)
The event: library now supports event:on-idle.

There are 3 priorities.

2 - fast immediate action, typically freeing memory that might be been
    in use during previous action
1 - slow immediate action, typically pane_refresh()
0 - slow background action, only one of these is performed per loop

Signed-off-by: NeilBrown <neil@brown.name>
DOC/Developer/04-events.md
lib-libevent.c
python/lib-glibevents.py

index c4a4d016c25306ce4fbf7f5ad33498d53154e3b7..97a305e78d3382feb27aca7d956cba6fbd967b3f 100644 (file)
@@ -43,6 +43,20 @@ The messages that the event subsystem listens for are:
   source of events doesn't always trigger event:read.  An example might
   be a stream that supports "unget".  In such a case there may be
   content to get, but not from the file descriptor.
+  comm2 should return a positive number if it did anything, and 0 if
+  if there was nothing to do this time.
+
+- event:on-idle - The comm2 command will be called soon after the
+  current event completes.  The command will only be called once and
+  must be explicitly rescheduled if it is wanted again.
+  'num' is a priority level.
+
+  0/ is for background tasks.  Only one of these is run before checking
+     for regular events.
+  1/ is for pane_refresh(), and maybe similar tasks.  It is probably
+     needed every time around the loop, and does non-trivial work
+  2/ is for simple high priority tasks like freeing memory that was in
+     using during the previous event.
 
 - event:free - Any register event from the focus pane which is supposed
   to call the given comm2 will be deactivated.  If no comm2 command is
index 6abf25734815f6b765a26ae67c1170f64c1dc2da..65b7a195c2c699eba26cbf3acc199d7c14e0fd2f 100644 (file)
@@ -28,6 +28,9 @@ DEF_LOOKUP_CMD(libevent_handle, libevent_map);
 enum {
        EV_LIST,        /* Events handled by libevent */
        POLL_LIST,      /* Events to poll before calling event_base_loop */
+       PRIO_0_LIST,    /* background task - run one per loop */
+       PRIO_1_LIST,    /* non-trivial follow-up tasks, line pane_refresh() */
+       PRIO_2_LIST,    /* fast follow-up tasks like freeing memory */
        NR_LISTS
 };
 
@@ -37,8 +40,8 @@ struct event_info {
        struct pane *home safe;
        int dont_block;
        int deactivated;
-       struct command read, write, signal, timer, poll, run, deactivate,
-               free, refresh, noblock;
+       struct command read, write, signal, timer, poll, on_idle,
+               run, deactivate, free, refresh, noblock;
 };
 
 struct evt {
@@ -249,7 +252,36 @@ DEF_CB(libevent_poll)
        return 1;
 }
 
-static int run_list(struct event_info *ei safe, int list, char *cb safe)
+DEF_CMD(libevent_on_idle)
+{
+       struct event_info *ei = container_of(ci->comm, struct event_info, on_idle);
+       struct evt *ev;
+       int prio = ci->num;
+
+       if (!ci->comm2)
+               return Enoarg;
+
+       ev = malloc(sizeof(*ev));
+
+       if (!ei->base)
+               ei->base = event_base_new();
+
+       ev->home = ci->focus;
+       pane_add_notify(ei->home, ev->home, "Notify:Close");
+       ev->comm = command_get(ci->comm2);
+       ev->active = 0;
+       ev->event = "event:on-idle";
+       if (prio < 0)
+               prio = 0;
+       if (prio > 2)
+               prio = 2;
+       ev->num = prio;
+       list_add(&ev->lst, &ei->event_list[PRIO_0_LIST + prio]);
+       return 1;
+}
+
+static int run_list(struct event_info *ei safe, int list, char *cb safe,
+                   bool stop_on_first)
 {
        bool dont_block = False;
        struct evt *ev;
@@ -258,14 +290,14 @@ static int run_list(struct event_info *ei safe, int list, char *cb safe)
                ev->active = 1;
                if (comm_call(ev->comm, cb, ev->home, ev->num) >= 1)
                        dont_block = True;
-               if (ev->active == 2) {
+               if (ev->active == 2 || list >= PRIO_0_LIST) {
                        list_del(&ev->lst);
                        command_put(ev->comm);
                        free(ev);
                        break;
                } else
                        ev->active = 0;
-               if (dont_block)
+               if (dont_block && stop_on_first)
                        /* Other things might have been removed from list */
                        break;
        }
@@ -292,14 +324,32 @@ DEF_CB(libevent_run)
        }
 
        /* First run any 'poll' events */
-       if (run_list(ei, POLL_LIST, "callback:poll"))
+       if (run_list(ei, POLL_LIST, "callback:poll", True))
                dont_block = True;
 
+       for (i = PRIO_0_LIST ; i <= PRIO_2_LIST; i++)
+               if (!list_empty(&ei->event_list[i]))
+                       dont_block = 1;
+
        /* Disable any alarm set by python (or other interpreter) */
        alarm(0);
        event_base_loop(b, EVLOOP_ONCE | (dont_block ? EVLOOP_NONBLOCK : 0));
+
+       time_start(TIME_IDLE);
+       /* Prio 2 comes first - unconditional */
+       run_list(ei, PRIO_2_LIST, "callback:on-idle", False);
+       /* Now prio1 */
+       run_list(ei, PRIO_1_LIST, "callback:on-idle", False);
+       /* Repeat PRIO_2 just in case */
+       run_list(ei, PRIO_2_LIST, "callback:on-idle", False);
+       /* And do one background task */
+       run_list(ei, PRIO_0_LIST, "callback:on-idle", True);
+       time_stop(TIME_IDLE);
+
+       /* Check if we have been deactivated. */
        if (ei->base == b)
                return 1;
+
        for (i = 0 ; i < NR_LISTS; i++) {
                while (!list_empty(&ei->event_list[i])) {
                        ev = list_first_entry(&ei->event_list[i], struct evt, lst);
@@ -410,6 +460,7 @@ DEF_CMD(libevent_activate)
        ei->signal = libevent_signal;
        ei->timer = libevent_timer;
        ei->poll = libevent_poll;
+       ei->on_idle = libevent_on_idle;
        ei->run = libevent_run;
        ei->deactivate = libevent_deactivate;
        ei->free = libevent_free;
@@ -431,6 +482,8 @@ DEF_CMD(libevent_activate)
                  0, NULL, "event:timer-zz");
        call_comm("global-set-command", ci->focus, &ei->poll,
                  0, NULL, "event:poll-zz");
+       call_comm("global-set-command", ci->focus, &ei->on_idle,
+                 0, NULL, "event:on-idle-zz");
        call_comm("global-set-command", ci->focus, &ei->run,
                  0, NULL, "event:run-zz");
        call_comm("global-set-command", ci->focus, &ei->deactivate,
index cd89b10010864678f5c6577469ec4fbb8acc03aa..c85c0c0aa4460e3c482ed77c664c57f034c22ac2 100644 (file)
@@ -20,6 +20,7 @@ class events(edlib.Pane):
         self.events = {}
         self.sigs = {}
         self.poll_list = []
+        self.idle_list = [ [], [], [] ]
         self.ev_num = 0
         self.dont_block = False
         self.maxloops = 10
@@ -102,6 +103,14 @@ class events(edlib.Pane):
         ev = self.add_ev(focus, comm2, 'event:poll', -2)
         self.poll_list.append(ev)
 
+    def on_idle(self, key, focus, num, comm2, **a):
+        if num < 0:
+            num = 0
+        if num > 2:
+            num = 2
+        ev = self.add_ev(focus, comm2, 'event:on-idle', num)
+        self.idle_list[num].append(ev)
+
     def dotimeout(self, comm2, focus, ev):
         if ev not in self.events:
             return False
@@ -119,13 +128,21 @@ class events(edlib.Pane):
     def nonblock(self, key, **a):
         self.dont_block = True
 
+    def run_idle_list(self, prio, just_one):
+        while self.idle_list[prio]:
+            s = self.idle_list[prio].pop()
+            f,c,e,n = self.events[s]
+            if c("callback:on-idle", f, n) > 0:
+                if just_one:
+                    return
+
     def run(self, key, **a):
         if self.active:
             dont_block = self.dont_block
             self.dont_block = False
             for s in self.poll_list:
                 f,c,e,n = self.events[s]
-                if c("callback:poll", f, -1) > 0:
+                if c("callback:poll", f, n) > 0:
                     dont_block = True
             if not dont_block:
                 # Disable any alarm set by python (or other interpreter)
@@ -136,6 +153,12 @@ class events(edlib.Pane):
                 signal.alarm(0)
                 Gtk.main_iteration_do(False)
                 events += 1
+            edlib.time_start(edlib.TIME_IDLE)
+            self.run_idle_list(2, False)
+            self.run_idle_list(1, False)
+            self.run_idle_list(2, False)
+            self.run_idle_list(0, True)
+            edlib.time_stop(edlib.TIME_IDLE)
         if self.active:
             return 1
         else:
@@ -171,6 +194,9 @@ class events(edlib.Pane):
                         pass
                 if source in self.poll_list:
                     self.poll_list.remove(source)
+                for i in range(0,3):
+                    if source in self.idle_list[i]:
+                        self.idle_list[i].remove(source)
                 try_again = True
                 break
 
@@ -202,6 +228,7 @@ def events_activate(focus):
     focus.call("global-set-command", "event:signal-python", ev.signal)
     focus.call("global-set-command", "event:timer-python", ev.timer)
     focus.call("global-set-command", "event:poll-python", ev.poll)
+    focus.call("global-set-command", "event:on-idle-python", ev.on_idle)
     focus.call("global-set-command", "event:run-python", ev.run)
     focus.call("global-set-command", "event:deactivate-python", ev.deactivate)
     focus.call("global-set-command", "event:free-python", ev.free)