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

bug with Tween.is_active, fixes #39760 #39801

Merged
merged 1 commit into from
Jul 20, 2020

Conversation

ChristopheLY
Copy link
Contributor

@ChristopheLY ChristopheLY commented Jun 24, 2020

Fixes #39760

@@ -713,6 +713,7 @@ void Tween::_tween_process(float p_delta) {

// Is the data not active or already finished? No need to go any further
if (!data.active || data.finish) {
all_finished = true;
Copy link
Member

Choose a reason for hiding this comment

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

The fix would be to fix the logic for all_finished on line 716, not hack it here, possibly losing information about a previous data which was active and not finished (so this would introduce a bug IMO).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it better to make this ?
all_finished = !data.active || data.finish;

Copy link
Member

Choose a reason for hiding this comment

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

No, that would erase the value from previous iterations.
It should be (on line 712):

all_finished = all_finished && (data.finish || !data.active)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but all_finished is false at this moment and it's because it false that set_active(false) and emit_signal("tween_all_completed") was not sent (line 816)

Copy link
Member

Choose a reason for hiding this comment

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

In your test project, sure. But if could be true if the previous iteration was true.
Its purpose is to check that all iterations are finished (or, as you're trying to change, inactive). If any iteration is active and not finished, it should end up false when the loop terminates.

Copy link
Member

Choose a reason for hiding this comment

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

After the last amend we're back to the initial state, which is wrong. See #39801 (comment)

@Calinou Calinou added bug cherrypick:3.x Considered for cherry-picking into a future 3.x release topic:core labels Jun 25, 2020
@Calinou Calinou added this to the 4.0 milestone Jun 25, 2020
@@ -713,6 +713,7 @@ void Tween::_tween_process(float p_delta) {

// Is the data not active or already finished? No need to go any further
if (!data.active || data.finish) {
all_finished = all_finished && (!data.active || data.finish);
Copy link

Choose a reason for hiding this comment

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

this line is useless : second part &&... is true by inclosing test ; then 'all_finished' is not changed by this line of code ; better suppress it if logic is good (do not know)

Copy link

Choose a reason for hiding this comment

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

all_finished = true; is simpler and forces state as well


// Is the data not active or already finished? No need to go any further
if (!data.active || data.finish) {
all_finished = true;
Copy link
Member

@akien-mga akien-mga Jul 20, 2020

Choose a reason for hiding this comment

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

It still doesn't seem correct to me. Read the comments and the code again and think about how this algorithm is meant to work:

  • all_finished is assumed true initially.
  • For each tween, we check if it's either inactive (!data.active) or finished (data.finish) to confirm if all_finished is still true (if the tween is active or not finished, then all_finished must become false. It must then stay false regardless of the state of future tweens, as at least one Tween is not finished.
  • If the current tween is finished, we skip to the next.

Your current code will make it so that as regardless of previous values of all_finished, the variable with be set to true any time the iterated tween is finished (so missed the point of the all_ in the variable name).

Unroll the code on paper if it helps. For the sake of simplicity, let's assumed "finished" means !data.active || data.finish, so either !active or finish is true.
Here's what your current PR does (two values for all_finished in the loop for each assignment (all_finished = all_finished && (!data.active || data.finish); first and all_finished = true; next, if finished):

Step Tween state all_finished
Before loop --- true
Tween 0 Finished true / true
Tween 1 Not finished false / ---
Tween 2 Finished false / true
After loop --- true

The Tween will be deactivated and emit tween_all_completed even though Tween 1 is not finished.


// Is the data not active or already finished? No need to go any further
if (!data.active || data.finish) {
if (all_finished) {
Copy link
Member

Choose a reason for hiding this comment

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

That's better, but still incorrect. This will now mean that if any previous Tween was unfinished, all_finished will become and stay false (which is good), but subsequent Tweens which are actually finished won't use the early continue, leading to unnecessary computation.

Here's the right fix:

diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 854db5fee2..d45fc7dd07 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -709,7 +709,7 @@ void Tween::_tween_process(float p_delta) {
                InterpolateData &data = E->get();
 
                // Track if we hit one that isn't finished yet
-               all_finished = all_finished && data.finish;
+               all_finished = all_finished && (!data.active || data.finish);
 
                // Is the data not active or already finished? No need to go any further
                if (!data.active || data.finish) {

@akien-mga
Copy link
Member

akien-mga commented Jul 20, 2020

Here's another option which might be better and fixes a possible extra bug in that else if (!repeat) branch, but I haven't tested so I can't say if it works as I think it should:

diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 854db5fee2..53dacbadbc 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -701,21 +701,21 @@ void Tween::_tween_process(float p_delta) {
 	}
 
 	// Are all of the tweens complete?
-	bool all_finished = true;
+	int any_unfinished = 0;
 
 	// For each tween we wish to interpolate...
 	for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
 		// Get the data from it
 		InterpolateData &data = E->get();
 
-		// Track if we hit one that isn't finished yet
-		all_finished = all_finished && data.finish;
-
 		// Is the data not active or already finished? No need to go any further
 		if (!data.active || data.finish) {
 			continue;
 		}
 
+		// Track if we hit one that isn't finished yet
+		any_unfinished++;
+
 		// Get the target object for this interpolation
 		Object *object = ObjectDB::get_instance(data.id);
 		if (object == nullptr) {
@@ -802,17 +802,15 @@ void Tween::_tween_process(float p_delta) {
 			// If we are not repeating the tween, remove it
 			if (!repeat) {
 				call_deferred("_remove_by_uid", data.uid);
+				any_unfinished--;
 			}
-		} else if (!repeat) {
-			// Check whether all tweens are finished
-			all_finished = all_finished && data.finish;
 		}
 	}
 	// One less update left to go
 	pending_update--;
 
 	// If all tweens are completed, we no longer need to be active
-	if (all_finished) {
+	if (any_unfinished == 0) {
 		set_active(false);
 		emit_signal("tween_all_completed");
 	}

@ChristopheLY
Copy link
Contributor Author

Should I apply the other option ?
It's work, like the last one

@akien-mga
Copy link
Member

I guess it might be safe enough, and easier to understand, yeah.

@ChristopheLY ChristopheLY force-pushed the tween-bool-state branch 2 times, most recently from c60b036 to b7b6c06 Compare July 20, 2020 17:31
@akien-mga akien-mga merged commit 80a8a36 into godotengine:master Jul 20, 2020
@akien-mga
Copy link
Member

Thanks!

@akien-mga
Copy link
Member

Cherry-picked for 3.2.3.

@akien-mga akien-mga removed the cherrypick:3.x Considered for cherry-picking into a future 3.x release label Jul 24, 2020
@akien-mga
Copy link
Member

I reverted the 3.2.3 cherry-pick due to the regression #40679.

Once the regression is solved in the master branch, we can look into cherry-pick this with the regression fix again.

@akien-mga akien-mga added the cherrypick:3.x Considered for cherry-picking into a future 3.x release label Aug 25, 2020
akien-mga added a commit that referenced this pull request Mar 3, 2021
Fix no tween repeat after stop and restart (Fix #39801)
@akien-mga
Copy link
Member

Cherry-picked/backported with regression fixed via #47142.

@akien-mga akien-mga removed the cherrypick:3.x Considered for cherry-picking into a future 3.x release label Jun 3, 2021
akien-mga pushed a commit that referenced this pull request Jun 3, 2021
Fix #39760 & #39801

These issues were resolved in master branch (and closed) but are still active in the 3.2 branch.
akien-mga added a commit that referenced this pull request Jun 3, 2021
…-after-stop-and-start

[3.x] Fix Tween.is_active() always true after stop() and then start() (Fix #39760 & #39801)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tween.is_active() always true after tween.stop() and then tween.start()
4 participants