On 26 sept. 2012, at 17:38, Joe Neeman <joenee...@gmail.com> wrote:

> On Wed, Sep 26, 2012 at 4:15 AM, m...@mikesolomon.org <m...@mikesolomon.org> 
> wrote:
> Hey all,
> 
> As was the case in a few of my previous projects, before I start something 
> new I make architecture changes that facilitate my work.  Working on 2801, 
> I've realized that any multi-pass algorithm for the spacing of grobs is 
> difficult because results of callback calculations are always cached.  So 
> triggering callbacks a second time is, in the current architecture, 
> impractical and requires a fair bit of kludgery.
> 
> Are you proposing not to cache callbacks at all? That sounds like a real 
> performance killer to me; we would probably end up re-typesetting each beam 
> hundreds of times.

No, I'm proposing to find way to mark things as dirty so that they are 
recalculated.

> 
> By the way, the problem is not only in the caching of callbacks; there are 
> also many other places with side effects (calls to set_property, 
> translate_axis, suicide, etc).
> 

Exactly - the proposal here is to get rid of the call to translate_axis in 
axis-group-interface.cc, which is the biggest side effect in the code base in 
terms of the number and variety of grobs affected.  This'll make it easier to 
mark things as dirty so that multiple passes can happen.

> To start this process, I'd like to expand the role of the 
> side-position-interface significantly.  All grobs would use this interface 
> for their X and Y offsets and they would have two different grob arrays for X 
> and Y side-position-elements.  This would allow me to eliminate two things:
> 
> 1) parents.  Grobs are X/Y aligned to parents, but these parents could be 
> elements in the side-position-elements array.  Functions like get_parent, 
> which could be kept around for legacy reasons, could return the first element 
> of these arrays for a given axis and raise a warning if there are multiple 
> elements.
> 
> If you get rid of unique parents, what would X-offset mean? Would it be 
> relative to the System? The first element of side-position-elements array? 
> 

Relative to the skyline of the elements of the side-position-elements array.

> 2) outside-staff-priority.  Saying a grob has outside staff priority is the 
> same thing as saying that a grob has as its Y side support elements all 
> inside-staff grobs plus all grobs with lower outside staff priority.  A 
> callback creating the side-position-elements arrays could do the sorting that 
> takes place in Axis_group_interface::skyline_spacing.
> 
> I think you'll have trouble making this fully callbacky/functional. For 
> example, we lay out the outside-staff grobs in a very particular order (the 
> left-to-right layout thing), which we don't know ahead of time. That is, 
> until we get _all_ of the outside-staff grobs together, we don't know which 
> grobs have to depend on which other grobs. That means it isn't really 
> possible for each grob to determine its own position; there needs to be one 
> grob that lays them out simultaneously. The same issue will come up, I think 
> in laying out Accidentals and VerticalAxisGroups.
> 

You're right that, in general, any time that a collecting grob does some type 
of organizing, this type of problem will come up.  However, there are ways 
around it.  For example, a grob can get the elements array from its vertical 
alignment or system, sort it from left to right, and trigger callbacks for all 
grobs to the left.  This avoids an overarching positioning grob while still 
achieving the same effect.

> At some point, I had grand plans to formalize this problem by having two 
> distinct kinds of properties: one that is user-overridable using callbacks, 
> and one that is internal and gets set by other grobs as a side-effect. 
> Everything would have explicit dependencies so that caches could be 
> invalidated. I even started to write a proof-of-concept in scala, but I never 
> finished it...

My thinking is that in order to make dependencies easier to handle and 
maintain, we need to reduce the number of side effects like translate_axis and 
set_property.

> 
> 
> The benefits of this are twofold:
> 
> 1) All work on a multi-pass algorithm could be done with respect to 
> side-positioning, making it simpler to find bugs and modify code.
> 
> Not all positioning is side-positioning.

True - my goal is to see if all positioning can be articulated this way.  For 
example, ScriptColumn could have used translate_axis, but whoever wrote it made 
the good decision to use side-position-interface.  I think this grob could be 
eliminated entirely if all scripts in a script column had a function that 
calculated the side-position-elements grob-array using the sorting algorithm at 
the heart of script-column.cc.

Another way to think of this is that, in general, we'd try to eliminate grobs 
like NoteColumn, ScriptColumn, DynamicLineSpanner, etc that only do 
traffic-coppery.  Or, inversely, we'd say that positioning only happens via 
traffic-cop grobs and no grob has the right to position itself.  But it seems 
like we need to pick one.

> 
> 2) A call to translate_axis in avoid_outside_staff_collisions would be 
> eliminated, and translate_axis is bad: it goes against the callback model at 
> the core of LilyPond layout.
> 
> Agreed, but this is just one instance of translate_axis, which is just one 
> example of a side-effect in lilypond. Is your eventual goal to remove them 
> all?

Yes - that'd be great.  That'll make explicit dependencies a lot easier to 
handle - everything can be calculated in terms of callbacks.

> 
> The disadvantage is that this results in the construction of many more 
> skylines (one for each call to 
> Side_position_interface::skyline_side_position).  How this will impact 
> performance is uncertain, but it'd probably require more optimization in 
> skyline.cc and/or caching of skylines.
> 
> I would suggest that you completely ignore performance in favor of a clean 
> design. Performance can come later.

I agree.

I haven't been in LilyPond long enough to know how conceptual models have 
evolved over the years, but I think that there are two competing models in 
spacing and we need to choose one.  Either there are collection grobs that 
space their elements (like NoteCollision) or grobs that determine their own 
spacing from a collection of grobs (like Script).  I am leaning towards the 
latter because it seems less prone to error.  In the former, if a grob is 
placed under the auspices of two Y-axis positioning grobs, we do not know which 
one take precedence.  In the latter, grobs control their own spacing, so there 
is no doubt.

To keep the main thing the main thing, the goal is to be able to wipe certain 
caches, restore the original callbacks, and know that all grobs properties that 
depended on the restored property will also be recalculated.  My idea is an 
idea for simplifying LilyPond so that this work is easier, but there is no 
point in doing that if it won't help reach that goal.  I'm open to any and all 
suggestions.

Cheers,
MS
_______________________________________________
lilypond-devel mailing list
lilypond-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/lilypond-devel

Reply via email to