2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2005--2014 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 ()
37 dispatchers_ = SCM_EOL;
38 listen_classes_ = SCM_EOL;
40 // TODO: use resizable hash (guile 1.8)
41 // listeners_ = scm_c_make_hash_table (0);
42 listeners_ = scm_c_make_hash_table (17);
47 Dispatcher::mark_smob (SCM sm)
49 Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (sm);
50 scm_gc_mark (me->dispatchers_);
51 scm_gc_mark (me->listen_classes_);
52 return me->listeners_;
56 Dispatcher::print_smob (SCM s, SCM p, scm_print_state *)
58 Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (s);
59 scm_puts ("#<Dispatcher ", p);
60 scm_write (scm_call_1 (ly_lily_module_constant ("hash-table->alist"),
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.
73 The only case where listeners with equal priority may exist is when
74 two dispatchers are connected for more than one event type. In that
75 case, the respective listeners all have the same priority, making
76 sure that any event is only dispatched at most once for that
77 combination of dispatchers, even if it matches more than one event
80 IMPLEMENT_LISTENER (Dispatcher, dispatch);
82 Dispatcher::dispatch (SCM sev)
84 Stream_event *ev = Stream_event::unsmob (sev);
85 SCM class_list = ev->get_property ("class");
86 if (!scm_is_pair (class_list))
88 ev->origin ()->warning (_ ("Event class should be a list"));
95 int num_classes = scm_ilength (class_list);
98 For each event class there is a list of listeners, which is
99 ordered by priority. Our next task is to call these listeners, in
100 priority order. A priority queue stores the next element in each
101 listener list, and the lowest priority element is repeatedly
102 extracted and called.
104 The priority queue is implemented as an insertion-sorted C
105 array. Using the stack instead of native Scheme datastructures
106 avoids overheads for memory allocation. The queue is usually small
107 (around 2 elements), so the quadratic sorting time is not a
108 problem. (if this changes, it's easy to rewrite this routine using
111 The first step is to collect all listener lists and to initially
112 insert them in the priority queue.
114 struct { int prio; SCM list; } lists[num_classes + 1];
116 for (SCM cl = class_list; scm_is_pair (cl); cl = scm_cdr (cl))
118 SCM list = scm_hashq_ref (listeners_, scm_car (cl), SCM_EOL);
119 if (!scm_is_pair (list))
124 int prio = scm_to_int (scm_caar (list));
126 for (j = i; j > 0 && lists[j - 1].prio > prio; j--)
127 lists[j] = lists[j - 1];
128 lists[j].prio = prio;
129 lists[j].list = list;
133 lists[num_classes].prio = INT_MAX;
135 // Never send an event to two listeners with equal priority.
136 int last_priority = -1;
138 Each iteration extracts the lowest-priority element, which is a
139 list of listeners. The first listener is called, and the tail of
140 the list is pushed back into the priority queue.
144 // Send the event, if we haven't already sent it to this target.
145 if (lists[0].prio != last_priority)
147 // process the listener
148 assert (lists[0].prio > last_priority);
149 last_priority = lists[0].prio;
151 Listener *l = Listener::unsmob (scm_cdar (lists[0].list));
152 l->listen (ev->self_scm ());
157 // go to the next listener; bubble-sort the class list.
158 SCM next = scm_cdr (lists[0].list);
159 if (!scm_is_pair (next))
161 int prio = (scm_is_pair (next)) ? scm_to_int (scm_caar (next)) : INT_MAX;
162 for (i = 0; prio > lists[i + 1].prio; i++)
163 lists[i] = lists[i + 1];
164 lists[i].prio = prio;
165 lists[i].list = next;
169 /* TODO: Uncomment. */
171 warning (_f ("Junking event: %s", ly_symbol2string (class_symbol).c_str ()));
176 Dispatcher::is_listened_class (SCM cl)
178 for (; scm_is_pair (cl); cl = scm_cdr (cl))
180 SCM list = scm_hashq_ref (listeners_, scm_car (cl), SCM_EOL);
181 if (scm_is_pair (list))
188 accumulate_types (void * /* closure */,
193 if (scm_is_pair (val))
194 return scm_cons (key, result);
199 Dispatcher::listened_types ()
201 return scm_internal_hash_fold ((scm_t_hash_fold_fn) &accumulate_types,
202 NULL, SCM_EOL, listeners_);
206 Dispatcher::broadcast (Stream_event *ev)
208 dispatch (ev->self_scm ());
211 // add_listener will always assign a new priority for each call
213 Dispatcher::add_listener (Listener l, SCM ev_class)
215 internal_add_listener (l, ev_class, ++priority_count_);
219 Dispatcher::internal_add_listener (Listener l, SCM ev_class, int priority)
221 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
222 // if ev_class is not yet listened to, we go through our list of
223 // source dispatchers and register ourselves there with the priority
224 // we have reserved for this dispatcher. The priority system
225 // usually distributes events in the order events are registered.
226 // The reuse of a previous priority when registering another event
227 // for a dispatcher/dispatcher connection bypasses the normal
228 // ordering, but it is the mechanism by which duplicate broadcasts
229 // of the same event from one dispatcher to another are avoided.
230 if (!scm_is_pair (list))
232 /* Tell all dispatchers that we listen to, that we want to hear ev_class
234 for (SCM disp = dispatchers_; scm_is_pair (disp); disp = scm_cdr (disp))
236 int priority = scm_to_int (scm_cdar (disp));
237 Dispatcher *d = Dispatcher::unsmob (scm_caar (disp));
238 d->internal_add_listener (GET_LISTENER (dispatch), ev_class, priority);
240 listen_classes_ = scm_cons (ev_class, listen_classes_);
242 SCM entry = scm_cons (scm_from_int (priority), l.smobbed_copy ());
243 list = scm_merge (list, scm_list_1 (entry), ly_lily_module_constant ("car<"));
244 scm_hashq_set_x (listeners_, ev_class, list);
248 Dispatcher::remove_listener (Listener l, SCM ev_class)
250 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
254 programming_error ("remove_listener called with incorrect class.");
258 // We just remove the listener once.
261 SCM dummy = scm_cons (SCM_EOL, list);
263 while (scm_is_pair (scm_cdr (e)))
264 if (*Listener::unsmob (scm_cdadr (e)) == l && first)
266 scm_set_cdr_x (e, scm_cddr (e));
272 list = scm_cdr (dummy);
273 scm_hashq_set_x (listeners_, ev_class, list);
276 warning (_ ("Attempting to remove nonexisting listener."));
277 else if (!scm_is_pair (list))
279 /* Unregister with all dispatchers. */
280 for (SCM disp = dispatchers_; scm_is_pair (disp); disp = scm_cdr (disp))
282 Dispatcher *d = Dispatcher::unsmob (scm_caar (disp));
283 d->remove_listener (GET_LISTENER (dispatch), ev_class);
285 listen_classes_ = scm_delq_x (ev_class, listen_classes_);
289 /* Register as a listener to another dispatcher. */
291 Dispatcher::register_as_listener (Dispatcher *disp)
293 // We are creating and remembering the priority _we_ have with the
294 // foreign dispatcher. All events are dispatched with the same
295 // priority. The result is that, for example, a single event class
296 // will only trigger an event listener once.
297 int priority = ++disp->priority_count_;
299 // Don't register twice to the same dispatcher.
300 if (scm_assq (disp->self_scm (), dispatchers_) != SCM_BOOL_F)
302 warning (_ ("Already listening to dispatcher, ignoring request"));
306 dispatchers_ = scm_acons (disp->self_scm (), scm_from_int (priority), dispatchers_);
308 Listener list = GET_LISTENER (dispatch);
309 for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
311 disp->internal_add_listener (list, scm_car (cl), priority);
315 /* Unregister as a listener to another dispatcher. */
317 Dispatcher::unregister_as_listener (Dispatcher *disp)
319 dispatchers_ = scm_assq_remove_x (dispatchers_, disp->self_scm ());
321 Listener listener = GET_LISTENER (dispatch);
322 for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
324 disp->remove_listener (listener, scm_car (cl));