From a98e31aa3a78e9b9521407f04971eb9e066177ad Mon Sep 17 00:00:00 2001
From: Hugo Locurcio <hugo.locurcio@hugo.pro>
Date: Sat, 10 Jul 2021 20:19:47 +0200
Subject: [PATCH] Add `any()` and `all()` methods to Array

These can be used as faster, more convenient shorthands to
using `filter()` + `size()`.
---
 core/variant/array.cpp        | 44 ++++++++++++++++++++++++++++++++++
 core/variant/array.h          |  2 ++
 core/variant/variant_call.cpp |  2 ++
 doc/classes/Array.xml         | 45 +++++++++++++++++++++++++++++++++++
 4 files changed, 93 insertions(+)

diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 7551350c95a..b1e142d2395 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -501,6 +501,50 @@ Variant Array::reduce(const Callable &p_callable, const Variant &p_accum) const
 	return ret;
 }
 
+bool Array::any(const Callable &p_callable) const {
+	const Variant *argptrs[1];
+	for (int i = 0; i < size(); i++) {
+		argptrs[0] = &get(i);
+
+		Variant result;
+		Callable::CallError ce;
+		p_callable.call(argptrs, 1, result, ce);
+		if (ce.error != Callable::CallError::CALL_OK) {
+			ERR_FAIL_V_MSG(false, "Error calling method from 'any': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+		}
+
+		if (result.operator bool()) {
+			// Return as early as possible when one of the conditions is `true`.
+			// This improves performance compared to relying on `filter(...).size() >= 1`.
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool Array::all(const Callable &p_callable) const {
+	const Variant *argptrs[1];
+	for (int i = 0; i < size(); i++) {
+		argptrs[0] = &get(i);
+
+		Variant result;
+		Callable::CallError ce;
+		p_callable.call(argptrs, 1, result, ce);
+		if (ce.error != Callable::CallError::CALL_OK) {
+			ERR_FAIL_V_MSG(false, "Error calling method from 'all': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+		}
+
+		if (!(result.operator bool())) {
+			// Return as early as possible when one of the inverted conditions is `false`.
+			// This improves performance compared to relying on `filter(...).size() >= array_size().`.
+			return false;
+		}
+	}
+
+	return true;
+}
+
 struct _ArrayVariantSort {
 	_FORCE_INLINE_ bool operator()(const Variant &p_l, const Variant &p_r) const {
 		bool valid = false;
diff --git a/core/variant/array.h b/core/variant/array.h
index f537700f995..c0073767346 100644
--- a/core/variant/array.h
+++ b/core/variant/array.h
@@ -108,6 +108,8 @@ public:
 	Array filter(const Callable &p_callable) const;
 	Array map(const Callable &p_callable) const;
 	Variant reduce(const Callable &p_callable, const Variant &p_accum) const;
+	bool any(const Callable &p_callable) const;
+	bool all(const Callable &p_callable) const;
 
 	bool operator<(const Array &p_array) const;
 	bool operator<=(const Array &p_array) const;
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index a3568a8d6a6..d86bc70d215 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1853,6 +1853,8 @@ static void _register_variant_builtin_methods() {
 	bind_method(Array, filter, sarray("method"), varray());
 	bind_method(Array, map, sarray("method"), varray());
 	bind_method(Array, reduce, sarray("method", "accum"), varray(Variant()));
+	bind_method(Array, any, sarray("method"), varray());
+	bind_method(Array, all, sarray("method"), varray());
 	bind_method(Array, max, sarray(), varray());
 	bind_method(Array, min, sarray(), varray());
 
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index ef4f86f1a9a..94181db95f2 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -123,6 +123,48 @@
 		</constructor>
 	</constructors>
 	<methods>
+		<method name="all" qualifiers="const">
+			<return type="bool" />
+			<argument index="0" name="method" type="Callable" />
+			<description>
+				Calls the provided [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]all[/i] elements in the array. If the [Callable] returns [code]false[/code] for one array element or more, this method returns [code]false[/code].
+				The callable's method should take one [Variant] parameter (the current array element) and return a boolean value.
+				[codeblock]
+				func _ready():
+				    print([6, 10, 6].all(greater_than_5))  # Prints True (3 elements evaluate to `true`).
+				    print([4, 10, 4].all(greater_than_5))  # Prints False (1 elements evaluate to `true`).
+				    print([4, 4, 4].all(greater_than_5))  # Prints False (0 elements evaluate to `true`).
+
+				    print([6, 10, 6].all(func(number): return number &gt; 5))  # Prints True. Same as the first line above, but using lambda function.
+
+				func greater_than_5(number):
+				    return number &gt; 5
+				[/codeblock]
+				See also [method any], [method filter], [method map] and [method reduce].
+				[b]Note:[/b] Unlike relying on the size of an array returned by [method filter], this method will return as early as possible to improve performance (especially with large arrays).
+			</description>
+		</method>
+		<method name="any" qualifiers="const">
+			<return type="bool" />
+			<argument index="0" name="method" type="Callable" />
+			<description>
+				Calls the provided [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]one or more[/i] elements in the array. If the [Callable] returns [code]false[/code] for all elements in the array, this method returns [code]false[/code].
+				The callable's method should take one [Variant] parameter (the current array element) and return a boolean value.
+				[codeblock]
+				func _ready():
+				    print([6, 10, 6].any(greater_than_5))  # Prints True (3 elements evaluate to `true`).
+				    print([4, 10, 4].any(greater_than_5))  # Prints True (1 elements evaluate to `true`).
+				    print([4, 4, 4].any(greater_than_5))  # Prints False (0 elements evaluate to `true`).
+
+				    print([6, 10, 6].any(func(number): return number &gt; 5))  # Prints True. Same as the first line above, but using lambda function.
+
+				func greater_than_5(number):
+				    return number &gt; 5
+				[/codeblock]
+				See also [method all], [method filter], [method map] and [method reduce].
+				[b]Note:[/b] Unlike relying on the size of an array returned by [method filter], this method will return as early as possible to improve performance (especially with large arrays).
+			</description>
+		</method>
 		<method name="append">
 			<return type="void" />
 			<argument index="0" name="value" type="Variant" />
@@ -232,6 +274,7 @@
 				func remove_1(number):
 				    return number != 1
 				[/codeblock]
+				See also [method any], [method all], [method map] and [method reduce].
 			</description>
 		</method>
 		<method name="find" qualifiers="const">
@@ -333,6 +376,7 @@
 				func negate(number):
 				    return -number
 				[/codeblock]
+				See also [method filter], [method reduce], [method any] and [method all].
 			</description>
 		</method>
 		<method name="max" qualifiers="const">
@@ -398,6 +442,7 @@
 				func sum(accum, number):
 				    return accum + number
 				[/codeblock]
+				See also [method map], [method filter], [method any] and [method all].
 			</description>
 		</method>
 		<method name="remove_at">