diff options
-rw-r--r-- | core/templates/safe_refcount.h | 5 | ||||
-rw-r--r-- | scene/2d/node_2d.cpp | 50 | ||||
-rw-r--r-- | scene/2d/node_2d.h | 15 | ||||
-rw-r--r-- | scene/3d/node_3d.cpp | 75 | ||||
-rw-r--r-- | scene/3d/node_3d.h | 8 | ||||
-rw-r--r-- | scene/main/canvas_item.cpp | 26 | ||||
-rw-r--r-- | scene/main/canvas_item.h | 5 | ||||
-rw-r--r-- | scene/main/node.h | 24 |
8 files changed, 145 insertions, 63 deletions
diff --git a/core/templates/safe_refcount.h b/core/templates/safe_refcount.h index 8669bcaeeb..bfc9f6fc9a 100644 --- a/core/templates/safe_refcount.h +++ b/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 { diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 24478fd847..a0eab67f28 100644 --- a/scene/2d/node_2d.cpp +++ b/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); diff --git a/scene/2d/node_2d.h b/scene/2d/node_2d.h index 6bdf5d6eb5..de46dbd7d6 100644 --- a/scene/2d/node_2d.h +++ b/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); diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 446d9f6ee8..80289bac52 100644 --- a/scene/3d/node_3d.cpp +++ b/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) { diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 4fb77eeb9f..b274a6af88 100644 --- a/scene/3d/node_3d.h +++ b/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); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 598f6aa4c6..ae01e8b009 100644 --- a/scene/main/canvas_item.cpp +++ b/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) { diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 55267abab8..d7771cda53 100644 --- a/scene/main/canvas_item.h +++ b/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(); diff --git a/scene/main/node.h b/scene/main/node.h index b7462b4468..4d4e71ee56 100644 --- a/scene/main/node.h +++ b/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; |