Fix type predicates/docstrings for two music properties.
[lilypond/mpolesky.git] / lily / dispatcher.cc
blobb91b4fa5e935f15120e070b4cbf6111cd41d7816
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2005--2010 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"
21 #include "input.hh"
22 #include "international.hh"
23 #include "ly-smobs.icc"
24 #include "warn.hh"
26 IMPLEMENT_SMOBS (Dispatcher);
27 IMPLEMENT_TYPE_P (Dispatcher, "ly:dispatcher?");
28 IMPLEMENT_DEFAULT_EQUAL_P (Dispatcher);
30 Dispatcher::~Dispatcher ()
34 Dispatcher::Dispatcher ()
36 self_scm_ = SCM_EOL;
37 listeners_ = SCM_EOL;
38 dispatchers_ = SCM_EOL;
39 listen_classes_ = SCM_EOL;
40 smobify_self ();
41 // TODO: use resizable hash (guile 1.8)
42 // listeners_ = scm_c_make_hash_table (0);
43 listeners_ = scm_c_make_hash_table (17);
44 priority_count_ = 0;
47 SCM
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_;
56 int
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);
62 scm_puts (">", p);
63 return 1;
67 Event dispatching:
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);
75 void
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"));
83 return;
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 ()));
90 return;
92 bool sent = false;
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
107 a heap)
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];
113 int i = 0;
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))
118 num_classes--;
119 else
121 // bubblesort.
122 int prio = scm_to_int (scm_caar (list));
123 int j;
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;
128 i++;
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.
140 while (num_classes)
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 ());
151 sent = true;
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))
156 num_classes--;
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;
164 /* TODO: Uncomment.
165 if (!sent)
166 warning (_f ("Junking event: %s", ly_symbol2string (class_symbol).c_str ()));
170 void
171 Dispatcher::broadcast (Stream_event *ev)
173 dispatch (ev->self_scm ());
176 void
177 Dispatcher::add_listener (Listener l, SCM ev_class)
179 internal_add_listener (l, ev_class, ++priority_count_);
182 inline void
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
189 events */
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);
203 void
204 Dispatcher::remove_listener (Listener l, SCM ev_class)
206 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
208 if (list == SCM_EOL)
210 programming_error ("remove_listener called with incorrect class.");
211 return;
214 // We just remove the listener once.
215 bool first = true;
217 SCM dummy = scm_cons (SCM_EOL, list);
218 SCM e = dummy;
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));
223 first = false;
224 break;
226 else
227 e = scm_cdr (e);
228 list = scm_cdr (dummy);
229 scm_hashq_set_x (listeners_, ev_class, list);
231 if (first)
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. */
246 void
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");
255 return;
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. */
268 void
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));