On Mon, 2 Sep 2024 16:24:33 GMT, Michael Strauß <mstra...@openjdk.org> wrote:

> Yes, it's a sizable chunk of the specification: 
> https://www.w3.org/TR/css-transitions-1/#starting

Thanks, somehow I had trouble finding it.

> The algorithm as prescribed by the specification is one of several algorithms 
> that were considered by the W3C working group back in 2013. It is clearly 
> tuned for two-state application scenarios, and might simply not be the right 
> solution for your problem. I've thought a lot about the problem, and while it 
> may be possible to come up with an algorithm that covers more edge cases, 
> that takes a huge toll on the complexity/maintainability budget.

Alright, if CSS specifies it like this, then there's little point discussing 
improvements here.

> I don't think this would work at all, because you're setting the value 
> programmatically. You'll only get a transition if you let the CSS system set 
> the value. If you do this, and then query the value of the property under 
> transition, you will get an intermediate value. This simply wouldn't work 
> otherwise, considering how JavaFX properties work. We can't have properties 
> change their value over time, but when you look at them programmatically, 
> they already have the target value.

Yeah, I starting seeing this halfway through my review and playing with the 
code.  It just doesn't apply to programmatically set values at all; my own work 
on throttled and animated bindings may have contributed to the confusion.

> Yes, having an adapter between the styleable property implementation and the 
> transition timer was an improvement that [you 
> suggested](https://github.com/openjdk/jfx/pull/870#discussion_r1392551399); 
> it seems like we've come full circle. Sure, clearing the mediator reference 
> is not great, but it is a tiny and isolated part (as in, this implementation 
> detail doesn't leak out of the class in which it is used). I wouldn't call 
> this a serious problem of the design.

It's been a while, and I didn't even remember suggesting that anymore, so 
imagine my surprise when I see my own suggestion reflected in 
`TransitionMediator`.

My responses are usually triggered because of something in the solution that I 
find confusing or overly complicated. What I didn't like in the original was 
the subclassing and passing in that subclass to a static method of the same 
class. What I'm not liking in the current one is the married nature of the two 
classes, where a mediator is both tracked and passed to the timer.  The 
conditional cancellation and reference clearing logic are the primary things 
that trigger me here, but also the new reversing logic where a mediator is 
passed in, then casted.

I'm just making suggestions for you to think about (after spending hours 
looking at the code and trying to follow its logic).  The suggestions usually 
originate from something in the code that is hard to follow or doing things in 
a seemingly roundabout way.  They're suggestions as I may not be seeing all the 
edge cases, and you should be able to see much better than I if these 
suggestions have merit.  I didn't quite see the conditional way you are 
clearing a reference in the previous PR (I may have missed it, or perhaps I 
didn't review it again afterwards).

I still think there's room for improvement, which perhaps will allow you to 
eliminate some of the duplication and confusing parts:

1. You can have `TransitionTimer.run` return an object for cancellation.  This 
is IMHO nicer than tracking an object you passed to it.  What I'm further 
disliking here is that it returns a timer, which is already started, and which 
you're not supposed to start/stop, and which has an extra method `cancel` 
(which differs from `stop`).  This would be better separated, either by hiding 
its `AnimationTimer` nature (by using composition) or by not returning a full 
timer.  The opposite is also possible (ie. do expose its `AnimationTimer` 
nature, but then don't auto start it; have the caller start it.  Use `stop` for 
cancels, not have two methods for similar purposes).

2. You can move the check if a timer should be started based on the target 
value to `TransitionTimer.run`; if the end value is the same as the timer is 
already aware off then have it return the existing timer.  Callers of 
`TransitionTimer.run` could look something like:

            this.timer = TransitionTimer.run(
                this,
                newValue,
                this::callback,
                transitionDefinition,
                Toolkit.getToolkit().getPrimaryTimer().nanos()
            );

To find the startValue it can call `getValue`.  For component transitionables, 
`equals` implementation should do a deep equals.

Motivation: eliminates some of the code duplication in the property 
implementations and leaves the decision whether a new timer is started (or 
perhaps even internally re-purposed) up to `TransitionTimer`.

3. You can avoid the `cancel(false)` by calling `super.set` from a method other 
than `set`.  
 
For example:

      private void callback(double progress, double startValue, double 
endValue) {
         super.set(progress < 1 ? startValue + (endValue - startValue) * 
progress : endValue);
 
         if (progress >= 1) {
             timer = null;
         }
      }

You can now always clear the timer reference in `set` and `bind`:

     private void clearTransition() {
         origin = StyleOrigin.USER;

         if (timer != null) {
             timer .cancel(true);
             timer = null;
         }
     }

Motivation: timer management is a bit more compact and clear.  Timers always 
get cancelled and cleared in `set` and `bind`, and the callback will know 
exactly when the timer has served its purpose.  No need to do a reference 
equality check to conditionally clear the reference even for reversals.

4. Reversing should be determinable now within `TransitionTimer.run` instead of 
needing a custom implementation for each type.  I think you may be able to 
avoid `deepEquals`, or have the wrapper around multiple properties handle this 
in its own `equals`.

Motivation: removes ugly casting in `updateReversingAdjustedStartValue` 

5. Not a point of improvement, but something that I think is poorly implemented 
currently in FX (similar to a post from a few weeks ago on the mailinglist).  I 
see that you are passing in the nano time each time, and it seems we may have 
no choice to do this because `Toolkit.getToolkit().getPrimaryTimer().nanos()` 
is not returning a constant value within the scope of a single pulse.  This is 
IMHO a serious oversight; what's the point of adding a random number of nanos 
depending on the exact time it is called during a pulse.  This should have been 
constant, in which case the nanos could simply be obtained by 
`TransitionTimer.run` instead of having it passed in each time.

You could consider providing an overloaded variant where the nanos are optional 
to avoid the ugly `Toolkit.getToolkit().getPrimaryTimer().nanos()` incantation 
except where you have no choice.
 
> As for your ideas about refactoring the code to get rid of 
> `TransitionMediator`, I'd like to understand a bit more context because this 
> seems pretty random to me. Sure, I can imagine any number of implementations 
> that would do the job, but what is your main motivation for rejecting the 
> current implementation in favor for another approach? The purpose of 
> `TransitionMediator` and `TransitionTimer` seem sufficiently clear-cut to me, 
> and the implementation is reasonably clean given the complex problem being 
> solved. I don't see an _obvious_ shortcoming of the current implementation.

They're only suggestions (or ideas as you call them).  IMHO you as the author 
know best if there is merit in these suggestions.  Perhaps a suggestion 
contains something you didn't think of allowing you to simplify the code a bit 
further.  I've tried adding my reasoning now.

As for the current implementation, I think it works, but it is a bit hard to 
see if all edge cases are covered.

I did notice one thing while experimenting with the code that you may want to 
address.  `StyleableProperty_transition_Test` doesn't seem to care for reversal 
logic.  If I mess it up (always return `false` for one of the properties), the 
test still passes.

-------------

PR Comment: https://git.openjdk.org/jfx/pull/1522#issuecomment-2325475048

Reply via email to