]> git.donarmstrong.com Git - lilypond.git/blob - lily/dispatcher.cc
Run fixcc.py with astyle 2.02.
[lilypond.git] / lily / dispatcher.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2005--2012 Erik Sandberg  <mandolaerik@gmail.com>
5
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.
10
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.
15
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/>.
18 */
19
20 #include "dispatcher.hh"
21 #include "input.hh"
22 #include "international.hh"
23 #include "ly-smobs.icc"
24 #include "warn.hh"
25
26 IMPLEMENT_SMOBS (Dispatcher);
27 IMPLEMENT_TYPE_P (Dispatcher, "ly:dispatcher?");
28 IMPLEMENT_DEFAULT_EQUAL_P (Dispatcher);
29
30 Dispatcher::~Dispatcher ()
31 {
32 }
33
34 Dispatcher::Dispatcher ()
35 {
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;
45 }
46
47 SCM
48 Dispatcher::mark_smob (SCM sm)
49 {
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_;
54 }
55
56 int
57 Dispatcher::print_smob (SCM s, SCM p, scm_print_state *)
58 {
59   Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (s);
60   scm_puts ("#<Dispatcher ", p);
61   scm_write (scm_call_1 (ly_lily_module_constant ("hash-table->alist"),
62                          me->listeners_), p);
63   scm_puts (">", p);
64   return 1;
65 }
66
67 /*
68 Event dispatching:
69 - Collect a list of listeners for each relevant class
70 - Send the event to each of these listeners, in increasing priority order.
71   This is done by keeping a priority queue of listener lists,
72   and iteratively send the event to the lowest-priority listener.
73 - An event is never sent twice to listeners with equal priority.
74 */
75 IMPLEMENT_LISTENER (Dispatcher, dispatch);
76 void
77 Dispatcher::dispatch (SCM sev)
78 {
79   Stream_event *ev = unsmob_stream_event (sev);
80   SCM class_list = ev->get_property ("class");
81   if (!scm_is_pair (class_list))
82     {
83       ev->origin ()->warning (_ ("Event class should be a list"));
84       return;
85     }
86
87 #if 0
88   bool sent = false;
89 #endif
90   int num_classes = scm_ilength (class_list);
91
92   /*
93     For each event class there is a list of listeners, which is
94     ordered by priority. Our next task is to call these listeners, in
95     priority order.  A priority queue stores the next element in each
96     listener list, and the lowest priority element is repeatedly
97     extracted and called.
98
99     The priority queue is implemented as a bubble-sorted C
100     array. Using the stack instead of native Scheme datastructures
101     avoids overheads for memory allocation. The queue is usually small
102     (around 2 elements), so the quadratic sorting time is not a
103     problem. (if this changes, it's easy to rewrite this routine using
104     a heap)
105
106     The first step is to collect all listener lists and to initially
107     insert them in the priority queue.
108   */
109   struct { int prio; SCM list; } lists[num_classes + 1];
110   int i = 0;
111   for (SCM cl = class_list; scm_is_pair (cl); cl = scm_cdr (cl))
112     {
113       SCM list = scm_hashq_ref (listeners_, scm_car (cl), SCM_EOL);
114       if (!scm_is_pair (list))
115         num_classes--;
116       else
117         {
118           // bubblesort.
119           int prio = scm_to_int (scm_caar (list));
120           int j;
121           for (j = i; j > 0 && lists[j - 1].prio > prio; j--)
122             lists[j] = lists[j - 1];
123           lists[j].prio = prio;
124           lists[j].list = list;
125           i++;
126         }
127     }
128   lists[num_classes].prio = INT_MAX;
129
130   // Never send an event to two listeners with equal priority.
131   int last_priority = -1;
132   /*
133     Each iteration extracts the lowest-priority element, which is a
134     list of listeners. The first listener is called, and the tail of
135     the list is pushed back into the priority queue.
136   */
137   while (num_classes)
138     {
139       // Send the event, if we haven't already sent it to this target.
140       if (lists[0].prio != last_priority)
141         {
142           // process the listener
143           assert (lists[0].prio > last_priority);
144           last_priority = lists[0].prio;
145
146           Listener *l = unsmob_listener (scm_cdar (lists[0].list));
147           l->listen (ev->self_scm ());
148 #if 0
149           sent = true;
150 #endif
151         }
152       // go to the next listener; bubble-sort the class list.
153       SCM next = scm_cdr (lists[0].list);
154       if (!scm_is_pair (next))
155         num_classes--;
156       int prio = (scm_is_pair (next)) ? scm_to_int (scm_caar (next)) : INT_MAX;
157       for (i = 0; prio > lists[i + 1].prio; i++)
158         lists[i] = lists[i + 1];
159       lists[i].prio = prio;
160       lists[i].list = next;
161     }
162
163 #if 0
164   /* TODO: Uncomment. */
165   if (!sent)
166     warning (_f ("Junking event: %s", ly_symbol2string (class_symbol).c_str ()));
167 #endif
168 }
169
170 bool
171 Dispatcher::is_listened_class (SCM cl)
172 {
173   for (; scm_is_pair (cl); cl = scm_cdr (cl))
174     {
175       SCM list = scm_hashq_ref (listeners_, scm_car (cl), SCM_EOL);
176       if (scm_is_pair (list))
177         return true;
178     }
179   return false;
180 }
181
182 static SCM
183 accumulate_types (void * /* closure */,
184                   SCM key,
185                   SCM val,
186                   SCM result)
187 {
188   if (scm_is_pair (val))
189     return scm_cons (key, result);
190   return result;
191 }
192
193 SCM
194 Dispatcher::listened_types ()
195 {
196   return scm_internal_hash_fold ((scm_t_hash_fold_fn) &accumulate_types,
197                                  NULL, SCM_EOL, listeners_);
198 }
199
200 void
201 Dispatcher::broadcast (Stream_event *ev)
202 {
203   dispatch (ev->self_scm ());
204 }
205
206 void
207 Dispatcher::add_listener (Listener l, SCM ev_class)
208 {
209   internal_add_listener (l, ev_class, ++priority_count_);
210 }
211
212 inline void
213 Dispatcher::internal_add_listener (Listener l, SCM ev_class, int priority)
214 {
215   SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
216   if (!scm_is_pair (list))
217     {
218       /* Tell all dispatchers that we listen to, that we want to hear ev_class
219          events */
220       for (SCM disp = dispatchers_; scm_is_pair (disp); disp = scm_cdr (disp))
221         {
222           int priority = scm_to_int (scm_cdar (disp));
223           Dispatcher *d = unsmob_dispatcher (scm_caar (disp));
224           d->internal_add_listener (GET_LISTENER (dispatch), ev_class, priority);
225         }
226       listen_classes_ = scm_cons (ev_class, listen_classes_);
227     }
228   SCM entry = scm_cons (scm_from_int (priority), l.smobbed_copy ());
229   list = scm_merge (list, scm_list_1 (entry), ly_lily_module_constant ("car<"));
230   scm_hashq_set_x (listeners_, ev_class, list);
231 }
232
233 void
234 Dispatcher::remove_listener (Listener l, SCM ev_class)
235 {
236   SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
237
238   if (list == SCM_EOL)
239     {
240       programming_error ("remove_listener called with incorrect class.");
241       return;
242     }
243
244   // We just remove the listener once.
245   bool first = true;
246
247   SCM dummy = scm_cons (SCM_EOL, list);
248   SCM e = dummy;
249   while (scm_is_pair (scm_cdr (e)))
250     if (*unsmob_listener (scm_cdadr (e)) == l && first)
251       {
252         scm_set_cdr_x (e, scm_cddr (e));
253         first = false;
254         break;
255       }
256     else
257       e = scm_cdr (e);
258   list = scm_cdr (dummy);
259   scm_hashq_set_x (listeners_, ev_class, list);
260
261   if (first)
262     warning (_ ("Attempting to remove nonexisting listener."));
263   else if (!scm_is_pair (list))
264     {
265       /* Unregister with all dispatchers. */
266       for (SCM disp = dispatchers_; scm_is_pair (disp); disp = scm_cdr (disp))
267         {
268           Dispatcher *d = unsmob_dispatcher (scm_caar (disp));
269           d->remove_listener (GET_LISTENER (dispatch), ev_class);
270         }
271       listen_classes_ = scm_delq_x (ev_class, listen_classes_);
272     }
273 }
274
275 /* Register as a listener to another dispatcher. */
276 void
277 Dispatcher::register_as_listener (Dispatcher *disp)
278 {
279   int priority = ++disp->priority_count_;
280
281   // Don't register twice to the same dispatcher.
282   if (scm_assq (disp->self_scm (), dispatchers_) != SCM_BOOL_F)
283     {
284       warning (_ ("Already listening to dispatcher, ignoring request"));
285       return;
286     }
287
288   dispatchers_ = scm_acons (disp->self_scm (), scm_from_int (priority), dispatchers_);
289
290   Listener list = GET_LISTENER (dispatch);
291   for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
292     {
293       disp->internal_add_listener (list, scm_car (cl), priority);
294     }
295 }
296
297 /* Unregister as a listener to another dispatcher. */
298 void
299 Dispatcher::unregister_as_listener (Dispatcher *disp)
300 {
301   dispatchers_ = scm_assq_remove_x (dispatchers_, disp->self_scm ());
302
303   Listener listener = GET_LISTENER (dispatch);
304   for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
305     {
306       disp->remove_listener (listener, scm_car (cl));
307     }
308 }