2
0
Эх сурвалжийг харах

Merge pull request #77145 from RandomShaper/enh_fix_mt_proc

Enhance/fix MT-processing related things
Rémi Verschelde 2 жил өмнө
parent
commit
d803304c62

+ 4 - 1
core/templates/safe_refcount.h

@@ -50,11 +50,14 @@
 //   value and, as an important benefit, you can be sure the value is properly synchronized
 //   even with threads that are already running.
 
-// This is used in very specific areas of the engine where it's critical that these guarantees are held
+// These are used in very specific areas of the engine where it's critical that these guarantees are held
 #define SAFE_NUMERIC_TYPE_PUN_GUARANTEES(m_type)                    \
 	static_assert(sizeof(SafeNumeric<m_type>) == sizeof(m_type));   \
 	static_assert(alignof(SafeNumeric<m_type>) == alignof(m_type)); \
 	static_assert(std::is_trivially_destructible<std::atomic<m_type>>::value);
+#define SAFE_FLAG_TYPE_PUN_GUARANTEES                \
+	static_assert(sizeof(SafeFlag) == sizeof(bool)); \
+	static_assert(alignof(SafeFlag) == alignof(bool));
 
 template <class T>
 class SafeNumeric {

+ 31 - 19
scene/2d/node_2d.cpp

@@ -112,12 +112,24 @@ void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) {
 }
 #endif
 
