Nitpick: ly:spanner-bound grob name slur -> spanner.
[lilypond.git] / lily / dispatcher.cc
blob29ad2ba3763c256f712ba5c7e442ed16d9d997e4
1 /*
2 dispatcher.cc -- implement Dispatcher
4 source file of the GNU LilyPond music typesetter
6 (c) 2005-2006 Erik Sandberg <mandolaerik@gmail.com>
7 */
9 #include "dispatcher.hh"
10 #include "input.hh"
11 #include "international.hh"
12 #include "ly-smobs.icc"
13 #include "warn.hh"
15 IMPLEMENT_SMOBS (Dispatcher);
16 IMPLEMENT_TYPE_P (Dispatcher, "dispatcher");
17 IMPLEMENT_DEFAULT_EQUAL_P (Dispatcher);
19 Dispatcher::~Dispatcher ()
23 Dispatcher::Dispatcher ()
25 self_scm_ = SCM_EOL;
26 listeners_ = SCM_EOL;
27 dispatchers_ = SCM_EOL;
28 listen_classes_ = SCM_EOL;
29 smobify_self ();
30 // TODO: use resizable hash (guile 1.8)
31 // listeners_ = scm_c_make_hash_table (0);
32 listeners_ = scm_c_make_hash_table (17);
33 priority_count_ = 0;
36 SCM
37 Dispatcher::mark_smob (SCM sm)
39 Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (sm);
40 scm_gc_mark (me->dispatchers_);
41 scm_gc_mark (me->listen_classes_);
42 return me->listeners_;
45 int
46 Dispatcher::print_smob (SCM s, SCM p, scm_print_state*)
48 Dispatcher *me = (Dispatcher *) SCM_CELL_WORD_1 (s);
49 scm_puts ("#<Dispatcher ", p);
50 scm_write (scm_vector_to_list (me->listeners_), p);
51 scm_puts (">", p);
52 return 1;
56 Event dispatching:
57 - Collect a list of listeners for each relevant class
58 - Send the event to each of these listeners, in increasing priority order.
59 This is done by keeping a priority queue of listener lists,
60 and iteratively send the event to the lowest-priority listener.
61 - An event is never sent twice to listeners with equal priority.
63 IMPLEMENT_LISTENER (Dispatcher, dispatch);
64 void
65 Dispatcher::dispatch (SCM sev)
67 Stream_event *ev = unsmob_stream_event (sev);
68 SCM class_symbol = ev->get_property ("class");
69 if (!scm_symbol_p (class_symbol))
71 warning (_ ("Event class should be a symbol"));
72 return;
75 SCM class_list = scm_call_1 (ly_lily_module_constant ("ly:make-event-class"), class_symbol);
76 if (!scm_is_pair (class_list))
78 ev->origin ()->warning (_f ("Unknown event class %s", ly_symbol2string (class_symbol).c_str ()));
79 return;
81 bool sent = false;
82 int num_classes = scm_ilength (class_list);
85 For each event class there is a list of listeners, which is
86 ordered by priority. Our next task is to call these listeners, in
87 priority order. A priority queue stores the next element in each
88 listener list, and the lowest priority element is repeatedly
89 extracted and called.
91 The priority queue is implemented as a bubble-sorted C
92 array. Using the stack instead of native Scheme datastructures
93 avoids overheads for memory allocation. The queue is usually small
94 (around 2 elements), so the quadratic sorting time is not a
95 problem. (if this changes, it's easy to rewrite this routine using
96 a heap)
98 The first step is to collect all listener lists and to initially
99 insert them in the priority queue.
101 struct { int prio; SCM list; } lists[num_classes+1];
102 int i = 0;
103 for (SCM cl = class_list; scm_is_pair (cl); cl = scm_cdr (cl))
105 SCM list = scm_hashq_ref (listeners_, scm_car (cl), SCM_EOL);
106 if (!scm_is_pair (list))
107 num_classes--;
108 else
110 // bubblesort.
111 int prio = scm_to_int (scm_caar (list));
112 int j;
113 for (j = i; j > 0 && lists[j-1].prio > prio; j--)
114 lists[j] = lists[j-1];
115 lists[j].prio = prio;
116 lists[j].list = list;
117 i++;
120 lists[num_classes].prio = INT_MAX;
122 // Never send an event to two listeners with equal priority.
123 int last_priority = -1;
125 Each iteration extracts the lowest-priority element, which is a
126 list of listeners. The first listener is called, and the tail of
127 the list is pushed back into the priority queue.
129 while (num_classes)
131 // Send the event, if we haven't already sent it to this target.
132 if (lists[0].prio != last_priority)
134 // process the listener
135 assert (lists[0].prio > last_priority);
136 last_priority = lists[0].prio;
138 Listener *l = unsmob_listener (scm_cdar (lists[0].list));
139 l->listen (ev->self_scm ());
140 sent = true;
142 // go to the next listener; bubble-sort the class list.
143 SCM next = scm_cdr (lists[0].list);
144 if (!scm_is_pair (next))
145 num_classes--;
146 int prio = (scm_is_pair (next)) ? scm_to_int (scm_caar (next)) : INT_MAX;
147 for (i = 0; prio > lists[i+1].prio; i++)
148 lists[i] = lists[i+1];
149 lists[i].prio = prio;
150 lists[i].list = next;
153 /* TODO: Uncomment.
154 if (!sent)
155 warning (_f ("Junking event: %s", ly_symbol2string (class_symbol).c_str ()));
159 void
160 Dispatcher::broadcast (Stream_event *ev)
162 dispatch (ev->self_scm ());
165 void
166 Dispatcher::add_listener (Listener l, SCM ev_class)
168 internal_add_listener (l, ev_class, ++priority_count_);
171 inline void
172 Dispatcher::internal_add_listener (Listener l, SCM ev_class, int priority)
174 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
175 if (!scm_is_pair (list))
177 /* Tell all dispatchers that we listen to, that we want to hear ev_class
178 events */
179 for (SCM disp = dispatchers_; scm_is_pair (disp); disp = scm_cdr (disp))
181 int priority = scm_to_int (scm_cdar (disp));
182 Dispatcher *d = unsmob_dispatcher (scm_caar (disp));
183 d->internal_add_listener (GET_LISTENER (dispatch), ev_class, priority);
185 listen_classes_ = scm_cons (ev_class, listen_classes_);
187 SCM entry = scm_cons (scm_int2num (priority), l.smobbed_copy ());
188 list = scm_merge (list, scm_list_1 (entry), ly_lily_module_constant ("car<"));
189 scm_hashq_set_x (listeners_, ev_class, list);
192 void
193 Dispatcher::remove_listener (Listener l, SCM ev_class)
195 SCM list = scm_hashq_ref (listeners_, ev_class, SCM_EOL);
197 if (list == SCM_EOL)
199 programming_error ("remove_listener called with incorrect class.");
200 return;
203 // We just remove the listener once.
204 bool first = true;
206 SCM dummy = scm_cons (SCM_EOL, list);
207 SCM e = dummy;
208 while (scm_is_pair (scm_cdr (e)))
209 if (*unsmob_listener (scm_cdadr (e)) == l && first)
211 scm_set_cdr_x (e, scm_cddr (e));
212 first = false;
213 break;
215 else
216 e = scm_cdr (e);
217 list = scm_cdr (dummy);
218 scm_hashq_set_x (listeners_, ev_class, list);
220 if (first)
221 warning ("Attempting to remove nonexisting listener.");
222 else if (!scm_is_pair (list))
224 /* Unregister with all dispatchers. */
225 for (SCM disp = dispatchers_; scm_is_pair (disp); disp = scm_cdr (disp))
227 Dispatcher *d = unsmob_dispatcher (scm_caar (disp));
228 d->remove_listener (GET_LISTENER (dispatch), ev_class);
230 listen_classes_ = scm_delq_x (ev_class, listen_classes_);
234 /* Register as a listener to another dispatcher. */
235 void
236 Dispatcher::register_as_listener (Dispatcher *disp)
238 int priority = ++disp->priority_count_;
240 // Don't register twice to the same dispatcher.
241 if (scm_assq (disp->self_scm (), dispatchers_) != SCM_BOOL_F)
243 warning ("Already listening to dispatcher, ignoring request");
244 return;
247 dispatchers_ = scm_acons (disp->self_scm (), scm_int2num (priority), dispatchers_);
249 Listener list = GET_LISTENER (dispatch);
250 for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
252 disp->internal_add_listener (list, scm_car (cl), priority);
256 /* Unregister as a listener to another dispatcher. */
257 void
258 Dispatcher::unregister_as_listener (Dispatcher *disp)
260 dispatchers_ = scm_assq_remove_x (dispatchers_, disp->self_scm ());
262 Listener listener = GET_LISTENER (dispatch);
263 for (SCM cl = listen_classes_; scm_is_pair (cl); cl = scm_cdr (cl))
265 disp->remove_listener (listener, scm_car (cl));