2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2005-2006 Erik Sandberg <mandolaerik@gmail.com>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "dispatcher.hh"
22 #include "international.hh"
23 #include "ly-smobs.icc"
26 IMPLEMENT_SMOBS (Dispatcher);
27 IMPLEMENT_TYPE_P (Dispatcher, "ly:dispatcher?");
28 IMPLEMENT_DEFAULT_EQUAL_P (Dispatcher);
30 Dispatcher::~Dispatcher ()
34 Dispatcher::Dispatcher ()
38 dispatchers_ = SCM_EOL;
39 listen_classes_ = SCM_EOL;
41 // TODO: use resizable hash (guile 1.8)
42 // listeners_ = scm_c_make_hash_table (0);
43 listeners_ = scm_c_make_hash_table (17);
48 Dispatcher::mark_smob (SCM sm)
50 Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (sm);
51 scm_gc_mark (me->dispatchers_);
52 scm_gc_mark (me->listen_classes_);
53 return me->listeners_;
57 Dispatcher::print_smob (SCM s, SCM p, scm_print_state*)
59 Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (s);
60 scm_puts ("#<Dispatcher ", p);
61 scm_write (scm_vector_to_list (me->listeners_), p);
68 - Collect a list of listeners for each relevant class
69 - Send the event to each of these listeners, in increasing priority order.
70 This is done by keeping a priority queue of listener lists,
71 and iteratively send the event to the lowest-priority listener.
72 - An event is never sent twice to listeners with equal priority.
74 IMPLEMENT_LISTENER (Dispatcher, dispatch);
76 Dispatcher::dispatch (SCM sev)
78 Stream_event *ev = unsmob_stream_event (sev);
79 SCM class_symbol = ev->get_property ("class");
80 if (!scm_symbol_p (class_symbol))
82 warning (_ ("Event class should be a symbol"));
86 SCM class_list = scm_call_1 (ly_lily_module_constant ("ly:make-event-class"), class_symbol);
87 if (!scm_is_pair (class_list))
89 ev->origin ()->warning (_f ("Unknown event class %s", ly_symbol2string (class_symbol).c_str ()));
93 int num_classes = scm_ilength (class_list);
96 For each event class there is a list of listeners, which is
97 ordered by priority. Our next task is to call these listeners, in
98 priority order. A priority queue stores the next element in each
99 listener list, and the lowest priority element is repeatedly
100 extracted and called.
102 The priority queue is implemented as a bubble-sorted C
103 array. Using the stack instead of native Scheme datastructures
104 avoids overheads for memory allocation. The queue is usually small
105 (around 2 elements), so the quadratic sorting time is not a
106 problem. (if this changes, it's easy to rewrite this routine using
109 The first step is to collect all listener lists and to initially
110 insert them in the priority queue.
112 struct { int prio; SCM list; } lists[num_classes+1];
114 for (SCM cl = class_list; scm_is_pair (cl); cl = scm_cdr (cl))
116 SCM list = scm_hashq_ref (listeners_, scm_car (cl), SCM_EOL);
117 if (!scm_is_pair (list))
122 int prio = scm_to_int (scm_caar (list));
124 for (j = i; j > 0 && lists[j-1].prio > prio; j--)
125 lists[j] = lists[j-1];
126 lists[j].prio = prio;
127 lists[j].list = list;
131 lists[num_classes].prio = INT_MAX;
133 // Never send an event to two listeners with equal priority.
134 int last_priority = -1;
136 Each iteration extracts the lowest-priority element, which is a
137 list of listeners. The first listener is called, and the tail of
138 the list is pushed back into the priority queue.
142 // Send the event, if we haven't already sent it to this target.
143 if (lists[0].prio != last_priority)
145 // process the listener
146 assert (lists[0].prio > last_priority);
147 last_priority = lists[0].prio;
149 Listener *l = unsmob_listener (scm_cdar (lists[0].list));
150 l->listen (ev->self_scm ());
153 // go to the next listener; bubble-sort the class list.
154 SCM next = scm_cdr (lists[0].list);
155 if (!scm_is_pair (next))
157 int prio = (scm_is_pair (next)) ? scm_to_int (scm_caar (next)) : INT_MAX;
158 for (i = 0; prio > lists[i+1].prio; i++)
159 lists[i] = lists[i+1];
160 lists[i].prio = prio;
161 lists[i].list = next;
166 warning (_f ("Junking event: %s", ly_symbol2string (class_symbol).c_str ()));
171 Dispatcher::broadcast (Stream_event *ev)
173 dispatch (ev->self_scm ());
177 Dispatcher::add_listener (Listener l, SCM ev_class)
179 internal_add_listener (l, ev_class, ++priority_count_);
183 Dispatcher::internal_add_listener (Listener l, SCM ev_class, int priority)
185 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
186 if (!scm_is_pair (list))
188 /* Tell all dispatchers that we listen to, that we want to hear ev_class
190 for (SCM disp = dispatchers_; scm_is_pair (disp); disp = scm_cdr (disp))
192 int priority = scm_to_int (scm_cdar (disp));
193 Dispatcher *d = unsmob_dispatcher (scm_caar (disp));
194 d->internal_add_listener (GET_LISTENER (dispatch), ev_class, priority);
196 listen_classes_ = scm_cons (ev_class, listen_classes_);
198 SCM entry = scm_cons (scm_int2num (priority), l.smobbed_copy ());
199 list = scm_merge (list, scm_list_1 (entry), ly_lily_module_constant ("car<"));
200 scm_hashq_set_x (listeners_, ev_class, list);
204 Dispatcher::remove_listener (Listener l, SCM ev_class)
206 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
210 programming_error ("remove_listener called with incorrect class.");
214 // We just remove the listener once.
217 SCM dummy = scm_cons (SCM_EOL, list);
219 while (scm_is_pair (scm_cdr (e)))
220 if (*unsmob_listener (scm_cdadr (e)) == l && first)
222 scm_set_cdr_x (e, scm_cddr (e));
228 list = scm_cdr (dummy);
229 scm_hashq_set_x (listeners_, ev_class, list);
232 warning ("Attempting to remove nonexisting listener.");
233 else if (!scm_is_pair (list))
235 /* Unregister with all dispatchers. */
236 for (SCM disp = dispatchers_; scm_is_pair (disp); disp = scm_cdr (disp))
238 Dispatcher *d = unsmob_dispatcher (scm_caar (disp));
239 d->remove_listener (GET_LISTENER (dispatch), ev_class);
241 listen_classes_ = scm_delq_x (ev_class, listen_classes_);
245 /* Register as a listener to another dispatcher. */
247 Dispatcher::register_as_listener (Dispatcher *disp)
249 int priority = ++disp->priority_count_;
251 // Don't register twice to the same dispatcher.
252 if (scm_assq (disp->self_scm (), dispatchers_) != SCM_BOOL_F)
254 warning ("Already listening to dispatcher, ignoring request");
258 dispatchers_ = scm_acons (disp->self_scm (), scm_int2num (priority), dispatchers_);
260 Listener list = GET_LISTENER (dispatch);
261 for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
263 disp->internal_add_listener (list, scm_car (cl), priority);
267 /* Unregister as a listener to another dispatcher. */
269 Dispatcher::unregister_as_listener (Dispatcher *disp)
271 dispatchers_ = scm_assq_remove_x (dispatchers_, disp->self_scm ());
273 Listener listener = GET_LISTENER (dispatch);
274 for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
276 disp->remove_listener (listener, scm_car (cl));