2 dispatcher.cc -- implement Dispatcher
4 source file of the GNU LilyPond music typesetter
6 (c) 2005-2006 Erik Sandberg <mandolaerik@gmail.com>
9 #include "dispatcher.hh"
11 #include "international.hh"
12 #include "ly-smobs.icc"
15 IMPLEMENT_SMOBS (Dispatcher
);
16 IMPLEMENT_TYPE_P (Dispatcher
, "dispatcher");
17 IMPLEMENT_DEFAULT_EQUAL_P (Dispatcher
);
19 Dispatcher::~Dispatcher ()
23 Dispatcher::Dispatcher ()
27 dispatchers_
= SCM_EOL
;
28 listen_classes_
= SCM_EOL
;
30 // TODO: use resizable hash (guile 1.8)
31 // listeners_ = scm_c_make_hash_table (0);
32 listeners_
= scm_c_make_hash_table (17);
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_
;
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
);
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
);
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 (_f ("Event class should be a symbol"));
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 ()));
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
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
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];
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
))
111 int prio
= scm_to_int (scm_caar (list
));
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
;
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.
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 ());
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
))
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
;
155 warning (_f ("Junking event: %s", ly_symbol2string (class_symbol).c_str ()));
160 Dispatcher::broadcast (Stream_event
*ev
)
162 dispatch (ev
->self_scm ());
166 Dispatcher::add_listener (Listener l
, SCM ev_class
)
168 internal_add_listener (l
, ev_class
, ++priority_count_
);
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
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
);
193 Dispatcher::remove_listener (Listener l
, SCM ev_class
)
195 SCM list
= scm_hashq_ref (listeners_
, ev_class
, SCM_EOL
);
199 programming_error ("remove_listener called with incorrect class.");
203 // We just remove the listener once.
206 SCM dummy
= scm_cons (SCM_EOL
, list
);
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
));
217 list
= scm_cdr (dummy
);
218 scm_hashq_set_x (listeners_
, ev_class
, list
);
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. */
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");
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. */
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
));