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

Allow Tween to accept Arrays & Packed*Arrays #66771

Closed
wants to merge 1 commit into from

Conversation

Mickeon
Copy link
Contributor

@Mickeon Mickeon commented Oct 2, 2022

See this discussion godotengine/godot-proposals#5501.

This PR allows Arrays, but most especially Packed*Arrays to be tweened with a single tween_property() call. For example:

PackedColorArray PackedVector2Array
Showcase Aaa
Code Code
PackedStringArray
Showcase
Code

Interpolation fails if the starting array and the new array do not have the same size (this behavior can be further discussed).

By extension, it also fixes a crash that would occur when passing any Packed*Array.
It seemed like the internal Animation.interpolate_variant() had partial support for interpolating these arrays, already. There just was never any occasions to put the gears to work, so to speak. In fact, the previous casting was also kind of botched, as it would simply return nothing and later crash.

@KoBeWi KoBeWi added this to the 4.x milestone Oct 2, 2022
@TokageItLab
Copy link
Member

TokageItLab commented Oct 2, 2022

Animation::interpolate_variant() already supports PackedArrays Not only Color, but also some types. You can apply it to other calc methods for variant with that as a reference.

@Mickeon
Copy link
Contributor Author

Mickeon commented Oct 2, 2022

@TokageItLab Let me reiterate, the previous support in interpolate_variant() seemed to be flawed, at least to me. The Variant casting would fail, returning a NUL pointer, and the engine would subsequently crash. Copying and pasting similar code to all of the functions necessary for this has previously lead to the same result.

@TokageItLab
Copy link
Member

Fixing bugs is fine, but I would like to see it applied to more than just Color for consistency.

@Mickeon
Copy link
Contributor Author

Mickeon commented Oct 2, 2022

Yep, indeed, of course, it's a draft for that reason.

@Halfmania33
Copy link

@Mickeon Oh what a good surprise !! Thank you for trying to implement this feature ! I'm looking forward to seeing the end result !!!

@TokageItLab TokageItLab requested a review from a team October 2, 2022 19:33
@Mickeon
Copy link
Contributor Author

Mickeon commented Oct 3, 2022

Updated this PR to support all Packed*Arrays and even Arrays, as well. However, I really, really need a technical review, if that may asked, because I am afraid that the way I avoided to write duplicate code, by converting every Packed*Array, is way too slow.

I tried using templates, and that would work, actually, but it'd still duplicate code in a way that felt unnecessarily bloated to me.

