From: NeilBrown Date: Fri, 9 Jun 2023 02:12:36 +0000 (+1000) Subject: event: add on-idle support X-Git-Url: http://git.neil.brown.name/?a=commitdiff_plain;h=9d918c03a9a4dbbf46aad633abd4e5ae3f8b00bc;p=edlib.git event: add on-idle support 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 --- diff --git a/DOC/Developer/04-events.md b/DOC/Developer/04-events.md index c4a4d016..97a305e7 100644 --- a/DOC/Developer/04-events.md +++ b/DOC/Developer/04-events.md @@ -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 diff --git a/lib-libevent.c b/lib-libevent.c index 6abf2573..65b7a195 100644 --- a/lib-libevent.c +++ b/lib-libevent.c @@ -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, diff --git a/python/lib-glibevents.py b/python/lib-glibevents.py index cd89b100..c85c0c0a 100644 --- a/python/lib-glibevents.py +++ b/python/lib-glibevents.py @@ -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)