2 break-substitution.cc -- implement grob break substitution.
4 source file of the GNU LilyPond music typesetter
6 (c) 2001--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
15 #include "grob-array.hh"
17 static SCM break_criterion
;
19 set_break_subsititution (SCM criterion
)
21 break_criterion
= criterion
;
25 Perform the substitution for a single grob.
28 substitute_grob (Grob
*sc
)
30 if (scm_is_integer (break_criterion
))
32 Item
*i
= dynamic_cast<Item
*> (sc
);
33 Direction d
= to_dir (break_criterion
);
34 if (i
&& i
->break_status_dir () != d
)
36 Item
*br
= i
->find_prebroken_piece (d
);
43 = dynamic_cast<System
*> (unsmob_grob (break_criterion
));
44 if (sc
->get_system () != line
)
45 sc
= sc
->find_broken_piece (line
);
47 /* now: !sc || (sc && sc->get_system () == line) */
51 /* now: sc && sc->get_system () == line */
56 We don't return SCM_UNDEFINED for
57 suicided grobs, for two reasons
59 - it doesn't work (strange disappearing objects)
61 - it forces us to mark the parents of a grob, leading to
62 a huge recursion in the GC routine.
65 if (sc
->common_refpoint (line
, X_AXIS
)
66 && sc
->common_refpoint (line
, Y_AXIS
))
75 Do break substitution in S, using CRITERION. Return new value.
76 CRITERION is either a SMOB pointer to the desired line, or a number
77 representing the break direction. Do not modify SRC.
79 It is rather tightly coded, since it takes a lot of time; it is
80 one of the top functions in the profile.
82 We don't pass break_criterion as a parameter, since it is
83 `constant', but takes up stack space.
85 It would be nice if we could do this in-place partially. We now
86 generate a lot of garbage.
89 do_break_substitution (SCM src
)
93 if (unsmob_grob (src
))
95 Grob
*new_ptr
= substitute_grob (unsmob_grob (src
));
96 return new_ptr
? new_ptr
->self_scm () : SCM_UNDEFINED
;
98 else if (scm_is_vector (src
))
100 int len
= scm_c_vector_length (src
);
101 SCM nv
= scm_c_make_vector (len
, SCM_UNDEFINED
);
102 for (int i
= 0; i
< len
; i
++)
104 SCM si
= scm_from_int (i
);
105 scm_vector_set_x (nv
, si
,
106 do_break_substitution (scm_vector_ref (src
, si
)));
109 else if (scm_is_pair (src
))
112 UGH! breaks on circular lists.
114 SCM newcar
= do_break_substitution (scm_car (src
));
115 SCM oldcdr
= scm_cdr (src
);
117 if (newcar
== SCM_UNDEFINED
118 && (scm_is_pair (oldcdr
) || oldcdr
== SCM_EOL
))
121 This is tail-recursion, ie.
123 return do_break_substution (cdr);
125 We don't want to rely on the compiler to do this. Without
126 tail-recursion, this easily crashes with a stack overflow. */
131 return scm_cons (newcar
, do_break_substitution (oldcdr
));
140 Perform substitution on GROB_LIST using a constant amount of stack.
142 vector
<Grob
*> temporary_substition_array
;
144 substitute_grob_array (Grob_array
*grob_arr
, Grob_array
*new_arr
)
146 vector
<Grob
*> &old_grobs (grob_arr
->array_reference ());
147 vector
<Grob
*> *new_grobs (new_arr
== grob_arr
148 ? & temporary_substition_array
149 : &new_arr
->array_reference ());
151 new_grobs
->resize (old_grobs
.size ());
152 Grob
**array
= (Grob
**) new_grobs
->data ();
154 for (vsize i
= 0; i
< old_grobs
.size (); i
++)
156 Grob
*orig
= old_grobs
[i
];
157 Grob
*new_grob
= substitute_grob (orig
);
162 new_grobs
->resize (ptr
- array
);
163 if (new_arr
== grob_arr
)
164 new_arr
->set_array (*new_grobs
);
170 forall b in broken-childs:
171 forall p in properties:
172 forall g in p (if grob-list):
175 for spanners since this is O (SYSTEMCOUNT * GROBCOUNT), and SYSTEMCOUNT =
176 O (GROBCOUNT), we have a quadratic algorithm. --for a single spanner
178 This is problematic: with large (long) scores, the costs can be
179 significant; especially all-elements in System, can become huge. For
180 a typical 50 page score, it requires running through a 100k list 50
185 forall p in properties:
188 put grob list in array,
190 reorder array so spanners are separate -- O (grobcount)
192 find first and last indexes of grobs on a specific system
194 for items this is O (itemcount)
196 for spanners this is O (sum-of spanner-system-ranges)
198 perform the substitution O (sum-of spanner-system-ranges)
201 The complexity is harder to determine, but should be subquadratic;
203 For the situation above, we run through the entire 100k list once,
204 and also (more or less) once through the item part of the 100k (say
205 98k elements) of the list.
208 These timings were measured without -O2.
210 lehre, before 28.98 seconds, after: 27.91 seconds, 3.5 %.
212 coriolan, before 2:30, after: 1:59. Increase of 20%.
214 moz-k498-p1, before 24.10, after: 19.790s, Increase of 18%
218 spanner_system_range (Spanner
*sp
)
222 if (System
*st
= sp
->get_system ())
223 rv
= Slice (st
->get_rank (), st
->get_rank ());
226 if (sp
->broken_intos_
.size ())
227 rv
= Slice (sp
->broken_intos_
[0]->get_system ()->get_rank (),
228 sp
->broken_intos_
.back ()->get_system ()->get_rank ());
234 item_system_range (Item
*it
)
236 if (System
*st
= it
->get_system ())
237 return Slice (st
->get_rank (), st
->get_rank ());
243 Item
*bi
= it
->find_prebroken_piece (d
);
244 if (bi
&& bi
->get_system ())
245 sr
.add_point (bi
->get_system ()->get_rank ());
247 while (flip (&d
) != LEFT
);
253 grob_system_range (Grob
*g
)
255 if (Spanner
*s
= dynamic_cast<Spanner
*> (g
))
256 return spanner_system_range (s
);
257 else if (Item
*it
= dynamic_cast<Item
*> (g
))
258 return item_system_range (it
);
263 struct Substitution_entry
269 void set (Grob
*g
, Slice sr
)
273 duh, don't support scores with more than 32000 systems.
278 overflow if we don't treat this specially.
289 Substitution_entry ()
295 int length () { return right_
- left_
; }
297 item_compare (void const *a
, void const *b
)
299 return ((Substitution_entry
*)a
)->left_
300 - ((Substitution_entry
*)b
)->left_
;
304 spanner_compare (void const *a
, void const *b
)
306 return ((Substitution_entry
*)a
)->length ()
307 - ((Substitution_entry
*)b
)->length ();
312 Spanner::fast_substitute_grob_array (SCM sym
,
313 Grob_array
*grob_array
)
315 int len
= grob_array
->size ();
317 if (grob_array
->ordered ())
324 We store items on the left, spanners on the right in this vector.
326 FIXME: will not multithread.
328 static Substitution_entry
*vec
;
333 vec
= (Substitution_entry
*) realloc (vec
, sizeof (Substitution_entry
) * len
);
337 Slice system_range
= spanner_system_range (this);
339 int spanner_index
= len
;
342 for (vsize i
= 0; i
< grob_array
->size (); i
++)
344 Grob
*g
= grob_array
->grob (i
);
346 Slice sr
= grob_system_range (g
);
347 sr
.intersect (system_range
);
350 if (dynamic_cast<Spanner
*> (g
))
351 idx
= --spanner_index
;
352 else if (dynamic_cast<Item
*> (g
))
355 vec
[idx
].set (g
, sr
);
358 qsort (vec
, item_index
,
359 sizeof (Substitution_entry
), &Substitution_entry::item_compare
);
361 vector
<Slice
> item_indices
;
362 vector
<Slice
> spanner_indices
;
363 for (int i
= 0; i
<= system_range
.length (); i
++)
365 item_indices
.push_back (Slice (len
, 0));
366 spanner_indices
.push_back (Slice (len
, 0));
369 vector
<Slice
> *arrs
[]
371 &item_indices
, &spanner_indices
374 for (int i
= 0; i
< item_index
;i
++)
376 for (int j
= vec
[i
].left_
; j
<= vec
[i
].right_
; j
++)
377 item_indices
[j
- system_range
[LEFT
]].add_point (i
);
381 sorting vec[spanner_index.. len]
382 is a waste of time -- the staff-spanners screw up the
383 ordering, since they go across the entire score.
385 for (vsize i
= spanner_indices
.size (); i
--;)
386 spanner_indices
[i
] = Slice (spanner_index
, len
- 1);
388 assert (item_index
<= spanner_index
);
390 assert ((broken_intos_
.size () == (vsize
)system_range
.length () + 1)
391 || (broken_intos_
.empty () && system_range
.length () == 0));
392 for (vsize i
= 0; i
< broken_intos_
.size (); i
++)
394 Grob
*sc
= broken_intos_
[i
];
395 System
*l
= sc
->get_system ();
396 set_break_subsititution (l
? l
->self_scm () : SCM_UNDEFINED
);
398 SCM newval
= sc
->internal_get_object (sym
);
399 if (!unsmob_grob_array (newval
))
401 newval
= Grob_array::make_array ();
402 sc
->set_object (sym
, newval
);
405 Grob_array
*new_array
= unsmob_grob_array (newval
);
406 for (int k
= 0; k
< 2;k
++)
407 for (int j
= (*arrs
[k
])[i
][LEFT
]; j
<= (*arrs
[k
])[i
][RIGHT
]; j
++)
409 Grob
*substituted
= substitute_grob (vec
[j
].grob_
);
411 new_array
->add (substituted
);
415 printf ("%d (%d), sp %d (%d)\n",
416 item_indices
[i
].length (), item_index
,
417 spanner_indices
[i
].length (), len
-spanner_index
);
420 SCM l1
= substitute_grob_list (grob_list
);
421 assert (scm_ilength (l1
) == scm_ilength (newval
));
430 Although the substitution can be written as
432 property_alist = do_substitution (other_property_alist),
434 we have a special function here: we want to invoke a special
435 function for lists of grobs. These can be very long for large
436 orchestral scores (eg. 1M elements). do_break_substitution () can
437 recurse many levels, taking lots of stack space.
439 This becomes a problem if lily is linked against guile with
440 pthreads. pthreads impose small limits on the stack size.
443 substitute_object_alist (SCM alist
, SCM dest
)
447 for (SCM s
= alist
; scm_is_pair (s
); s
= scm_cdr (s
))
449 SCM sym
= scm_caar (s
);
450 SCM val
= scm_cdar (s
);
452 if (Grob_array
*orig
= unsmob_grob_array (val
))
454 SCM handle
= scm_assq (sym
, dest
);
456 = (scm_is_pair (handle
))
458 : Grob_array::make_array ();
460 Grob_array
*new_arr
= unsmob_grob_array (newval
);
462 substitute_grob_array (orig
, new_arr
);
466 val
= do_break_substitution (val
);
468 if (val
!= SCM_UNDEFINED
)
471 for ly:grob? properties, SCM_UNDEFINED could leak out
472 through ly:grob-property
474 *tail
= scm_cons (scm_cons (sym
, val
), SCM_EOL
);
475 tail
= SCM_CDRLOC (*tail
);
482 Spanner::substitute_one_mutable_property (SCM sym
,
487 bool fast_done
= false;
488 Grob_array
*grob_array
= unsmob_grob_array (val
);
490 fast_done
= s
->fast_substitute_grob_array (sym
, grob_array
);
493 for (vsize i
= 0; i
< s
->broken_intos_
.size (); i
++)
495 Grob
*sc
= s
->broken_intos_
[i
];
496 System
*l
= sc
->get_system ();
497 set_break_subsititution (l
? l
->self_scm () : SCM_UNDEFINED
);
501 SCM newval
= sc
->internal_get_object (sym
);
502 if (!unsmob_grob_array (newval
))
504 newval
= Grob_array::make_array ();
505 sc
->set_object (sym
, newval
);
507 substitute_grob_array (grob_array
, unsmob_grob_array (newval
));
511 SCM newval
= do_break_substitution (val
);
512 sc
->set_object (sym
, newval
);
518 Grob::substitute_object_links (SCM crit
, SCM orig
)
520 set_break_subsititution (crit
);
521 object_alist_
= substitute_object_alist (orig
, object_alist_
);