-void Node2D::_update_xform_values() {
+void Node2D::_set_xform_dirty(bool p_dirty) const {
+	if (is_group_processing()) {
+		if (p_dirty) {
+			xform_dirty.mt.set();
+		} else {
+			xform_dirty.mt.clear();
+		}
+	} else {
+		xform_dirty.st = p_dirty;
+	}
+}
+
+void Node2D::_update_xform_values() const {
 	rotation = transform.get_rotation();
 	skew = transform.get_skew();
 	position = transform.columns[2];
 	scale = transform.get_scale();
-	xform_dirty.clear();
+	_set_xform_dirty(false);
 }
 
 void Node2D::_update_transform() {
@@ -144,8 +156,8 @@ void Node2D::reparent(Node *p_parent, bool p_keep_global_transform) {
 
 void Node2D::set_position(const Point2 &p_pos) {
 	ERR_THREAD_GUARD;
-	if (xform_dirty.is_set()) {
-		const_cast<Node2D *>(this)->_update_xform_values();
+	if (_is_xform_dirty()) {
+		_update_xform_values();
 	}
 	position = p_pos;
 	_update_transform();
@@ -153,8 +165,8 @@ void Node2D::set_position(const Point2 &p_pos) {
 
 void Node2D::set_rotation(real_t p_radians) {
 	ERR_THREAD_GUARD;
-	if (xform_dirty.is_set()) {
-		const_cast<Node2D *>(this)->_update_xform_values();
+	if (_is_xform_dirty()) {
+		_update_xform_values();
 	}
 	rotation = p_radians;
 	_update_transform();
@@ -167,8 +179,8 @@ void Node2D::set_rotation_degrees(real_t p_degrees) {
 
 void Node2D::set_skew(real_t p_radians) {
 	ERR_THREAD_GUARD;
-	if (xform_dirty.is_set()) {
-		const_cast<Node2D *>(this)->_update_xform_values();
+	if (_is_xform_dirty()) {
+		_update_xform_values();
 	}
 	skew = p_radians;
 	_update_transform();
@@ -176,8 +188,8 @@ void Node2D::set_skew(real_t p_radians) {
 
 void Node2D::set_scale(const Size2 &p_scale) {
 	ERR_THREAD_GUARD;
-	if (xform_dirty.is_set()) {
-		const_cast<Node2D *>(this)->_update_xform_values();
+	if (_is_xform_dirty()) {
+		_update_xform_values();
 	}
 	scale = p_scale;
 	// Avoid having 0 scale values, can lead to errors in physics and rendering.
@@ -192,8 +204,8 @@ void Node2D::set_scale(const Size2 &p_scale) {
 
 Point2 Node2D::get_position() const {
 	ERR_READ_THREAD_GUARD_V(Point2());
-	if (xform_dirty.is_set()) {
-		const_cast<Node2D *>(this)->_update_xform_values();
+	if (_is_xform_dirty()) {
+		_update_xform_values();
 	}
 
 	return position;
@@ -201,8 +213,8 @@ Point2 Node2D::get_position() const {
 
 real_t Node2D::get_rotation() const {
 	ERR_READ_THREAD_GUARD_V(0);
-	if (xform_dirty.is_set()) {
-		const_cast<Node2D *>(this)->_update_xform_values();
+	if (_is_xform_dirty()) {
+		_update_xform_values();
 	}
 
 	return rotation;
@@ -215,8 +227,8 @@ real_t Node2D::get_rotation_degrees() const {
 
 real_t Node2D::get_skew() const {
 	ERR_READ_THREAD_GUARD_V(0);
-	if (xform_dirty.is_set()) {
-		const_cast<Node2D *>(this)->_update_xform_values();
+	if (_is_xform_dirty()) {
+		_update_xform_values();
 	}
 
 	return skew;
@@ -224,8 +236,8 @@ real_t Node2D::get_skew() const {
 
 Size2 Node2D::get_scale() const {
 	ERR_READ_THREAD_GUARD_V(Size2());
-	if (xform_dirty.is_set()) {
-		const_cast<Node2D *>(this)->_update_xform_values();
+	if (_is_xform_dirty()) {
+		_update_xform_values();
 	}
 
 	return scale;
@@ -362,7 +374,7 @@ void Node2D::set_global_scale(const Size2 &p_scale) {
 void Node2D::set_transform(const Transform2D &p_transform) {
 	ERR_THREAD_GUARD;
 	transform = p_transform;
-	xform_dirty.set();
+	_set_xform_dirty(true);
 
 	RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);
 

+ 9 - 6
scene/2d/node_2d.h

@@ -36,17 +36,20 @@
 class Node2D : public CanvasItem {
 	GDCLASS(Node2D, CanvasItem);
 
-	SafeFlag xform_dirty;
-	Point2 position;
-	real_t rotation = 0.0;
-	Size2 scale = Vector2(1, 1);
-	real_t skew = 0.0;
+	mutable MTFlag xform_dirty;
+	mutable Point2 position;
+	mutable real_t rotation = 0.0;
+	mutable Size2 scale = Vector2(1, 1);
+	mutable real_t skew = 0.0;
 
 	Transform2D transform;
 
+	_FORCE_INLINE_ bool _is_xform_dirty() const { return is_group_processing() ? xform_dirty.mt.is_set() : xform_dirty.st; }
+	void _set_xform_dirty(bool p_dirty) const;
+
 	void _update_transform();
 
-	void _update_xform_values();
+	void _update_xform_values() const;
 
 protected:
 	void _notification(int p_notification);

+ 50 - 25
scene/3d/node_3d.cpp

@@ -87,7 +87,7 @@ void Node3D::_notify_dirty() {
 void Node3D::_update_local_transform() const {
 	// This function is called when the local transform (data.local_transform) is dirty and the right value is contained in the Euler rotation and scale.
 	data.local_transform.basis.set_euler_scale(data.euler_rotation, data.scale, data.euler_rotation_order);
-	data.dirty.bit_and(~DIRTY_LOCAL_TRANSFORM);
+	_clear_dirty_bits(DIRTY_LOCAL_TRANSFORM);
 }
 
 void Node3D::_update_rotation_and_scale() const {
@@ -95,7 +95,7 @@ void Node3D::_update_rotation_and_scale() const {
 
 	data.scale = data.local_transform.basis.get_scale();
 	data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
-	data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE);
+	_clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE);
 }
 
 void Node3D::_propagate_transform_changed_deferred() {
@@ -127,7 +127,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
 			MessageQueue::get_singleton()->push_callable(callable_mp(this, &Node3D::_propagate_transform_changed_deferred));
 		}
 	}
-	data.dirty.bit_or(DIRTY_GLOBAL_TRANSFORM);
+	_set_dirty_bits(DIRTY_GLOBAL_TRANSFORM);
 }
 
 void Node3D::_notification(int p_what) {
@@ -151,12 +151,12 @@ void Node3D::_notification(int p_what) {
 			if (data.top_level && !Engine::get_singleton()->is_editor_hint()) {
 				if (data.parent) {
 					data.local_transform = data.parent->get_global_transform() * get_transform();
-					data.dirty.set(DIRTY_EULER_ROTATION_AND_SCALE); // As local transform was updated, rot/scale should be dirty.
+					_replace_dirty_mask(DIRTY_EULER_ROTATION_AND_SCALE); // As local transform was updated, rot/scale should be dirty.
 				}
 				data.top_level_active = true;
 			}
 
-			data.dirty.bit_or(DIRTY_GLOBAL_TRANSFORM); // Global is always dirty upon entering a scene.
+			_set_dirty_bits(DIRTY_GLOBAL_TRANSFORM); // Global is always dirty upon entering a scene.
 			_notify_dirty();
 
 			notification(NOTIFICATION_ENTER_WORLD);
@@ -230,16 +230,16 @@ void Node3D::set_basis(const Basis &p_basis) {
 void Node3D::set_quaternion(const Quaternion &p_quaternion) {
 	ERR_THREAD_GUARD;
 
-	if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
+	if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
 		// We need the scale part, so if these are dirty, update it
 		data.scale = data.local_transform.basis.get_scale();
-		data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE);
+		_clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE);
 	}
 	data.local_transform.basis = Basis(p_quaternion, data.scale);
 	// Rotscale should not be marked dirty because that would cause precision loss issues with the scale. Instead reconstruct rotation now.
 	data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
 
-	data.dirty.set(DIRTY_NONE);
+	_replace_dirty_mask(DIRTY_NONE);
 
 	_propagate_transform_changed(this);
 	if (data.notify_local_transform) {
@@ -286,7 +286,7 @@ void Node3D::set_global_rotation_degrees(const Vector3 &p_euler_degrees) {
 void Node3D::set_transform(const Transform3D &p_transform) {
 	ERR_THREAD_GUARD;
 	data.local_transform = p_transform;
-	data.dirty.set(DIRTY_EULER_ROTATION_AND_SCALE); // Make rot/scale dirty.
+	_replace_dirty_mask(DIRTY_EULER_ROTATION_AND_SCALE); // Make rot/scale dirty.
 
 	_propagate_transform_changed(this);
 	if (data.notify_local_transform) {
@@ -314,7 +314,7 @@ void Node3D::set_global_transform(const Transform3D &p_transform) {
 
 Transform3D Node3D::get_transform() const {
 	ERR_READ_THREAD_GUARD_V(Transform3D());
-	if (data.dirty.get() & DIRTY_LOCAL_TRANSFORM) {
+	if (_test_dirty_bits(DIRTY_LOCAL_TRANSFORM)) {
 		// This update can happen if needed over multiple threads.
 		_update_local_transform();
 	}
@@ -330,7 +330,7 @@ Transform3D Node3D::get_global_transform() const {
 	 * the dirty/update process is thread safe by utilizing atomic copies.
 	 */
 
-	uint32_t dirty = data.dirty.get();
+	uint32_t dirty = _read_dirty_mask();
 	if (dirty & DIRTY_GLOBAL_TRANSFORM) {
 		if (dirty & DIRTY_LOCAL_TRANSFORM) {
 			_update_local_transform(); // Update local transform atomically.
@@ -348,7 +348,7 @@ Transform3D Node3D::get_global_transform() const {
 		}
 
 		data.global_transform = new_global;
-		data.dirty.bit_and(~DIRTY_GLOBAL_TRANSFORM);
+		_clear_dirty_bits(DIRTY_GLOBAL_TRANSFORM);
 	}
 
 	return data.global_transform;
@@ -404,14 +404,14 @@ void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) {
 	}
 
 	bool transform_changed = false;
-	if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !(data.dirty.get() & DIRTY_LOCAL_TRANSFORM)) {
+	if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !_test_dirty_bits(DIRTY_LOCAL_TRANSFORM)) {
 		data.local_transform.orthogonalize();
 		transform_changed = true;
 	}
 
 	data.rotation_edit_mode = p_mode;
 
-	if (p_mode == ROTATION_EDIT_MODE_EULER && (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE)) {
+	if (p_mode == ROTATION_EDIT_MODE_EULER && _test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
 		// If going to Euler mode, ensure that vectors are _not_ dirty, else the retrieved value may be wrong.
 		// Otherwise keep what is there, so switching back and forth between modes does not break the vectors.
 
@@ -442,13 +442,14 @@ void Node3D::set_rotation_order(EulerOrder p_order) {
 	ERR_FAIL_INDEX(int32_t(p_order), 6);
 	bool transform_changed = false;
 
-	if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
+	uint32_t dirty = _read_dirty_mask();
+	if ((dirty & DIRTY_EULER_ROTATION_AND_SCALE)) {
 		_update_rotation_and_scale();
-	} else if (data.dirty.get() & DIRTY_LOCAL_TRANSFORM) {
+	} else if ((dirty & DIRTY_LOCAL_TRANSFORM)) {
 		data.euler_rotation = Basis::from_euler(data.euler_rotation, data.euler_rotation_order).get_euler_normalized(p_order);
 		transform_changed = true;
 	} else {
-		data.dirty.bit_or(DIRTY_LOCAL_TRANSFORM);
+		_set_dirty_bits(DIRTY_LOCAL_TRANSFORM);
 		transform_changed = true;
 	}
 
@@ -470,14 +471,14 @@ EulerOrder Node3D::get_rotation_order() const {
 
 void Node3D::set_rotation(const Vector3 &p_euler_rad) {
 	ERR_THREAD_GUARD;
-	if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
+	if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
 		// Update scale only if rotation and scale are dirty, as rotation will be overridden.
 		data.scale = data.local_transform.basis.get_scale();
-		data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE);
+		_clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE);
 	}
 
 	data.euler_rotation = p_euler_rad;
-	data.dirty.set(DIRTY_LOCAL_TRANSFORM);
+	_replace_dirty_mask(DIRTY_LOCAL_TRANSFORM);
 	_propagate_transform_changed(this);
 	if (data.notify_local_transform) {
 		notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
@@ -492,14 +493,14 @@ void Node3D::set_rotation_degrees(const Vector3 &p_euler_degrees) {
 
 void Node3D::set_scale(const Vector3 &p_scale) {
 	ERR_THREAD_GUARD;
-	if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
+	if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
 		// Update rotation only if rotation and scale are dirty, as scale will be overridden.
 		data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
-		data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE);
+		_clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE);
 	}
 
 	data.scale = p_scale;
-	data.dirty.set(DIRTY_LOCAL_TRANSFORM);
+	_replace_dirty_mask(DIRTY_LOCAL_TRANSFORM);
 	_propagate_transform_changed(this);
 	if (data.notify_local_transform) {
 		notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
@@ -513,7 +514,7 @@ Vector3 Node3D::get_position() const {
 
 Vector3 Node3D::get_rotation() const {
 	ERR_READ_THREAD_GUARD_V(Vector3());
-	if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
+	if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
 		_update_rotation_and_scale();
 	}
 
@@ -528,7 +529,7 @@ Vector3 Node3D::get_rotation_degrees() const {
 
 Vector3 Node3D::get_scale() const {
 	ERR_READ_THREAD_GUARD_V(Vector3());
-	if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
+	if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
 		_update_rotation_and_scale();
 	}
 
@@ -645,6 +646,30 @@ Vector<Ref<Node3DGizmo>> Node3D::get_gizmos() const {
 #endif
 }
 
+void Node3D::_replace_dirty_mask(uint32_t p_mask) const {
+	if (is_group_processing()) {
+		data.dirty.mt.set(p_mask);
+	} else {
+		data.dirty.st = p_mask;
+	}
+}
+
+void Node3D::_set_dirty_bits(uint32_t p_bits) const {
+	if (is_group_processing()) {
+		data.dirty.mt.bit_or(p_bits);
+	} else {
+		data.dirty.st |= p_bits;
+	}
+}
+
+void Node3D::_clear_dirty_bits(uint32_t p_bits) const {
+	if (is_group_processing()) {
+		data.dirty.mt.bit_and(~p_bits);
+	} else {
+		data.dirty.st &= ~p_bits;
+	}
+}
+
 void Node3D::_update_gizmos() {
 #ifdef TOOLS_ENABLED
 	if (data.gizmos_disabled || !is_inside_world() || !data.gizmos_dirty) {

+ 7 - 1
scene/3d/node_3d.h

@@ -97,7 +97,7 @@ private:
 		mutable Vector3 scale = Vector3(1, 1, 1);
 		mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER;
 
-		mutable SafeNumeric<uint32_t> dirty;
+		mutable MTNumeric<uint32_t> dirty;
 
 		Viewport *viewport = nullptr;
 
@@ -129,6 +129,12 @@ private:
 
 	NodePath visibility_parent_path;
 
+	_FORCE_INLINE_ uint32_t _read_dirty_mask() const { return is_group_processing() ? data.dirty.mt.get() : data.dirty.st; }
+	_FORCE_INLINE_ bool _test_dirty_bits(uint32_t p_bits) const { return is_group_processing() ? data.dirty.mt.bit_and(p_bits) : (data.dirty.st & p_bits); }
+	void _replace_dirty_mask(uint32_t p_mask) const;
+	void _set_dirty_bits(uint32_t p_bits) const;
+	void _clear_dirty_bits(uint32_t p_bits) const;
+
 	void _update_gizmos();
 	void _notify_dirty();
 	void _propagate_transform_changed(Node3D *p_origin);

+ 19 - 7
scene/main/canvas_item.cpp

@@ -148,7 +148,7 @@ void CanvasItem::_redraw_callback() {
 }
 
 void CanvasItem::_invalidate_global_transform() {
-	global_invalid.set();
+	_set_global_invalid(true);
 }
 
 Transform2D CanvasItem::get_global_transform_with_canvas() const {
@@ -171,7 +171,7 @@ Transform2D CanvasItem::get_screen_transform() const {
 Transform2D CanvasItem::get_global_transform() const {
 	ERR_READ_THREAD_GUARD_V(Transform2D());
 
-	if (global_invalid.is_set()) {
+	if (_is_global_invalid()) {
 		// This code can enter multiple times from threads if dirty, this is expected.
 		const CanvasItem *pi = get_parent_item();
 		Transform2D new_global;
@@ -182,12 +182,24 @@ Transform2D CanvasItem::get_global_transform() const {
 		}
 
 		global_transform = new_global;
-		global_invalid.clear();
+		_set_global_invalid(false);
 	}
 
 	return global_transform;
 }
 
+void CanvasItem::_set_global_invalid(bool p_invalid) const {
+	if (is_group_processing()) {
+		if (p_invalid) {
+			global_invalid.mt.set();
+		} else {
+			global_invalid.mt.clear();
+		}
+	} else {
+		global_invalid.st = p_invalid;
+	}
+}
+
 void CanvasItem::_top_level_raise_self() {
 	if (!is_inside_tree()) {
 		return;
@@ -308,7 +320,7 @@ void CanvasItem::_notification(int p_what) {
 				}
 			}
 
-			global_invalid.set();
+			_set_global_invalid(true);
 			_enter_canvas();
 
 			RenderingServer::get_singleton()->canvas_item_set_visible(canvas_item, is_visible_in_tree()); // The visibility of the parent may change.
@@ -341,7 +353,7 @@ void CanvasItem::_notification(int p_what) {
 				window->disconnect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed));
 				window = nullptr;
 			}
-			global_invalid.set();
+			_set_global_invalid(true);
 			parent_visible_in_tree = false;
 
 			if (get_viewport()) {
@@ -869,11 +881,11 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) {
 	 * notification anyway).
 	 */
 
-	if (/*p_node->xform_change.in_list() &&*/ p_node->global_invalid.is_set()) {
+	if (/*p_node->xform_change.in_list() &&*/ p_node->_is_global_invalid()) {
 		return; //nothing to do
 	}
 
-	p_node->global_invalid.set();
+	p_node->_set_global_invalid(true);
 
 	if (p_node->notify_transform && !p_node->xform_change.in_list()) {
 		if (!p_node->block_transform_notify) {

+ 4 - 1
scene/main/canvas_item.h

@@ -118,7 +118,10 @@ private:
 	Ref<Material> material;
 
 	mutable Transform2D global_transform;
-	mutable SafeFlag global_invalid;
+	mutable MTFlag global_invalid;
+
+	_FORCE_INLINE_ bool _is_global_invalid() const { return is_group_processing() ? global_invalid.mt.is_set() : global_invalid.st; }
+	void _set_global_invalid(bool p_invalid) const;
 
 	void _top_level_raise_self();
 

+ 21 - 3
scene/main/node.h

@@ -42,9 +42,25 @@ class SceneState;
 class Tween;
 class PropertyTweener;
 
+SAFE_FLAG_TYPE_PUN_GUARANTEES
+SAFE_NUMERIC_TYPE_PUN_GUARANTEES(uint32_t)
+
 class Node : public Object {
 	GDCLASS(Node, Object);
 
+protected:
+	// During group processing, these are thread-safe.
+	// Outside group processing, these avoid the cost of sync by working as plain primitive types.
+	union MTFlag {
+		SafeFlag mt{};
+		bool st;
+	};
+	template <class T>
+	union MTNumeric {
+		SafeNumeric<T> mt{};
+		T st;
+	};
+
 public:
 	enum ProcessMode {
 		PROCESS_MODE_INHERIT, // same as parent node
@@ -522,8 +538,8 @@ public:
 	_FORCE_INLINE_ bool is_accessible_from_caller_thread() const {
 		if (current_process_thread_group == nullptr) {
 			// Not thread processing. Only accessible if node is outside the scene tree,
-			// or if accessing from the main thread.
-			return !data.inside_tree || Thread::is_main_thread();
+			// if accessing from the main thread or being loaded.
+			return !data.inside_tree || Thread::is_main_thread() || ResourceLoader::is_within_load();
 		} else {
 			// Thread processing
 			return current_process_thread_group == data.process_thread_group_owner;
@@ -532,12 +548,14 @@ public:
 
 	_FORCE_INLINE_ bool is_readable_from_caller_thread() const {
 		if (current_process_thread_group == nullptr) {
-			return Thread::is_main_thread();
+			return Thread::is_main_thread() || ResourceLoader::is_within_load();
 		} else {
 			return true;
 		}
 	}
 
+	_FORCE_INLINE_ static bool is_group_processing() { return current_process_thread_group; }
+
 	void set_process_thread_messages(BitField<ProcessThreadMessages> p_flags);
 	BitField<ProcessThreadMessages> get_process_thread_messages() const;