2 dispatcher.cc -- implement Dispatcher
4 source file of the GNU LilyPond music typesetter
6 (c) 2005-2006 Erik Sandberg <mandolaerik@gmail.com>
9 #include "dispatcher.hh"
11 #include "international.hh"
12 #include "ly-smobs.icc"
13 #include "stream-event.hh"
16 IMPLEMENT_SMOBS (Dispatcher);
17 IMPLEMENT_TYPE_P (Dispatcher, "dispatcher");
18 IMPLEMENT_DEFAULT_EQUAL_P (Dispatcher);
20 Dispatcher::~Dispatcher ()
24 Dispatcher::Dispatcher ()
28 dispatchers_ = SCM_EOL;
29 listen_classes_ = SCM_EOL;
31 // TODO: use resizable hash (guile 1.8)
32 // listeners_ = scm_c_make_hash_table (0);
33 listeners_ = scm_c_make_hash_table (17);
38 Dispatcher::mark_smob (SCM sm)
40 Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (sm);
41 scm_gc_mark (me->dispatchers_);
42 scm_gc_mark (me->listen_classes_);
43 return me->listeners_;
47 Dispatcher::print_smob (SCM s, SCM p, scm_print_state*)
49 Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (s);
50 scm_puts ("#<Dispatcher ", p);
51 scm_write (scm_vector_to_list (me->listeners_), p);
58 - Collect a list of listeners for each relevant class
59 - Send the event to each of these listeners, in increasing priority order.
60 This is done by keeping a priority queue of listener lists,
61 and iteratively send the event to the lowest-priority listener.
62 - An event is never sent twice to listeners with equal priority.
64 IMPLEMENT_LISTENER (Dispatcher, dispatch);
66 Dispatcher::dispatch (SCM sev)
68 Stream_event *ev = unsmob_stream_event (sev);
69 SCM class_symbol = ev->get_property ("class");
70 if (!scm_symbol_p (class_symbol))
72 warning (_f ("Event class should be a symbol"));
76 SCM class_list = scm_call_1 (ly_lily_module_constant ("ly:make-event-class"), class_symbol);
77 if (!scm_is_pair (class_list))
79 ev->origin ()->warning (_f ("Unknown event class %s", ly_symbol2string (class_symbol).c_str ()));
83 int num_classes = scm_ilength (class_list);
86 For each event class there is a list of listeners, which is
87 ordered by priority. Our next task is to call these listeners, in
88 priority order. A priority queue stores the next element in each
89 listener list, and the lowest priority element is repeatedly
92 The priority queue is implemented as a bubble-sorted C
93 array. Using the stack instead of native Scheme datastructures
94 avoids overheads for memory allocation. The queue is usually small
95 (around 2 elements), so the quadratic sorting time is not a
96 problem. (if this changes, it's easy to rewrite this routine using
99 The first step is to collect all listener lists and to initially
100 insert them in the priority queue.
102 struct { int prio; SCM list; } lists[num_classes+1];
104 for (SCM cl = class_list; scm_is_pair(cl); cl = scm_cdr (cl))
106 SCM list = scm_hashq_ref (listeners_, scm_car (cl), SCM_EOL);
107 if (!scm_is_pair(list))
112 int prio = scm_to_int (scm_caar (list));
114 for (j = i; j > 0 && lists[j-1].prio > prio; j--)
115 lists[j] = lists[j-1];
116 lists[j].prio = prio;
117 lists[j].list = list;
121 lists[num_classes].prio = INT_MAX;
123 // Never send an event to two listeners with equal priority.
124 int last_priority = -1;
126 Each iteration extracts the lowest-priority element, which is a
127 list of listeners. The first listener is called, and the tail of
128 the list is pushed back into the priority queue.
132 // Send the event, if we haven't already sent it to this target.
133 if (lists[0].prio != last_priority)
135 // process the listener
136 assert (lists[0].prio > last_priority);
137 last_priority = lists[0].prio;
139 Listener *l = unsmob_listener (scm_cdar (lists[0].list));
140 l->listen (ev->self_scm ());
143 // go to the next listener; bubble-sort the class list.
144 SCM next = scm_cdr (lists[0].list);
145 if (!scm_is_pair(next))
147 int prio = (scm_is_pair(next)) ? scm_to_int (scm_caar (next)) : INT_MAX;
148 for (i = 0; prio > lists[i+1].prio; i++)
149 lists[i] = lists[i+1];
150 lists[i].prio = prio;
151 lists[i].list = next;
156 warning (_f ("Junking event: %s", ly_symbol2string (class_symbol).c_str ()));
161 Dispatcher::broadcast (Stream_event *ev)
163 dispatch (ev->self_scm ());
167 Dispatcher::add_listener (Listener l, SCM ev_class)
169 internal_add_listener (l, ev_class, ++priority_count_);
173 Dispatcher::internal_add_listener (Listener l, SCM ev_class, int priority)
175 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
176 if (!scm_is_pair (list))
178 /* Tell all dispatchers that we listen to, that we want to hear ev_class
180 for (SCM disp = dispatchers_; scm_is_pair(disp); disp = scm_cdr (disp))
182 int priority = scm_to_int (scm_cdar (disp));
183 Dispatcher *d = unsmob_dispatcher (scm_caar (disp));
184 d->internal_add_listener (GET_LISTENER (dispatch), ev_class, priority);
186 listen_classes_ = scm_cons (ev_class, listen_classes_);
188 SCM entry = scm_cons (scm_int2num (priority), l.smobbed_copy ());
189 list = scm_merge (list, scm_list_1 (entry), ly_lily_module_constant ("car<"));
190 scm_hashq_set_x (listeners_, ev_class, list);
194 Dispatcher::remove_listener (Listener l, SCM ev_class)
196 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
200 programming_error ("remove_listener called with incorrect class.");
204 // We just remove the listener once.
207 SCM dummy = scm_cons (SCM_EOL, list);
209 while (scm_is_pair(scm_cdr (e)))
210 if (*unsmob_listener (scm_cdadr (e)) == l && first)
212 scm_set_cdr_x (e, scm_cddr(e));
218 list = scm_cdr (dummy);
219 scm_hashq_set_x (listeners_, ev_class, list);
222 warning ("Attempting to remove nonexisting listener.");
223 else if (!scm_is_pair (list))
225 /* Unregister with all dispatchers. */
226 for (SCM disp = dispatchers_; disp != SCM_EOL; disp = scm_cdr (disp))
228 Dispatcher *d = unsmob_dispatcher (scm_caar (disp));
229 d->remove_listener (GET_LISTENER (dispatch), ev_class);
231 listen_classes_ = scm_delq_x (ev_class, listen_classes_);
235 /* Register as a listener to another dispatcher. */
237 Dispatcher::register_as_listener (Dispatcher *disp)
239 int priority = ++disp->priority_count_;
241 // Don't register twice to the same dispatcher.
242 if (scm_assq (disp->self_scm (), dispatchers_) != SCM_BOOL_F)
244 warning ("Already listening to dispatcher, ignoring request");
248 dispatchers_ = scm_acons (disp->self_scm (), scm_int2num (priority), dispatchers_);
250 Listener list = GET_LISTENER (dispatch);
251 for (SCM cl = listen_classes_; cl != SCM_EOL; cl = scm_cdr (cl))
253 disp->internal_add_listener (list, scm_car (cl), priority);
257 /* Unregister as a listener to another dispatcher. */
259 Dispatcher::unregister_as_listener (Dispatcher *disp)
261 dispatchers_ = scm_assq_remove_x (dispatchers_, disp->self_scm ());
263 Listener listener = GET_LISTENER (dispatch);
264 for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
266 disp->remove_listener (listener, scm_car (cl));