Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: New AlternatePattern argument for SequenceEffect #3322

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

TheMaverickProgrammer
Copy link
Contributor

@TheMaverickProgrammer TheMaverickProgrammer commented Sep 24, 2024

  • This PR fixes math in MoveToEffect so that negative progress did not reflect the direction of the delta offset vec.
  • Corrected progress getter overload in SequenceEffect to correctly map to the progress when _index < 0.
  • Added onStart() in recede() initial condition so that effects like MoveToEffect can recalculate their initial data.
  • Removed unnecessary math in DurationEffectController to avoid potential negative zeros.
  • Introduced a new type AlternatePattern which allows users to include or exclude the last effect in the sequence when the pattern reverses. NOTE: When playing forward, the last effect is List.last, but in reverse it is List.first.
  • Default value is now AlternatePattern.repeatLast.
  • Made the TiledGame example project more aesthetically interesting.
    • The camera now pans to all 4 corners WRT the bounds of the loaded map and uses the new AlternatePattern feature along with all the new fixes.
  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • I have updated/added relevant examples in examples or docs.

TO DISCUSS

  1. I have skipped the test checkbox until I had a review and we could discuss these changes.
  2. We may need to consider an onEnd() for Effects. Or provide onStart() with an initial progress value to calculate from.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

I looked but didn't immediately see anyone reporting these issues. I discovered them myself.

…the direction of the delta offset vec. Corrected progress getter overload in SequenceEffect to correctly map to the progess when _index < 0. Added onStart() in the initial conidition of ecede() so that effects like MoveEffect can recalculate their initial data. Removed unnecessary math in DurationEffectController to avoid potential negative zeros. Replaced �lternate boolean for SequenceEffect with a new type AlternatePattern which allows users to include or exlude the last effect in the sequence when the pattern reverses. Default value is now AlternatePattern.none. Updated all SequenceEffect examples and tests to reflect changes. Made the TiledGame example project more aesthetically interesting. The camera now pans to all 4 corners WRT the bounds of the loaded map and uses the new AlternatePattern feature along with all the new fixes.
@TheMaverickProgrammer TheMaverickProgrammer changed the title feature!: AlternatePattern for SequenceEffect. fix: bad math in MoveToEffect. feature!: AlternatePattern for SequenceEffect (and fix wrong math in MoveToEffect). Sep 24, 2024
@TheMaverickProgrammer TheMaverickProgrammer changed the title feature!: AlternatePattern for SequenceEffect (and fix wrong math in MoveToEffect). feat!: AlternatePattern for SequenceEffect (and fix wrong math in MoveToEffect). Sep 24, 2024
Copy link
Member

@spydon spydon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, it needs tests and docs though.
EDIT: Ah you already wrote that in the PR description.

