On Sun, 4 Aug 2024 23:01:48 GMT, John Hendrikx <[email protected]> wrote:
>> In practice, any list that is passed into this method is already
>> unmodifiable. This isn't a general-purpose method, it's tailored towards its
>> usage for `Border` and `Background` interpolation, and the optimization
>> scheme that is used in these classes.
>
> Alright, I get how it works now, and that there is a lot of reference
> equality checking going on by callers.
Let me explain how I got here in the first place. My observation is that
`Background` and `Border` graphs can be quite large, and they are deeply
immutable. But often, only tiny parts of it change during a transition. So my
question was: wouldn't it be nice if we could re-use the unmodified parts of
the graph, instead of copying it entirely, potentially tens of thousands of
times per second?
The `Interpolatable` contract gives us permission to return an existing
instance (and thereby reusing unmodified parts of the graph), so that's good.
An initial implementation might look like this (note: this is not the
implementation of this PR):
public Border interpolate(Border endValue, double t) {
Objects.requireNonNull(endValue, "endValue cannot be null");
1: if (t <= 0 || equals(endValue)) {
return this;
}
if (t >= 1) {
return endValue;
}
List<BorderImage> newImages =
InterpolationUtils.interpolateListsPairwise(images, endValue.images, t);
List<BorderStroke> newStrokes =
InterpolationUtils.interpolateListsPairwise(strokes, endValue.strokes,
t);
2: if (this.images.equals(newImages) && this.strokes.equals(newStrokes)) {
return this;
}
3: if (endValue.images.equals(newImages) &&
endValue.strokes.equals(newStrokes)) {
return endValue;
}
return new Border(newStrokes, newImages);
}
I've marked three important lines with `1:` to `3:`. These equality comparisons
traverse through the object graph many times, and unnecessarily so: each of the
sub-objects' `interpolate` method will do the same thing, comparing the same
objects recursively again.
So instead of doing a "root to leaf" equality comparison (i.e. starting at
`Border` and comparing upwards), I figured that a "leaf to root" comparison
would be much simpler in terms of the raw amount of computation required. This
reduces a complete sub-graph traversal to a simple identity comparison (but
requires cooperating `interpolate()` implementations).
I didn't benchmark whether the time spent on naively copying the entire object
graph, or doing the "root to leaf" equality comparison amounts to a significant
share of computation, or when it starts becoming a problem (is it hundreds or
tens of thousands of transitions?). But I do think that the approach is sound.
At this time, I am not inclined to change the implementation, but to improve
the code documentation to make it easier to understand what's going on.
-------------
PR Review Comment: https://git.openjdk.org/jfx/pull/1522#discussion_r1703420442