To keep it in mind, open this to see the alternative method.
if (a.is_array() && a.get_type() != Variant::ARRAY) {
switch (a.get_type()) {
	case Variant::PACKED_COLOR_ARRAY: {
		return interpolate_packed_array(PackedColorArray(a), PackedColorArray(b), c);
	}
	case Variant::PACKED_VECTOR2_ARRAY: {
		return interpolate_packed_array(PackedVector2Array(a), PackedVector2Array(b), c);
	}
}
				
template <class T>
Vector<T> Animation::interpolate_packed_array(Vector<T> a, Vector<T> b, float c) {
	int size = a.size();
	//WARN_PRINT(vformat("%d, %d", a.size(), b.size())); 
	if (size == 0 || b.size() != size) {
		return a;
	} else {
		Vector<T> result;
		result.resize(size);

		T *result_ptr = result.ptrw();
		const T *a_ptr = a.ptr();
		const T *b_ptr = b.ptr();

		for (int i = 0; i < size; i++) {
			result_ptr[i] = interpolate_variant(a_ptr[i], b_ptr[i], c);
		}
		//WARN_PRINT(vformat("%s, %s", a[0], result[0]));
		return result;
	}
}

@Mickeon Mickeon changed the title Allow Tween to accept Packed*Arrays Allow Tween to accept Arrays & Packed*Arrays Oct 3, 2022
@Mickeon Mickeon marked this pull request as ready for review October 3, 2022 14:46
@TokageItLab
Copy link
Member

TokageItLab commented Oct 3, 2022

That implementation would result in processing a large number of unnecessary switch/case statements (but I understand that this is better than the current status that requires a for loop externally), so other approaches seem better IMO. Are @akien-mga or @reduz familiar with this area?

@kleonc
Copy link
Member

kleonc commented Oct 3, 2022

I think the current impementation is not really acceptable, there should be no conversions when not needed.

  1. When interpolating packed arrays of the same type the result should be of the same type and there should be no conversions to generic arrays whatsoever (it's inefficient).
  2. Then there's a question what should be the behavior for e.g. PackedInt64Array and PackedFloat64Array args as there's this part in Animation::interpolate_variant allowing interpolating any number types:
    Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float c) {
    if (a.get_type() != b.get_type()) {
    if (a.is_num() && b.is_num()) {
    real_t va = a;
    real_t vb = b;
    return va + (vb - va) * c;
    }
    return a;
    }

    Also note that currently there's no simliar parts in Animation::add_variant/Animation::subtract_variant so this inconsistency should probably be addressed too (probably out of this PR's scope):
    Variant Animation::add_variant(const Variant &a, const Variant &b) {
    if (a.get_type() != b.get_type()) {
    return a;
    }

    Variant Animation::subtract_variant(const Variant &a, const Variant &b) {
    if (a.get_type() != b.get_type()) {
    return a;
    }
  3. Then typed arrays should also be respected. When interpolating e.g. two Array[int] (TypedArray<int64_t>) I'd expect the result to also be such typed array.
  4. Then for two generic Arrays something like the current implementation.
  5. Then if the array types mismatch (potentially besides the case 2. from the list above) maybe it should convert to generic Arrays like in the current implementation. Or maybe it should be disallowed. Not sure.

Regarding the code duplication I'd say in this specific case (arrays so potentially big chunks of data) the execution speed should definitely be prioritized. So yes for reducing the code duplication but not if it would hurt the performance.

@Mickeon
Copy link
Contributor Author

Mickeon commented Oct 3, 2022

Thank you very much for your insightful feedback, it was very necessary!

  1. Then there's a question what should be the behavior for e.g. PackedInt64Array and PackedFloat64Array args as there's this part in Animation::interpolate_variant allowing interpolating any number types [...]

Conversion between float and int is pretty common everywhere, but I feel like for how "hardcoded" all Packed Arrays are, that there should be no subtle casting here. Casting between PackedInt64Array and PackedInt32Array within the Animation functions feels like asking for trouble, and may even warrant a more noticeable error so that the user can handle it more graciously.

  1. Then if the array types mismatch (potentially besides the case 2. from the list above) maybe it should convert to generic Arrays like in the current implementation. Or maybe it should be disallowed. Not sure.

It's niche, but it avoids some annoyance. It shouldn't be too difficult to make it work between TypedArrays of float and int, at least for now, as that would be consistent across the engine.

@Mickeon
Copy link
Contributor Author

Mickeon commented Oct 4, 2022

Updated the PR following the suggestions above.

2. Also note that currently there's no simliar parts in Animation::add_variant/Animation::subtract_variant so this inconsistency should probably be addressed too (probably out of this PR's scope):

This had to be implemented here, otherwise interpolating between [0.5, 2.75] and [1, 0] would not work as expected, for example.

If the starting and final arrays have different sizes, tweening will not happen.

If the arrays are typed and they're different, tweening will not happen, except between floats and ints.
scene/resources/animation.cpp Show resolved Hide resolved

Array result;

if (arr_a.is_typed() && arr_b.is_typed()) {
Copy link
Member

Choose a reason for hiding this comment

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

if (!arr_a.is_typed() || !arr_b.is_typed()) {
	return a;
}

is maybe better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What would the suggestion do?

Copy link
Member

Choose a reason for hiding this comment

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

It just recommends an earlier return for non-typed arrays.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea was to allow interpolation of non-typed Arrays, so long as a[i] and b[i] are of the same type

Copy link
Member

Choose a reason for hiding this comment

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

We need to think about it a bit to see if it will work and if it will be the correct casting. What do you think? @reduz @kleonc @KoBeWi

Copy link
Member

Choose a reason for hiding this comment

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

So what happens when a[i] and b[i] have different type? Are they skipped? Is there an error?
I think it's ok to support untyped arrays if it doesn't affect performance of the typed ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current behavior of this PR is that, if a[i] and b[i] are different types they are skipped altogether.

However, as I come back to this PR, I realise that these methods are also generally used by AnimationPlayer. So, I'm not sure if the current implementation break or introduce some odd and unnecessary code smells, right now.

Comment on lines +5613 to +5616
int size = arr_a.size();
if (size == 0 || arr_b.size() != size) {
return a;
}
Copy link
Member

Choose a reason for hiding this comment

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

int size = MAX(arr_a.size(), arr_b.size());

Shouldn't we consider the maximum size? It seems that at least in blend_packed_array() the maximum size should be considered if you implement it.

Copy link
Contributor Author

@Mickeon Mickeon Oct 11, 2022

Choose a reason for hiding this comment

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

If maximum size is used, it would go out of bounds when operating on one of the two arrays when the sizes do not match. If anything, it could be MIN(), and anything above that index is ignored.

Copy link
Member

@TokageItLab TokageItLab Oct 11, 2022

Choose a reason for hiding this comment

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

You can use initial values for elements out of bounds. Then if resize() is properly done, access out of bounds will not occur.

Copy link
Member

Choose a reason for hiding this comment

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

For example, if we do the following tween:

[1, 2] -> [2, 4, 2] -> [2, 1]

The result would be as follows:

When using MIN: [1, 2] -> [2, 4] -> [2, 1]
When using MAX: [1, 2] -> [2, 4, 2] -> [2, 1, 0]

If you use MIN, the final result may be smaller than the element in the middle of the tween, so someone may access out of bounds unless they know after the tween is over that it is smaller than the element in the middle of the tween. So it is safer to use MAX.

Copy link
Contributor Author

@Mickeon Mickeon Oct 11, 2022

Choose a reason for hiding this comment

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

I do understand your theoretical implementation. I'm just not sure it would be expected behavior to make a tweener capable of resizing the Array it ends up returning, on the first frame the tween starts.

Copy link
Member

@TokageItLab TokageItLab Oct 11, 2022

Choose a reason for hiding this comment

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

At least interpolate_variant() implicitly resizes the array for PackedStringArray.

Copy link
Member

Choose a reason for hiding this comment

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

I tried interpolating polygon from the default color array to another array and the result was that the value jumped to the final value at the end of animation. Would using MAX handle this more gracefully?

Comment on lines +505 to +511
template <class T>
static Vector<T> add_packed_array(Vector<T> a, Vector<T> b);
template <class T>
static Vector<T> subtract_packed_array(Vector<T> a, Vector<T> b);
template <class T>
static Vector<T> interpolate_packed_array(Vector<T> a, Vector<T> b, float c);

Copy link
Member

Choose a reason for hiding this comment

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

static Vector<T> blend_packed_array(Vector<T> a, Vector<T> b, float c);

Could we also add a blend_packed_array() for consistency?

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 could, although this PR is specifically for supporting tweening the Packed Arrays, which doesn't technically need the method.

@TokageItLab
Copy link
Member

@Mickeon What is the progress on this? Also, we currently do not handle Array types in blend_variant() (#73342) and it would be worth brushing up and merging this.

@Halfmania33
Copy link

Halfmania33 commented Jun 12, 2023

Hello Hardworkers !

I come back to check if there are somes good news.
Is there some hope about implementing this feature in 4.1 ? And backporting it to 3.X ?

@Calinou
Copy link
Member

Calinou commented Jun 12, 2023

Is there some hope about implementing this feature in 4.1 ? And backporting it to 3.X ?

4.1 is in feature freeze, so any new feature development can only be merged in 4.2 at the earliest. A 3.x backport for 3.6 is feasible, but the feature needs to be merged in master first before it can be merged in 3.x.

Also, this PR needs additional work before it can be merged.

@TokageItLab
Copy link
Member

TokageItLab commented Oct 15, 2023

As we mentions in #73342 and #83384, this issue came to the surface after implement AnimationMixer (#80813), so I will try to salvage this PR later.

Godot 4.2 features have been frozen and @Calinou said this is a new feature. However, I believe there is no problem to include it in 4.2 as a bug fix as long as during beta, since there is no danger for breaking compatibility. Considering #83384, this is just a fix for regression.

@akien-mga
Copy link
Member

Superseded by #84815.

@akien-mga akien-mga closed this Nov 14, 2023
@AThousandShips AThousandShips removed this from the 4.x milestone Nov 15, 2023
@Mickeon Mickeon deleted the tween-packed-arrays branch December 30, 2023 11:57
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.

9 participants