examples/lib/stories/effects/sequence_effect_example.dart Outdated Show resolved Hide resolved
@@ -40,7 +40,7 @@ class MoveToEffect extends MoveEffect {
@override
void apply(double progress) {
final dProgress = progress - previousProgress;
target.position += _offset * dProgress;
target.position += _offset * dProgress.abs();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, is this really correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about it some more yesterday I think the issue wasn't the reflected vector itself, but rather that the direction vector was incorrect when the camera has a viewport-aware bounds behavior. This causes it to never reach its destination and so the reflective vector is incorrect. That's why taking the absolute value was ok after I added the step to recalculate the vector.

The camera won't be the only component that may never complete its effect with regards to MoveToEffect so this matter should be resolved in the PR imo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this will potentially overshoot the destination right? And also it won't be matching the time anymore?
If you are at 0.99 progress and the delta progress is -0.02 when the effect is going forward it should have moved 0.03 "steps", since it'll start going back again, but with this it will simply move to 1.01.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I'm understanding your question precisely, but it seems that you've noticed the possibility of under-fitting the expected step amounts, time-wise. Yes this does happen with the viewport-aware behavior as described (the camera cannot exceed the bounds, and therefore never arrives to its destination). ATM with the changes I've added, I have not seen any noticeable problems.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simply put; I don't think it will always be at the expected place at each time step when abs is used, that was what I tried to describe in the last comment, the delta doesn't become correct. If it overshoots its destination it should start going back, in the cases where it is alternating.

Copy link
Contributor Author

@TheMaverickProgrammer TheMaverickProgrammer Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

progress will be decrementing dt units from 1.0 during the reverse phase of the alternating pattern, while previousProgress retains the progress+dt value from before. Therefore dProgress = progress - previousProgress would be negative, which we don't want to use. It's better to recalculate the starting position and use positive scalars instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it'll surely overshoot the end value then?

Copy link
Contributor Author

@TheMaverickProgrammer TheMaverickProgrammer Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi. No it never does for this effect or any as far as I can tell. The same math could have overshot in the reverse direction when dProgress was negative.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case that dProgress becomes negative it is supposed to go backwards. Say that the previous progress is 0.9 and it it supposed to move backwards to 0.8, then dProgress should be -0.1, not 0.1.
Try running only the the MoveToEffect with alternate: true.

Copy link
Contributor Author

@TheMaverickProgrammer TheMaverickProgrammer Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi. A negative progress value for this effect specifically is not only unintuitive but also does not solve the MoveToEffect starting vector issue.

enum AlternatePattern {
none(0),
includeLast(1),
excludeLast(2);
Copy link
Member

@spydon spydon Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If infinite (or N runs) is used together with alternate, shouldn't the first also be excluded on runs that are after the first and not the last?

Copy link
Contributor Author

@TheMaverickProgrammer TheMaverickProgrammer Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It already behaved this way. Only the last one was ever being repeated.

Copy link
Contributor Author

@TheMaverickProgrammer TheMaverickProgrammer Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've corrected the default pattern behavior. The alternating pattern now plays both the first and last Effect elements in the list, instead of just the last element as it did before this PR. This default behavior is AlternatePattern.repeatLast. The term "last" used here is relative to the playback direction.

To avoid playing first and last Effects twice-in-a-row when the sequence reverses, AlternatePattern.doNotRepeatLast is appropriate.

@spydon spydon changed the title feat!: AlternatePattern for SequenceEffect (and fix wrong math in MoveToEffect). feat!: AlternatePattern for SequenceEffect (and fix wrong math in MoveToEffect) Sep 25, 2024
@spydon spydon changed the title feat!: AlternatePattern for SequenceEffect (and fix wrong math in MoveToEffect) feat!: New AlternatePattern argument for SequenceEffect Sep 25, 2024
@TheMaverickProgrammer TheMaverickProgrammer marked this pull request as draft September 25, 2024 18:06
@TheMaverickProgrammer
Copy link
Contributor Author

There's still the topic to discuss: I'm in favor of providing onStart() with an initial progress value to calculate the starting conditions from since MoveEffect can be affected by bounds checks.

if (alternate) {
totalDuration *= 2;

if (alternatePattern == AlternatePattern.doNotRepeatLast) {
totalDuration -= effects.first.controller.duration ?? 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come the first one is removed here too? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi. This has been explained in the PR, code comments, and here:
#3322 (comment)

@@ -136,7 +197,7 @@ class _SequenceEffectEffectController extends EffectController {
}

@override
double get progress => (_index + 1) / n;
double get progress => (_index < 0 ? -_index : _index + 1) / n;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this a bit? In what cases is the _index negative? This doesn't seem like it would follow how progress works for the rest of the effects.

Copy link
Contributor Author

@TheMaverickProgrammer TheMaverickProgrammer Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way the SequenceEffect has been written for some time now is that it uses a negative _index to determine if the pattern is in its reverse phase. To be clear, I did not write it this way. This code was like this when I started this PR,

@@ -40,7 +40,7 @@ class MoveToEffect extends MoveEffect {
@override
void apply(double progress) {
final dProgress = progress - previousProgress;
target.position += _offset * dProgress;
target.position += _offset * dProgress.abs();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case that dProgress becomes negative it is supposed to go backwards. Say that the previous progress is 0.9 and it it supposed to move backwards to 0.8, then dProgress should be -0.1, not 0.1.
Try running only the the MoveToEffect with alternate: true.

@TheMaverickProgrammer
Copy link
Contributor Author

TheMaverickProgrammer commented Nov 7, 2024

@spydon

In the case that dProgress becomes negative it is supposed to go backwards. Say that the previous progress is 0.9 and it it supposed to move backwards to 0.8, then dProgress should be -0.1, not 0.1.
Try running only the the MoveToEffect with alternate: true.

Please try running the tiled_example/lib/main.dart example with from my PR and compare it with the same example in upstream after adding the ViewportAwareBounds to the camera in the upstream version. From our conversation, this issue seems to be missed and it's crucial to understanding the problem that is being addressed with the addition to onStart() in the recede() function and using a non-negative duration value.

@spydon
Copy link
Member

spydon commented Nov 7, 2024

I'll give it a proper try when I'm back home again next week!

@spydon
Copy link
Member

spydon commented Dec 10, 2024

@TheMaverickProgrammer I haven't had the time to test it properly yet unfortunately (forgot that it was on my table 😅), but there is a test failing, could you have a look at that first?

 test/effects/sequence_effect_test.dart 271:9

@TheMaverickProgrammer
Copy link
Contributor Author

@TheMaverickProgrammer I haven't had the time to test it properly yet unfortunately (forgot that it was on my table 😅), but there is a test failing, could you have a look at that first?

 test/effects/sequence_effect_test.dart 271:9

The test failing is related to the issue at hand. I have thought a lot about it and can propose a quick solution but it's not ideal: There can be a MoveTo effect and a MoveToWithCorrection effect. The latter can be used for components that may not arrive at their destination and need to be adjusted when the effect begins. This new effect would update its deltas every time onStart() is invoked (see PR for the additional callsite for onStart()) and the former effect would simply cache its delta the first time onStart() is ran. This way the test can pass and then this MoveToWithCorrection component can be used on components like the Camera when ViewportAwareBounds behavior is used.

@spydon
Copy link
Member

spydon commented Dec 11, 2024

There can be a MoveTo effect and a MoveToWithCorrection effect.

Hmm, I'm not too fond of that idea, it feels like a less clean API.
How come that it currently passes the test? I didn't think MoveToEffect had any correction built in to it, since it wasn't designed to stack with multiple effects of the same type (like MoveByEffect is)?

@TheMaverickProgrammer
Copy link
Contributor Author

There can be a MoveTo effect and a MoveToWithCorrection effect.

Hmm, I'm not too fond of that idea, it feels like a less clean API. How come that it currently passes the test? I didn't think MoveToEffect had any correction built in to it, since it wasn't designed to stack with multiple effects of the same type (like MoveByEffect is)?

Because in the PR I said I added a new callsite to onStart() for effects in order to allow MoveTo effect to recalculate its starting position whenever it was unable to ensure its component arrived to its destination. This edge case happens in the camera with viewport aware bounds behavior but can potentially happen for other effects/behaviors that dictate a component's position.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants