initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

6
scene/main/SCsub Normal file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")

View File

@@ -0,0 +1,91 @@
/**************************************************************************/
/* canvas_item.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void CanvasItem::_draw_string_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, const Color &p_modulate, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const {
draw_string(p_font, p_pos, p_text, p_alignment, p_width, p_font_size, p_modulate, p_jst_flags, p_direction, p_orientation, 0.0);
}
void CanvasItem::_draw_multiline_string_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_max_lines, const Color &p_modulate, BitField<TextServer::LineBreakFlag> p_brk_flags, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const {
draw_multiline_string(p_font, p_pos, p_text, p_alignment, p_width, p_font_size, p_max_lines, p_modulate, p_brk_flags, p_jst_flags, p_direction, p_orientation, 0.0);
}
void CanvasItem::_draw_string_outline_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_size, const Color &p_modulate, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const {
draw_string_outline(p_font, p_pos, p_text, p_alignment, p_width, p_font_size, p_size, p_modulate, p_jst_flags, p_direction, p_orientation, 0.0);
}
void CanvasItem::_draw_multiline_string_outline_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment, float p_width, int p_font_size, int p_max_lines, int p_size, const Color &p_modulate, BitField<TextServer::LineBreakFlag> p_brk_flags, BitField<TextServer::JustificationFlag> p_jst_flags, TextServer::Direction p_direction, TextServer::Orientation p_orientation) const {
draw_multiline_string_outline(p_font, p_pos, p_text, p_alignment, p_width, p_font_size, p_max_lines, p_size, p_modulate, p_brk_flags, p_jst_flags, p_direction, p_orientation, 0.0);
}
void CanvasItem::_draw_char_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size, const Color &p_modulate) const {
draw_char(p_font, p_pos, p_char, p_font_size, p_modulate, 0.0);
}
void CanvasItem::_draw_char_outline_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size, int p_size, const Color &p_modulate) const {
draw_char_outline(p_font, p_pos, p_char, p_font_size, p_size, p_modulate, 0.0);
}
void CanvasItem::_draw_circle_bind_compat_84472(const Point2 &p_pos, real_t p_radius, const Color &p_color) {
draw_circle(p_pos, p_radius, p_color, true, -1.0, false);
}
void CanvasItem::_draw_rect_bind_compat_84523(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width) {
draw_rect(p_rect, p_color, p_filled, p_width, false);
}
void CanvasItem::_draw_dashed_line_bind_compat_84523(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned) {
draw_dashed_line(p_from, p_to, p_color, p_width, p_dash, p_aligned, false);
}
void CanvasItem::_draw_multiline_bind_compat_84523(const Vector<Point2> &p_points, const Color &p_color, real_t p_width) {
draw_multiline(p_points, p_color, p_width, false);
}
void CanvasItem::_draw_multiline_colors_bind_compat_84523(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width) {
draw_multiline_colors(p_points, p_colors, p_width, false);
}
void CanvasItem::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("draw_string", "font", "pos", "text", "alignment", "width", "font_size", "modulate", "justification_flags", "direction", "orientation"), &CanvasItem::_draw_string_bind_compat_104872, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL));
ClassDB::bind_compatibility_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "alignment", "width", "font_size", "max_lines", "modulate", "brk_flags", "justification_flags", "direction", "orientation"), &CanvasItem::_draw_multiline_string_bind_compat_104872, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL));
ClassDB::bind_compatibility_method(D_METHOD("draw_string_outline", "font", "pos", "text", "alignment", "width", "font_size", "size", "modulate", "justification_flags", "direction", "orientation"), &CanvasItem::_draw_string_outline_bind_compat_104872, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL));
ClassDB::bind_compatibility_method(D_METHOD("draw_multiline_string_outline", "font", "pos", "text", "alignment", "width", "font_size", "max_lines", "size", "modulate", "brk_flags", "justification_flags", "direction", "orientation"), &CanvasItem::_draw_multiline_string_outline_bind_compat_104872, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL));
ClassDB::bind_compatibility_method(D_METHOD("draw_char", "font", "pos", "char", "font_size", "modulate"), &CanvasItem::_draw_char_bind_compat_104872, DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0)));
ClassDB::bind_compatibility_method(D_METHOD("draw_char_outline", "font", "pos", "char", "font_size", "size", "modulate"), &CanvasItem::_draw_char_outline_bind_compat_104872, DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0)));
ClassDB::bind_compatibility_method(D_METHOD("draw_circle", "position", "radius", "color"), &CanvasItem::_draw_circle_bind_compat_84472);
ClassDB::bind_compatibility_method(D_METHOD("draw_rect", "rect", "color", "filled", "width"), &CanvasItem::_draw_rect_bind_compat_84523, DEFVAL(true), DEFVAL(-1.0));
ClassDB::bind_compatibility_method(D_METHOD("draw_dashed_line", "from", "to", "color", "width", "dash", "aligned"), &CanvasItem::_draw_dashed_line_bind_compat_84523, DEFVAL(-1.0), DEFVAL(2.0), DEFVAL(true));
ClassDB::bind_compatibility_method(D_METHOD("draw_multiline", "points", "color", "width"), &CanvasItem::_draw_multiline_bind_compat_84523, DEFVAL(-1.0));
ClassDB::bind_compatibility_method(D_METHOD("draw_multiline_colors", "points", "colors", "width"), &CanvasItem::_draw_multiline_colors_bind_compat_84523, DEFVAL(-1.0));
}
#endif

1919
scene/main/canvas_item.cpp Normal file

File diff suppressed because it is too large Load Diff

469
scene/main/canvas_item.h Normal file
View File

@@ -0,0 +1,469 @@
/**************************************************************************/
/* canvas_item.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
#include "scene/resources/font.h"
class CanvasLayer;
class MultiMesh;
class StyleBox;
class Window;
class World2D;
class CanvasItem : public Node {
GDCLASS(CanvasItem, Node);
friend class CanvasLayer;
public:
enum TextureFilter {
TEXTURE_FILTER_PARENT_NODE,
TEXTURE_FILTER_NEAREST,
TEXTURE_FILTER_LINEAR,
TEXTURE_FILTER_NEAREST_WITH_MIPMAPS,
TEXTURE_FILTER_LINEAR_WITH_MIPMAPS,
TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC,
TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC,
TEXTURE_FILTER_MAX
};
enum TextureRepeat {
TEXTURE_REPEAT_PARENT_NODE,
TEXTURE_REPEAT_DISABLED,
TEXTURE_REPEAT_ENABLED,
TEXTURE_REPEAT_MIRROR,
TEXTURE_REPEAT_MAX,
};
enum ClipChildrenMode {
CLIP_CHILDREN_DISABLED,
CLIP_CHILDREN_ONLY,
CLIP_CHILDREN_AND_DRAW,
CLIP_CHILDREN_MAX,
};
private:
mutable SelfList<Node>
xform_change;
RID canvas_item;
StringName canvas_group;
CanvasLayer *canvas_layer = nullptr;
Color modulate = Color(1, 1, 1, 1);
Color self_modulate = Color(1, 1, 1, 1);
List<CanvasItem *> children_items;
List<CanvasItem *>::Element *C = nullptr;
int light_mask = 1;
uint32_t visibility_layer = 1;
int z_index = 0;
bool z_relative = true;
bool y_sort_enabled = false;
Window *window = nullptr;
bool visible = true;
bool parent_visible_in_tree = false;
bool pending_update = false;
bool top_level = false;
bool drawing = false;
bool block_transform_notify = false;
bool behind = false;
bool use_parent_material = false;
bool notify_local_transform = false;
bool notify_transform = false;
bool hide_clip_children = false;
ClipChildrenMode clip_children_mode = CLIP_CHILDREN_DISABLED;
mutable RS::CanvasItemTextureFilter texture_filter_cache = RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR;
mutable RS::CanvasItemTextureRepeat texture_repeat_cache = RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED;
TextureFilter texture_filter = TEXTURE_FILTER_PARENT_NODE;
TextureRepeat texture_repeat = TEXTURE_REPEAT_PARENT_NODE;
Ref<Material> material;
mutable HashMap<StringName, Variant> instance_shader_parameters;
mutable HashMap<StringName, StringName> instance_shader_parameter_property_remap;
mutable Transform2D global_transform;
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();
void _propagate_visibility_changed(bool p_parent_visible_in_tree);
void _handle_visibility_change(bool p_visible);
virtual void _top_level_changed();
virtual void _top_level_changed_on_parent();
void _redraw_callback();
void _enter_canvas();
void _exit_canvas();
void _window_visibility_changed();
void _notify_transform(CanvasItem *p_node);
virtual void _physics_interpolated_changed() override;
static CanvasItem *current_item_drawn;
friend class Viewport;
void _refresh_texture_repeat_cache() const;
void _update_texture_repeat_changed(bool p_propagate);
void _refresh_texture_filter_cache() const;
void _update_texture_filter_changed(bool p_propagate);
void _notify_transform_deferred();
const StringName *_instance_shader_parameter_get_remap(const StringName &p_name) const;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
virtual void _update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat);
virtual void _update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter);
_FORCE_INLINE_ void _notify_transform() {
_notify_transform(this);
if (is_inside_tree() && !block_transform_notify && notify_local_transform) {
notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
}
}
void item_rect_changed(bool p_size_changed = true);
void set_canvas_item_use_identity_transform(bool p_enable);
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
void _draw_string_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
void _draw_multiline_string_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
void _draw_string_outline_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
void _draw_multiline_string_outline_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
void _draw_char_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const;
void _draw_char_outline_bind_compat_104872(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const;
void _draw_circle_bind_compat_84472(const Point2 &p_pos, real_t p_radius, const Color &p_color);
void _draw_rect_bind_compat_84523(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width);
void _draw_dashed_line_bind_compat_84523(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned);
void _draw_multiline_bind_compat_84523(const Vector<Point2> &p_points, const Color &p_color, real_t p_width);
void _draw_multiline_colors_bind_compat_84523(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width);
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
void _validate_property(PropertyInfo &p_property) const;
_FORCE_INLINE_ void set_hide_clip_children(bool p_value) { hide_clip_children = p_value; }
GDVIRTUAL0(_draw)
public:
enum {
NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED, //unique
NOTIFICATION_DRAW = 30,
NOTIFICATION_VISIBILITY_CHANGED = 31,
NOTIFICATION_ENTER_CANVAS = 32,
NOTIFICATION_EXIT_CANVAS = 33,
NOTIFICATION_LOCAL_TRANSFORM_CHANGED = 35,
NOTIFICATION_WORLD_2D_CHANGED = 36,
};
/* EDITOR AND DEBUGGING */
#ifdef TOOLS_ENABLED
// Save and restore a CanvasItem state
virtual void _edit_set_state(const Dictionary &p_state) {}
virtual Dictionary _edit_get_state() const { return Dictionary(); }
// Used to move the node
virtual void _edit_set_position(const Point2 &p_position) = 0;
virtual Point2 _edit_get_position() const = 0;
// Used to scale the node
virtual void _edit_set_scale(const Size2 &p_scale) = 0;
virtual Size2 _edit_get_scale() const = 0;
// Used to rotate the node
virtual bool _edit_use_rotation() const { return false; }
virtual void _edit_set_rotation(real_t p_rotation) {}
virtual real_t _edit_get_rotation() const { return 0.0; }
// Used to resize/move the node
virtual void _edit_set_rect(const Rect2 &p_rect) {}
virtual Size2 _edit_get_minimum_size() const { return Size2(-1, -1); } // LOOKS WEIRD
// Used to set a pivot
virtual bool _edit_use_pivot() const { return false; }
virtual void _edit_set_pivot(const Point2 &p_pivot) {}
virtual Point2 _edit_get_pivot() const { return Point2(); }
virtual Transform2D _edit_get_transform() const;
#endif // TOOLS_ENABLED
#ifdef DEBUG_ENABLED
// Those need to be available in debug runtime, to allow for node selection.
// Select the node.
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
// Used to resize/move the node.
virtual bool _edit_use_rect() const { return false; } // Maybe replace with _edit_get_editmode().
virtual Rect2 _edit_get_rect() const { return Rect2(0, 0, 0, 0); }
#endif // DEBUG_ENABLED
void update_draw_order();
/* VISIBILITY */
void set_visible(bool p_visible);
bool is_visible() const;
bool is_visible_in_tree() const;
void show();
void hide();
void queue_redraw();
void move_to_front();
void set_clip_children_mode(ClipChildrenMode p_clip_mode);
ClipChildrenMode get_clip_children_mode() const;
virtual void set_light_mask(int p_light_mask);
int get_light_mask() const;
void set_modulate(const Color &p_modulate);
Color get_modulate() const;
Color get_modulate_in_tree() const;
virtual void set_self_modulate(const Color &p_self_modulate);
Color get_self_modulate() const;
void set_visibility_layer(uint32_t p_visibility_layer);
uint32_t get_visibility_layer() const;
void set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable);
bool get_visibility_layer_bit(uint32_t p_visibility_layer) const;
/* ORDERING */
virtual void set_z_index(int p_z);
int get_z_index() const;
int get_effective_z_index() const;
void set_z_as_relative(bool p_enabled);
bool is_z_relative() const;
virtual void set_y_sort_enabled(bool p_enabled);
virtual bool is_y_sort_enabled() const;
/* DRAWING API */
void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, real_t p_dash = 2.0, bool p_aligned = true, bool p_antialiased = false);
void draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_polyline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_polyline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0, bool p_antialiased = false);
void draw_arc(const Vector2 &p_center, real_t p_radius, real_t p_start_angle, real_t p_end_angle, int p_point_count, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0, bool p_antialiased = false);
void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, real_t p_width = -1.0, bool p_antialiased = false);
void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color, bool p_filled = true, real_t p_width = -1.0, bool p_antialiased = false);
void draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1));
void draw_texture_rect(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false);
void draw_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false);
void draw_msdf_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), double p_outline = 0.0, double p_pixel_range = 4.0, double p_scale = 1.0);
void draw_lcd_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1));
void draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p_rect);
void draw_primitive(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, Ref<Texture2D> p_texture = Ref<Texture2D>());
void draw_polygon(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture2D> p_texture = Ref<Texture2D>());
void draw_colored_polygon(const Vector<Point2> &p_points, const Color &p_color, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture2D> p_texture = Ref<Texture2D>());
void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture2D> &p_texture, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1));
void draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture2D> &p_texture);
void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL, float p_oversampling = 0.0) const;
void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL, float p_oversampling = 0.0) const;
void draw_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL, float p_oversampling = 0.0) const;
void draw_multiline_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL, float p_oversampling = 0.0) const;
void draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), float p_oversampling = 0.0) const;
void draw_char_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), float p_oversampling = 0.0) const;
void draw_set_transform(const Point2 &p_offset, real_t p_rot = 0.0, const Size2 &p_scale = Size2(1.0, 1.0));
void draw_set_transform_matrix(const Transform2D &p_matrix);
void draw_animation_slice(double p_animation_length, double p_slice_begin, double p_slice_end, double p_offset = 0);
void draw_end_animation();
static CanvasItem *get_current_item_drawn();
/* RECT / TRANSFORM */
void set_as_top_level(bool p_top_level);
bool is_set_as_top_level() const;
void set_draw_behind_parent(bool p_enable);
bool is_draw_behind_parent_enabled() const;
CanvasItem *get_parent_item() const;
virtual Transform2D get_transform() const = 0;
virtual Transform2D get_global_transform() const;
virtual Transform2D get_global_transform_const() const;
virtual Transform2D get_global_transform_with_canvas() const;
virtual Transform2D get_screen_transform() const;
CanvasItem *get_top_level() const;
_FORCE_INLINE_ RID get_canvas_item() const {
return canvas_item;
}
void set_block_transform_notify(bool p_enable);
bool is_block_transform_notify_enabled() const;
Transform2D get_canvas_transform() const;
Transform2D get_viewport_transform() const;
Rect2 get_viewport_rect() const;
RID get_viewport_rid() const;
RID get_canvas() const;
ObjectID get_canvas_layer_instance_id() const;
Ref<World2D> get_world_2d() const;
virtual void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
void set_instance_shader_parameter(const StringName &p_name, const Variant &p_value);
Variant get_instance_shader_parameter(const StringName &p_name) const;
virtual void set_use_parent_material(bool p_use_parent_material);
bool get_use_parent_material() const;
Ref<InputEvent> make_input_local(const Ref<InputEvent> &p_event) const;
Vector2 make_canvas_position_local(const Vector2 &screen_point) const;
Vector2 get_global_mouse_position() const;
Vector2 get_local_mouse_position() const;
void set_notify_local_transform(bool p_enable);
bool is_local_transform_notification_enabled() const;
void set_notify_transform(bool p_enable);
bool is_transform_notification_enabled() const;
void force_update_transform();
virtual void set_texture_filter(TextureFilter p_texture_filter);
TextureFilter get_texture_filter() const;
virtual void set_texture_repeat(TextureRepeat p_texture_repeat);
TextureRepeat get_texture_repeat() const;
TextureFilter get_texture_filter_in_tree() const;
TextureRepeat get_texture_repeat_in_tree() const;
// Used by control nodes to retrieve the parent's anchorable area
virtual Rect2 get_anchorable_rect() const { return Rect2(0, 0, 0, 0); }
int get_canvas_layer() const;
CanvasLayer *get_canvas_layer_node() const;
virtual PackedStringArray get_configuration_warnings() const override;
CanvasItem();
~CanvasItem();
};
VARIANT_ENUM_CAST(CanvasItem::TextureFilter)
VARIANT_ENUM_CAST(CanvasItem::TextureRepeat)
VARIANT_ENUM_CAST(CanvasItem::ClipChildrenMode)
class CanvasTexture : public Texture2D {
GDCLASS(CanvasTexture, Texture2D);
OBJ_SAVE_TYPE(Texture2D); // Saves derived classes with common type so they can be interchanged.
Ref<Texture2D> diffuse_texture;
Ref<Texture2D> normal_texture;
Ref<Texture2D> specular_texture;
Color specular = Color(1, 1, 1, 1);
real_t shininess = 1.0;
RID canvas_texture;
CanvasItem::TextureFilter texture_filter = CanvasItem::TEXTURE_FILTER_PARENT_NODE;
CanvasItem::TextureRepeat texture_repeat = CanvasItem::TEXTURE_REPEAT_PARENT_NODE;
protected:
static void _bind_methods();
public:
void set_diffuse_texture(const Ref<Texture2D> &p_diffuse);
Ref<Texture2D> get_diffuse_texture() const;
void set_normal_texture(const Ref<Texture2D> &p_normal);
Ref<Texture2D> get_normal_texture() const;
void set_specular_texture(const Ref<Texture2D> &p_specular);
Ref<Texture2D> get_specular_texture() const;
void set_specular_color(const Color &p_color);
Color get_specular_color() const;
void set_specular_shininess(real_t p_shininess);
real_t get_specular_shininess() const;
void set_texture_filter(CanvasItem::TextureFilter p_filter);
CanvasItem::TextureFilter get_texture_filter() const;
void set_texture_repeat(CanvasItem::TextureRepeat p_repeat);
CanvasItem::TextureRepeat get_texture_repeat() const;
virtual int get_width() const override;
virtual int get_height() const override;
virtual bool is_pixel_opaque(int p_x, int p_y) const override;
virtual bool has_alpha() const override;
virtual Ref<Image> get_image() const override;
virtual RID get_rid() const override;
CanvasTexture();
~CanvasTexture();
};

363
scene/main/canvas_layer.cpp Normal file
View File

@@ -0,0 +1,363 @@
/**************************************************************************/
/* canvas_layer.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "canvas_layer.h"
#include "scene/main/canvas_item.h"
#include "scene/main/viewport.h"
#include "scene/resources/world_2d.h"
void CanvasLayer::set_layer(int p_xform) {
layer = p_xform;
if (viewport.is_valid()) {
RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index());
vp->gui_set_root_order_dirty();
}
}
int CanvasLayer::get_layer() const {
return layer;
}
void CanvasLayer::set_visible(bool p_visible) {
if (p_visible == visible) {
return;
}
visible = p_visible;
emit_signal(SceneStringName(visibility_changed));
for (int i = 0; i < get_child_count(); i++) {
CanvasItem *c = Object::cast_to<CanvasItem>(get_child(i));
if (c) {
RenderingServer::get_singleton()->canvas_item_set_visible(c->get_canvas_item(), p_visible && c->is_visible());
c->_propagate_visibility_changed(p_visible);
}
}
}
void CanvasLayer::show() {
set_visible(true);
}
void CanvasLayer::hide() {
set_visible(false);
}
bool CanvasLayer::is_visible() const {
return visible;
}
void CanvasLayer::set_transform(const Transform2D &p_xform) {
transform = p_xform;
locrotscale_dirty = true;
if (viewport.is_valid()) {
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform);
}
}
Transform2D CanvasLayer::get_transform() const {
return transform;
}
Transform2D CanvasLayer::get_final_transform() const {
if (is_following_viewport()) {
Transform2D follow;
follow.scale(Vector2(get_follow_viewport_scale(), get_follow_viewport_scale()));
if (vp) {
follow = vp->get_canvas_transform() * follow;
}
return follow * transform;
}
return transform;
}
void CanvasLayer::_update_xform() {
transform.set_rotation_and_scale(rot, scale);
transform.set_origin(ofs);
if (viewport.is_valid()) {
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform);
}
}
void CanvasLayer::_update_locrotscale() const {
ofs = transform.columns[2];
rot = transform.get_rotation();
scale = transform.get_scale();
locrotscale_dirty = false;
}
void CanvasLayer::set_offset(const Vector2 &p_offset) {
if (locrotscale_dirty) {
_update_locrotscale();
}
ofs = p_offset;
_update_xform();
}
Vector2 CanvasLayer::get_offset() const {
if (locrotscale_dirty) {
_update_locrotscale();
}
return ofs;
}
void CanvasLayer::set_rotation(real_t p_radians) {
if (locrotscale_dirty) {
_update_locrotscale();
}
rot = p_radians;
_update_xform();
}
real_t CanvasLayer::get_rotation() const {
if (locrotscale_dirty) {
_update_locrotscale();
}
return rot;
}
void CanvasLayer::set_scale(const Vector2 &p_scale) {
if (locrotscale_dirty) {
_update_locrotscale();
}
scale = p_scale;
_update_xform();
}
Vector2 CanvasLayer::get_scale() const {
if (locrotscale_dirty) {
_update_locrotscale();
}
return scale;
}
void CanvasLayer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (custom_viewport && ObjectDB::get_instance(custom_viewport_id)) {
vp = custom_viewport;
} else {
vp = Node::get_viewport();
}
ERR_FAIL_NULL_MSG(vp, "Viewport is not initialized.");
vp->_canvas_layer_add(this);
viewport = vp->get_viewport_rid();
RenderingServer::get_singleton()->viewport_attach_canvas(viewport, canvas);
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform);
_update_follow_viewport();
if (vp) {
get_parent()->connect(SNAME("child_order_changed"), callable_mp(vp, &Viewport::canvas_parent_mark_dirty).bind(get_parent()), CONNECT_REFERENCE_COUNTED);
vp->canvas_parent_mark_dirty(get_parent());
}
} break;
case NOTIFICATION_EXIT_TREE: {
ERR_FAIL_NULL_MSG(vp, "Viewport is not initialized.");
get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(vp, &Viewport::canvas_parent_mark_dirty).bind(get_parent()));
vp->_canvas_layer_remove(this);
RenderingServer::get_singleton()->viewport_remove_canvas(viewport, canvas);
viewport = RID();
_update_follow_viewport(false);
} break;
}
}
void CanvasLayer::update_draw_order() {
if (is_inside_tree()) {
RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index());
}
}
Size2 CanvasLayer::get_viewport_size() const {
if (!is_inside_tree()) {
return Size2(1, 1);
}
ERR_FAIL_NULL_V_MSG(vp, Size2(1, 1), "Viewport is not initialized.");
Rect2 r = vp->get_visible_rect();
return r.size;
}
RID CanvasLayer::get_viewport() const {
return viewport;
}
void CanvasLayer::set_custom_viewport(Node *p_viewport) {
ERR_FAIL_NULL_MSG(p_viewport, "Cannot set viewport to nullptr.");
if (is_inside_tree()) {
vp->_canvas_layer_remove(this);
RenderingServer::get_singleton()->viewport_remove_canvas(viewport, canvas);
viewport = RID();
}
custom_viewport = Object::cast_to<Viewport>(p_viewport);
if (custom_viewport) {
custom_viewport_id = custom_viewport->get_instance_id();
} else {
custom_viewport_id = ObjectID();
}
if (is_inside_tree()) {
if (custom_viewport) {
vp = custom_viewport;
} else {
vp = Node::get_viewport();
}
vp->_canvas_layer_add(this);
viewport = vp->get_viewport_rid();
RenderingServer::get_singleton()->viewport_attach_canvas(viewport, canvas);
RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index());
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform);
}
}
Node *CanvasLayer::get_custom_viewport() const {
return custom_viewport;
}
void CanvasLayer::reset_sort_index() {
sort_index = 0;
}
int CanvasLayer::get_sort_index() {
return sort_index++;
}
RID CanvasLayer::get_canvas() const {
return canvas;
}
void CanvasLayer::set_follow_viewport(bool p_enable) {
if (follow_viewport == p_enable) {
return;
}
follow_viewport = p_enable;
_update_follow_viewport();
}
bool CanvasLayer::is_following_viewport() const {
return follow_viewport;
}
void CanvasLayer::set_follow_viewport_scale(float p_ratio) {
follow_viewport_scale = p_ratio;
_update_follow_viewport();
}
float CanvasLayer::get_follow_viewport_scale() const {
return follow_viewport_scale;
}
void CanvasLayer::_update_follow_viewport(bool p_force_exit) {
if (!is_inside_tree()) {
return;
}
if (p_force_exit || !follow_viewport) {
RS::get_singleton()->canvas_set_parent(canvas, RID(), 1.0);
} else {
RS::get_singleton()->canvas_set_parent(canvas, vp->get_world_2d()->get_canvas(), follow_viewport_scale);
}
}
void CanvasLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_layer", "layer"), &CanvasLayer::set_layer);
ClassDB::bind_method(D_METHOD("get_layer"), &CanvasLayer::get_layer);
ClassDB::bind_method(D_METHOD("set_visible", "visible"), &CanvasLayer::set_visible);
ClassDB::bind_method(D_METHOD("is_visible"), &CanvasLayer::is_visible);
ClassDB::bind_method(D_METHOD("show"), &CanvasLayer::show);
ClassDB::bind_method(D_METHOD("hide"), &CanvasLayer::hide);
ClassDB::bind_method(D_METHOD("set_transform", "transform"), &CanvasLayer::set_transform);
ClassDB::bind_method(D_METHOD("get_transform"), &CanvasLayer::get_transform);
ClassDB::bind_method(D_METHOD("get_final_transform"), &CanvasLayer::get_final_transform);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &CanvasLayer::set_offset);
ClassDB::bind_method(D_METHOD("get_offset"), &CanvasLayer::get_offset);
ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &CanvasLayer::set_rotation);
ClassDB::bind_method(D_METHOD("get_rotation"), &CanvasLayer::get_rotation);
ClassDB::bind_method(D_METHOD("set_scale", "scale"), &CanvasLayer::set_scale);
ClassDB::bind_method(D_METHOD("get_scale"), &CanvasLayer::get_scale);
ClassDB::bind_method(D_METHOD("set_follow_viewport", "enable"), &CanvasLayer::set_follow_viewport);
ClassDB::bind_method(D_METHOD("is_following_viewport"), &CanvasLayer::is_following_viewport);
ClassDB::bind_method(D_METHOD("set_follow_viewport_scale", "scale"), &CanvasLayer::set_follow_viewport_scale);
ClassDB::bind_method(D_METHOD("get_follow_viewport_scale"), &CanvasLayer::get_follow_viewport_scale);
ClassDB::bind_method(D_METHOD("set_custom_viewport", "viewport"), &CanvasLayer::set_custom_viewport);
ClassDB::bind_method(D_METHOD("get_custom_viewport"), &CanvasLayer::get_custom_viewport);
ClassDB::bind_method(D_METHOD("get_canvas"), &CanvasLayer::get_canvas);
ADD_GROUP("Layer", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "layer", PROPERTY_HINT_RANGE, itos(RS::CANVAS_LAYER_MIN) + "," + itos(RS::CANVAS_LAYER_MAX) + ",1"), "set_layer", "get_layer");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
ADD_GROUP("Transform", "");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-1080,1080,0.1,or_less,or_greater,radians_as_degrees"), "set_rotation", "get_rotation");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "suffix:px"), "set_transform", "get_transform");
ADD_GROUP("", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport", PROPERTY_USAGE_NONE), "set_custom_viewport", "get_custom_viewport");
ADD_GROUP("Follow Viewport", "follow_viewport");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_viewport_enabled", PROPERTY_HINT_GROUP_ENABLE), "set_follow_viewport", "is_following_viewport");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "follow_viewport_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001,or_greater,or_less"), "set_follow_viewport_scale", "get_follow_viewport_scale");
ADD_SIGNAL(MethodInfo("visibility_changed"));
}
CanvasLayer::CanvasLayer() {
canvas = RS::get_singleton()->canvas_create();
}
CanvasLayer::~CanvasLayer() {
ERR_FAIL_NULL(RenderingServer::get_singleton());
RS::get_singleton()->free(canvas);
}

111
scene/main/canvas_layer.h Normal file
View File

@@ -0,0 +1,111 @@
/**************************************************************************/
/* canvas_layer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
class Viewport;
class CanvasLayer : public Node {
GDCLASS(CanvasLayer, Node);
mutable bool locrotscale_dirty = false;
mutable Vector2 ofs;
mutable Size2 scale = Vector2(1, 1);
mutable real_t rot = 0.0;
int layer = 1;
Transform2D transform;
RID canvas;
ObjectID custom_viewport_id; // to check validity
Viewport *custom_viewport = nullptr;
RID viewport;
Viewport *vp = nullptr;
int sort_index = 0;
bool visible = true;
bool follow_viewport = false;
float follow_viewport_scale = 1.0;
void _update_xform();
void _update_locrotscale() const;
void _update_follow_viewport(bool p_force_exit = false);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void update_draw_order();
void set_layer(int p_xform);
int get_layer() const;
void set_visible(bool p_visible);
bool is_visible() const;
void show();
void hide();
void set_transform(const Transform2D &p_xform);
Transform2D get_transform() const;
Transform2D get_final_transform() const;
void set_offset(const Vector2 &p_offset);
Vector2 get_offset() const;
void set_rotation(real_t p_radians);
real_t get_rotation() const;
void set_scale(const Size2 &p_scale);
Size2 get_scale() const;
Size2 get_viewport_size() const;
RID get_viewport() const;
void set_custom_viewport(Node *p_viewport);
Node *get_custom_viewport() const;
void reset_sort_index();
int get_sort_index();
void set_follow_viewport(bool p_enable);
bool is_following_viewport() const;
void set_follow_viewport_scale(float p_ratio);
float get_follow_viewport_scale() const;
RID get_canvas() const;
CanvasLayer();
~CanvasLayer();
};

671
scene/main/http_request.cpp Normal file
View File

@@ -0,0 +1,671 @@
/**************************************************************************/
/* http_request.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "http_request.h"
#include "scene/main/timer.h"
Error HTTPRequest::_request() {
return client->connect_to_host(url, port, use_tls ? tls_options : nullptr);
}
Error HTTPRequest::_parse_url(const String &p_url) {
use_tls = false;
request_string = "";
port = 80;
request_sent = false;
got_response = false;
body_len = -1;
body.clear();
downloaded.set(0);
final_body_size.set(0);
redirections = 0;
String scheme;
String fragment;
Error err = p_url.parse_url(scheme, url, port, request_string, fragment);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error parsing URL: '%s'.", p_url));
if (scheme == "https://") {
use_tls = true;
} else if (scheme != "http://") {
ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("Invalid URL scheme: '%s'.", scheme));
}
if (port == 0) {
port = use_tls ? 443 : 80;
}
if (request_string.is_empty()) {
request_string = "/";
}
return OK;
}
bool HTTPRequest::has_header(const PackedStringArray &p_headers, const String &p_header_name) {
bool exists = false;
String lower_case_header_name = p_header_name.to_lower();
for (int i = 0; i < p_headers.size() && !exists; i++) {
String sanitized = p_headers[i].strip_edges().to_lower();
if (sanitized.begins_with(lower_case_header_name)) {
exists = true;
}
}
return exists;
}
String HTTPRequest::get_header_value(const PackedStringArray &p_headers, const String &p_header_name) {
String value = "";
String lowwer_case_header_name = p_header_name.to_lower();
for (int i = 0; i < p_headers.size(); i++) {
if (p_headers[i].find_char(':') > 0) {
Vector<String> parts = p_headers[i].split(":", false, 1);
if (parts.size() > 1 && parts[0].strip_edges().to_lower() == lowwer_case_header_name) {
value = parts[1].strip_edges();
break;
}
}
}
return value;
}
Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_headers, HTTPClient::Method p_method, const String &p_request_data) {
// Copy the string into a raw buffer.
Vector<uint8_t> raw_data;
CharString charstr = p_request_data.utf8();
size_t len = charstr.length();
if (len > 0) {
raw_data.resize(len);
uint8_t *w = raw_data.ptrw();
memcpy(w, charstr.ptr(), len);
}
return request_raw(p_url, p_custom_headers, p_method, raw_data);
}
Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &p_custom_headers, HTTPClient::Method p_method, const Vector<uint8_t> &p_request_data_raw) {
ERR_FAIL_COND_V(!is_inside_tree(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V_MSG(requesting, ERR_BUSY, "HTTPRequest is processing a request. Wait for completion or cancel it before attempting a new one.");
if (timeout > 0) {
timer->stop();
timer->start(timeout);
}
method = p_method;
Error err = _parse_url(p_url);
if (err) {
return err;
}
headers = p_custom_headers;
if (accept_gzip) {
// If the user has specified an Accept-Encoding header, don't overwrite it.
if (!has_header(headers, "Accept-Encoding")) {
headers.push_back("Accept-Encoding: gzip, deflate");
}
}
request_data = p_request_data_raw;
requesting = true;
if (use_threads.is_set()) {
thread_done.clear();
thread_request_quit.clear();
client->set_blocking_mode(true);
thread.start(_thread_func, this);
} else {
client->set_blocking_mode(false);
err = _request();
if (err != OK) {
_defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray());
return ERR_CANT_CONNECT;
}
set_process_internal(true);
}
return OK;
}
void HTTPRequest::_thread_func(void *p_userdata) {
HTTPRequest *hr = static_cast<HTTPRequest *>(p_userdata);
Error err = hr->_request();
if (err != OK) {
hr->_defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray());
} else {
while (!hr->thread_request_quit.is_set()) {
bool exit = hr->_update_connection();
if (exit) {
break;
}
OS::get_singleton()->delay_usec(1);
}
}
hr->thread_done.set();
}
void HTTPRequest::cancel_request() {
timer->stop();
if (!requesting) {
return;
}
if (!use_threads.is_set()) {
set_process_internal(false);
} else {
thread_request_quit.set();
if (thread.is_started()) {
thread.wait_to_finish();
}
}
file.unref();
decompressor.unref();
client->close();
body.clear();
got_response = false;
response_code = -1;
request_sent = false;
requesting = false;
}
bool HTTPRequest::_handle_response(bool *ret_value) {
if (!client->has_response()) {
_defer_done(RESULT_NO_RESPONSE, 0, PackedStringArray(), PackedByteArray());
*ret_value = true;
return true;
}
got_response = true;
response_code = client->get_response_code();
List<String> rheaders;
client->get_response_headers(&rheaders);
response_headers.clear();
downloaded.set(0);
final_body_size.set(0);
decompressor.unref();
for (const String &E : rheaders) {
response_headers.push_back(E);
}
if (response_code == 301 || response_code == 302) {
// Handle redirect.
if (max_redirects >= 0 && redirections >= max_redirects) {
_defer_done(RESULT_REDIRECT_LIMIT_REACHED, response_code, response_headers, PackedByteArray());
*ret_value = true;
return true;
}
String new_request;
for (const String &E : rheaders) {
if (E.to_lower().begins_with("location: ")) {
new_request = E.substr(9).strip_edges();
}
}
if (!new_request.is_empty()) {
// Process redirect.
client->close();
int new_redirs = redirections + 1; // Because _request() will clear it.
Error err;
if (new_request.begins_with("http")) {
// New url, new request.
_parse_url(new_request);
} else {
request_string = new_request;
}
err = _request();
if (err == OK) {
request_sent = false;
got_response = false;
body_len = -1;
body.clear();
downloaded.set(0);
final_body_size.set(0);
redirections = new_redirs;
*ret_value = false;
return true;
}
}
}
// Check if we need to start streaming decompression.
String content_encoding;
if (accept_gzip) {
content_encoding = get_header_value(response_headers, "Content-Encoding").to_lower();
}
if (content_encoding == "gzip") {
decompressor.instantiate();
decompressor->start_decompression(false, get_download_chunk_size());
} else if (content_encoding == "deflate") {
decompressor.instantiate();
decompressor->start_decompression(true, get_download_chunk_size());
}
return false;
}
bool HTTPRequest::_update_connection() {
switch (client->get_status()) {
case HTTPClient::STATUS_DISCONNECTED: {
_defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray());
return true; // End it, since it's disconnected.
} break;
case HTTPClient::STATUS_RESOLVING: {
client->poll();
// Must wait.
return false;
} break;
case HTTPClient::STATUS_CANT_RESOLVE: {
_defer_done(RESULT_CANT_RESOLVE, 0, PackedStringArray(), PackedByteArray());
return true;
} break;
case HTTPClient::STATUS_CONNECTING: {
client->poll();
// Must wait.
return false;
} break; // Connecting to IP.
case HTTPClient::STATUS_CANT_CONNECT: {
_defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray());
return true;
} break;
case HTTPClient::STATUS_CONNECTED: {
if (request_sent) {
if (!got_response) {
// No body.
bool ret_value;
if (_handle_response(&ret_value)) {
return ret_value;
}
_defer_done(RESULT_SUCCESS, response_code, response_headers, PackedByteArray());
return true;
}
if (body_len < 0) {
// Chunked transfer is done.
_defer_done(RESULT_SUCCESS, response_code, response_headers, body);
return true;
}
_defer_done(RESULT_CHUNKED_BODY_SIZE_MISMATCH, response_code, response_headers, PackedByteArray());
return true;
// Request might have been done.
} else {
// Did not request yet, do request.
int size = request_data.size();
Error err = client->request(method, request_string, headers, size > 0 ? request_data.ptr() : nullptr, size);
if (err != OK) {
_defer_done(RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray());
return true;
}
request_sent = true;
return false;
}
} break; // Connected: break requests only accepted here.
case HTTPClient::STATUS_REQUESTING: {
// Must wait, still requesting.
client->poll();
return false;
} break; // Request in progress.
case HTTPClient::STATUS_BODY: {
if (!got_response) {
bool ret_value;
if (_handle_response(&ret_value)) {
return ret_value;
}
if (!client->is_response_chunked() && client->get_response_body_length() == 0) {
_defer_done(RESULT_SUCCESS, response_code, response_headers, PackedByteArray());
return true;
}
// No body len (-1) if chunked or no content-length header was provided.
// Change your webserver configuration if you want body len.
body_len = client->get_response_body_length();
if (body_size_limit >= 0 && body_len > body_size_limit) {
_defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray());
return true;
}
if (!download_to_file.is_empty()) {
file = FileAccess::open(download_to_file, FileAccess::WRITE);
if (file.is_null()) {
_defer_done(RESULT_DOWNLOAD_FILE_CANT_OPEN, response_code, response_headers, PackedByteArray());
return true;
}
}
}
client->poll();
if (client->get_status() != HTTPClient::STATUS_BODY) {
return false;
}
PackedByteArray chunk;
if (decompressor.is_null()) {
// Chunk can be read directly.
chunk = client->read_response_body_chunk();
downloaded.add(chunk.size());
} else {
// Chunk is the result of decompression.
PackedByteArray compressed = client->read_response_body_chunk();
downloaded.add(compressed.size());
int pos = 0;
int left = compressed.size();
while (left) {
int w = 0;
Error err = decompressor->put_partial_data(compressed.ptr() + pos, left, w);
if (err == OK) {
PackedByteArray dc;
dc.resize(decompressor->get_available_bytes());
err = decompressor->get_data(dc.ptrw(), dc.size());
chunk.append_array(dc);
}
if (err != OK) {
_defer_done(RESULT_BODY_DECOMPRESS_FAILED, response_code, response_headers, PackedByteArray());
return true;
}
// We need this check here because a "zip bomb" could result in a chunk of few kilos decompressing into gigabytes of data.
if (body_size_limit >= 0 && final_body_size.get() + chunk.size() > body_size_limit) {
_defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray());
return true;
}
pos += w;
left -= w;
}
}
final_body_size.add(chunk.size());
if (body_size_limit >= 0 && final_body_size.get() > body_size_limit) {
_defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray());
return true;
}
if (chunk.size()) {
if (file.is_valid()) {
const uint8_t *r = chunk.ptr();
file->store_buffer(r, chunk.size());
if (file->get_error() != OK) {
_defer_done(RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PackedByteArray());
return true;
}
} else {
body.append_array(chunk);
}
}
if (body_len >= 0) {
if (downloaded.get() == body_len) {
_defer_done(RESULT_SUCCESS, response_code, response_headers, body);
return true;
}
} else if (client->get_status() == HTTPClient::STATUS_DISCONNECTED) {
// We read till EOF, with no errors. Request is done.
_defer_done(RESULT_SUCCESS, response_code, response_headers, body);
return true;
}
return false;
} break; // Request resulted in body: break which must be read.
case HTTPClient::STATUS_CONNECTION_ERROR: {
_defer_done(RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray());
return true;
} break;
case HTTPClient::STATUS_TLS_HANDSHAKE_ERROR: {
_defer_done(RESULT_TLS_HANDSHAKE_ERROR, 0, PackedStringArray(), PackedByteArray());
return true;
} break;
}
ERR_FAIL_V(false);
}
void HTTPRequest::_defer_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data) {
callable_mp(this, &HTTPRequest::_request_done).call_deferred(p_status, p_code, p_headers, p_data);
}
void HTTPRequest::_request_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data) {
cancel_request();
emit_signal(SNAME("request_completed"), p_status, p_code, p_headers, p_data);
}
void HTTPRequest::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
if (use_threads.is_set()) {
return;
}
bool done = _update_connection();
if (done) {
set_process_internal(false);
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (requesting) {
cancel_request();
}
} break;
}
}
void HTTPRequest::set_use_threads(bool p_use) {
ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
#ifdef THREADS_ENABLED
use_threads.set_to(p_use);
#endif
}
bool HTTPRequest::is_using_threads() const {
return use_threads.is_set();
}
void HTTPRequest::set_accept_gzip(bool p_gzip) {
accept_gzip = p_gzip;
}
bool HTTPRequest::is_accepting_gzip() const {
return accept_gzip;
}
void HTTPRequest::set_body_size_limit(int p_bytes) {
ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
body_size_limit = p_bytes;
}
int HTTPRequest::get_body_size_limit() const {
return body_size_limit;
}
void HTTPRequest::set_download_file(const String &p_file) {
ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
download_to_file = p_file;
}
String HTTPRequest::get_download_file() const {
return download_to_file;
}
void HTTPRequest::set_download_chunk_size(int p_chunk_size) {
ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
client->set_read_chunk_size(p_chunk_size);
}
int HTTPRequest::get_download_chunk_size() const {
return client->get_read_chunk_size();
}
HTTPClient::Status HTTPRequest::get_http_client_status() const {
return client->get_status();
}
void HTTPRequest::set_max_redirects(int p_max) {
max_redirects = p_max;
}
int HTTPRequest::get_max_redirects() const {
return max_redirects;
}
int HTTPRequest::get_downloaded_bytes() const {
return downloaded.get();
}
int HTTPRequest::get_body_size() const {
return body_len;
}
void HTTPRequest::set_http_proxy(const String &p_host, int p_port) {
client->set_http_proxy(p_host, p_port);
}
void HTTPRequest::set_https_proxy(const String &p_host, int p_port) {
client->set_https_proxy(p_host, p_port);
}
void HTTPRequest::set_timeout(double p_timeout) {
ERR_FAIL_COND(p_timeout < 0);
timeout = p_timeout;
}
double HTTPRequest::get_timeout() {
return timeout;
}
void HTTPRequest::_timeout() {
cancel_request();
_defer_done(RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray());
}
void HTTPRequest::set_tls_options(const Ref<TLSOptions> &p_options) {
ERR_FAIL_COND(p_options.is_null() || p_options->is_server());
tls_options = p_options;
}
void HTTPRequest::_bind_methods() {
ClassDB::bind_method(D_METHOD("request", "url", "custom_headers", "method", "request_data"), &HTTPRequest::request, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String()));
ClassDB::bind_method(D_METHOD("request_raw", "url", "custom_headers", "method", "request_data_raw"), &HTTPRequest::request_raw, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(PackedByteArray()));
ClassDB::bind_method(D_METHOD("cancel_request"), &HTTPRequest::cancel_request);
ClassDB::bind_method(D_METHOD("set_tls_options", "client_options"), &HTTPRequest::set_tls_options);
ClassDB::bind_method(D_METHOD("get_http_client_status"), &HTTPRequest::get_http_client_status);
ClassDB::bind_method(D_METHOD("set_use_threads", "enable"), &HTTPRequest::set_use_threads);
ClassDB::bind_method(D_METHOD("is_using_threads"), &HTTPRequest::is_using_threads);
ClassDB::bind_method(D_METHOD("set_accept_gzip", "enable"), &HTTPRequest::set_accept_gzip);
ClassDB::bind_method(D_METHOD("is_accepting_gzip"), &HTTPRequest::is_accepting_gzip);
ClassDB::bind_method(D_METHOD("set_body_size_limit", "bytes"), &HTTPRequest::set_body_size_limit);
ClassDB::bind_method(D_METHOD("get_body_size_limit"), &HTTPRequest::get_body_size_limit);
ClassDB::bind_method(D_METHOD("set_max_redirects", "amount"), &HTTPRequest::set_max_redirects);
ClassDB::bind_method(D_METHOD("get_max_redirects"), &HTTPRequest::get_max_redirects);
ClassDB::bind_method(D_METHOD("set_download_file", "path"), &HTTPRequest::set_download_file);
ClassDB::bind_method(D_METHOD("get_download_file"), &HTTPRequest::get_download_file);
ClassDB::bind_method(D_METHOD("get_downloaded_bytes"), &HTTPRequest::get_downloaded_bytes);
ClassDB::bind_method(D_METHOD("get_body_size"), &HTTPRequest::get_body_size);
ClassDB::bind_method(D_METHOD("set_timeout", "timeout"), &HTTPRequest::set_timeout);
ClassDB::bind_method(D_METHOD("get_timeout"), &HTTPRequest::get_timeout);
ClassDB::bind_method(D_METHOD("set_download_chunk_size", "chunk_size"), &HTTPRequest::set_download_chunk_size);
ClassDB::bind_method(D_METHOD("get_download_chunk_size"), &HTTPRequest::get_download_chunk_size);
ClassDB::bind_method(D_METHOD("set_http_proxy", "host", "port"), &HTTPRequest::set_http_proxy);
ClassDB::bind_method(D_METHOD("set_https_proxy", "host", "port"), &HTTPRequest::set_https_proxy);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "download_file", PROPERTY_HINT_FILE_PATH), "set_download_file", "get_download_file");
ADD_PROPERTY(PropertyInfo(Variant::INT, "download_chunk_size", PROPERTY_HINT_RANGE, "256,16777216,suffix:B"), "set_download_chunk_size", "get_download_chunk_size");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_threads"), "set_use_threads", "is_using_threads");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "accept_gzip"), "set_accept_gzip", "is_accepting_gzip");
ADD_PROPERTY(PropertyInfo(Variant::INT, "body_size_limit", PROPERTY_HINT_RANGE, "-1,2000000000,suffix:B"), "set_body_size_limit", "get_body_size_limit");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_redirects", PROPERTY_HINT_RANGE, "-1,64"), "set_max_redirects", "get_max_redirects");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeout", PROPERTY_HINT_RANGE, "0,3600,0.1,or_greater,suffix:s"), "set_timeout", "get_timeout");
ADD_SIGNAL(MethodInfo("request_completed", PropertyInfo(Variant::INT, "result"), PropertyInfo(Variant::INT, "response_code"), PropertyInfo(Variant::PACKED_STRING_ARRAY, "headers"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "body")));
BIND_ENUM_CONSTANT(RESULT_SUCCESS);
BIND_ENUM_CONSTANT(RESULT_CHUNKED_BODY_SIZE_MISMATCH);
BIND_ENUM_CONSTANT(RESULT_CANT_CONNECT);
BIND_ENUM_CONSTANT(RESULT_CANT_RESOLVE);
BIND_ENUM_CONSTANT(RESULT_CONNECTION_ERROR);
BIND_ENUM_CONSTANT(RESULT_TLS_HANDSHAKE_ERROR);
BIND_ENUM_CONSTANT(RESULT_NO_RESPONSE);
BIND_ENUM_CONSTANT(RESULT_BODY_SIZE_LIMIT_EXCEEDED);
BIND_ENUM_CONSTANT(RESULT_BODY_DECOMPRESS_FAILED);
BIND_ENUM_CONSTANT(RESULT_REQUEST_FAILED);
BIND_ENUM_CONSTANT(RESULT_DOWNLOAD_FILE_CANT_OPEN);
BIND_ENUM_CONSTANT(RESULT_DOWNLOAD_FILE_WRITE_ERROR);
BIND_ENUM_CONSTANT(RESULT_REDIRECT_LIMIT_REACHED);
BIND_ENUM_CONSTANT(RESULT_TIMEOUT);
}
HTTPRequest::HTTPRequest() {
client = Ref<HTTPClient>(HTTPClient::create());
tls_options = TLSOptions::client();
timer = memnew(Timer);
timer->set_one_shot(true);
timer->connect("timeout", callable_mp(this, &HTTPRequest::_timeout));
add_child(timer);
}

168
scene/main/http_request.h Normal file
View File

@@ -0,0 +1,168 @@
/**************************************************************************/
/* http_request.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/http_client.h"
#include "core/io/stream_peer_gzip.h"
#include "core/os/thread.h"
#include "core/templates/safe_refcount.h"
#include "scene/main/node.h"
class Timer;
class HTTPRequest : public Node {
GDCLASS(HTTPRequest, Node);
public:
enum Result {
RESULT_SUCCESS,
RESULT_CHUNKED_BODY_SIZE_MISMATCH,
RESULT_CANT_CONNECT,
RESULT_CANT_RESOLVE,
RESULT_CONNECTION_ERROR,
RESULT_TLS_HANDSHAKE_ERROR,
RESULT_NO_RESPONSE,
RESULT_BODY_SIZE_LIMIT_EXCEEDED,
RESULT_BODY_DECOMPRESS_FAILED,
RESULT_REQUEST_FAILED,
RESULT_DOWNLOAD_FILE_CANT_OPEN,
RESULT_DOWNLOAD_FILE_WRITE_ERROR,
RESULT_REDIRECT_LIMIT_REACHED,
RESULT_TIMEOUT
};
private:
bool requesting = false;
String request_string;
String url;
int port = 80;
Vector<String> headers;
bool use_tls = false;
Ref<TLSOptions> tls_options;
HTTPClient::Method method;
Vector<uint8_t> request_data;
bool request_sent = false;
Ref<HTTPClient> client;
PackedByteArray body;
SafeFlag use_threads;
bool accept_gzip = true;
bool got_response = false;
int response_code = 0;
Vector<String> response_headers;
String download_to_file;
Ref<StreamPeerGZIP> decompressor;
Ref<FileAccess> file;
int body_len = -1;
SafeNumeric<int> downloaded;
SafeNumeric<int> final_body_size;
int body_size_limit = -1;
int redirections = 0;
bool _update_connection();
int max_redirects = 8;
double timeout = 0;
void _redirect_request(const String &p_new_url);
bool _handle_response(bool *ret_value);
Error _parse_url(const String &p_url);
Error _request();
bool has_header(const PackedStringArray &p_headers, const String &p_header_name);
String get_header_value(const PackedStringArray &p_headers, const String &header_name);
SafeFlag thread_done;
SafeFlag thread_request_quit;
Thread thread;
void _defer_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data);
void _request_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data);
static void _thread_func(void *p_userdata);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
Error request(const String &p_url, const Vector<String> &p_custom_headers = Vector<String>(), HTTPClient::Method p_method = HTTPClient::METHOD_GET, const String &p_request_data = ""); //connects to a full url and perform request
Error request_raw(const String &p_url, const Vector<String> &p_custom_headers = Vector<String>(), HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Vector<uint8_t> &p_request_data_raw = Vector<uint8_t>()); //connects to a full url and perform request
void cancel_request();
HTTPClient::Status get_http_client_status() const;
void set_use_threads(bool p_use);
bool is_using_threads() const;
void set_accept_gzip(bool p_gzip);
bool is_accepting_gzip() const;
void set_download_file(const String &p_file);
String get_download_file() const;
void set_download_chunk_size(int p_chunk_size);
int get_download_chunk_size() const;
void set_body_size_limit(int p_bytes);
int get_body_size_limit() const;
void set_max_redirects(int p_max);
int get_max_redirects() const;
Timer *timer = nullptr;
void set_timeout(double p_timeout);
double get_timeout();
void _timeout();
int get_downloaded_bytes() const;
int get_body_size() const;
void set_http_proxy(const String &p_host, int p_port);
void set_https_proxy(const String &p_host, int p_port);
void set_tls_options(const Ref<TLSOptions> &p_options);
HTTPRequest();
};
VARIANT_ENUM_CAST(HTTPRequest::Result);

View File

@@ -0,0 +1,257 @@
/**************************************************************************/
/* instance_placeholder.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "instance_placeholder.h"
#include "core/io/resource_loader.h"
#include "scene/resources/packed_scene.h"
bool InstancePlaceholder::_set(const StringName &p_name, const Variant &p_value) {
PropSet ps;
ps.name = p_name;
ps.value = p_value;
stored_values.push_back(ps);
return true;
}
bool InstancePlaceholder::_get(const StringName &p_name, Variant &r_ret) const {
for (const PropSet &E : stored_values) {
if (E.name == p_name) {
r_ret = E.value;
return true;
}
}
return false;
}
void InstancePlaceholder::_get_property_list(List<PropertyInfo> *p_list) const {
for (const PropSet &E : stored_values) {
PropertyInfo pi;
pi.name = E.name;
pi.type = E.value.get_type();
pi.usage = PROPERTY_USAGE_STORAGE;
p_list->push_back(pi);
}
}
void InstancePlaceholder::set_instance_path(const String &p_name) {
path = p_name;
}
String InstancePlaceholder::get_instance_path() const {
return path;
}
Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene> &p_custom_scene) {
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
Node *base = get_parent();
if (!base) {
return nullptr;
}
Ref<PackedScene> ps;
if (p_custom_scene.is_valid()) {
ps = p_custom_scene;
} else {
ps = ResourceLoader::load(path, "PackedScene");
}
if (ps.is_null()) {
return nullptr;
}
Node *instance = ps->instantiate();
if (!instance) {
return nullptr;
}
instance->set_name(get_name());
instance->set_multiplayer_authority(get_multiplayer_authority());
int pos = get_index();
for (const PropSet &E : stored_values) {
set_value_on_instance(this, instance, E);
}
if (p_replace) {
queue_free();
base->remove_child(this);
}
base->add_child(instance);
base->move_child(instance, pos);
return instance;
}
// This method will attempt to set the correct values on the placeholder instance
// for regular types this is trivial and unnecessary.
// For nodes however this becomes a bit tricky because they might now have existed until the instantiation,
// so this method will try to find the correct nodes and resolve them.
void InstancePlaceholder::set_value_on_instance(InstancePlaceholder *p_placeholder, Node *p_instance, const PropSet &p_set) {
bool is_valid;
// If we don't have any info, we can't do anything,
// so try setting the value directly.
Variant current = p_instance->get(p_set.name, &is_valid);
if (!is_valid) {
p_instance->set(p_set.name, p_set.value, &is_valid);
return;
}
Variant::Type current_type = current.get_type();
Variant::Type placeholder_type = p_set.value.get_type();
// Arrays are a special case, because their containing type might be different.
if (current_type != Variant::Type::ARRAY) {
// Check if the variant types match.
if (Variant::evaluate(Variant::OP_EQUAL, current_type, placeholder_type)) {
p_instance->set(p_set.name, p_set.value, &is_valid);
if (is_valid) {
return;
}
// Types match but setting failed? This is strange, so let's print a warning!
WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name()));
return;
}
} else {
// We are dealing with an Array.
// Let's check if the subtype of the array matches first.
// This is needed because the set method of ScriptInstance checks for type,
// but the ClassDB set method doesn't! So we cannot reliably know what actually happens.
Array current_array = current;
Array placeholder_array = p_set.value;
if (current_array.is_same_typed(placeholder_array)) {
p_instance->set(p_set.name, p_set.value, &is_valid);
if (is_valid) {
return;
}
// Internal array types match but setting failed? This is strange, so let's print a warning!
WARN_PRINT(vformat("Array Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(Variant::Type(current_array.get_typed_builtin())), p_placeholder->get_name()));
}
// Arrays are not the same internal type. This should be happening because we have a NodePath Array,
// but the instance wants a Node Array.
}
switch (current_type) {
case Variant::Type::NIL: {
Ref<Resource> resource = p_set.value;
if (placeholder_type != Variant::Type::NODE_PATH && resource.is_null()) {
break;
}
// If it's nil but we have a NodePath or a Resource, we guess what works.
p_instance->set(p_set.name, p_set.value, &is_valid);
if (is_valid) {
break;
}
p_instance->set(p_set.name, try_get_node(p_placeholder, p_instance, p_set.value), &is_valid);
break;
}
case Variant::Type::OBJECT: {
if (placeholder_type != Variant::Type::NODE_PATH) {
break;
}
// Easiest case, we want a node, but we have a deferred NodePath.
p_instance->set(p_set.name, try_get_node(p_placeholder, p_instance, p_set.value));
break;
}
case Variant::Type::ARRAY: {
// If we have reached here it means our array types don't match,
// so we will convert the placeholder array into the correct type
// and resolve nodes if necessary.
Array current_array = current;
Array converted_array;
Array placeholder_array = p_set.value;
converted_array = current_array.duplicate();
converted_array.resize(placeholder_array.size());
if (Variant::evaluate(Variant::OP_EQUAL, current_array.get_typed_builtin(), Variant::Type::NODE_PATH)) {
// We want a typed NodePath array.
for (int i = 0; i < placeholder_array.size(); i++) {
converted_array.set(i, placeholder_array[i]);
}
} else {
// We want Nodes, convert NodePaths.
for (int i = 0; i < placeholder_array.size(); i++) {
converted_array.set(i, try_get_node(p_placeholder, p_instance, placeholder_array[i]));
}
}
p_instance->set(p_set.name, converted_array, &is_valid);
if (!is_valid) {
WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name()));
}
break;
}
default: {
WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name()));
break;
}
}
}
Node *InstancePlaceholder::try_get_node(InstancePlaceholder *p_placeholder, Node *p_instance, const NodePath &p_path) {
// First try to resolve internally,
// if that fails try resolving externally.
Node *node = p_instance->get_node_or_null(p_path);
if (node == nullptr) {
node = p_placeholder->get_node_or_null(p_path);
}
return node;
}
Dictionary InstancePlaceholder::get_stored_values(bool p_with_order) {
Dictionary ret;
PackedStringArray order;
for (const PropSet &E : stored_values) {
ret[E.name] = E.value;
if (p_with_order) {
order.push_back(E.name);
}
};
if (p_with_order) {
ret[".order"] = order;
}
return ret;
}
void InstancePlaceholder::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_stored_values", "with_order"), &InstancePlaceholder::get_stored_values, DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_instance", "replace", "custom_scene"), &InstancePlaceholder::create_instance, DEFVAL(false), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("get_instance_path"), &InstancePlaceholder::get_instance_path);
}
InstancePlaceholder::InstancePlaceholder() {
}

View File

@@ -0,0 +1,68 @@
/**************************************************************************/
/* instance_placeholder.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
class PackedScene;
class InstancePlaceholder : public Node {
GDCLASS(InstancePlaceholder, Node);
String path;
struct PropSet {
StringName name;
Variant value;
};
List<PropSet> stored_values;
private:
void set_value_on_instance(InstancePlaceholder *p_placeholder, Node *p_instance, const PropSet &p_set);
Node *try_get_node(InstancePlaceholder *p_placeholder, Node *p_instance, const NodePath &p_path);
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
void set_instance_path(const String &p_name);
String get_instance_path() const;
Dictionary get_stored_values(bool p_with_order = false);
Node *create_instance(bool p_replace = false, const Ref<PackedScene> &p_custom_scene = Ref<PackedScene>());
InstancePlaceholder();
};

117
scene/main/missing_node.cpp Normal file
View File

@@ -0,0 +1,117 @@
/**************************************************************************/
/* missing_node.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "missing_node.h"
bool MissingNode::_set(const StringName &p_name, const Variant &p_value) {
if (is_recording_properties()) {
properties.insert(p_name, p_value);
return true; //always valid to set (add)
} else {
if (!properties.has(p_name)) {
return false;
}
properties[p_name] = p_value;
return true;
}
}
bool MissingNode::_get(const StringName &p_name, Variant &r_ret) const {
if (!properties.has(p_name)) {
return false;
}
r_ret = properties[p_name];
return true;
}
void MissingNode::_get_property_list(List<PropertyInfo> *p_list) const {
for (const KeyValue<StringName, Variant> &E : properties) {
p_list->push_back(PropertyInfo(E.value.get_type(), E.key));
}
}
void MissingNode::set_original_class(const String &p_class) {
original_class = p_class;
}
String MissingNode::get_original_class() const {
return original_class;
}
void MissingNode::set_original_scene(const String &p_scene) {
original_scene = p_scene;
}
String MissingNode::get_original_scene() const {
return original_scene;
}
void MissingNode::set_recording_properties(bool p_enable) {
recording_properties = p_enable;
}
bool MissingNode::is_recording_properties() const {
return recording_properties;
}
PackedStringArray MissingNode::get_configuration_warnings() const {
// The mere existence of this node is warning.
PackedStringArray warnings = Node::get_configuration_warnings();
if (!original_scene.is_empty()) {
warnings.push_back(vformat(RTR("This node was an instance of scene '%s', which was no longer available when this scene was loaded."), original_scene));
warnings.push_back(vformat(RTR("Saving current scene will discard instance and all its properties, including editable children edits (if existing).")));
} else if (!original_class.is_empty()) {
warnings.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class));
warnings.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss."));
} else {
warnings.push_back(RTR("Unrecognized missing node. Check scene dependency errors for details."));
}
return warnings;
}
void MissingNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_original_class", "name"), &MissingNode::set_original_class);
ClassDB::bind_method(D_METHOD("get_original_class"), &MissingNode::get_original_class);
ClassDB::bind_method(D_METHOD("set_original_scene", "name"), &MissingNode::set_original_scene);
ClassDB::bind_method(D_METHOD("get_original_scene"), &MissingNode::get_original_scene);
ClassDB::bind_method(D_METHOD("set_recording_properties", "enable"), &MissingNode::set_recording_properties);
ClassDB::bind_method(D_METHOD("is_recording_properties"), &MissingNode::is_recording_properties);
// Expose, but not save.
ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_class", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_original_class", "get_original_class");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_scene", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_original_scene", "get_original_scene");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "recording_properties", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_recording_properties", "is_recording_properties");
}
MissingNode::MissingNode() {
}

63
scene/main/missing_node.h Normal file
View File

@@ -0,0 +1,63 @@
/**************************************************************************/
/* missing_node.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
class MissingNode : public Node {
GDCLASS(MissingNode, Node)
HashMap<StringName, Variant> properties;
String original_class;
String original_scene;
bool recording_properties = false;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
public:
void set_original_class(const String &p_class);
String get_original_class() const;
void set_original_scene(const String &p_scene);
String get_original_scene() const;
void set_recording_properties(bool p_enable);
bool is_recording_properties() const;
virtual PackedStringArray get_configuration_warnings() const override;
MissingNode();
};

View File

@@ -0,0 +1,388 @@
/**************************************************************************/
/* multiplayer_api.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "multiplayer_api.h"
#include "core/io/marshalls.h"
StringName MultiplayerAPI::default_interface;
void MultiplayerAPI::set_default_interface(const StringName &p_interface) {
ERR_FAIL_COND_MSG(!ClassDB::is_parent_class(p_interface, MultiplayerAPI::get_class_static()), vformat("Can't make %s the default multiplayer interface since it does not extend MultiplayerAPI.", p_interface));
default_interface = StringName(p_interface, true);
}
StringName MultiplayerAPI::get_default_interface() {
return default_interface;
}
Ref<MultiplayerAPI> MultiplayerAPI::create_default_interface() {
if (default_interface != StringName()) {
return Ref<MultiplayerAPI>(Object::cast_to<MultiplayerAPI>(ClassDB::instantiate(default_interface)));
}
return Ref<MultiplayerAPI>(memnew(MultiplayerAPIExtension));
}
// The variant is compressed and encoded; The first byte contains all the meta
// information and the format is:
// - The first LSB 6 bits are used for the variant type.
// - The next two bits are used to store the encoding mode.
// - Boolean values uses the encoding mode to store the value.
#define VARIANT_META_TYPE_MASK 0x3F
#define VARIANT_META_EMODE_MASK 0xC0
#define VARIANT_META_BOOL_MASK 0x80
#define ENCODE_8 0 << 6
#define ENCODE_16 1 << 6
#define ENCODE_32 2 << 6
#define ENCODE_64 3 << 6
Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_allow_object_decoding) {
// Unreachable because `VARIANT_MAX` == 38 and `ENCODE_VARIANT_MASK` == 77
CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK);
uint8_t *buf = r_buffer;
r_len = 0;
uint8_t encode_mode = 0;
switch (p_variant.get_type()) {
case Variant::BOOL: {
if (buf) {
// We don't use encode_mode for booleans, so we can use it to store the value.
buf[0] = (p_variant.operator bool()) ? (1 << 7) : 0;
buf[0] |= p_variant.get_type();
}
r_len += 1;
} break;
case Variant::INT: {
if (buf) {
// Reserve the first byte for the meta.
buf += 1;
}
r_len += 1;
int64_t val = p_variant;
if (val <= (int64_t)INT8_MAX && val >= (int64_t)INT8_MIN) {
// Use 8 bit
encode_mode = ENCODE_8;
if (buf) {
buf[0] = val;
}
r_len += 1;
} else if (val <= (int64_t)INT16_MAX && val >= (int64_t)INT16_MIN) {
// Use 16 bit
encode_mode = ENCODE_16;
if (buf) {
encode_uint16(val, buf);
}
r_len += 2;
} else if (val <= (int64_t)INT32_MAX && val >= (int64_t)INT32_MIN) {
// Use 32 bit
encode_mode = ENCODE_32;
if (buf) {
encode_uint32(val, buf);
}
r_len += 4;
} else {
// Use 64 bit
encode_mode = ENCODE_64;
if (buf) {
encode_uint64(val, buf);
}
r_len += 8;
}
// Store the meta
if (buf) {
buf -= 1;
buf[0] = encode_mode | p_variant.get_type();
}
} break;
default:
// Any other case is not yet compressed.
Error err = encode_variant(p_variant, r_buffer, r_len, p_allow_object_decoding);
if (err != OK) {
return err;
}
if (r_buffer) {
// The first byte is not used by the marshaling, so store the type
// so we know how to decompress and decode this variant.
r_buffer[0] = p_variant.get_type();
}
}
return OK;
}
Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding) {
const uint8_t *buf = p_buffer;
int len = p_len;
ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA);
uint8_t type = buf[0] & VARIANT_META_TYPE_MASK;
uint8_t encode_mode = buf[0] & VARIANT_META_EMODE_MASK;
ERR_FAIL_COND_V(type >= Variant::VARIANT_MAX, ERR_INVALID_DATA);
switch (type) {
case Variant::BOOL: {
bool val = (buf[0] & VARIANT_META_BOOL_MASK) > 0;
r_variant = val;
if (r_len) {
*r_len = 1;
}
} break;
case Variant::INT: {
buf += 1;
len -= 1;
if (r_len) {
*r_len = 1;
}
if (encode_mode == ENCODE_8) {
// 8 bits.
ERR_FAIL_COND_V(len < 1, ERR_INVALID_DATA);
int8_t val = buf[0];
r_variant = val;
if (r_len) {
(*r_len) += 1;
}
} else if (encode_mode == ENCODE_16) {
// 16 bits.
ERR_FAIL_COND_V(len < 2, ERR_INVALID_DATA);
int16_t val = decode_uint16(buf);
r_variant = val;
if (r_len) {
(*r_len) += 2;
}
} else if (encode_mode == ENCODE_32) {
// 32 bits.
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
int32_t val = decode_uint32(buf);
r_variant = val;
if (r_len) {
(*r_len) += 4;
}
} else {
// 64 bits.
ERR_FAIL_COND_V(len < 8, ERR_INVALID_DATA);
int64_t val = decode_uint64(buf);
r_variant = val;
if (r_len) {
(*r_len) += 8;
}
}
} break;
default:
Error err = decode_variant(r_variant, p_buffer, p_len, r_len, p_allow_object_decoding);
if (err != OK) {
return err;
}
}
return OK;
}
Error MultiplayerAPI::encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw, bool p_allow_object_decoding) {
r_len = 0;
int size = 0;
if (p_count == 0) {
if (r_raw) {
*r_raw = true;
}
return OK;
}
// Try raw encoding optimization.
if (r_raw && p_count == 1) {
*r_raw = false;
const Variant &v = *(p_variants[0]);
if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
*r_raw = true;
const PackedByteArray pba = v;
if (p_buffer) {
memcpy(p_buffer, pba.ptr(), pba.size());
}
r_len += pba.size();
} else {
encode_and_compress_variant(v, p_buffer, size, p_allow_object_decoding);
r_len += size;
}
return OK;
}
// Regular encoding.
for (int i = 0; i < p_count; i++) {
const Variant &v = *(p_variants[i]);
encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size, p_allow_object_decoding);
r_len += size;
}
return OK;
}
Error MultiplayerAPI::decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw, bool p_allow_object_decoding) {
r_len = 0;
int argc = r_variants.size();
if (argc == 0 && p_raw) {
return OK;
}
ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA);
if (p_raw) {
r_len = p_len;
PackedByteArray pba;
pba.resize(p_len);
memcpy(pba.ptrw(), p_buffer, p_len);
r_variants.write[0] = pba;
return OK;
}
for (int i = 0; i < argc; i++) {
ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small.");
int vlen;
Error err = MultiplayerAPI::decode_and_decompress_variant(r_variants.write[i], &p_buffer[r_len], p_len - r_len, &vlen, p_allow_object_decoding);
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable.");
r_len += vlen;
}
return OK;
}
Error MultiplayerAPI::_rpc_bind(int p_peer, Object *p_object, const StringName &p_method, Array p_args) {
Vector<Variant> args;
Vector<const Variant *> argsp;
args.resize(p_args.size());
argsp.resize(p_args.size());
Variant *ptr = args.ptrw();
const Variant **pptr = argsp.ptrw();
for (int i = 0; i < p_args.size(); i++) {
ptr[i] = p_args[i];
pptr[i] = &ptr[i];
}
return rpcp(p_object, p_peer, p_method, argsp.size() ? argsp.ptrw() : nullptr, argsp.size());
}
void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_multiplayer_peer"), &MultiplayerAPI::has_multiplayer_peer);
ClassDB::bind_method(D_METHOD("get_multiplayer_peer"), &MultiplayerAPI::get_multiplayer_peer);
ClassDB::bind_method(D_METHOD("set_multiplayer_peer", "peer"), &MultiplayerAPI::set_multiplayer_peer);
ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerAPI::get_unique_id);
ClassDB::bind_method(D_METHOD("is_server"), &MultiplayerAPI::is_server);
ClassDB::bind_method(D_METHOD("get_remote_sender_id"), &MultiplayerAPI::get_remote_sender_id);
ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll);
ClassDB::bind_method(D_METHOD("rpc", "peer", "object", "method", "arguments"), &MultiplayerAPI::_rpc_bind, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("object_configuration_add", "object", "configuration"), &MultiplayerAPI::object_configuration_add);
ClassDB::bind_method(D_METHOD("object_configuration_remove", "object", "configuration"), &MultiplayerAPI::object_configuration_remove);
ClassDB::bind_method(D_METHOD("get_peers"), &MultiplayerAPI::get_peer_ids);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_multiplayer_peer", "get_multiplayer_peer");
ClassDB::bind_static_method("MultiplayerAPI", D_METHOD("set_default_interface", "interface_name"), &MultiplayerAPI::set_default_interface);
ClassDB::bind_static_method("MultiplayerAPI", D_METHOD("get_default_interface"), &MultiplayerAPI::get_default_interface);
ClassDB::bind_static_method("MultiplayerAPI", D_METHOD("create_default_interface"), &MultiplayerAPI::create_default_interface);
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("connected_to_server"));
ADD_SIGNAL(MethodInfo("connection_failed"));
ADD_SIGNAL(MethodInfo("server_disconnected"));
BIND_ENUM_CONSTANT(RPC_MODE_DISABLED);
BIND_ENUM_CONSTANT(RPC_MODE_ANY_PEER);
BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY);
}
/// MultiplayerAPIExtension
Error MultiplayerAPIExtension::poll() {
Error err = OK;
GDVIRTUAL_CALL(_poll, err);
return err;
}
void MultiplayerAPIExtension::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) {
GDVIRTUAL_CALL(_set_multiplayer_peer, p_peer);
}
Ref<MultiplayerPeer> MultiplayerAPIExtension::get_multiplayer_peer() {
Ref<MultiplayerPeer> peer;
GDVIRTUAL_CALL(_get_multiplayer_peer, peer);
return peer;
}
int MultiplayerAPIExtension::get_unique_id() {
int id = 1;
GDVIRTUAL_CALL(_get_unique_id, id);
return id;
}
Vector<int> MultiplayerAPIExtension::get_peer_ids() {
Vector<int> ids;
GDVIRTUAL_CALL(_get_peer_ids, ids);
return ids;
}
Error MultiplayerAPIExtension::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
if (!GDVIRTUAL_IS_OVERRIDDEN(_rpc)) {
return ERR_UNAVAILABLE;
}
Array args;
for (int i = 0; i < p_argcount; i++) {
args.push_back(*p_arg[i]);
}
Error ret = FAILED;
GDVIRTUAL_CALL(_rpc, p_peer_id, p_obj, p_method, args, ret);
return ret;
}
int MultiplayerAPIExtension::get_remote_sender_id() {
int id = 0;
GDVIRTUAL_CALL(_get_remote_sender_id, id);
return id;
}
Error MultiplayerAPIExtension::object_configuration_add(Object *p_object, Variant p_config) {
Error err = ERR_UNAVAILABLE;
GDVIRTUAL_CALL(_object_configuration_add, p_object, p_config, err);
return err;
}
Error MultiplayerAPIExtension::object_configuration_remove(Object *p_object, Variant p_config) {
Error err = ERR_UNAVAILABLE;
GDVIRTUAL_CALL(_object_configuration_remove, p_object, p_config, err);
return err;
}
void MultiplayerAPIExtension::_bind_methods() {
GDVIRTUAL_BIND(_poll);
GDVIRTUAL_BIND(_set_multiplayer_peer, "multiplayer_peer");
GDVIRTUAL_BIND(_get_multiplayer_peer);
GDVIRTUAL_BIND(_get_unique_id);
GDVIRTUAL_BIND(_get_peer_ids);
GDVIRTUAL_BIND(_rpc, "peer", "object", "method", "args");
GDVIRTUAL_BIND(_get_remote_sender_id);
GDVIRTUAL_BIND(_object_configuration_add, "object", "configuration");
GDVIRTUAL_BIND(_object_configuration_remove, "object", "configuration");
}

View File

@@ -0,0 +1,111 @@
/**************************************************************************/
/* multiplayer_api.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/object/ref_counted.h"
#include "scene/main/multiplayer_peer.h"
class MultiplayerAPI : public RefCounted {
GDCLASS(MultiplayerAPI, RefCounted);
private:
static StringName default_interface;
protected:
static void _bind_methods();
Error _rpc_bind(int p_peer, Object *p_obj, const StringName &p_method, Array args = Array());
public:
enum RPCMode {
RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default)
RPC_MODE_ANY_PEER, // Any peer can call this RPC
RPC_MODE_AUTHORITY, // Only the node's multiplayer authority (server by default) can call this RPC
};
static Ref<MultiplayerAPI> create_default_interface();
static void set_default_interface(const StringName &p_interface);
static StringName get_default_interface();
static Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len, bool p_allow_object_decoding);
static Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding);
static Error encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr, bool p_allow_object_decoding = false);
static Error decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false, bool p_allow_object_decoding = false);
virtual Error poll() = 0;
virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) = 0;
virtual Ref<MultiplayerPeer> get_multiplayer_peer() = 0;
virtual int get_unique_id() = 0;
virtual Vector<int> get_peer_ids() = 0;
virtual Error rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) = 0;
virtual int get_remote_sender_id() = 0;
virtual Error object_configuration_add(Object *p_object, Variant p_config) = 0;
virtual Error object_configuration_remove(Object *p_object, Variant p_config) = 0;
bool has_multiplayer_peer() { return get_multiplayer_peer().is_valid(); }
bool is_server() { return get_unique_id() == MultiplayerPeer::TARGET_PEER_SERVER; }
virtual ~MultiplayerAPI() {}
};
VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode);
class MultiplayerAPIExtension : public MultiplayerAPI {
GDCLASS(MultiplayerAPIExtension, MultiplayerAPI);
protected:
static void _bind_methods();
public:
virtual Error poll() override;
virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) override;
virtual Ref<MultiplayerPeer> get_multiplayer_peer() override;
virtual int get_unique_id() override;
virtual Vector<int> get_peer_ids() override;
virtual Error rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) override;
virtual int get_remote_sender_id() override;
virtual Error object_configuration_add(Object *p_object, Variant p_config) override;
virtual Error object_configuration_remove(Object *p_object, Variant p_config) override;
// Extensions
GDVIRTUAL0R(Error, _poll);
GDVIRTUAL1(_set_multiplayer_peer, Ref<MultiplayerPeer>);
GDVIRTUAL0R(Ref<MultiplayerPeer>, _get_multiplayer_peer);
GDVIRTUAL0RC(int, _get_unique_id);
GDVIRTUAL0RC(PackedInt32Array, _get_peer_ids);
GDVIRTUAL4R(Error, _rpc, int, Object *, StringName, Array);
GDVIRTUAL0RC(int, _get_remote_sender_id);
GDVIRTUAL2R(Error, _object_configuration_add, Object *, Variant);
GDVIRTUAL2R(Error, _object_configuration_remove, Object *, Variant);
};

View File

@@ -0,0 +1,228 @@
/**************************************************************************/
/* multiplayer_peer.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "multiplayer_peer.h"
#include "core/os/os.h"
uint32_t MultiplayerPeer::generate_unique_id() const {
uint32_t hash = 0;
while (hash == 0 || hash == 1) {
hash = hash_murmur3_one_32(
(uint32_t)OS::get_singleton()->get_ticks_usec());
hash = hash_murmur3_one_32(
(uint32_t)OS::get_singleton()->get_unix_time(), hash);
hash = hash_murmur3_one_32(
(uint32_t)OS::get_singleton()->get_user_data_dir().hash64(), hash);
hash = hash_murmur3_one_32(
(uint32_t)((uint64_t)this), hash); // Rely on ASLR heap
hash = hash_murmur3_one_32(
(uint32_t)((uint64_t)&hash), hash); // Rely on ASLR stack
hash = hash_fmix32(hash);
hash = hash & 0x7FFFFFFF; // Make it compatible with unsigned, since negative ID is used for exclusion
}
return hash;
}
void MultiplayerPeer::set_transfer_channel(int p_channel) {
transfer_channel = p_channel;
}
int MultiplayerPeer::get_transfer_channel() const {
return transfer_channel;
}
void MultiplayerPeer::set_transfer_mode(TransferMode p_mode) {
transfer_mode = p_mode;
}
MultiplayerPeer::TransferMode MultiplayerPeer::get_transfer_mode() const {
return transfer_mode;
}
void MultiplayerPeer::set_refuse_new_connections(bool p_enable) {
refuse_connections = p_enable;
}
bool MultiplayerPeer::is_refusing_new_connections() const {
return refuse_connections;
}
bool MultiplayerPeer::is_server_relay_supported() const {
return false;
}
void MultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &MultiplayerPeer::set_transfer_channel);
ClassDB::bind_method(D_METHOD("get_transfer_channel"), &MultiplayerPeer::get_transfer_channel);
ClassDB::bind_method(D_METHOD("set_transfer_mode", "mode"), &MultiplayerPeer::set_transfer_mode);
ClassDB::bind_method(D_METHOD("get_transfer_mode"), &MultiplayerPeer::get_transfer_mode);
ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &MultiplayerPeer::set_target_peer);
ClassDB::bind_method(D_METHOD("get_packet_peer"), &MultiplayerPeer::get_packet_peer);
ClassDB::bind_method(D_METHOD("get_packet_channel"), &MultiplayerPeer::get_packet_channel);
ClassDB::bind_method(D_METHOD("get_packet_mode"), &MultiplayerPeer::get_packet_mode);
ClassDB::bind_method(D_METHOD("poll"), &MultiplayerPeer::poll);
ClassDB::bind_method(D_METHOD("close"), &MultiplayerPeer::close);
ClassDB::bind_method(D_METHOD("disconnect_peer", "peer", "force"), &MultiplayerPeer::disconnect_peer, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_connection_status"), &MultiplayerPeer::get_connection_status);
ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerPeer::get_unique_id);
ClassDB::bind_method(D_METHOD("generate_unique_id"), &MultiplayerPeer::generate_unique_id);
ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "enable"), &MultiplayerPeer::set_refuse_new_connections);
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerPeer::is_refusing_new_connections);
ClassDB::bind_method(D_METHOD("is_server_relay_supported"), &MultiplayerPeer::is_server_relay_supported);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections");
ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel");
BIND_ENUM_CONSTANT(CONNECTION_DISCONNECTED);
BIND_ENUM_CONSTANT(CONNECTION_CONNECTING);
BIND_ENUM_CONSTANT(CONNECTION_CONNECTED);
BIND_CONSTANT(TARGET_PEER_BROADCAST);
BIND_CONSTANT(TARGET_PEER_SERVER);
BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE);
BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED);
BIND_ENUM_CONSTANT(TRANSFER_MODE_RELIABLE);
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
}
/*************/
Error MultiplayerPeerExtension::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
Error err;
if (GDVIRTUAL_CALL(_get_packet, r_buffer, &r_buffer_size, err)) {
return err;
}
if (GDVIRTUAL_IS_OVERRIDDEN(_get_packet_script)) {
if (!GDVIRTUAL_CALL(_get_packet_script, script_buffer)) {
return FAILED;
}
if (script_buffer.is_empty()) {
return Error::ERR_UNAVAILABLE;
}
*r_buffer = script_buffer.ptr();
r_buffer_size = script_buffer.size();
return Error::OK;
}
WARN_PRINT_ONCE("MultiplayerPeerExtension::_get_packet_native is unimplemented!");
return FAILED;
}
Error MultiplayerPeerExtension::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
Error err;
if (GDVIRTUAL_CALL(_put_packet, p_buffer, p_buffer_size, err)) {
return err;
}
if (GDVIRTUAL_IS_OVERRIDDEN(_put_packet_script)) {
PackedByteArray a;
a.resize(p_buffer_size);
memcpy(a.ptrw(), p_buffer, p_buffer_size);
if (!GDVIRTUAL_CALL(_put_packet_script, a, err)) {
return FAILED;
}
return err;
}
WARN_PRINT_ONCE("MultiplayerPeerExtension::_put_packet_native is unimplemented!");
return FAILED;
}
void MultiplayerPeerExtension::set_refuse_new_connections(bool p_enable) {
if (GDVIRTUAL_CALL(_set_refuse_new_connections, p_enable)) {
return;
}
MultiplayerPeer::set_refuse_new_connections(p_enable);
}
bool MultiplayerPeerExtension::is_refusing_new_connections() const {
bool refusing;
if (GDVIRTUAL_CALL(_is_refusing_new_connections, refusing)) {
return refusing;
}
return MultiplayerPeer::is_refusing_new_connections();
}
bool MultiplayerPeerExtension::is_server_relay_supported() const {
bool can_relay;
if (GDVIRTUAL_CALL(_is_server_relay_supported, can_relay)) {
return can_relay;
}
return MultiplayerPeer::is_server_relay_supported();
}
void MultiplayerPeerExtension::_bind_methods() {
GDVIRTUAL_BIND(_get_packet, "r_buffer", "r_buffer_size");
GDVIRTUAL_BIND(_put_packet, "p_buffer", "p_buffer_size");
GDVIRTUAL_BIND(_get_available_packet_count);
GDVIRTUAL_BIND(_get_max_packet_size);
GDVIRTUAL_BIND(_get_packet_script)
GDVIRTUAL_BIND(_put_packet_script, "p_buffer");
GDVIRTUAL_BIND(_get_packet_channel);
GDVIRTUAL_BIND(_get_packet_mode);
GDVIRTUAL_BIND(_set_transfer_channel, "p_channel");
GDVIRTUAL_BIND(_get_transfer_channel);
GDVIRTUAL_BIND(_set_transfer_mode, "p_mode");
GDVIRTUAL_BIND(_get_transfer_mode);
GDVIRTUAL_BIND(_set_target_peer, "p_peer");
GDVIRTUAL_BIND(_get_packet_peer);
GDVIRTUAL_BIND(_is_server);
GDVIRTUAL_BIND(_poll);
GDVIRTUAL_BIND(_close);
GDVIRTUAL_BIND(_disconnect_peer, "p_peer", "p_force");
GDVIRTUAL_BIND(_get_unique_id);
GDVIRTUAL_BIND(_set_refuse_new_connections, "p_enable");
GDVIRTUAL_BIND(_is_refusing_new_connections);
GDVIRTUAL_BIND(_is_server_relay_supported);
GDVIRTUAL_BIND(_get_connection_status);
ADD_PROPERTY_DEFAULT("transfer_mode", TRANSFER_MODE_RELIABLE);
ADD_PROPERTY_DEFAULT("transfer_channel", 0);
}

View File

@@ -0,0 +1,145 @@
/**************************************************************************/
/* multiplayer_peer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/packet_peer.h"
#include "core/extension/ext_wrappers.gen.inc"
#include "core/object/gdvirtual.gen.inc"
#include "core/variant/native_ptr.h"
class MultiplayerPeer : public PacketPeer {
GDCLASS(MultiplayerPeer, PacketPeer);
public:
enum TransferMode {
TRANSFER_MODE_UNRELIABLE,
TRANSFER_MODE_UNRELIABLE_ORDERED,
TRANSFER_MODE_RELIABLE
};
protected:
static void _bind_methods();
private:
int transfer_channel = 0;
TransferMode transfer_mode = TRANSFER_MODE_RELIABLE;
bool refuse_connections = false;
public:
enum {
TARGET_PEER_BROADCAST = 0,
TARGET_PEER_SERVER = 1
};
enum ConnectionStatus {
CONNECTION_DISCONNECTED,
CONNECTION_CONNECTING,
CONNECTION_CONNECTED,
};
virtual void set_transfer_channel(int p_channel);
virtual int get_transfer_channel() const;
virtual void set_transfer_mode(TransferMode p_mode);
virtual TransferMode get_transfer_mode() const;
virtual void set_refuse_new_connections(bool p_enable);
virtual bool is_refusing_new_connections() const;
virtual bool is_server_relay_supported() const;
virtual void set_target_peer(int p_peer_id) = 0;
virtual int get_packet_peer() const = 0;
virtual TransferMode get_packet_mode() const = 0;
virtual int get_packet_channel() const = 0;
virtual void disconnect_peer(int p_peer, bool p_force = false) = 0;
virtual bool is_server() const = 0;
virtual void poll() = 0;
virtual void close() = 0;
virtual int get_unique_id() const = 0;
virtual ConnectionStatus get_connection_status() const = 0;
uint32_t generate_unique_id() const;
};
VARIANT_ENUM_CAST(MultiplayerPeer::ConnectionStatus);
VARIANT_ENUM_CAST(MultiplayerPeer::TransferMode);
class MultiplayerPeerExtension : public MultiplayerPeer {
GDCLASS(MultiplayerPeerExtension, MultiplayerPeer);
protected:
static void _bind_methods();
PackedByteArray script_buffer;
public:
/* PacketPeer extension */
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; ///< buffer is GONE after next get_packet
GDVIRTUAL2R(Error, _get_packet, GDExtensionConstPtr<const uint8_t *>, GDExtensionPtr<int>);
GDVIRTUAL0R(PackedByteArray, _get_packet_script); // For GDScript.
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
GDVIRTUAL2R(Error, _put_packet, GDExtensionConstPtr<const uint8_t>, int);
GDVIRTUAL1R(Error, _put_packet_script, PackedByteArray); // For GDScript.
EXBIND0RC(int, get_available_packet_count);
EXBIND0RC(int, get_max_packet_size);
/* MultiplayerPeer extension */
virtual void set_refuse_new_connections(bool p_enable) override;
GDVIRTUAL1(_set_refuse_new_connections, bool); // Optional.
virtual bool is_refusing_new_connections() const override;
GDVIRTUAL0RC(bool, _is_refusing_new_connections); // Optional.
virtual bool is_server_relay_supported() const override;
GDVIRTUAL0RC(bool, _is_server_relay_supported); // Optional.
EXBIND1(set_transfer_channel, int);
EXBIND0RC(int, get_transfer_channel);
EXBIND1(set_transfer_mode, TransferMode);
EXBIND0RC(TransferMode, get_transfer_mode);
EXBIND1(set_target_peer, int);
EXBIND0RC(int, get_packet_peer);
EXBIND0RC(TransferMode, get_packet_mode);
EXBIND0RC(int, get_packet_channel);
EXBIND0RC(bool, is_server);
EXBIND0(poll);
EXBIND0(close);
EXBIND2(disconnect_peer, int, bool);
EXBIND0RC(int, get_unique_id);
EXBIND0RC(ConnectionStatus, get_connection_status);
};

View File

@@ -0,0 +1,46 @@
/**************************************************************************/
/* node.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void Node::_set_name_bind_compat_76560(const String &p_name) {
set_name(p_name);
}
Variant Node::_get_rpc_config_bind_compat_106848() const {
return _get_node_rpc_config_bind();
}
void Node::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("set_name", "name"), &Node::_set_name_bind_compat_76560);
ClassDB::bind_compatibility_method(D_METHOD("get_rpc_config"), &Node::_get_rpc_config_bind_compat_106848);
}
#endif

4213
scene/main/node.cpp Normal file

File diff suppressed because it is too large Load Diff

897
scene/main/node.h Normal file
View File

@@ -0,0 +1,897 @@
/**************************************************************************/
/* node.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/string/node_path.h"
#include "core/variant/typed_array.h"
#include "scene/main/scene_tree.h"
#include "scene/scene_string_names.h"
class Viewport;
class Window;
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);
friend class SceneTreeFTI;
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;
MTFlag() :
mt{} {}
};
template <typename T>
union MTNumeric {
SafeNumeric<T> mt;
T st;
MTNumeric() :
mt{} {}
};
public:
// N.B. Any enum stored as a bitfield should be specified as UNSIGNED to work around
// some compilers trying to store it as signed, and requiring 1 more bit than necessary.
enum ProcessMode : unsigned int {
PROCESS_MODE_INHERIT, // same as parent node
PROCESS_MODE_PAUSABLE, // process only if not paused
PROCESS_MODE_WHEN_PAUSED, // process only if paused
PROCESS_MODE_ALWAYS, // process always
PROCESS_MODE_DISABLED, // never process
};
enum ProcessThreadGroup {
PROCESS_THREAD_GROUP_INHERIT,
PROCESS_THREAD_GROUP_MAIN_THREAD,
PROCESS_THREAD_GROUP_SUB_THREAD,
};
enum ProcessThreadMessages {
FLAG_PROCESS_THREAD_MESSAGES = 1,
FLAG_PROCESS_THREAD_MESSAGES_PHYSICS = 2,
FLAG_PROCESS_THREAD_MESSAGES_ALL = 3,
};
enum PhysicsInterpolationMode : unsigned int {
PHYSICS_INTERPOLATION_MODE_INHERIT,
PHYSICS_INTERPOLATION_MODE_ON,
PHYSICS_INTERPOLATION_MODE_OFF,
};
enum DuplicateFlags {
DUPLICATE_SIGNALS = 1,
DUPLICATE_GROUPS = 2,
DUPLICATE_SCRIPTS = 4,
DUPLICATE_USE_INSTANTIATION = 8,
#ifdef TOOLS_ENABLED
DUPLICATE_FROM_EDITOR = 16,
#endif
};
enum NameCasing {
NAME_CASING_PASCAL_CASE,
NAME_CASING_CAMEL_CASE,
NAME_CASING_SNAKE_CASE,
NAME_CASING_KEBAB_CASE,
};
enum InternalMode {
INTERNAL_MODE_DISABLED,
INTERNAL_MODE_FRONT,
INTERNAL_MODE_BACK,
};
enum AutoTranslateMode : unsigned int {
AUTO_TRANSLATE_MODE_INHERIT,
AUTO_TRANSLATE_MODE_ALWAYS,
AUTO_TRANSLATE_MODE_DISABLED,
};
struct Comparator {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); }
};
static int orphan_node_count;
void _update_process(bool p_enable, bool p_for_children);
private:
struct GroupData {
bool persistent = false;
SceneTree::Group *group = nullptr;
};
struct ComparatorByIndex {
bool operator()(const Node *p_left, const Node *p_right) const {
static const uint32_t order[3] = { 1, 0, 2 };
uint32_t order_left = order[p_left->data.internal_mode];
uint32_t order_right = order[p_right->data.internal_mode];
if (order_left == order_right) {
return p_left->data.index < p_right->data.index;
}
return order_left < order_right;
}
};
struct ComparatorWithPriority {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.process_priority == p_a->data.process_priority ? p_b->is_greater_than(p_a) : p_b->data.process_priority > p_a->data.process_priority; }
};
struct ComparatorWithPhysicsPriority {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.physics_process_priority == p_a->data.physics_process_priority ? p_b->is_greater_than(p_a) : p_b->data.physics_process_priority > p_a->data.physics_process_priority; }
};
// This Data struct is to avoid namespace pollution in derived classes.
struct Data {
String scene_file_path;
Ref<SceneState> instance_state;
Ref<SceneState> inherited_state;
Node *parent = nullptr;
Node *owner = nullptr;
HashMap<StringName, Node *> children;
mutable bool children_cache_dirty = true;
mutable LocalVector<Node *> children_cache;
HashMap<StringName, Node *> owned_unique_nodes;
bool unique_name_in_owner = false;
InternalMode internal_mode = INTERNAL_MODE_DISABLED;
mutable int internal_children_front_count_cache = 0;
mutable int internal_children_back_count_cache = 0;
mutable int external_children_count_cache = 0;
mutable int index = -1; // relative to front, normal or back.
int32_t depth = -1;
int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
StringName name;
SceneTree *tree = nullptr;
#ifdef TOOLS_ENABLED
NodePath import_path; // Path used when imported, used by scene editors to keep tracking.
#endif
String editor_description;
Viewport *viewport = nullptr;
mutable RID accessibility_element;
HashMap<StringName, GroupData> grouped;
List<Node *>::Element *OW = nullptr; // Owned element.
List<Node *> owned;
Node *process_owner = nullptr;
ProcessThreadGroup process_thread_group = PROCESS_THREAD_GROUP_INHERIT;
Node *process_thread_group_owner = nullptr;
int process_thread_group_order = 0;
BitField<ProcessThreadMessages> process_thread_messages = {};
void *process_group = nullptr; // to avoid cyclic dependency
int multiplayer_authority = 1; // Server by default.
Variant rpc_config;
// Variables used to properly sort the node when processing, ignored otherwise.
int process_priority = 0;
int physics_process_priority = 0;
// Keep bitpacked values together to get better packing.
ProcessMode process_mode : 3;
PhysicsInterpolationMode physics_interpolation_mode : 2;
AutoTranslateMode auto_translate_mode : 2;
bool physics_process : 1;
bool process : 1;
bool physics_process_internal : 1;
bool process_internal : 1;
bool input : 1;
bool shortcut_input : 1;
bool unhandled_input : 1;
bool unhandled_key_input : 1;
// Physics interpolation can be turned on and off on a per node basis.
// This only takes effect when the SceneTree (or project setting) physics interpolation
// is switched on.
bool physics_interpolated : 1;
// We can auto-reset physics interpolation when e.g. adding a node for the first time.
bool physics_interpolation_reset_requested : 1;
// Most nodes need not be interpolated in the scene tree, physics interpolation
// is normally only needed in the RenderingServer. However if we need to read the
// interpolated transform of a node in the SceneTree, it is necessary to duplicate
// the interpolation logic client side, in order to prevent stalling the RenderingServer
// by reading back.
bool physics_interpolated_client_side : 1;
// For certain nodes (e.g. CPU particles in global mode)
// it can be useful to not send the instance transform to the
// RenderingServer, and specify the mesh in world space.
bool use_identity_transform : 1;
bool use_placeholder : 1;
bool display_folded : 1;
bool editable_instance : 1;
bool ready_notified : 1;
bool ready_first : 1;
mutable bool is_auto_translating : 1;
mutable bool is_auto_translate_dirty : 1;
mutable bool is_translation_domain_inherited : 1;
mutable bool is_translation_domain_dirty : 1;
mutable NodePath *path_cache = nullptr;
} data;
Ref<MultiplayerAPI> multiplayer;
String _get_tree_string_pretty(const String &p_prefix, bool p_last);
String _get_tree_string(const Node *p_node);
Node *_get_child_by_name(const StringName &p_name) const;
void _replace_connections_target(Node *p_new_target);
void _validate_child_name(Node *p_child, bool p_force_human_readable = false);
void _generate_serial_child_name(const Node *p_child, StringName &name) const;
void _propagate_reverse_notification(int p_notification);
void _propagate_deferred_notification(int p_notification, bool p_reverse);
void _propagate_enter_tree();
void _propagate_ready();
void _propagate_exit_tree();
void _propagate_after_exit_tree();
void _propagate_physics_interpolated(bool p_interpolated);
void _propagate_physics_interpolation_reset_requested(bool p_requested);
void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
void _propagate_groups_dirty();
void _propagate_translation_domain_dirty();
Array _get_node_and_resource(const NodePath &p_path);
void _duplicate_properties(const Node *p_root, const Node *p_original, Node *p_copy, int p_flags) const;
void _duplicate_signals(const Node *p_original, Node *p_copy) const;
Node *_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap = nullptr) const;
TypedArray<StringName> _get_groups() const;
Error _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Error _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
friend class SceneTree;
void _set_tree(SceneTree *p_tree);
void _propagate_pause_notification(bool p_enable);
void _propagate_suspend_notification(bool p_enable);
_FORCE_INLINE_ bool _can_process(bool p_paused) const;
_FORCE_INLINE_ bool _is_enabled() const;
void _release_unique_name_in_owner();
void _acquire_unique_name_in_owner();
void _clean_up_owner();
_FORCE_INLINE_ void _update_children_cache() const {
if (unlikely(data.children_cache_dirty)) {
_update_children_cache_impl();
}
}
void _update_children_cache_impl() const;
// Process group management
void _add_process_group();
void _remove_process_group();
void _add_to_process_thread_group();
void _remove_from_process_thread_group();
void _remove_tree_from_process_thread_group();
void _add_tree_to_process_thread_group(Node *p_owner);
static thread_local Node *current_process_thread_group;
Variant _call_deferred_thread_group_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Variant _call_thread_safe_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
// Editor only signal to keep the SceneTreeEditor in sync.
#ifdef TOOLS_ENABLED
void _emit_editor_state_changed();
#else
void _emit_editor_state_changed() {}
#endif
protected:
void _block() { data.blocked++; }
void _unblock() { data.blocked--; }
void _notification(int p_notification);
virtual void _physics_interpolated_changed();
virtual void add_child_notify(Node *p_child);
virtual void remove_child_notify(Node *p_child);
virtual void move_child_notify(Node *p_child);
virtual void owner_changed_notify();
void _propagate_replace_owner(Node *p_owner, Node *p_by_owner);
static void _bind_methods();
static String _get_name_num_separator();
friend class SceneState;
void _add_child_nocheck(Node *p_child, const StringName &p_name, InternalMode p_internal_mode = INTERNAL_MODE_DISABLED);
void _set_owner_nocheck(Node *p_owner);
void _set_name_nocheck(const StringName &p_name);
void _set_physics_interpolated_client_side(bool p_enable) { data.physics_interpolated_client_side = p_enable; }
bool _is_physics_interpolated_client_side() const { return data.physics_interpolated_client_side; }
void _set_physics_interpolation_reset_requested(bool p_enable) { data.physics_interpolation_reset_requested = p_enable; }
bool _is_physics_interpolation_reset_requested() const { return data.physics_interpolation_reset_requested; }
void _set_use_identity_transform(bool p_enable) { data.use_identity_transform = p_enable; }
bool _is_using_identity_transform() const { return data.use_identity_transform; }
int32_t _get_scene_tree_depth() const { return data.depth; }
//call from SceneTree
void _call_input(const Ref<InputEvent> &p_event);
void _call_shortcut_input(const Ref<InputEvent> &p_event);
void _call_unhandled_input(const Ref<InputEvent> &p_event);
void _call_unhandled_key_input(const Ref<InputEvent> &p_event);
void _validate_property(PropertyInfo &p_property) const;
Variant _get_node_rpc_config_bind() const {
return get_node_rpc_config().duplicate(true);
}
protected:
virtual bool _uses_signal_mutex() const override { return false; } // Node uses thread guards instead.
virtual void input(const Ref<InputEvent> &p_event);
virtual void shortcut_input(const Ref<InputEvent> &p_key_event);
virtual void unhandled_input(const Ref<InputEvent> &p_event);
virtual void unhandled_key_input(const Ref<InputEvent> &p_key_event);
GDVIRTUAL1(_process, double)
GDVIRTUAL1(_physics_process, double)
GDVIRTUAL0(_enter_tree)
GDVIRTUAL0(_exit_tree)
GDVIRTUAL0(_ready)
GDVIRTUAL0RC(Vector<String>, _get_accessibility_configuration_warnings)
GDVIRTUAL0RC(Vector<String>, _get_configuration_warnings)
GDVIRTUAL1(_input, Ref<InputEvent>)
GDVIRTUAL1(_shortcut_input, Ref<InputEvent>)
GDVIRTUAL1(_unhandled_input, Ref<InputEvent>)
GDVIRTUAL1(_unhandled_key_input, Ref<InputEvent>)
GDVIRTUAL0RC(RID, _get_focused_accessibility_element)
#ifndef DISABLE_DEPRECATED
void _set_name_bind_compat_76560(const String &p_name);
Variant _get_rpc_config_bind_compat_106848() const;
static void _bind_compatibility_methods();
#endif
public:
enum {
// You can make your own, but don't use the same numbers as other notifications in other nodes.
NOTIFICATION_ENTER_TREE = 10,
NOTIFICATION_EXIT_TREE = 11,
NOTIFICATION_MOVED_IN_PARENT = 12,
NOTIFICATION_READY = 13,
NOTIFICATION_PAUSED = 14,
NOTIFICATION_UNPAUSED = 15,
NOTIFICATION_PHYSICS_PROCESS = 16,
NOTIFICATION_PROCESS = 17,
NOTIFICATION_PARENTED = 18,
NOTIFICATION_UNPARENTED = 19,
NOTIFICATION_SCENE_INSTANTIATED = 20,
NOTIFICATION_DRAG_BEGIN = 21,
NOTIFICATION_DRAG_END = 22,
NOTIFICATION_PATH_RENAMED = 23,
NOTIFICATION_CHILD_ORDER_CHANGED = 24,
NOTIFICATION_INTERNAL_PROCESS = 25,
NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26,
NOTIFICATION_POST_ENTER_TREE = 27,
NOTIFICATION_DISABLED = 28,
NOTIFICATION_ENABLED = 29,
NOTIFICATION_RESET_PHYSICS_INTERPOLATION = 2001, // A GodotSpace Odyssey.
// Keep these linked to Node.
NOTIFICATION_ACCESSIBILITY_UPDATE = 3000,
NOTIFICATION_ACCESSIBILITY_INVALIDATE = 3001,
NOTIFICATION_WM_MOUSE_ENTER = 1002,
NOTIFICATION_WM_MOUSE_EXIT = 1003,
NOTIFICATION_WM_WINDOW_FOCUS_IN = 1004,
NOTIFICATION_WM_WINDOW_FOCUS_OUT = 1005,
NOTIFICATION_WM_CLOSE_REQUEST = 1006,
NOTIFICATION_WM_GO_BACK_REQUEST = 1007,
NOTIFICATION_WM_SIZE_CHANGED = 1008,
NOTIFICATION_WM_DPI_CHANGE = 1009,
NOTIFICATION_VP_MOUSE_ENTER = 1010,
NOTIFICATION_VP_MOUSE_EXIT = 1011,
NOTIFICATION_WM_POSITION_CHANGED = 1012,
NOTIFICATION_OS_MEMORY_WARNING = MainLoop::NOTIFICATION_OS_MEMORY_WARNING,
NOTIFICATION_TRANSLATION_CHANGED = MainLoop::NOTIFICATION_TRANSLATION_CHANGED,
NOTIFICATION_WM_ABOUT = MainLoop::NOTIFICATION_WM_ABOUT,
NOTIFICATION_CRASH = MainLoop::NOTIFICATION_CRASH,
NOTIFICATION_OS_IME_UPDATE = MainLoop::NOTIFICATION_OS_IME_UPDATE,
NOTIFICATION_APPLICATION_RESUMED = MainLoop::NOTIFICATION_APPLICATION_RESUMED,
NOTIFICATION_APPLICATION_PAUSED = MainLoop::NOTIFICATION_APPLICATION_PAUSED,
NOTIFICATION_APPLICATION_FOCUS_IN = MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN,
NOTIFICATION_APPLICATION_FOCUS_OUT = MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT,
NOTIFICATION_TEXT_SERVER_CHANGED = MainLoop::NOTIFICATION_TEXT_SERVER_CHANGED,
// Editor specific node notifications
NOTIFICATION_EDITOR_PRE_SAVE = 9001,
NOTIFICATION_EDITOR_POST_SAVE = 9002,
NOTIFICATION_SUSPENDED = 9003,
NOTIFICATION_UNSUSPENDED = 9004
};
/* NODE/TREE */
StringName get_name() const;
String get_description() const;
void set_name(const StringName &p_name);
InternalMode get_internal_mode() const;
void add_child(Node *p_child, bool p_force_readable_name = false, InternalMode p_internal = INTERNAL_MODE_DISABLED);
void add_sibling(Node *p_sibling, bool p_force_readable_name = false);
void remove_child(Node *p_child);
int get_child_count(bool p_include_internal = true) const;
Node *get_child(int p_index, bool p_include_internal = true) const;
TypedArray<Node> get_children(bool p_include_internal = true) const;
bool has_node(const NodePath &p_path) const;
Node *get_node(const NodePath &p_path) const;
Node *get_node_or_null(const NodePath &p_path) const;
Node *find_child(const String &p_pattern, bool p_recursive = true, bool p_owned = true) const;
TypedArray<Node> find_children(const String &p_pattern, const String &p_type = "", bool p_recursive = true, bool p_owned = true) const;
bool has_node_and_resource(const NodePath &p_path) const;
Node *get_node_and_resource(const NodePath &p_path, Ref<Resource> &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property = true) const;
virtual void reparent(Node *p_parent, bool p_keep_global_transform = true);
Node *get_parent() const;
Node *find_parent(const String &p_pattern) const;
Window *get_window() const;
Window *get_non_popup_window() const;
Window *get_last_exclusive_window() const;
_FORCE_INLINE_ SceneTree *get_tree() const {
ERR_FAIL_NULL_V(data.tree, nullptr);
return data.tree;
}
_FORCE_INLINE_ bool is_inside_tree() const { return data.tree; }
bool is_internal() const { return data.internal_mode != INTERNAL_MODE_DISABLED; }
bool is_ancestor_of(const Node *p_node) const;
bool is_greater_than(const Node *p_node) const;
NodePath get_path() const;
NodePath get_path_to(const Node *p_node, bool p_use_unique_path = false) const;
Node *find_common_parent_with(const Node *p_node) const;
void add_to_group(const StringName &p_identifier, bool p_persistent = false);
void remove_from_group(const StringName &p_identifier);
bool is_in_group(const StringName &p_identifier) const;
struct GroupInfo {
StringName name;
bool persistent = false;
};
void get_groups(List<GroupInfo> *p_groups) const;
int get_persistent_group_count() const;
void move_child(Node *p_child, int p_index);
void _move_child(Node *p_child, int p_index, bool p_ignore_end = false);
void set_owner(Node *p_owner);
Node *get_owner() const;
void get_owned_by(Node *p_by, List<Node *> *p_owned);
void set_unique_name_in_owner(bool p_enabled);
bool is_unique_name_in_owner() const;
_FORCE_INLINE_ int get_index(bool p_include_internal = true) const {
// p_include_internal = false doesn't make sense if the node is internal.
ERR_FAIL_COND_V_MSG(!p_include_internal && data.internal_mode != INTERNAL_MODE_DISABLED, -1, "Node is internal. Can't get index with 'include_internal' being false.");
if (!data.parent) {
return data.index;
}
data.parent->_update_children_cache();
if (!p_include_internal) {
return data.index;
} else {
switch (data.internal_mode) {
case INTERNAL_MODE_DISABLED: {
return data.parent->data.internal_children_front_count_cache + data.index;
} break;
case INTERNAL_MODE_FRONT: {
return data.index;
} break;
case INTERNAL_MODE_BACK: {
return data.parent->data.internal_children_front_count_cache + data.parent->data.external_children_count_cache + data.index;
} break;
}
return -1;
}
}
Ref<Tween> create_tween();
void print_tree();
void print_tree_pretty();
String get_tree_string();
String get_tree_string_pretty();
void set_scene_file_path(const String &p_scene_file_path);
String get_scene_file_path() const;
void set_editor_description(const String &p_editor_description);
String get_editor_description() const;
void set_editable_instance(Node *p_node, bool p_editable);
bool is_editable_instance(const Node *p_node) const;
Node *get_deepest_editable_node(Node *p_start_node) const;
#ifdef TOOLS_ENABLED
void set_property_pinned(const String &p_property, bool p_pinned);
bool is_property_pinned(const StringName &p_property) const;
virtual StringName get_property_store_alias(const StringName &p_property) const;
bool is_part_of_edited_scene() const;
#else
bool is_part_of_edited_scene() const { return false; }
#endif
void get_storable_properties(HashSet<StringName> &r_storable_properties) const;
virtual String to_string() override;
/* NOTIFICATIONS */
void propagate_notification(int p_notification);
void propagate_call(const StringName &p_method, const Array &p_args = Array(), const bool p_parent_first = false);
/* PROCESSING */
void set_physics_process(bool p_process);
double get_physics_process_delta_time() const;
bool is_physics_processing() const;
void set_process(bool p_process);
double get_process_delta_time() const;
bool is_processing() const;
void set_physics_process_internal(bool p_process_internal);
bool is_physics_processing_internal() const;
void set_process_internal(bool p_process_internal);
bool is_processing_internal() const;
void set_process_priority(int p_priority);
int get_process_priority() const;
void set_process_thread_group_order(int p_order);
int get_process_thread_group_order() const;
void set_physics_process_priority(int p_priority);
int get_physics_process_priority() const;
void set_process_input(bool p_enable);
bool is_processing_input() const;
void set_process_shortcut_input(bool p_enable);
bool is_processing_shortcut_input() const;
void set_process_unhandled_input(bool p_enable);
bool is_processing_unhandled_input() const;
void set_process_unhandled_key_input(bool p_enable);
bool is_processing_unhandled_key_input() const;
_FORCE_INLINE_ bool _is_any_processing() const {
return data.process || data.process_internal || data.physics_process || data.physics_process_internal;
}
_FORCE_INLINE_ bool is_accessible_from_caller_thread() const {
if (current_process_thread_group == nullptr) {
// No thread processing.
// Only accessible if node is outside the scene tree
// or access will happen from a node-safe thread.
return !data.tree || is_current_thread_safe_for_nodes();
} else {
// Thread processing.
return current_process_thread_group == data.process_thread_group_owner;
}
}
_FORCE_INLINE_ bool is_readable_from_caller_thread() const {
if (current_process_thread_group == nullptr) {
// No thread processing.
// Only accessible if node is outside the scene tree
// or access will happen from a node-safe thread.
return is_current_thread_safe_for_nodes() || unlikely(!data.tree);
} else {
// Thread processing.
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;
void queue_accessibility_update();
virtual RID get_accessibility_element() const;
virtual RID get_focused_accessibility_element() const;
virtual bool accessibility_override_tree_hierarchy() const { return false; }
virtual PackedStringArray get_accessibility_configuration_warnings() const;
Node *duplicate(int p_flags = DUPLICATE_GROUPS | DUPLICATE_SIGNALS | DUPLICATE_SCRIPTS) const;
#ifdef TOOLS_ENABLED
Node *duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap) const;
Node *duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const;
void remap_node_resources(Node *p_node, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const;
void remap_nested_resources(Ref<Resource> p_resource, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const;
#endif
// used by editors, to save what has changed only
void set_scene_instance_state(const Ref<SceneState> &p_state);
Ref<SceneState> get_scene_instance_state() const;
void set_scene_inherited_state(const Ref<SceneState> &p_state);
Ref<SceneState> get_scene_inherited_state() const;
void set_scene_instance_load_placeholder(bool p_enable);
bool get_scene_instance_load_placeholder() const;
template <typename... VarArgs>
Vector<Variant> make_binds(VarArgs... p_args) {
Vector<Variant> binds = { p_args... };
return binds;
}
void replace_by(Node *p_node, bool p_keep_groups = false);
void set_process_mode(ProcessMode p_mode);
ProcessMode get_process_mode() const;
bool can_process() const;
bool can_process_notification(int p_what) const;
void set_physics_interpolation_mode(PhysicsInterpolationMode p_mode);
PhysicsInterpolationMode get_physics_interpolation_mode() const { return data.physics_interpolation_mode; }
_FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; }
_FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return SceneTree::is_fti_enabled() && is_physics_interpolated(); }
void reset_physics_interpolation();
bool is_enabled() const;
bool is_ready() const;
void request_ready();
void set_process_thread_group(ProcessThreadGroup p_mode);
ProcessThreadGroup get_process_thread_group() const;
static void print_orphan_nodes();
static TypedArray<int> get_orphan_node_ids();
#ifdef TOOLS_ENABLED
String validate_child_name(Node *p_child);
String prevalidate_child_name(Node *p_child, StringName p_name);
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
static String adjust_name_casing(const String &p_name);
void queue_free();
//hacks for speed
static void init_node_hrcr();
void set_import_path(const NodePath &p_import_path); //path used when imported, used by scene editors to keep tracking
NodePath get_import_path() const;
bool is_owned_by_parent() const;
void clear_internal_tree_resource_paths();
_FORCE_INLINE_ Viewport *get_viewport() const { return data.viewport; }
virtual PackedStringArray get_configuration_warnings() const;
void update_configuration_warnings();
void set_display_folded(bool p_folded);
bool is_displayed_folded() const;
/* NETWORK */
virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true);
int get_multiplayer_authority() const;
bool is_multiplayer_authority() const;
void rpc_config(const StringName &p_method, const Variant &p_config); // config a local method for RPC
const Variant get_node_rpc_config() const;
template <typename... VarArgs>
Error rpc(const StringName &p_method, VarArgs... p_args);
template <typename... VarArgs>
Error rpc_id(int p_peer_id, const StringName &p_method, VarArgs... p_args);
Error rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount);
Ref<MultiplayerAPI> get_multiplayer() const;
/* INTERNATIONALIZATION */
void set_auto_translate_mode(AutoTranslateMode p_mode);
AutoTranslateMode get_auto_translate_mode() const;
bool can_auto_translate() const;
virtual StringName get_translation_domain() const override;
virtual void set_translation_domain(const StringName &p_domain) override;
void set_translation_domain_inherited();
_FORCE_INLINE_ String atr(const String &p_message, const StringName &p_context = "") const { return can_auto_translate() ? tr(p_message, p_context) : p_message; }
_FORCE_INLINE_ String atr_n(const String &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const {
if (can_auto_translate()) {
return tr_n(p_message, p_message_plural, p_n, p_context);
}
return p_n == 1 ? p_message : String(p_message_plural);
}
/* THREADING */
void call_deferred_thread_groupp(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false);
template <typename... VarArgs>
void call_deferred_thread_group(const StringName &p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
call_deferred_thread_groupp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
void set_deferred_thread_group(const StringName &p_property, const Variant &p_value);
void notify_deferred_thread_group(int p_notification);
void call_thread_safep(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false);
template <typename... VarArgs>
void call_thread_safe(const StringName &p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
call_deferred_thread_groupp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
void set_thread_safe(const StringName &p_property, const Variant &p_value);
void notify_thread_safe(int p_notification);
// These inherited functions need proper multithread locking when overridden in Node.
#ifdef DEBUG_ENABLED
virtual void set_script(const Variant &p_script) override;
virtual Variant get_script() const override;
virtual bool has_meta(const StringName &p_name) const override;
virtual void set_meta(const StringName &p_name, const Variant &p_value) override;
virtual void remove_meta(const StringName &p_name) override;
virtual Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const override;
virtual void get_meta_list(List<StringName> *p_list) const override;
virtual Error emit_signalp(const StringName &p_name, const Variant **p_args, int p_argcount) override;
virtual bool has_signal(const StringName &p_name) const override;
virtual void get_signal_list(List<MethodInfo> *p_signals) const override;
virtual void get_signal_connection_list(const StringName &p_signal, List<Connection> *p_connections) const override;
virtual void get_all_signal_connections(List<Connection> *p_connections) const override;
virtual int get_persistent_signal_connection_count() const override;
virtual void get_signals_connected_to_this(List<Connection> *p_connections) const override;
virtual Error connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags = 0) override;
virtual void disconnect(const StringName &p_signal, const Callable &p_callable) override;
virtual bool is_connected(const StringName &p_signal, const Callable &p_callable) const override;
virtual bool has_connections(const StringName &p_signal) const override;
#endif
Node();
~Node();
};
VARIANT_ENUM_CAST(Node::DuplicateFlags);
VARIANT_ENUM_CAST(Node::ProcessMode);
VARIANT_ENUM_CAST(Node::ProcessThreadGroup);
VARIANT_BITFIELD_CAST(Node::ProcessThreadMessages);
VARIANT_ENUM_CAST(Node::InternalMode);
VARIANT_ENUM_CAST(Node::PhysicsInterpolationMode);
VARIANT_ENUM_CAST(Node::AutoTranslateMode);
typedef HashSet<Node *, Node::Comparator> NodeSet;
// Template definitions must be in the header so they are always fully initialized before their usage.
// See this StackOverflow question for more information: https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file
template <typename... VarArgs>
Error Node::rpc(const StringName &p_method, VarArgs... p_args) {
return rpc_id(0, p_method, p_args...);
}
template <typename... VarArgs>
Error Node::rpc_id(int p_peer_id, const StringName &p_method, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
return rpcp(p_peer_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
#ifdef DEBUG_ENABLED
#define ERR_THREAD_GUARD ERR_FAIL_COND_MSG(!is_accessible_from_caller_thread(), vformat("Caller thread can't call this function in this node (%s). Use call_deferred() or call_thread_group() instead.", get_description()));
#define ERR_THREAD_GUARD_V(m_ret) ERR_FAIL_COND_V_MSG(!is_accessible_from_caller_thread(), (m_ret), vformat("Caller thread can't call this function in this node (%s). Use call_deferred() or call_thread_group() instead.", get_description()));
#define ERR_MAIN_THREAD_GUARD ERR_FAIL_COND_MSG(is_inside_tree() && !is_current_thread_safe_for_nodes(), vformat("This function in this node (%s) can only be accessed from the main thread. Use call_deferred() instead.", get_description()));
#define ERR_MAIN_THREAD_GUARD_V(m_ret) ERR_FAIL_COND_V_MSG(is_inside_tree() && !is_current_thread_safe_for_nodes(), (m_ret), vformat("This function in this node (%s) can only be accessed from the main thread. Use call_deferred() instead.", get_description()));
#define ERR_READ_THREAD_GUARD ERR_FAIL_COND_MSG(!is_readable_from_caller_thread(), vformat("This function in this node (%s) can only be accessed from either the main thread or a thread group. Use call_deferred() instead.", get_description()));
#define ERR_READ_THREAD_GUARD_V(m_ret) ERR_FAIL_COND_V_MSG(!is_readable_from_caller_thread(), (m_ret), vformat("This function in this node (%s) can only be accessed from either the main thread or a thread group. Use call_deferred() instead.", get_description()));
#else
#define ERR_THREAD_GUARD
#define ERR_THREAD_GUARD_V(m_ret)
#define ERR_MAIN_THREAD_GUARD
#define ERR_MAIN_THREAD_GUARD_V(m_ret)
#define ERR_READ_THREAD_GUARD
#define ERR_READ_THREAD_GUARD_V(m_ret)
#endif
// Add these macro to your class's 'get_configuration_warnings' function to have warnings show up in the scene tree inspector.
#define DEPRECATED_NODE_WARNING warnings.push_back(RTR("This node is marked as deprecated and will be removed in future versions.\nPlease check the Godot documentation for information about migration."));
#define EXPERIMENTAL_NODE_WARNING warnings.push_back(RTR("This node is marked as experimental and may be subject to removal or major changes in future versions."));

View File

@@ -0,0 +1,152 @@
/**************************************************************************/
/* resource_preloader.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "resource_preloader.h"
#include "core/templates/rb_set.h"
void ResourcePreloader::_set_resources(const Array &p_data) {
resources.clear();
ERR_FAIL_COND(p_data.size() != 2);
Vector<String> names = p_data[0];
Array resdata = p_data[1];
ERR_FAIL_COND(names.size() != resdata.size());
for (int i = 0; i < resdata.size(); i++) {
Ref<Resource> resource = resdata[i];
ERR_CONTINUE(resource.is_null());
resources[names[i]] = resource;
//add_resource(names[i],resource);
}
}
Array ResourcePreloader::_get_resources() const {
Vector<String> names;
Array arr;
arr.resize(resources.size());
names.resize(resources.size());
RBSet<String> sorted_names;
for (const KeyValue<StringName, Ref<Resource>> &E : resources) {
sorted_names.insert(E.key);
}
int i = 0;
for (const String &E : sorted_names) {
names.set(i, E);
arr[i] = resources[E];
i++;
}
Array res = { names, arr };
return res;
}
void ResourcePreloader::add_resource(const StringName &p_name, const Ref<Resource> &p_resource) {
ERR_FAIL_COND(p_resource.is_null());
if (resources.has(p_name)) {
StringName new_name;
int idx = 2;
while (true) {
new_name = p_name.operator String() + " " + itos(idx);
if (resources.has(new_name)) {
idx++;
continue;
}
break;
}
add_resource(new_name, p_resource);
} else {
resources[p_name] = p_resource;
}
}
void ResourcePreloader::remove_resource(const StringName &p_name) {
ERR_FAIL_COND(!resources.has(p_name));
resources.erase(p_name);
}
void ResourcePreloader::rename_resource(const StringName &p_from_name, const StringName &p_to_name) {
ERR_FAIL_COND(!resources.has(p_from_name));
Ref<Resource> res = resources[p_from_name];
resources.erase(p_from_name);
add_resource(p_to_name, res);
}
bool ResourcePreloader::has_resource(const StringName &p_name) const {
return resources.has(p_name);
}
Ref<Resource> ResourcePreloader::get_resource(const StringName &p_name) const {
ERR_FAIL_COND_V(!resources.has(p_name), Ref<Resource>());
return resources[p_name];
}
Vector<String> ResourcePreloader::_get_resource_list() const {
Vector<String> res;
res.resize(resources.size());
int i = 0;
for (const KeyValue<StringName, Ref<Resource>> &E : resources) {
res.set(i, E.key);
i++;
}
return res;
}
void ResourcePreloader::get_resource_list(List<StringName> *p_list) {
for (const KeyValue<StringName, Ref<Resource>> &E : resources) {
p_list->push_back(E.key);
}
}
void ResourcePreloader::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_resources", "resources"), &ResourcePreloader::_set_resources);
ClassDB::bind_method(D_METHOD("_get_resources"), &ResourcePreloader::_get_resources);
ClassDB::bind_method(D_METHOD("add_resource", "name", "resource"), &ResourcePreloader::add_resource);
ClassDB::bind_method(D_METHOD("remove_resource", "name"), &ResourcePreloader::remove_resource);
ClassDB::bind_method(D_METHOD("rename_resource", "name", "newname"), &ResourcePreloader::rename_resource);
ClassDB::bind_method(D_METHOD("has_resource", "name"), &ResourcePreloader::has_resource);
ClassDB::bind_method(D_METHOD("get_resource", "name"), &ResourcePreloader::get_resource);
ClassDB::bind_method(D_METHOD("get_resource_list"), &ResourcePreloader::_get_resource_list);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "resources", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_resources", "_get_resources");
}
ResourcePreloader::ResourcePreloader() {
}

View File

@@ -0,0 +1,57 @@
/**************************************************************************/
/* resource_preloader.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
class ResourcePreloader : public Node {
GDCLASS(ResourcePreloader, Node);
HashMap<StringName, Ref<Resource>> resources;
void _set_resources(const Array &p_data);
Array _get_resources() const;
Vector<String> _get_resource_list() const;
protected:
static void _bind_methods();
public:
void add_resource(const StringName &p_name, const Ref<Resource> &p_resource);
void remove_resource(const StringName &p_name);
void rename_resource(const StringName &p_from_name, const StringName &p_to_name);
bool has_resource(const StringName &p_name) const;
Ref<Resource> get_resource(const StringName &p_name) const;
void get_resource_list(List<StringName> *p_list);
ResourcePreloader();
};

2223
scene/main/scene_tree.cpp Normal file

File diff suppressed because it is too large Load Diff

473
scene/main/scene_tree.h Normal file
View File

@@ -0,0 +1,473 @@
/**************************************************************************/
/* scene_tree.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/os/main_loop.h"
#include "core/os/thread_safe.h"
#include "core/templates/paged_allocator.h"
#include "core/templates/self_list.h"
#include "scene/main/scene_tree_fti.h"
#include "scene/resources/mesh.h"
#undef Window
class PackedScene;
class Node;
#ifndef _3D_DISABLED
class Node3D;
#endif
class Window;
class Material;
class Mesh;
class MultiplayerAPI;
class SceneDebugger;
class Tween;
class Viewport;
class SceneTreeTimer : public RefCounted {
GDCLASS(SceneTreeTimer, RefCounted);
double time_left = 0.0;
bool process_always = true;
bool process_in_physics = false;
bool ignore_time_scale = false;
protected:
static void _bind_methods();
public:
void set_time_left(double p_time);
double get_time_left() const;
void set_process_always(bool p_process_always);
bool is_process_always();
void set_process_in_physics(bool p_process_in_physics);
bool is_process_in_physics();
void set_ignore_time_scale(bool p_ignore);
bool is_ignoring_time_scale();
void release_connections();
};
class SceneTree : public MainLoop {
_THREAD_SAFE_CLASS_
GDCLASS(SceneTree, MainLoop);
public:
typedef void (*IdleCallback)();
private:
CallQueue::Allocator *process_group_call_queue_allocator = nullptr;
struct ProcessGroup {
CallQueue call_queue;
Vector<Node *> nodes;
Vector<Node *> physics_nodes;
bool node_order_dirty = true;
bool physics_node_order_dirty = true;
bool removed = false;
Node *owner = nullptr;
uint64_t last_pass = 0;
};
struct ProcessGroupSort {
_FORCE_INLINE_ bool operator()(const ProcessGroup *p_left, const ProcessGroup *p_right) const;
};
PagedAllocator<ProcessGroup, true> group_allocator; // Allocate groups on pages, to enhance cache usage.
LocalVector<ProcessGroup *> process_groups;
bool process_groups_dirty = true;
LocalVector<ProcessGroup *> local_process_group_cache; // Used when processing to group what needs to
uint64_t process_last_pass = 1;
ProcessGroup default_process_group;
bool node_threading_disabled = false;
struct Group {
Vector<Node *> nodes;
bool changed = false;
};
#ifndef _3D_DISABLED
struct ClientPhysicsInterpolation {
SelfList<Node3D>::List _node_3d_list;
void physics_process();
} _client_physics_interpolation;
#endif
Window *root = nullptr;
double physics_process_time = 0.0;
double process_time = 0.0;
bool accept_quit = true;
bool quit_on_go_back = true;
#ifdef DEBUG_ENABLED
bool debug_collisions_hint = false;
bool debug_paths_hint = false;
bool debug_navigation_hint = false;
#endif
bool paused = false;
bool suspended = false;
HashMap<StringName, Group> group_map;
bool _quit = false;
// Static so we can get directly instead of via SceneTree pointer.
static bool _physics_interpolation_enabled;
// Note that physics interpolation is hard coded to OFF in the editor,
// therefore we have a second bool to enable e.g. configuration warnings
// to only take effect when the project is using physics interpolation.
static bool _physics_interpolation_enabled_in_project;
SceneTreeFTI scene_tree_fti;
StringName tree_changed_name = "tree_changed";
StringName node_added_name = "node_added";
StringName node_removed_name = "node_removed";
StringName node_renamed_name = "node_renamed";
int64_t current_frame = 0;
int nodes_in_tree_count = 0;
#ifdef TOOLS_ENABLED
Node *edited_scene_root = nullptr;
#endif
struct UGCall {
StringName group;
StringName call;
static uint32_t hash(const UGCall &p_val) {
return p_val.group.hash() ^ p_val.call.hash();
}
bool operator==(const UGCall &p_with) const { return group == p_with.group && call == p_with.call; }
bool operator<(const UGCall &p_with) const { return group == p_with.group ? call < p_with.call : group < p_with.group; }
};
// Safety for when a node is deleted while a group is being called.
int nodes_removed_on_group_call_lock = 0;
HashSet<Node *> nodes_removed_on_group_call; // Skip erased nodes.
List<ObjectID> delete_queue;
uint64_t accessibility_upd_per_sec = 0;
bool accessibility_force_update = true;
HashSet<ObjectID> accessibility_change_queue;
uint64_t accessibility_last_update = 0;
HashMap<UGCall, Vector<Variant>, UGCall> unique_group_calls;
bool ugc_locked = false;
void _flush_ugc();
_FORCE_INLINE_ void _update_group_order(Group &g);
TypedArray<Node> _get_nodes_in_group(const StringName &p_group);
Node *current_scene = nullptr;
ObjectID prev_scene_id;
ObjectID pending_new_scene_id;
Color debug_collisions_color;
Color debug_collision_contact_color;
Color debug_paths_color;
float debug_paths_width = 1.0f;
Ref<ArrayMesh> debug_contact_mesh;
Ref<Material> debug_paths_material;
Ref<Material> collision_material;
int collision_debug_contacts;
void _flush_scene_change();
List<Ref<SceneTreeTimer>> timers;
List<Ref<Tween>> tweens;
///network///
Ref<MultiplayerAPI> multiplayer;
HashMap<NodePath, Ref<MultiplayerAPI>> custom_multiplayers;
bool multiplayer_poll = true;
static SceneTree *singleton;
friend class Node;
void tree_changed();
void node_added(Node *p_node);
void node_removed(Node *p_node);
void node_renamed(Node *p_node);
void process_timers(double p_delta, bool p_physics_frame);
void process_tweens(double p_delta, bool p_physics_frame);
Group *add_to_group(const StringName &p_group, Node *p_node);
void remove_from_group(const StringName &p_group, Node *p_node);
void _process_group(ProcessGroup *p_group, bool p_physics);
void _process_groups_thread(uint32_t p_index, bool p_physics);
void _process(bool p_physics);
void _remove_process_group(Node *p_node);
void _add_process_group(Node *p_node);
void _remove_node_from_process_group(Node *p_node, Node *p_owner);
void _add_node_to_process_group(Node *p_node, Node *p_owner);
void _call_group_flags(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
void _call_group(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
void _flush_delete_queue();
// Optimization.
friend class CanvasItem;
friend class Node3D;
friend class Viewport;
SelfList<Node>::List xform_change_list;
#ifdef DEBUG_ENABLED // No live editor in release build.
friend class LiveEditor;
#endif
enum {
MAX_IDLE_CALLBACKS = 256
};
static IdleCallback idle_callbacks[MAX_IDLE_CALLBACKS];
static int idle_callback_count;
void _call_idle_callbacks();
void _main_window_focus_in();
void _main_window_close();
void _main_window_go_back();
enum CallInputType {
CALL_INPUT_TYPE_INPUT,
CALL_INPUT_TYPE_SHORTCUT_INPUT,
CALL_INPUT_TYPE_UNHANDLED_INPUT,
CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT,
};
//used by viewport
void _call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref<InputEvent> &p_input, Viewport *p_viewport);
protected:
void _notification(int p_notification);
static void _bind_methods();
public:
enum {
NOTIFICATION_TRANSFORM_CHANGED = 2000
};
enum GroupCallFlags {
GROUP_CALL_DEFAULT = 0,
GROUP_CALL_REVERSE = 1,
GROUP_CALL_DEFERRED = 2,
GROUP_CALL_UNIQUE = 4,
};
_FORCE_INLINE_ Window *get_root() const { return root; }
void call_group_flagsp(uint32_t p_call_flags, const StringName &p_group, const StringName &p_function, const Variant **p_args, int p_argcount);
void notify_group_flags(uint32_t p_call_flags, const StringName &p_group, int p_notification);
void set_group_flags(uint32_t p_call_flags, const StringName &p_group, const String &p_name, const Variant &p_value);
// `notify_group()` is immediate by default since Godot 4.0.
void notify_group(const StringName &p_group, int p_notification);
// `set_group()` is immediate by default since Godot 4.0.
void set_group(const StringName &p_group, const String &p_name, const Variant &p_value);
template <typename... VarArgs>
// `call_group()` is immediate by default since Godot 4.0.
void call_group(const StringName &p_group, const StringName &p_function, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
call_group_flagsp(GROUP_CALL_DEFAULT, p_group, p_function, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
template <typename... VarArgs>
void call_group_flags(uint32_t p_flags, const StringName &p_group, const StringName &p_function, VarArgs... p_args) {
Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
const Variant *argptrs[sizeof...(p_args) + 1];
for (uint32_t i = 0; i < sizeof...(p_args); i++) {
argptrs[i] = &args[i];
}
call_group_flagsp(p_flags, p_group, p_function, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
void flush_transform_notifications();
bool is_accessibility_enabled() const;
bool is_accessibility_supported() const;
void _accessibility_force_update();
void _accessibility_notify_change(const Node *p_node, bool p_remove = false);
void _flush_accessibility_changes();
void _process_accessibility_changes(DisplayServer::WindowID p_window_id);
virtual void initialize() override;
virtual void iteration_prepare() override;
virtual bool physics_process(double p_time) override;
virtual void iteration_end() override;
virtual bool process(double p_time) override;
virtual void finalize() override;
bool is_auto_accept_quit() const;
void set_auto_accept_quit(bool p_enable);
bool is_quit_on_go_back() const;
void set_quit_on_go_back(bool p_enable);
void quit(int p_exit_code = EXIT_SUCCESS);
_FORCE_INLINE_ double get_physics_process_time() const { return physics_process_time; }
_FORCE_INLINE_ double get_process_time() const { return process_time; }
void set_pause(bool p_enabled);
bool is_paused() const;
void set_suspend(bool p_enabled);
bool is_suspended() const;
#ifdef DEBUG_ENABLED
void set_debug_collisions_hint(bool p_enabled);
bool is_debugging_collisions_hint() const;
void set_debug_paths_hint(bool p_enabled);
bool is_debugging_paths_hint() const;
void set_debug_navigation_hint(bool p_enabled);
bool is_debugging_navigation_hint() const;
#else
void set_debug_collisions_hint(bool p_enabled) {}
bool is_debugging_collisions_hint() const { return false; }
void set_debug_paths_hint(bool p_enabled) {}
bool is_debugging_paths_hint() const { return false; }
void set_debug_navigation_hint(bool p_enabled) {}
bool is_debugging_navigation_hint() const { return false; }
#endif
void set_debug_collisions_color(const Color &p_color);
Color get_debug_collisions_color() const;
void set_debug_collision_contact_color(const Color &p_color);
Color get_debug_collision_contact_color() const;
void set_debug_paths_color(const Color &p_color);
Color get_debug_paths_color() const;
void set_debug_paths_width(float p_width);
float get_debug_paths_width() const;
Ref<Material> get_debug_paths_material();
Ref<Material> get_debug_collision_material();
Ref<ArrayMesh> get_debug_contact_mesh();
int get_collision_debug_contact_count() { return collision_debug_contacts; }
int64_t get_frame() const;
int get_node_count() const;
void queue_delete(Object *p_object);
void get_nodes_in_group(const StringName &p_group, List<Node *> *p_list);
Node *get_first_node_in_group(const StringName &p_group);
bool has_group(const StringName &p_identifier) const;
int get_node_count_in_group(const StringName &p_group) const;
//void change_scene(const String& p_path);
//Node *get_loaded_scene();
void set_edited_scene_root(Node *p_node);
Node *get_edited_scene_root() const;
void set_current_scene(Node *p_scene);
Node *get_current_scene() const;
Error change_scene_to_file(const String &p_path);
Error change_scene_to_packed(const Ref<PackedScene> &p_scene);
Error reload_current_scene();
void unload_current_scene();
Ref<SceneTreeTimer> create_timer(double p_delay_sec, bool p_process_always = true, bool p_process_in_physics = false, bool p_ignore_time_scale = false);
Ref<Tween> create_tween();
void remove_tween(const Ref<Tween> &p_tween);
TypedArray<Tween> get_processed_tweens();
//used by Main::start, don't use otherwise
void add_current_scene(Node *p_current);
static SceneTree *get_singleton() { return singleton; }
#ifdef TOOLS_ENABLED
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
//network API
Ref<MultiplayerAPI> get_multiplayer(const NodePath &p_for_path = NodePath()) const;
void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer, const NodePath &p_root_path = NodePath());
void set_multiplayer_poll_enabled(bool p_enabled);
bool is_multiplayer_poll_enabled() const;
static void add_idle_callback(IdleCallback p_callback);
void set_disable_node_threading(bool p_disable);
//default texture settings
void set_physics_interpolation_enabled(bool p_enabled);
bool is_physics_interpolation_enabled() const { return _physics_interpolation_enabled; }
// Different name to disambiguate fast static versions from the user bound versions.
static bool is_fti_enabled() { return _physics_interpolation_enabled; }
static bool is_fti_enabled_in_project() { return _physics_interpolation_enabled_in_project; }
#ifndef _3D_DISABLED
void client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem);
void client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem);
#endif
SceneTreeFTI &get_scene_tree_fti() { return scene_tree_fti; }
SceneTree();
~SceneTree();
};
VARIANT_ENUM_CAST(SceneTree::GroupCallFlags);

View File

@@ -0,0 +1,818 @@
/**************************************************************************/
/* scene_tree_fti.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef _3D_DISABLED
#include "scene_tree_fti.h"
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/math/transform_interpolator.h"
#include "core/os/os.h"
#include "scene/3d/visual_instance_3d.h"
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
#include "scene_tree_fti_tests.h"
#endif
#ifdef DEV_ENABLED
// Uncomment this to enable some slow extra DEV_ENABLED
// checks to ensure there aren't more than one object added to the lists.
// #define GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
// Uncomment this to regularly print the tree that is being interpolated.
// #define GODOT_SCENE_TREE_FTI_PRINT_TREE
#endif
void SceneTreeFTI::_reset_node3d_flags(Node3D &r_node) {
r_node.data.fti_on_tick_xform_list = false;
r_node.data.fti_on_tick_property_list = false;
r_node.data.fti_on_frame_xform_list = false;
r_node.data.fti_on_frame_property_list = false;
r_node.data.fti_global_xform_interp_set = false;
r_node.data.fti_frame_xform_force_update = false;
r_node.data.fti_processed = false;
}
void SceneTreeFTI::_reset_flags(Node *p_node) {
Node3D *s = Object::cast_to<Node3D>(p_node);
if (s) {
_reset_node3d_flags(*s);
// In most cases the later NOTIFICATION_RESET_PHYSICS_INTERPOLATION
// will reset this, but this should help cover hidden nodes.
s->data.local_transform_prev = s->get_transform();
}
for (int n = 0; n < p_node->get_child_count(); n++) {
_reset_flags(p_node->get_child(n));
}
}
void SceneTreeFTI::set_enabled(Node *p_root, bool p_enabled) {
if (data.enabled == p_enabled) {
return;
}
MutexLock(data.mutex);
data.tick_xform_list[0].clear();
data.tick_xform_list[1].clear();
data.frame_xform_list.clear();
data.frame_xform_list_forced.clear();
data.tick_property_list[0].clear();
data.tick_property_list[1].clear();
data.frame_property_list.clear();
data.request_reset_list.clear();
_clear_depth_lists();
// Node3D flags must be reset.
if (p_root) {
_reset_flags(p_root);
}
data.enabled = p_enabled;
}
void SceneTreeFTI::tick_update() {
if (!data.enabled) {
return;
}
MutexLock(data.mutex);
_update_request_resets();
uint32_t curr_mirror = data.mirror;
uint32_t prev_mirror = curr_mirror ? 0 : 1;
LocalVector<Node3D *> &curr = data.tick_xform_list[curr_mirror];
LocalVector<Node3D *> &prev = data.tick_xform_list[prev_mirror];
// First detect on the previous list but not on this tick list.
for (uint32_t n = 0; n < prev.size(); n++) {
Node3D *s = prev[n];
if (!s->data.fti_on_tick_xform_list) {
// Needs a reset so jittering will stop.
s->fti_pump_xform();
// Optimization - detect whether we have rested at identity xform.
s->data.fti_is_identity_xform = s->data.local_transform == Transform3D();
// This may not get updated so set it to the same as global xform.
// TODO: double check this is the best value.
s->data.global_transform_interpolated = s->get_global_transform();
// Remove from interpolation list.
if (s->data.fti_on_frame_xform_list) {
_node_remove_from_frame_list(*s, false);
}
// Ensure that the node gets at least ONE further
// update in the resting position in the next frame update.
if (!s->data.fti_frame_xform_force_update) {
_node_add_to_frame_list(*s, true);
}
}
}
LocalVector<Node3D *> &curr_prop = data.tick_property_list[curr_mirror];
LocalVector<Node3D *> &prev_prop = data.tick_property_list[prev_mirror];
// Detect on the previous property list but not on this tick list.
for (uint32_t n = 0; n < prev_prop.size(); n++) {
Node3D *s = prev_prop[n];
if (!s->data.fti_on_tick_property_list) {
// Needs a reset so jittering will stop.
s->fti_pump_xform();
// Ensure the servers are up to date with the final resting value.
s->fti_update_servers_property();
// Remove from interpolation list.
if (s->data.fti_on_frame_property_list) {
s->data.fti_on_frame_property_list = false;
data.frame_property_list.erase_unordered(s);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.frame_property_list.find(s) == -1);
#endif
}
}
}
// Pump all on the property list that are NOT on the tick list.
for (uint32_t n = 0; n < curr_prop.size(); n++) {
Node3D *s = curr_prop[n];
// Reset, needs to be marked each tick.
s->data.fti_on_tick_property_list = false;
s->fti_pump_property();
}
// Now pump all on the current list.
for (uint32_t n = 0; n < curr.size(); n++) {
Node3D *s = curr[n];
// Reset, needs to be marked each tick.
s->data.fti_on_tick_xform_list = false;
// Pump.
s->fti_pump_xform();
}
// Clear previous list and flip.
prev.clear();
prev_prop.clear();
data.mirror = prev_mirror;
}
void SceneTreeFTI::_update_request_resets() {
// For instance when first adding to the tree, when the previous transform is
// unset, to prevent streaking from the origin.
for (uint32_t n = 0; n < data.request_reset_list.size(); n++) {
Node3D *s = data.request_reset_list[n];
if (s->_is_physics_interpolation_reset_requested()) {
if (s->_is_vi_visible() && !s->_is_using_identity_transform()) {
s->notification(Node3D::NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
}
s->_set_physics_interpolation_reset_requested(false);
}
}
data.request_reset_list.clear();
}
void SceneTreeFTI::node_3d_request_reset(Node3D *p_node) {
DEV_CHECK_ONCE(data.enabled);
DEV_ASSERT(p_node);
MutexLock(data.mutex);
if (!p_node->_is_physics_interpolation_reset_requested()) {
p_node->_set_physics_interpolation_reset_requested(true);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1);
#endif
data.request_reset_list.push_back(p_node);
}
}
void SceneTreeFTI::_node_3d_notify_set_property(Node3D &r_node) {
if (!r_node.is_physics_interpolated()) {
return;
}
DEV_CHECK_ONCE(data.enabled);
// Note that a Node3D can be on BOTH the transform list and the property list.
if (!r_node.data.fti_on_tick_property_list) {
r_node.data.fti_on_tick_property_list = true;
// Should only appear once in the property list.
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.tick_property_list[data.mirror].find(&r_node) == -1);
#endif
data.tick_property_list[data.mirror].push_back(&r_node);
}
if (!r_node.data.fti_on_frame_property_list) {
r_node.data.fti_on_frame_property_list = true;
// Should only appear once in the property frame list.
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.frame_property_list.find(&r_node) == -1);
#endif
data.frame_property_list.push_back(&r_node);
}
}
void SceneTreeFTI::_create_depth_lists() {
uint32_t first_list = data.frame_start ? 0 : 1;
for (uint32_t l = first_list; l < 2; l++) {
LocalVector<Node3D *> &source_list = l == 0 ? data.frame_xform_list : data.frame_xform_list_forced;
#ifdef DEBUG_ENABLED
bool log_nodes_moved_on_frame = (data.traversal_mode == TM_DEBUG) && !data.frame_start && data.periodic_debug_log;
if (log_nodes_moved_on_frame) {
if (source_list.size()) {
print_line(String("\n") + itos(source_list.size()) + " nodes moved during frame:");
} else {
print_line("0 nodes moved during frame.");
}
}
#endif
for (uint32_t n = 0; n < source_list.size(); n++) {
Node3D *s = source_list[n];
s->data.fti_processed = false;
int32_t depth = s->_get_scene_tree_depth();
// This shouldn't happen, but wouldn't be terrible if it did.
DEV_ASSERT(depth >= 0);
depth = MIN(depth, (int32_t)data.scene_tree_depth_limit);
LocalVector<Node3D *> &dest_list = data.dirty_node_depth_lists[depth];
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
// Shouldn't really happen, but duplicates don't really matter that much.
if (dest_list.find(s) != -1) {
ERR_FAIL_COND(dest_list.find(s) != -1);
}
#endif
#ifdef DEBUG_ENABLED
if (log_nodes_moved_on_frame) {
print_line("\t" + s->get_name());
}
#endif
// Prevent being added to the dest_list twice when on
// the frame_xform_list AND the frame_xform_list_forced.
if ((l == 0) && s->data.fti_frame_xform_force_update) {
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_ASSERT(data.frame_xform_list_forced.find(s) != -1);
#endif
continue;
}
dest_list.push_back(s);
}
}
}
void SceneTreeFTI::_clear_depth_lists() {
for (uint32_t d = 0; d < data.scene_tree_depth_limit; d++) {
data.dirty_node_depth_lists[d].clear();
}
}
void SceneTreeFTI::_node_add_to_frame_list(Node3D &r_node, bool p_forced) {
if (p_forced) {
DEV_ASSERT(!r_node.data.fti_frame_xform_force_update);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
int64_t found = data.frame_xform_list_forced.find(&r_node);
if (found != -1) {
ERR_FAIL_COND(found != -1);
}
#endif
data.frame_xform_list_forced.push_back(&r_node);
r_node.data.fti_frame_xform_force_update = true;
} else {
DEV_ASSERT(!r_node.data.fti_on_frame_xform_list);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
int64_t found = data.frame_xform_list.find(&r_node);
if (found != -1) {
ERR_FAIL_COND(found != -1);
}
#endif
data.frame_xform_list.push_back(&r_node);
r_node.data.fti_on_frame_xform_list = true;
}
}
void SceneTreeFTI::_node_remove_from_frame_list(Node3D &r_node, bool p_forced) {
if (p_forced) {
DEV_ASSERT(r_node.data.fti_frame_xform_force_update);
data.frame_xform_list_forced.erase_unordered(&r_node);
r_node.data.fti_frame_xform_force_update = false;
} else {
DEV_ASSERT(r_node.data.fti_on_frame_xform_list);
data.frame_xform_list.erase_unordered(&r_node);
r_node.data.fti_on_frame_xform_list = false;
}
}
void SceneTreeFTI::_node_3d_notify_set_xform(Node3D &r_node) {
DEV_CHECK_ONCE(data.enabled);
if (!r_node.is_physics_interpolated()) {
// Force an update of non-interpolated to servers
// on the next traversal.
if (!r_node.data.fti_frame_xform_force_update) {
_node_add_to_frame_list(r_node, true);
}
// ToDo: Double check this is a win,
// non-interpolated nodes we always check for identity,
// *just in case*.
r_node.data.fti_is_identity_xform = r_node.get_transform() == Transform3D();
return;
}
r_node.data.fti_is_identity_xform = false;
if (!r_node.data.fti_on_tick_xform_list) {
r_node.data.fti_on_tick_xform_list = true;
// Should only appear once in the xform list.
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
DEV_CHECK_ONCE(data.tick_xform_list[data.mirror].find(&r_node) == -1);
#endif
data.tick_xform_list[data.mirror].push_back(&r_node);
// The following flag could have been previously set
// (for removal from the tick list).
// We no longer need this guarantee,
// however there is probably no downside to leaving it set
// as it will be cleared on the next frame anyway.
// This line is left for reference.
// r_node.data.fti_frame_xform_force_update = false;
}
if (!r_node.data.fti_on_frame_xform_list) {
_node_add_to_frame_list(r_node, false);
}
// If we are in the second half of a frame, always add to the force update list,
// because we ignore the tick update list during the second update.
if (data.in_frame) {
if (!r_node.data.fti_frame_xform_force_update) {
_node_add_to_frame_list(r_node, true);
}
}
}
void SceneTreeFTI::node_3d_notify_delete(Node3D *p_node) {
if (!data.enabled) {
return;
}
ERR_FAIL_NULL(p_node);
MutexLock(data.mutex);
// Remove from frame lists.
if (p_node->data.fti_on_frame_xform_list) {
_node_remove_from_frame_list(*p_node, false);
}
if (p_node->data.fti_frame_xform_force_update) {
_node_remove_from_frame_list(*p_node, true);
}
// Ensure this is kept in sync with the lists, in case a node
// is removed and re-added to the scene tree multiple times
// on the same frame / tick.
p_node->_set_physics_interpolation_reset_requested(false);
// Keep flags consistent for the same as a new node,
// because this node may re-enter the scene tree.
_reset_node3d_flags(*p_node);
// This can potentially be optimized for large scenes with large churn,
// as it will be doing a linear search through the lists.
data.tick_xform_list[0].erase_unordered(p_node);
data.tick_xform_list[1].erase_unordered(p_node);
data.tick_property_list[0].erase_unordered(p_node);
data.tick_property_list[1].erase_unordered(p_node);
data.frame_property_list.erase_unordered(p_node);
data.request_reset_list.erase_unordered(p_node);
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
// There should only be one occurrence on the lists.
// Check this in DEV_ENABLED builds.
DEV_CHECK_ONCE(data.tick_xform_list[0].find(p_node) == -1);
DEV_CHECK_ONCE(data.tick_xform_list[1].find(p_node) == -1);
DEV_CHECK_ONCE(data.tick_property_list[0].find(p_node) == -1);
DEV_CHECK_ONCE(data.tick_property_list[1].find(p_node) == -1);
DEV_CHECK_ONCE(data.frame_property_list.find(p_node) == -1);
DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1);
DEV_CHECK_ONCE(data.frame_xform_list.find(p_node) == -1);
DEV_CHECK_ONCE(data.frame_xform_list_forced.find(p_node) == -1);
#endif
}
void SceneTreeFTI::_update_dirty_nodes(Node *p_node, uint32_t p_current_half_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform, int p_depth) {
Node3D *s = Object::cast_to<Node3D>(p_node);
#ifdef DEBUG_ENABLED
data.debug_node_count++;
#endif
// Don't recurse into hidden branches.
if (s && !s->data.visible) {
// NOTE : If we change from recursing entire tree, we should do an is_visible_in_tree()
// check for the first of the branch.
return;
}
// Temporary direct access to children cache for speed.
// Maybe replaced later by a more generic fast access method
// for children.
p_node->_update_children_cache();
Span<Node *> children = p_node->data.children_cache.span();
uint32_t num_children = children.size();
// Not a Node3D.
// Could be e.g. a viewport or something
// so we should still recurse to children.
if (!s) {
for (uint32_t n = 0; n < num_children; n++) {
_update_dirty_nodes(children.ptr()[n], p_current_half_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1);
}
return;
}
// We are going to be using data.global_transform, so
// we need to ensure data.global_transform is not dirty!
if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_TRANSFORM)) {
_ALLOW_DISCARD_ s->get_global_transform();
}
// Start the active interpolation chain from here onwards
// as we recurse further into the SceneTree.
// Once we hit an active (interpolated) node, we have to fully
// process all ancestors because their xform will also change.
// Anything not moving (inactive) higher in the tree need not be processed.
if (!p_active) {
if (data.frame_start) {
// On the frame start, activate whenever we hit something that requests interpolation.
if (s->data.fti_on_frame_xform_list || s->data.fti_frame_xform_force_update) {
p_active = true;
}
} else {
// On the frame end, we want to re-interpolate *anything* that has moved
// since the frame start.
if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM)) {
p_active = true;
#if 0
if (data.periodic_debug_log) {
print_line("activating on : " + s->get_name());
}
#endif
}
}
}
// ToDo : Check global_xform_interp is up to date for nodes
// that are not traversed by the depth lists.
if (data.frame_start) {
// Mark on the Node3D whether we have set global_transform_interp.
// This can later be used when calling `get_global_transform_interpolated()`
// to know which xform to return.
s->data.fti_global_xform_interp_set = p_active;
}
if (p_active) {
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
bool dirty = s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
if (data.periodic_debug_log && !data.use_optimized_traversal_method && !data.frame_start) {
String sz;
for (int n = 0; n < p_depth; n++) {
sz += "\t";
}
print_line(sz + p_node->get_name() + (dirty ? " DIRTY" : "") + (s->get_transform() == Transform3D() ? "\t[IDENTITY]" : ""));
}
#endif
// First calculate our local xform.
// This will either use interpolation, or just use the current local if not interpolated.
Transform3D local_interp;
if (s->is_physics_interpolated()) {
// There may be no need to interpolate if the node has not been moved recently
// and is therefore not on the tick list...
if (s->data.fti_on_tick_xform_list) {
// Make sure to call `get_transform()` rather than using local_transform directly, because
// local_transform may be dirty and need updating from rotation / scale.
TransformInterpolator::interpolate_transform_3d(s->data.local_transform_prev, s->get_transform(), local_interp, p_interpolation_fraction);
} else {
local_interp = s->get_transform();
}
} else {
local_interp = s->get_transform();
}
// Concatenate parent xform.
if (!s->is_set_as_top_level()) {
if (p_parent_global_xform) {
s->data.global_transform_interpolated = s->data.fti_is_identity_xform ? *p_parent_global_xform : ((*p_parent_global_xform) * local_interp);
} else {
const Node3D *parent = s->get_parent_node_3d();
if (parent) {
const Transform3D &parent_glob = parent->data.fti_global_xform_interp_set ? parent->data.global_transform_interpolated : parent->get_global_transform();
s->data.global_transform_interpolated = s->data.fti_is_identity_xform ? parent_glob : parent_glob * local_interp;
} else {
s->data.global_transform_interpolated = local_interp;
}
}
} else {
s->data.global_transform_interpolated = local_interp;
}
// Watch for this, disable_scale can cause incredibly confusing bugs
// and must be checked for when calculating global xforms.
if (s->data.disable_scale) {
s->data.global_transform_interpolated.basis.orthonormalize();
}
// Upload to RenderingServer the interpolated global xform.
s->fti_update_servers_xform();
// Ensure branches are only processed once on each traversal.
s->data.fti_processed = true;
#ifdef DEBUG_ENABLED
data.debug_nodes_processed++;
#endif
} // if active.
// Remove the dirty interp flag from EVERYTHING as we go.
s->_clear_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
// Recurse to children.
for (uint32_t n = 0; n < num_children; n++) {
_update_dirty_nodes(children.ptr()[n], p_current_half_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1);
}
}
void SceneTreeFTI::frame_update(Node *p_root, bool p_frame_start) {
if (!data.enabled || !p_root) {
return;
}
MutexLock(data.mutex);
data.frame_start = p_frame_start;
data.in_frame = true;
_update_request_resets();
float interpolation_fraction = Engine::get_singleton()->get_physics_interpolation_fraction();
uint32_t frame = Engine::get_singleton()->get_frames_drawn();
uint64_t before = 0;
#ifdef DEBUG_ENABLED
if (data.traversal_mode == TM_DEBUG) {
before = OS::get_singleton()->get_ticks_usec();
if (p_frame_start && ((frame % ((60 * 15) - 3)) == 0)) {
data.periodic_debug_log = true;
}
}
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
if (data.periodic_debug_log) {
print_line(String("\nScene: ") + (data.frame_start ? "start" : "end") + "\n");
}
#endif
#endif
data.debug_node_count = 0;
data.debug_nodes_processed = 0;
uint32_t half_frame = p_frame_start ? (frame * 2) : ((frame * 2) + 1);
bool print_debug_stats = false;
switch (data.traversal_mode) {
case TM_LEGACY: {
data.use_optimized_traversal_method = false;
} break;
case TM_DEBUG: {
// Switch on alternate frames between the two methods.
data.use_optimized_traversal_method = (frame % 2) == 1;
// Odd number ensures we debug stats for both methods.
print_debug_stats = (frame % ((60 * 8) - 1)) == 0;
} break;
default: {
data.use_optimized_traversal_method = true;
} break;
}
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
_tests->frame_update(p_root, half_frame, interpolation_fraction);
#else
uint32_t skipped = 0;
if (!data.use_optimized_traversal_method) {
// Reference approach.
// Traverse the entire scene tree.
// Slow, but robust.
_update_dirty_nodes(p_root, half_frame, interpolation_fraction, false);
} else {
// Optimized approach.
// Traverse from depth lists.
// Be sure to check against the reference
// implementation when making changes.
_create_depth_lists();
for (uint32_t d = 0; d < data.scene_tree_depth_limit; d++) {
const LocalVector<Node3D *> &list = data.dirty_node_depth_lists[d];
#if 0
if (list.size() > 0) {
print_line("depth " + itos(d) + ", contains " + itos(list.size()));
}
#endif
for (uint32_t n = 0; n < list.size(); n++) {
// Already processed this frame?
Node3D *s = list[n];
if (s->data.fti_processed) {
#ifdef DEBUG_ENABLED
skipped++;
#endif
continue;
}
// The first node requires a recursive visibility check
// up the tree, because `is_visible()` only returns the node
// local flag.
if (Object::cast_to<VisualInstance3D>(s)) {
if (!s->_is_vi_visible()) {
#ifdef DEBUG_ENABLED
skipped++;
#endif
continue;
}
} else if (!s->is_visible_in_tree()) {
#ifdef DEBUG_ENABLED
skipped++;
#endif
continue;
}
_update_dirty_nodes(s, half_frame, interpolation_fraction, true);
}
}
_clear_depth_lists();
}
if (print_debug_stats) {
uint64_t after = OS::get_singleton()->get_ticks_usec();
print_line(String(data.use_optimized_traversal_method ? "FTI optimized" : "FTI reference") + " nodes traversed : " + itos(data.debug_node_count) + (skipped == 0 ? "" : ", skipped " + itos(skipped)) + ", processed : " + itos(data.debug_nodes_processed) + ", took " + itos(after - before) + " usec " + (data.frame_start ? "(start)" : "(end)"));
}
#endif // not GODOT_SCENE_TREE_FTI_VERIFY
// In theory we could clear the `force_update` flags from the nodes in the traversal.
// The problem is that hidden nodes are not recursed into, therefore the flags would
// never get cleared and could get out of sync with the forced list.
// So instead we are clearing them here manually.
// This is not ideal in terms of cache coherence so perhaps another method can be
// explored in future.
uint32_t forced_list_size = data.frame_xform_list_forced.size();
for (uint32_t n = 0; n < forced_list_size; n++) {
Node3D *s = data.frame_xform_list_forced[n];
s->data.fti_frame_xform_force_update = false;
}
data.frame_xform_list_forced.clear();
if (!p_frame_start && data.periodic_debug_log) {
data.periodic_debug_log = false;
}
// Update the properties once off at the end of the frame.
// No need for two passes for properties.
if (!p_frame_start) {
for (uint32_t n = 0; n < data.frame_property_list.size(); n++) {
Node3D *s = data.frame_property_list[n];
s->fti_update_servers_property();
}
}
// Marks the end of the frame.
// Enables us to recognize when change notifications
// come in _during_ a frame (they get treated differently).
if (!data.frame_start) {
data.in_frame = false;
}
}
SceneTreeFTI::SceneTreeFTI() {
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
_tests = memnew(SceneTreeFTITests(*this));
#endif
Variant traversal_mode_string = GLOBAL_DEF("physics/3d/physics_interpolation/scene_traversal", "DEFAULT");
ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "physics/3d/physics_interpolation/scene_traversal", PROPERTY_HINT_ENUM, "DEFAULT,Legacy,Debug"));
data.traversal_mode = TM_DEFAULT;
if (traversal_mode_string == "Legacy") {
data.traversal_mode = TM_LEGACY;
} else if (traversal_mode_string == "Debug") {
// Don't allow debug mode in final exports,
// it will almost certainly be a mistake.
#ifdef DEBUG_ENABLED
data.traversal_mode = TM_DEBUG;
#else
data.traversal_mode = TM_DEFAULT;
#endif
}
switch (data.traversal_mode) {
default: {
print_verbose("SceneTreeFTI: traversal method DEFAULT");
} break;
case TM_LEGACY: {
print_verbose("SceneTreeFTI: traversal method Legacy");
} break;
case TM_DEBUG: {
print_verbose("SceneTreeFTI: traversal method Debug");
} break;
}
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
print_line("SceneTreeFTI : GODOT_SCENE_TREE_FTI_EXTRA_CHECKS defined");
#endif
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
print_line("SceneTreeFTI : GODOT_SCENE_TREE_FTI_PRINT_TREE defined");
#endif
}
SceneTreeFTI::~SceneTreeFTI() {
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
if (_tests) {
memfree(_tests);
_tests = nullptr;
}
#endif
}
#endif // ndef _3D_DISABLED

173
scene/main/scene_tree_fti.h Normal file
View File

@@ -0,0 +1,173 @@
/**************************************************************************/
/* scene_tree_fti.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/os/mutex.h"
#include "core/templates/local_vector.h"
class Node3D;
class Node;
struct Transform3D;
class SceneTreeFTITests;
#ifdef DEV_ENABLED
// Uncomment this to verify traversal method results.
// #define GODOT_SCENE_TREE_FTI_VERIFY
#endif
#ifdef _3D_DISABLED
// Stubs
class SceneTreeFTI {
public:
void frame_update(Node *p_root, bool p_frame_start) {}
void tick_update() {}
void set_enabled(Node *p_root, bool p_enabled) {}
bool is_enabled() const { return false; }
void node_3d_notify_changed(Node3D &r_node, bool p_transform_changed) {}
void node_3d_notify_delete(Node3D *p_node) {}
void node_3d_request_reset(Node3D *p_node) {}
};
#else
// Important.
// This class uses raw pointers, so it is essential that on deletion, this class is notified
// so that any references can be cleared up to prevent dangling pointer access.
// This class can be used from a custom SceneTree.
// Note we could potentially make SceneTreeFTI static / global to avoid the lookup through scene tree,
// but this covers the custom case of multiple scene trees.
class SceneTreeFTI {
friend class SceneTreeFTITests;
enum TraversalMode : unsigned {
TM_DEFAULT,
TM_LEGACY,
TM_DEBUG,
};
struct Data {
static const uint32_t scene_tree_depth_limit = 32;
// Prev / Curr lists of Node3Ds having local xforms pumped.
LocalVector<Node3D *> tick_xform_list[2];
// The frame lists are changed nodes that need to start traversal,
// either longterm (on the tick list) or single frame forced.
LocalVector<Node3D *> frame_xform_list;
LocalVector<Node3D *> frame_xform_list_forced;
// Prev / Curr lists of Node3Ds having actively interpolated properties.
LocalVector<Node3D *> tick_property_list[2];
LocalVector<Node3D *> frame_property_list;
LocalVector<Node3D *> request_reset_list;
LocalVector<Node3D *> dirty_node_depth_lists[scene_tree_depth_limit];
// When we are using two alternating lists,
// which one is current.
uint32_t mirror = 0;
// Global on / off switch for SceneTreeFTI.
bool enabled = false;
// Whether we are in physics ticks, or in a frame.
bool in_frame = false;
// Updating at the start of the frame, or the end on second pass.
bool frame_start = true;
Mutex mutex;
TraversalMode traversal_mode = TM_DEFAULT;
bool use_optimized_traversal_method = true;
// DEBUGGING
bool periodic_debug_log = false;
uint32_t debug_node_count = 0;
uint32_t debug_nodes_processed = 0;
} data;
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
SceneTreeFTITests *_tests = nullptr;
#endif
void _update_dirty_nodes(Node *p_node, uint32_t p_current_half_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform = nullptr, int p_depth = 0);
void _update_request_resets();
void _reset_flags(Node *p_node);
void _reset_node3d_flags(Node3D &r_node);
void _node_3d_notify_set_xform(Node3D &r_node);
void _node_3d_notify_set_property(Node3D &r_node);
void _node_add_to_frame_list(Node3D &r_node, bool p_forced);
void _node_remove_from_frame_list(Node3D &r_node, bool p_forced);
void _create_depth_lists();
void _clear_depth_lists();
public:
// Hottest function, allow inlining the data.enabled check.
void node_3d_notify_changed(Node3D &r_node, bool p_transform_changed) {
if (!data.enabled) {
return;
}
MutexLock(data.mutex);
if (p_transform_changed) {
_node_3d_notify_set_xform(r_node);
} else {
_node_3d_notify_set_property(r_node);
}
}
void node_3d_request_reset(Node3D *p_node);
void node_3d_notify_delete(Node3D *p_node);
// Calculate interpolated xforms, send to visual server.
void frame_update(Node *p_root, bool p_frame_start);
// Update local xform pumps.
void tick_update();
void set_enabled(Node *p_root, bool p_enabled);
bool is_enabled() const { return data.enabled; }
void set_debug_next_frame() { data.periodic_debug_log = true; }
SceneTreeFTI();
~SceneTreeFTI();
};
#endif // ndef _3D_DISABLED

View File

@@ -0,0 +1,246 @@
/**************************************************************************/
/* scene_tree_fti_tests.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef _3D_DISABLED
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
#include "scene_tree_fti_tests.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/visual_instance_3d.h"
#include "scene/main/scene_tree_fti.h"
void SceneTreeFTITests::debug_verify_failed(const Node3D *p_node_3d, const Transform3D &p_test) {
print_line("VERIFY FAILED\n");
print_line("test xform : " + String(Variant(p_test)));
bool first = true;
while (p_node_3d) {
int32_t depth = MAX(p_node_3d->_get_scene_tree_depth(), 0);
String tabs;
for (int32_t n = 0; n < depth; n++) {
tabs += "\t";
}
bool interp_equal = p_node_3d->_get_cached_global_transform_interpolated() == p_test;
bool glob_equal = p_node_3d->get_global_transform() == p_test;
String sz = tabs + p_node_3d->get_name() + " [ " + p_node_3d->get_class_name() + " ]\n";
if (first) {
sz += tabs + "... " + String(Variant(p_test)) + "\n";
}
sz += tabs + (p_node_3d->data.fti_global_xform_interp_set ? "[I] " : "[i] ") + String(Variant(p_node_3d->_get_cached_global_transform_interpolated())) + (interp_equal ? " ***" : "") + "\n";
sz += tabs + "[g] " + String(Variant(p_node_3d->get_global_transform())) + (glob_equal ? " ***" : "");
print_line(sz);
p_node_3d = p_node_3d->get_parent_node_3d();
first = false;
}
}
void SceneTreeFTITests::update_dirty_nodes(Node *p_node, uint32_t p_current_half_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform, int p_depth) {
SceneTreeFTI::Data &data = _fti.data;
// There are two runs going on here.
// FIRST the naive entire scene tree (reference), where we are
// setting state (i.e. writing out xforms, and other state)
// SECOND the optimized run, where we are NOT
// writing state, but only verifying that the xforms calculated
// match those from the reference approach.
bool should_verify = (data.traversal_mode == SceneTreeFTI::TM_DEBUG) && data.use_optimized_traversal_method;
bool set_state = !should_verify;
Node3D *s = Object::cast_to<Node3D>(p_node);
if (s && !s->is_visible()) {
return;
}
if (!s) {
for (int n = 0; n < p_node->get_child_count(); n++) {
update_dirty_nodes(p_node->get_child(n), p_current_half_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1);
}
return;
}
if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_TRANSFORM)) {
_ALLOW_DISCARD_ s->get_global_transform();
}
if (!p_active) {
if (data.frame_start) {
if (s->data.fti_on_frame_xform_list || s->data.fti_frame_xform_force_update) {
p_active = true;
}
} else {
if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM)) {
p_active = true;
}
}
}
if (data.frame_start) {
s->data.fti_global_xform_interp_set = p_active;
}
if (p_active) {
Transform3D local_interp;
if (s->is_physics_interpolated()) {
if (s->data.fti_on_tick_xform_list) {
TransformInterpolator::interpolate_transform_3d(s->data.local_transform_prev, s->get_transform(), local_interp, p_interpolation_fraction);
} else {
local_interp = s->get_transform();
}
} else {
local_interp = s->get_transform();
}
if (!s->is_set_as_top_level()) {
if (p_parent_global_xform) {
if (should_verify) {
Transform3D test = (*p_parent_global_xform) * local_interp;
if (s->data.disable_scale) {
test.basis.orthonormalize();
}
if (s->data.global_transform_interpolated != test) {
debug_verify_failed(s, test);
DEV_ASSERT(s->data.global_transform_interpolated == test);
}
} else {
s->data.global_transform_interpolated = s->data.fti_is_identity_xform ? (*p_parent_global_xform) : (*p_parent_global_xform) * local_interp;
}
} else {
const Node3D *parent = s->get_parent_node_3d();
if (parent) {
const Transform3D &parent_glob = parent->data.fti_global_xform_interp_set ? parent->data.global_transform_interpolated : parent->get_global_transform();
if (should_verify) {
Transform3D test = parent_glob * local_interp;
if (s->data.disable_scale) {
test.basis.orthonormalize();
}
if (s->data.global_transform_interpolated != test) {
debug_verify_failed(s, test);
DEV_ASSERT(s->data.global_transform_interpolated == test);
}
} else {
s->data.global_transform_interpolated = s->data.fti_is_identity_xform ? parent_glob : parent_glob * local_interp;
}
} else {
if (set_state) {
s->data.global_transform_interpolated = local_interp;
}
}
}
} else {
if (set_state) {
s->data.global_transform_interpolated = local_interp;
}
}
if (set_state) {
if (s->data.disable_scale) {
s->data.global_transform_interpolated.basis.orthonormalize();
}
s->fti_update_servers_xform();
s->data.fti_frame_xform_force_update = false;
}
s->data.fti_processed = true;
} // if active.
if (set_state) {
s->_clear_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
}
for (int n = 0; n < p_node->get_child_count(); n++) {
update_dirty_nodes(p_node->get_child(n), p_current_half_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1);
}
}
void SceneTreeFTITests::frame_update(Node *p_root, uint32_t p_half_frame, float p_interpolation_fraction) {
SceneTreeFTI::Data &data = _fti.data;
// For testing, use both methods.
// FIRST the entire tree, writing out state.
{
data.use_optimized_traversal_method = false;
update_dirty_nodes(p_root, p_half_frame, p_interpolation_fraction, false);
}
// SECOND the optimized depth lists only,
// no writing of state, and verifying results.
{
data.use_optimized_traversal_method = true;
_fti._create_depth_lists();
for (uint32_t d = 0; d < data.scene_tree_depth_limit; d++) {
const LocalVector<Node3D *> &list = data.dirty_node_depth_lists[d];
for (uint32_t n = 0; n < list.size(); n++) {
Node3D *s = list[n];
if (s->data.fti_processed) {
continue;
}
if (Object::cast_to<VisualInstance3D>(s)) {
if (!s->_is_vi_visible()) {
continue;
}
} else if (!s->is_visible_in_tree()) {
continue;
}
update_dirty_nodes(s, p_half_frame, p_interpolation_fraction, true);
}
}
_fti._clear_depth_lists();
}
}
SceneTreeFTITests::SceneTreeFTITests(SceneTreeFTI &p_fti) :
_fti(p_fti) {
print_line("SceneTreeFTI : GODOT_SCENE_TREE_FTI_VERIFY defined");
}
#endif // def GODOT_SCENE_TREE_FTI_VERIFY
#endif // ndef _3D_DISABLED

View File

@@ -0,0 +1,50 @@
/**************************************************************************/
/* scene_tree_fti_tests.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include <stdint.h>
class Node3D;
class Node;
struct Transform3D;
class SceneTreeFTI;
class SceneTreeFTITests {
SceneTreeFTI &_fti;
void debug_verify_failed(const Node3D *p_node, const Transform3D &p_test);
public:
void update_dirty_nodes(Node *p_node, uint32_t p_current_half_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform = nullptr, int p_depth = 0);
void frame_update(Node *p_root, uint32_t p_half_frame, float p_interpolation_fraction);
SceneTreeFTITests(SceneTreeFTI &p_fti);
};

View File

@@ -0,0 +1,290 @@
/**************************************************************************/
/* shader_globals_override.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "shader_globals_override.h"
#include "scene/main/node.h"
StringName *ShaderGlobalsOverride::_remap(const StringName &p_name) const {
StringName *r = param_remaps.getptr(p_name);
if (!r) {
//not cached, do caching
String p = p_name;
if (p.begins_with("params/")) {
String q = p.replace_first("params/", "");
param_remaps[p] = q;
r = param_remaps.getptr(p);
}
}
return r;
}
bool ShaderGlobalsOverride::_set(const StringName &p_name, const Variant &p_value) {
StringName *r = _remap(p_name);
if (r) {
Override *o = overrides.getptr(*r);
if (!o) {
Override ov;
ov.in_use = false;
overrides[*r] = ov;
o = overrides.getptr(*r);
}
if (o) {
o->override = p_value;
if (active) {
if (o->override.get_type() == Variant::OBJECT) {
RID tex_rid = p_value;
RS::get_singleton()->global_shader_parameter_set_override(*r, tex_rid);
} else {
RS::get_singleton()->global_shader_parameter_set_override(*r, p_value);
}
}
o->in_use = p_value.get_type() != Variant::NIL;
return true;
}
}
return false;
}
bool ShaderGlobalsOverride::_get(const StringName &p_name, Variant &r_ret) const {
StringName *r = _remap(p_name);
if (r) {
const Override *o = overrides.getptr(*r);
if (o) {
r_ret = o->override;
return true;
}
}
return false;
}
void ShaderGlobalsOverride::_get_property_list(List<PropertyInfo> *p_list) const {
Vector<StringName> variables;
variables = RS::get_singleton()->global_shader_parameter_get_list();
for (int i = 0; i < variables.size(); i++) {
PropertyInfo pinfo;
pinfo.name = "params/" + variables[i];
pinfo.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
switch (RS::get_singleton()->global_shader_parameter_get_type(variables[i])) {
case RS::GLOBAL_VAR_TYPE_BOOL: {
pinfo.type = Variant::BOOL;
} break;
case RS::GLOBAL_VAR_TYPE_BVEC2: {
pinfo.type = Variant::INT;
pinfo.hint = PROPERTY_HINT_FLAGS;
pinfo.hint_string = "x,y";
} break;
case RS::GLOBAL_VAR_TYPE_BVEC3: {
pinfo.type = Variant::INT;
pinfo.hint = PROPERTY_HINT_FLAGS;
pinfo.hint_string = "x,y,z";
} break;
case RS::GLOBAL_VAR_TYPE_BVEC4: {
pinfo.type = Variant::INT;
pinfo.hint = PROPERTY_HINT_FLAGS;
pinfo.hint_string = "x,y,z,w";
} break;
case RS::GLOBAL_VAR_TYPE_INT: {
pinfo.type = Variant::INT;
} break;
case RS::GLOBAL_VAR_TYPE_IVEC2: {
pinfo.type = Variant::VECTOR2I;
} break;
case RS::GLOBAL_VAR_TYPE_IVEC3: {
pinfo.type = Variant::VECTOR3I;
} break;
case RS::GLOBAL_VAR_TYPE_IVEC4: {
pinfo.type = Variant::VECTOR4I;
} break;
case RS::GLOBAL_VAR_TYPE_RECT2I: {
pinfo.type = Variant::RECT2I;
} break;
case RS::GLOBAL_VAR_TYPE_UINT: {
pinfo.type = Variant::INT;
} break;
case RS::GLOBAL_VAR_TYPE_UVEC2: {
pinfo.type = Variant::VECTOR2I;
} break;
case RS::GLOBAL_VAR_TYPE_UVEC3: {
pinfo.type = Variant::VECTOR3I;
} break;
case RS::GLOBAL_VAR_TYPE_UVEC4: {
pinfo.type = Variant::VECTOR4I;
} break;
case RS::GLOBAL_VAR_TYPE_FLOAT: {
pinfo.type = Variant::FLOAT;
} break;
case RS::GLOBAL_VAR_TYPE_VEC2: {
pinfo.type = Variant::VECTOR2;
} break;
case RS::GLOBAL_VAR_TYPE_VEC3: {
pinfo.type = Variant::VECTOR3;
} break;
case RS::GLOBAL_VAR_TYPE_VEC4: {
pinfo.type = Variant::VECTOR4;
} break;
case RS::GLOBAL_VAR_TYPE_RECT2: {
pinfo.type = Variant::RECT2;
} break;
case RS::GLOBAL_VAR_TYPE_COLOR: {
pinfo.type = Variant::COLOR;
} break;
case RS::GLOBAL_VAR_TYPE_MAT2: {
pinfo.type = Variant::PACKED_FLOAT32_ARRAY;
} break;
case RS::GLOBAL_VAR_TYPE_MAT3: {
pinfo.type = Variant::BASIS;
} break;
case RS::GLOBAL_VAR_TYPE_MAT4: {
pinfo.type = Variant::PROJECTION;
} break;
case RS::GLOBAL_VAR_TYPE_TRANSFORM_2D: {
pinfo.type = Variant::TRANSFORM2D;
} break;
case RS::GLOBAL_VAR_TYPE_TRANSFORM: {
pinfo.type = Variant::TRANSFORM3D;
} break;
case RS::GLOBAL_VAR_TYPE_SAMPLER2D: {
pinfo.type = Variant::OBJECT;
pinfo.hint = PROPERTY_HINT_RESOURCE_TYPE;
pinfo.hint_string = "Texture2D";
} break;
case RS::GLOBAL_VAR_TYPE_SAMPLER2DARRAY: {
pinfo.type = Variant::OBJECT;
pinfo.hint = PROPERTY_HINT_RESOURCE_TYPE;
pinfo.hint_string = "Texture2DArray";
} break;
case RS::GLOBAL_VAR_TYPE_SAMPLER3D: {
pinfo.type = Variant::OBJECT;
pinfo.hint = PROPERTY_HINT_RESOURCE_TYPE;
pinfo.hint_string = "Texture3D";
} break;
case RS::GLOBAL_VAR_TYPE_SAMPLERCUBE: {
pinfo.type = Variant::OBJECT;
pinfo.hint = PROPERTY_HINT_RESOURCE_TYPE;
pinfo.hint_string = "Cubemap";
} break;
case RS::GLOBAL_VAR_TYPE_SAMPLEREXT: {
pinfo.type = Variant::OBJECT;
pinfo.hint = PROPERTY_HINT_RESOURCE_TYPE;
pinfo.hint_string = "ExternalTexture";
} break;
default: {
} break;
}
if (!overrides.has(variables[i])) {
Override o;
o.in_use = false;
Callable::CallError ce;
Variant::construct(pinfo.type, o.override, nullptr, 0, ce);
overrides[variables[i]] = o;
}
Override *o = overrides.getptr(variables[i]);
if (o->in_use && o->override.get_type() != Variant::NIL) {
pinfo.usage |= PROPERTY_USAGE_CHECKED;
pinfo.usage |= PROPERTY_USAGE_STORAGE;
}
p_list->push_back(pinfo);
}
}
void ShaderGlobalsOverride::_activate() {
ERR_FAIL_NULL(get_tree());
List<Node *> nodes;
get_tree()->get_nodes_in_group(SceneStringName(shader_overrides_group_active), &nodes);
if (nodes.is_empty()) {
//good we are the only override, enable all
active = true;
add_to_group(SceneStringName(shader_overrides_group_active));
for (const KeyValue<StringName, Override> &E : overrides) {
const Override *o = &E.value;
if (o->in_use && o->override.get_type() != Variant::NIL) {
if (o->override.get_type() == Variant::OBJECT) {
RID tex_rid = o->override;
RS::get_singleton()->global_shader_parameter_set_override(E.key, tex_rid);
} else {
RS::get_singleton()->global_shader_parameter_set_override(E.key, o->override);
}
}
update_configuration_warnings(); //may have activated
}
}
}
void ShaderGlobalsOverride::_notification(int p_what) {
switch (p_what) {
case Node::NOTIFICATION_ENTER_TREE: {
add_to_group(SceneStringName(shader_overrides_group));
_activate();
} break;
case Node::NOTIFICATION_EXIT_TREE: {
if (active) {
//remove overrides
for (const KeyValue<StringName, Override> &E : overrides) {
const Override *o = &E.value;
if (o->in_use) {
RS::get_singleton()->global_shader_parameter_set_override(E.key, Variant());
}
}
}
remove_from_group(SceneStringName(shader_overrides_group_active));
remove_from_group(SceneStringName(shader_overrides_group));
get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(shader_overrides_group), "_activate"); //another may want to activate when this is removed
active = false;
} break;
}
}
PackedStringArray ShaderGlobalsOverride::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!active) {
warnings.push_back(RTR("ShaderGlobalsOverride is not active because another node of the same type is in the scene."));
}
return warnings;
}
void ShaderGlobalsOverride::_bind_methods() {
ClassDB::bind_method(D_METHOD("_activate"), &ShaderGlobalsOverride::_activate);
}

View File

@@ -0,0 +1,61 @@
/**************************************************************************/
/* shader_globals_override.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
class ShaderGlobalsOverride : public Node {
GDCLASS(ShaderGlobalsOverride, Node);
struct Override {
bool in_use = false;
Variant override;
};
StringName *_remap(const StringName &p_name) const;
bool active = false;
mutable HashMap<StringName, Override> overrides;
mutable HashMap<StringName, StringName> param_remaps;
void _activate();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _notification(int p_what);
static void _bind_methods();
public:
PackedStringArray get_configuration_warnings() const override;
};

View File

@@ -0,0 +1,192 @@
/**************************************************************************/
/* status_indicator.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "status_indicator.h"
#include "scene/gui/popup_menu.h"
void StatusIndicator::_notification(int p_what) {
ERR_MAIN_THREAD_GUARD;
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
return;
}
#endif
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) {
iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback));
PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
if (pm) {
RID menu_rid = pm->bind_global_menu();
DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid);
}
}
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
if (pm) {
pm->unbind_global_menu();
DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID());
}
DisplayServer::get_singleton()->delete_status_indicator(iid);
iid = DisplayServer::INVALID_INDICATOR_ID;
}
}
} break;
default:
break;
}
}
void StatusIndicator::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tooltip", "tooltip"), &StatusIndicator::set_tooltip);
ClassDB::bind_method(D_METHOD("get_tooltip"), &StatusIndicator::get_tooltip);
ClassDB::bind_method(D_METHOD("set_icon", "texture"), &StatusIndicator::set_icon);
ClassDB::bind_method(D_METHOD("get_icon"), &StatusIndicator::get_icon);
ClassDB::bind_method(D_METHOD("set_visible", "visible"), &StatusIndicator::set_visible);
ClassDB::bind_method(D_METHOD("is_visible"), &StatusIndicator::is_visible);
ClassDB::bind_method(D_METHOD("set_menu", "menu"), &StatusIndicator::set_menu);
ClassDB::bind_method(D_METHOD("get_menu"), &StatusIndicator::get_menu);
ClassDB::bind_method(D_METHOD("get_rect"), &StatusIndicator::get_rect);
ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::INT, "mouse_button"), PropertyInfo(Variant::VECTOR2I, "mouse_position")));
ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "get_tooltip");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_icon", "get_icon");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "menu", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PopupMenu"), "set_menu", "get_menu");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
}
void StatusIndicator::_callback(MouseButton p_index, const Point2i &p_pos) {
emit_signal(SceneStringName(pressed), p_index, p_pos);
}
void StatusIndicator::set_icon(const Ref<Texture2D> &p_icon) {
ERR_MAIN_THREAD_GUARD;
icon = p_icon;
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
DisplayServer::get_singleton()->status_indicator_set_icon(iid, icon);
}
}
Ref<Texture2D> StatusIndicator::get_icon() const {
return icon;
}
void StatusIndicator::set_tooltip(const String &p_tooltip) {
ERR_MAIN_THREAD_GUARD;
tooltip = p_tooltip;
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
DisplayServer::get_singleton()->status_indicator_set_tooltip(iid, tooltip);
}
}
String StatusIndicator::get_tooltip() const {
return tooltip;
}
void StatusIndicator::set_menu(const NodePath &p_menu) {
PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
if (pm) {
pm->unbind_global_menu();
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID());
}
}
menu = p_menu;
pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
if (pm) {
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
RID menu_rid = pm->bind_global_menu();
DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid);
}
}
}
NodePath StatusIndicator::get_menu() const {
return menu;
}
void StatusIndicator::set_visible(bool p_visible) {
ERR_MAIN_THREAD_GUARD;
if (visible == p_visible) {
return;
}
visible = p_visible;
if (!is_inside_tree()) {
return;
}
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
return;
}
#endif
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) {
iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback));
PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
if (pm) {
RID menu_rid = pm->bind_global_menu();
DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid);
}
}
if (!visible && iid != DisplayServer::INVALID_INDICATOR_ID) {
PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
if (pm) {
pm->unbind_global_menu();
DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID());
}
DisplayServer::get_singleton()->delete_status_indicator(iid);
iid = DisplayServer::INVALID_INDICATOR_ID;
}
}
}
bool StatusIndicator::is_visible() const {
return visible;
}
Rect2 StatusIndicator::get_rect() const {
if (iid == DisplayServer::INVALID_INDICATOR_ID) {
return Rect2();
}
return DisplayServer::get_singleton()->status_indicator_get_rect(iid);
}

View File

@@ -0,0 +1,65 @@
/**************************************************************************/
/* status_indicator.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
#include "servers/display_server.h"
class StatusIndicator : public Node {
GDCLASS(StatusIndicator, Node);
Ref<Texture2D> icon;
String tooltip;
bool visible = true;
DisplayServer::IndicatorID iid = DisplayServer::INVALID_INDICATOR_ID;
NodePath menu;
protected:
void _notification(int p_what);
static void _bind_methods();
void _callback(MouseButton p_index, const Point2i &p_pos);
public:
void set_icon(const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_icon() const;
void set_tooltip(const String &p_tooltip);
String get_tooltip() const;
void set_menu(const NodePath &p_menu);
NodePath get_menu() const;
void set_visible(bool p_visible);
bool is_visible() const;
Rect2 get_rect() const;
};

247
scene/main/timer.cpp Normal file
View File

@@ -0,0 +1,247 @@
/**************************************************************************/
/* timer.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "timer.h"
void Timer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (autostart) {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
break;
}
#endif
start();
autostart = false;
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (!processing || timer_process_callback == TIMER_PROCESS_PHYSICS || !is_processing_internal()) {
return;
}
if (ignore_time_scale) {
time_left -= Engine::get_singleton()->get_process_step();
} else {
time_left -= get_process_delta_time();
}
if (time_left < 0) {
if (!one_shot) {
time_left += wait_time;
} else {
stop();
}
emit_signal(SNAME("timeout"));
}
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (!processing || timer_process_callback == TIMER_PROCESS_IDLE || !is_physics_processing_internal()) {
return;
}
if (ignore_time_scale) {
time_left -= Engine::get_singleton()->get_process_step();
} else {
time_left -= get_physics_process_delta_time();
}
if (time_left < 0) {
if (!one_shot) {
time_left += wait_time;
} else {
stop();
}
emit_signal(SNAME("timeout"));
}
} break;
}
}
void Timer::set_wait_time(double p_time) {
ERR_FAIL_COND_MSG(p_time <= 0, "Time should be greater than zero.");
wait_time = p_time;
update_configuration_warnings();
}
double Timer::get_wait_time() const {
return wait_time;
}
void Timer::set_one_shot(bool p_one_shot) {
one_shot = p_one_shot;
}
bool Timer::is_one_shot() const {
return one_shot;
}
void Timer::set_autostart(bool p_start) {
autostart = p_start;
}
bool Timer::has_autostart() const {
return autostart;
}
void Timer::start(double p_time) {
ERR_FAIL_COND_MSG(!is_inside_tree(), "Unable to start the timer because it's not inside the scene tree. Either add it or set autostart to true.");
if (p_time > 0) {
set_wait_time(p_time);
}
time_left = wait_time;
_set_process(true);
}
void Timer::stop() {
time_left = -1;
_set_process(false);
autostart = false;
}
void Timer::set_paused(bool p_paused) {
if (paused == p_paused) {
return;
}
paused = p_paused;
_set_process(processing);
}
bool Timer::is_paused() const {
return paused;
}
void Timer::set_ignore_time_scale(bool p_ignore) {
ignore_time_scale = p_ignore;
}
bool Timer::is_ignoring_time_scale() {
return ignore_time_scale;
}
bool Timer::is_stopped() const {
return get_time_left() <= 0;
}
double Timer::get_time_left() const {
return time_left > 0 ? time_left : 0;
}
void Timer::set_timer_process_callback(TimerProcessCallback p_callback) {
if (timer_process_callback == p_callback) {
return;
}
switch (timer_process_callback) {
case TIMER_PROCESS_PHYSICS:
if (is_physics_processing_internal()) {
set_physics_process_internal(false);
set_process_internal(true);
}
break;
case TIMER_PROCESS_IDLE:
if (is_processing_internal()) {
set_process_internal(false);
set_physics_process_internal(true);
}
break;
}
timer_process_callback = p_callback;
}
Timer::TimerProcessCallback Timer::get_timer_process_callback() const {
return timer_process_callback;
}
void Timer::_set_process(bool p_process, bool p_force) {
switch (timer_process_callback) {
case TIMER_PROCESS_PHYSICS:
set_physics_process_internal(p_process && !paused);
break;
case TIMER_PROCESS_IDLE:
set_process_internal(p_process && !paused);
break;
}
processing = p_process;
}
PackedStringArray Timer::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (wait_time < 0.05 - CMP_EPSILON) {
warnings.push_back(RTR("Very low timer wait times (< 0.05 seconds) may behave in significantly different ways depending on the rendered or physics frame rate.\nConsider using a script's process loop instead of relying on a Timer for very low wait times."));
}
return warnings;
}
void Timer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_wait_time", "time_sec"), &Timer::set_wait_time);
ClassDB::bind_method(D_METHOD("get_wait_time"), &Timer::get_wait_time);
ClassDB::bind_method(D_METHOD("set_one_shot", "enable"), &Timer::set_one_shot);
ClassDB::bind_method(D_METHOD("is_one_shot"), &Timer::is_one_shot);
ClassDB::bind_method(D_METHOD("set_autostart", "enable"), &Timer::set_autostart);
ClassDB::bind_method(D_METHOD("has_autostart"), &Timer::has_autostart);
ClassDB::bind_method(D_METHOD("start", "time_sec"), &Timer::start, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("stop"), &Timer::stop);
ClassDB::bind_method(D_METHOD("set_paused", "paused"), &Timer::set_paused);
ClassDB::bind_method(D_METHOD("is_paused"), &Timer::is_paused);
ClassDB::bind_method(D_METHOD("set_ignore_time_scale", "ignore"), &Timer::set_ignore_time_scale);
ClassDB::bind_method(D_METHOD("is_ignoring_time_scale"), &Timer::is_ignoring_time_scale);
ClassDB::bind_method(D_METHOD("is_stopped"), &Timer::is_stopped);
ClassDB::bind_method(D_METHOD("get_time_left"), &Timer::get_time_left);
ClassDB::bind_method(D_METHOD("set_timer_process_callback", "callback"), &Timer::set_timer_process_callback);
ClassDB::bind_method(D_METHOD("get_timer_process_callback"), &Timer::get_timer_process_callback);
ADD_SIGNAL(MethodInfo("timeout"));
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_timer_process_callback", "get_timer_process_callback");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wait_time", PROPERTY_HINT_RANGE, "0.001,4096,0.001,or_greater,exp,suffix:s"), "set_wait_time", "get_wait_time");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "is_one_shot");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autostart"), "set_autostart", "has_autostart");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_paused", "is_paused");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_time_scale"), "set_ignore_time_scale", "is_ignoring_time_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_left", PROPERTY_HINT_NONE, "suffix:s", PROPERTY_USAGE_NONE), "", "get_time_left");
BIND_ENUM_CONSTANT(TIMER_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(TIMER_PROCESS_IDLE);
}

89
scene/main/timer.h Normal file
View File

@@ -0,0 +1,89 @@
/**************************************************************************/
/* timer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
class Timer : public Node {
GDCLASS(Timer, Node);
double wait_time = 1.0;
bool one_shot = false;
bool autostart = false;
bool processing = false;
bool paused = false;
bool ignore_time_scale = false;
double time_left = -1.0;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
enum TimerProcessCallback {
TIMER_PROCESS_PHYSICS,
TIMER_PROCESS_IDLE,
};
void set_wait_time(double p_time);
double get_wait_time() const;
void set_one_shot(bool p_one_shot);
bool is_one_shot() const;
void set_autostart(bool p_start);
bool has_autostart() const;
void start(double p_time = -1);
void stop();
void set_paused(bool p_paused);
bool is_paused() const;
void set_ignore_time_scale(bool p_ignore);
bool is_ignoring_time_scale();
bool is_stopped() const;
double get_time_left() const;
PackedStringArray get_configuration_warnings() const override;
void set_timer_process_callback(TimerProcessCallback p_callback);
TimerProcessCallback get_timer_process_callback() const;
private:
TimerProcessCallback timer_process_callback = TIMER_PROCESS_IDLE;
void _set_process(bool p_process, bool p_force = false);
};
VARIANT_ENUM_CAST(Timer::TimerProcessCallback);

5601
scene/main/viewport.cpp Normal file

File diff suppressed because it is too large Load Diff

930
scene/main/viewport.h Normal file
View File

@@ -0,0 +1,930 @@
/**************************************************************************/
/* viewport.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/node.h"
#include "scene/resources/texture.h"
#ifndef _3D_DISABLED
class Camera3D;
class CollisionObject3D;
class AudioListener3D;
class World3D;
#endif // _3D_DISABLED
class AudioListener2D;
class Camera2D;
class CanvasItem;
class CanvasLayer;
class Control;
class Label;
class SceneTreeTimer;
class Viewport;
class Window;
class World2D;
class ViewportTexture : public Texture2D {
GDCLASS(ViewportTexture, Texture2D);
NodePath path;
friend class Viewport;
Viewport *vp = nullptr;
bool vp_pending = false;
bool vp_changed = false;
void _setup_local_to_scene(const Node *p_loc_scene);
void _err_print_viewport_not_set() const;
mutable RID proxy_ph;
mutable RID proxy;
protected:
static void _bind_methods();
virtual void reset_local_to_scene() override;
public:
void set_viewport_path_in_scene(const NodePath &p_path);
NodePath get_viewport_path_in_scene() const;
virtual void setup_local_to_scene() override;
virtual int get_width() const override;
virtual int get_height() const override;
virtual Size2 get_size() const override;
virtual RID get_rid() const override;
virtual bool has_alpha() const override;
virtual Ref<Image> get_image() const override;
ViewportTexture();
~ViewportTexture();
};
class Viewport : public Node {
GDCLASS(Viewport, Node);
public:
enum Scaling3DMode {
SCALING_3D_MODE_BILINEAR,
SCALING_3D_MODE_FSR,
SCALING_3D_MODE_FSR2,
SCALING_3D_MODE_METALFX_SPATIAL,
SCALING_3D_MODE_METALFX_TEMPORAL,
SCALING_3D_MODE_MAX
};
enum PositionalShadowAtlasQuadrantSubdiv {
SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED,
SHADOW_ATLAS_QUADRANT_SUBDIV_1,
SHADOW_ATLAS_QUADRANT_SUBDIV_4,
SHADOW_ATLAS_QUADRANT_SUBDIV_16,
SHADOW_ATLAS_QUADRANT_SUBDIV_64,
SHADOW_ATLAS_QUADRANT_SUBDIV_256,
SHADOW_ATLAS_QUADRANT_SUBDIV_1024,
SHADOW_ATLAS_QUADRANT_SUBDIV_MAX,
};
enum MSAA {
MSAA_DISABLED,
MSAA_2X,
MSAA_4X,
MSAA_8X,
// 16x MSAA is not supported due to its high cost and driver bugs.
MSAA_MAX
};
enum AnisotropicFiltering {
ANISOTROPY_DISABLED,
ANISOTROPY_2X,
ANISOTROPY_4X,
ANISOTROPY_8X,
ANISOTROPY_16X,
ANISOTROPY_MAX
};
enum ScreenSpaceAA {
SCREEN_SPACE_AA_DISABLED,
SCREEN_SPACE_AA_FXAA,
SCREEN_SPACE_AA_SMAA,
SCREEN_SPACE_AA_MAX
};
enum RenderInfo {
RENDER_INFO_OBJECTS_IN_FRAME,
RENDER_INFO_PRIMITIVES_IN_FRAME,
RENDER_INFO_DRAW_CALLS_IN_FRAME,
RENDER_INFO_MAX
};
enum RenderInfoType {
RENDER_INFO_TYPE_VISIBLE,
RENDER_INFO_TYPE_SHADOW,
RENDER_INFO_TYPE_CANVAS,
RENDER_INFO_TYPE_MAX
};
enum DebugDraw {
DEBUG_DRAW_DISABLED,
DEBUG_DRAW_UNSHADED,
DEBUG_DRAW_LIGHTING,
DEBUG_DRAW_OVERDRAW,
DEBUG_DRAW_WIREFRAME,
DEBUG_DRAW_NORMAL_BUFFER,
DEBUG_DRAW_VOXEL_GI_ALBEDO,
DEBUG_DRAW_VOXEL_GI_LIGHTING,
DEBUG_DRAW_VOXEL_GI_EMISSION,
DEBUG_DRAW_SHADOW_ATLAS,
DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS,
DEBUG_DRAW_SCENE_LUMINANCE,
DEBUG_DRAW_SSAO,
DEBUG_DRAW_SSIL,
DEBUG_DRAW_PSSM_SPLITS,
DEBUG_DRAW_DECAL_ATLAS,
DEBUG_DRAW_SDFGI,
DEBUG_DRAW_SDFGI_PROBES,
DEBUG_DRAW_GI_BUFFER,
DEBUG_DRAW_DISABLE_LOD,
DEBUG_DRAW_CLUSTER_OMNI_LIGHTS,
DEBUG_DRAW_CLUSTER_SPOT_LIGHTS,
DEBUG_DRAW_CLUSTER_DECALS,
DEBUG_DRAW_CLUSTER_REFLECTION_PROBES,
DEBUG_DRAW_OCCLUDERS,
DEBUG_DRAW_MOTION_VECTORS,
DEBUG_DRAW_INTERNAL_BUFFER,
};
enum DefaultCanvasItemTextureFilter {
DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST,
DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR,
DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS,
DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS,
DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_MAX
};
enum DefaultCanvasItemTextureRepeat {
DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_DISABLED,
DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_ENABLED,
DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_MIRROR,
DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_MAX,
};
enum SDFOversize {
SDF_OVERSIZE_100_PERCENT,
SDF_OVERSIZE_120_PERCENT,
SDF_OVERSIZE_150_PERCENT,
SDF_OVERSIZE_200_PERCENT,
SDF_OVERSIZE_MAX
};
enum SDFScale {
SDF_SCALE_100_PERCENT,
SDF_SCALE_50_PERCENT,
SDF_SCALE_25_PERCENT,
SDF_SCALE_MAX
};
enum {
SUBWINDOW_CANVAS_LAYER = 1024
};
enum VRSMode {
VRS_DISABLED,
VRS_TEXTURE,
VRS_XR,
VRS_MAX
};
enum VRSUpdateMode {
VRS_UPDATE_DISABLED,
VRS_UPDATE_ONCE,
VRS_UPDATE_ALWAYS,
VRS_UPDATE_MAX
};
private:
friend class ViewportTexture;
Viewport *parent = nullptr;
Viewport *gui_parent = nullptr; // Whose gui.tooltip_popup it is.
HashSet<CanvasLayer *> canvas_layers;
bool use_font_oversampling = true;
float font_oversampling = 1.0;
float font_oversampling_override = 0.0;
RID viewport;
RID current_canvas;
RID subwindow_canvas;
bool override_canvas_transform = false;
Transform2D canvas_transform_override;
Transform2D canvas_transform;
Transform2D global_canvas_transform;
Transform2D stretch_transform;
Size2i size = Size2i(512, 512);
Size2 size_2d_override;
bool size_allocated = false;
RID contact_2d_debug;
RID contact_3d_debug_multimesh;
RID contact_3d_debug_instance;
Rect2 last_vp_rect;
bool transparent_bg = false;
bool use_hdr_2d = false;
bool gen_mipmaps = false;
bool snap_controls_to_pixels = true;
bool snap_2d_transforms_to_pixel = false;
bool snap_2d_vertices_to_pixel = false;
#if !defined(PHYSICS_2D_DISABLED) || !defined(PHYSICS_3D_DISABLED)
bool physics_object_picking = false;
bool physics_object_picking_sort = false;
bool physics_object_picking_first_only = false;
List<Ref<InputEvent>> physics_picking_events;
ObjectID physics_object_capture;
ObjectID physics_object_over;
Transform3D physics_last_object_transform;
Transform3D physics_last_camera_transform;
ObjectID physics_last_id;
#endif // !defined(PHYSICS_2D_DISABLED) || !defined(PHYSICS_3D_DISABLED)
bool handle_input_locally = true;
bool local_input_handled = false;
Ref<World2D> world_2d;
StringName input_group;
StringName shortcut_input_group;
StringName unhandled_input_group;
StringName unhandled_key_input_group;
void _update_audio_listener_2d();
bool disable_3d = false;
static void _propagate_drag_notification(Node *p_node, int p_what);
void _update_global_transform();
RID texture_rid;
DebugDraw debug_draw = DEBUG_DRAW_DISABLED;
int positional_shadow_atlas_size = 2048;
bool positional_shadow_atlas_16_bits = true;
PositionalShadowAtlasQuadrantSubdiv positional_shadow_atlas_quadrant_subdiv[4];
MSAA msaa_2d = MSAA_DISABLED;
MSAA msaa_3d = MSAA_DISABLED;
ScreenSpaceAA screen_space_aa = SCREEN_SPACE_AA_DISABLED;
bool use_taa = false;
Scaling3DMode scaling_3d_mode = SCALING_3D_MODE_BILINEAR;
float scaling_3d_scale = 1.0;
float fsr_sharpness = 0.2f;
float texture_mipmap_bias = 0.0f;
AnisotropicFiltering anisotropic_filtering_level = ANISOTROPY_4X;
bool use_debanding = false;
float mesh_lod_threshold = 1.0;
bool use_occlusion_culling = false;
Ref<ViewportTexture> default_texture;
HashSet<ViewportTexture *> viewport_textures;
void _update_viewport_path();
SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT;
SDFScale sdf_scale = SDF_SCALE_50_PERCENT;
uint32_t canvas_cull_mask = 0xffffffff; // by default show everything
enum SubWindowDrag {
SUB_WINDOW_DRAG_DISABLED,
SUB_WINDOW_DRAG_MOVE,
SUB_WINDOW_DRAG_CLOSE,
SUB_WINDOW_DRAG_RESIZE,
};
enum SubWindowResize {
SUB_WINDOW_RESIZE_DISABLED,
SUB_WINDOW_RESIZE_TOP_LEFT,
SUB_WINDOW_RESIZE_TOP,
SUB_WINDOW_RESIZE_TOP_RIGHT,
SUB_WINDOW_RESIZE_LEFT,
SUB_WINDOW_RESIZE_RIGHT,
SUB_WINDOW_RESIZE_BOTTOM_LEFT,
SUB_WINDOW_RESIZE_BOTTOM,
SUB_WINDOW_RESIZE_BOTTOM_RIGHT,
SUB_WINDOW_RESIZE_MAX
};
struct SubWindow {
Window *window = nullptr;
RID canvas_item;
Rect2i parent_safe_rect;
bool pending_window_update = false;
};
// VRS
VRSMode vrs_mode = VRS_DISABLED;
VRSUpdateMode vrs_update_mode = VRS_UPDATE_ONCE;
Ref<Texture2D> vrs_texture;
struct GUI {
bool mouse_in_viewport = false;
HashMap<int, ObjectID> touch_focus;
Control *mouse_focus = nullptr;
Control *mouse_click_grabber = nullptr;
BitField<MouseButtonMask> mouse_focus_mask = MouseButtonMask::NONE;
Control *key_focus = nullptr;
Control *mouse_over = nullptr;
LocalVector<Control *> mouse_over_hierarchy;
bool sending_mouse_enter_exit_notifications = false;
Window *subwindow_over = nullptr; // mouse_over and subwindow_over are mutually exclusive. At all times at least one of them is nullptr.
Window *windowmanager_window_over = nullptr; // Only used in root Viewport.
Control *drag_mouse_over = nullptr;
Control *tooltip_control = nullptr;
Window *tooltip_popup = nullptr;
Label *tooltip_label = nullptr;
String tooltip_text;
Point2 tooltip_pos;
Point2 last_mouse_pos;
Point2 drag_accum;
bool drag_attempted = false;
Variant drag_data; // Only used in root-Viewport and SubViewports, that are not children of a SubViewportContainer.
ObjectID drag_preview_id;
String drag_description;
Ref<SceneTreeTimer> tooltip_timer;
double tooltip_delay = 0.0;
bool roots_order_dirty = false;
List<Control *> roots;
HashSet<ObjectID> canvas_parents_with_dirty_order;
int canvas_sort_index = 0; //for sorting items with canvas as root
bool dragging = false; // Is true in the viewport in which dragging started while dragging is active.
bool global_dragging = false; // Is true while dragging is active. Only used in root-Viewport and SubViewports that are not children of a SubViewportContainer.
bool drag_successful = false;
Control *target_control = nullptr; // Control that the mouse is over in the innermost nested Viewport. Only used in root-Viewport and SubViewports, that are not children of a SubViewportContainer.
bool embed_subwindows_hint = false;
Window *subwindow_focused = nullptr;
Window *currently_dragged_subwindow = nullptr;
SubWindowDrag subwindow_drag = SUB_WINDOW_DRAG_DISABLED;
Vector2 subwindow_drag_from;
Vector2 subwindow_drag_pos;
Rect2i subwindow_drag_close_rect;
bool subwindow_drag_close_inside = false;
SubWindowResize subwindow_resize_mode;
Rect2i subwindow_resize_from_rect;
Vector<SubWindow> sub_windows; // Don't obtain references or pointers to the elements, as their location can change.
} gui;
DefaultCanvasItemTextureFilter default_canvas_item_texture_filter = DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR;
DefaultCanvasItemTextureRepeat default_canvas_item_texture_repeat = DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_DISABLED;
bool disable_input = false;
bool disable_input_override = false;
void _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input);
void _gui_call_notification(Control *p_control, int p_what);
void _gui_sort_roots();
Control *_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform);
void _gui_input_event(Ref<InputEvent> p_event);
void _perform_drop(Control *p_control = nullptr);
void _gui_cleanup_internal_state(Ref<InputEvent> p_event);
void _push_unhandled_input_internal(const Ref<InputEvent> &p_event);
Ref<InputEvent> _make_input_local(const Ref<InputEvent> &ev);
friend class Control;
List<Control *>::Element *_gui_add_root_control(Control *p_control);
void _gui_remove_root_control(List<Control *>::Element *RI);
String _gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Control **r_tooltip_owner = nullptr);
void _gui_cancel_tooltip();
void _gui_show_tooltip();
void _gui_show_tooltip_at(const Point2i &p_pos);
void _gui_remove_control(Control *p_control);
void _gui_hide_control(Control *p_control);
void _gui_update_mouse_over();
void _gui_force_drag_start();
void _gui_force_drag_cancel();
void _gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control);
void _gui_set_drag_preview(Control *p_base, Control *p_control);
Control *_gui_get_drag_preview();
void _gui_remove_focus_for_window(Node *p_window);
void _gui_unfocus_control(Control *p_control);
bool _gui_control_has_focus(const Control *p_control);
void _gui_control_grab_focus(Control *p_control);
void _gui_grab_click_focus(Control *p_control);
void _post_gui_grab_click_focus();
void _gui_accept_event();
bool _gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check);
friend class CanvasLayer;
void _canvas_layer_add(CanvasLayer *p_canvas_layer);
void _canvas_layer_remove(CanvasLayer *p_canvas_layer);
void _drop_mouse_over(Control *p_until_control = nullptr);
void _drop_mouse_focus();
void _drop_physics_mouseover(bool p_paused_only = false);
void _update_canvas_items(Node *p_node);
friend class Window;
void _sub_window_update_order();
void _sub_window_register(Window *p_window);
void _sub_window_update(Window *p_window);
void _sub_window_grab_focus(Window *p_window);
void _sub_window_remove(Window *p_window);
int _sub_window_find(Window *p_window) const;
bool _sub_windows_forward_input(const Ref<InputEvent> &p_event);
SubWindowResize _sub_window_get_resize_margin(Window *p_subwindow, const Point2 &p_point);
void _update_mouse_over();
virtual void _update_mouse_over(Vector2 p_pos);
virtual void _mouse_leave_viewport();
virtual bool _can_consume_input_events() const { return true; }
uint64_t event_count = 0;
void _process_dirty_canvas_parent_orders();
void _propagate_world_2d_changed(Node *p_node);
void _window_start_drag(Window *p_window);
void _window_start_resize(SubWindowResize p_edge, Window *p_window);
protected:
bool _set_size(const Size2i &p_size, const Size2 &p_size_2d_override, bool p_allocated);
Size2i _get_size() const;
Size2 _get_size_2d_override() const;
bool _is_size_allocated() const;
void _notification(int p_what);
#if !defined(PHYSICS_2D_DISABLED) || !defined(PHYSICS_3D_DISABLED)
void _process_picking();
#endif // !defined(PHYSICS_2D_DISABLED) || !defined(PHYSICS_3D_DISABLED)
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
public:
void canvas_parent_mark_dirty(Node *p_node);
void canvas_item_top_level_changed();
uint64_t get_processed_events_count() const { return event_count; }
void cancel_tooltip();
void show_tooltip(Control *p_control);
void update_canvas_items();
Rect2 get_visible_rect() const;
RID get_viewport_rid() const;
void set_world_2d(const Ref<World2D> &p_world_2d);
Ref<World2D> get_world_2d() const;
Ref<World2D> find_world_2d() const;
void enable_canvas_transform_override(bool p_enable);
bool is_canvas_transform_override_enabled() const;
void set_canvas_transform_override(const Transform2D &p_transform);
Transform2D get_canvas_transform_override() const;
void set_canvas_transform(const Transform2D &p_transform);
Transform2D get_canvas_transform() const;
void set_global_canvas_transform(const Transform2D &p_transform);
Transform2D get_global_canvas_transform() const;
Transform2D get_stretch_transform() const;
virtual Transform2D get_final_transform() const;
void gui_set_root_order_dirty();
void set_transparent_background(bool p_enable);
bool has_transparent_background() const;
void set_use_hdr_2d(bool p_enable);
bool is_using_hdr_2d() const;
Ref<ViewportTexture> get_texture() const;
void set_positional_shadow_atlas_size(int p_size);
int get_positional_shadow_atlas_size() const;
void set_positional_shadow_atlas_16_bits(bool p_16_bits);
bool get_positional_shadow_atlas_16_bits() const;
void set_positional_shadow_atlas_quadrant_subdiv(int p_quadrant, PositionalShadowAtlasQuadrantSubdiv p_subdiv);
PositionalShadowAtlasQuadrantSubdiv get_positional_shadow_atlas_quadrant_subdiv(int p_quadrant) const;
void set_msaa_2d(MSAA p_msaa);
MSAA get_msaa_2d() const;
void set_msaa_3d(MSAA p_msaa);
MSAA get_msaa_3d() const;
void set_screen_space_aa(ScreenSpaceAA p_screen_space_aa);
ScreenSpaceAA get_screen_space_aa() const;
void set_use_taa(bool p_use_taa);
bool is_using_taa() const;
void set_use_oversampling(bool p_oversampling);
bool is_using_oversampling() const;
void set_oversampling_override(float p_oversampling);
float get_oversampling_override() const;
float get_oversampling() const { return font_oversampling; }
void set_scaling_3d_mode(Scaling3DMode p_scaling_3d_mode);
Scaling3DMode get_scaling_3d_mode() const;
void set_scaling_3d_scale(float p_scaling_3d_scale);
float get_scaling_3d_scale() const;
void set_fsr_sharpness(float p_fsr_sharpness);
float get_fsr_sharpness() const;
void set_texture_mipmap_bias(float p_texture_mipmap_bias);
float get_texture_mipmap_bias() const;
void set_anisotropic_filtering_level(AnisotropicFiltering p_anisotropic_filtering_level);
AnisotropicFiltering get_anisotropic_filtering_level() const;
void set_use_debanding(bool p_use_debanding);
bool is_using_debanding() const;
void set_mesh_lod_threshold(float p_pixels);
float get_mesh_lod_threshold() const;
void set_use_occlusion_culling(bool p_us_occlusion_culling);
bool is_using_occlusion_culling() const;
Vector2 get_camera_coords(const Vector2 &p_viewport_coords) const;
Vector2 get_camera_rect_size() const;
void push_text_input(const String &p_text);
void push_input(const Ref<InputEvent> &p_event, bool p_local_coords = false);
#ifndef DISABLE_DEPRECATED
void push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords = false);
#endif // DISABLE_DEPRECATED
void notify_mouse_entered();
void notify_mouse_exited();
void set_disable_input(bool p_disable);
bool is_input_disabled() const;
void set_disable_input_override(bool p_disable);
Vector2 get_mouse_position() const;
void warp_mouse(const Vector2 &p_position);
Point2 wrap_mouse_in_rect(const Vector2 &p_relative, const Rect2 &p_rect);
virtual void update_mouse_cursor_state();
#if !defined(PHYSICS_2D_DISABLED) || !defined(PHYSICS_3D_DISABLED)
void set_physics_object_picking(bool p_enable);
bool get_physics_object_picking();
void set_physics_object_picking_sort(bool p_enable);
bool get_physics_object_picking_sort();
void set_physics_object_picking_first_only(bool p_enable);
bool get_physics_object_picking_first_only();
#endif // !defined(PHYSICS_2D_DISABLED) || !defined(PHYSICS_3D_DISABLED)
Variant gui_get_drag_data() const;
String gui_get_drag_description() const;
void gui_set_drag_description(const String &p_description);
void gui_reset_canvas_sort_index();
int gui_get_canvas_sort_index();
void gui_release_focus();
Control *gui_get_focus_owner() const;
Control *gui_get_hovered_control() const;
Window *get_focused_subwindow() const { return gui.subwindow_focused; }
PackedStringArray get_configuration_warnings() const override;
void set_debug_draw(DebugDraw p_debug_draw);
DebugDraw get_debug_draw() const;
int get_render_info(RenderInfoType p_type, RenderInfo p_info);
void set_snap_controls_to_pixels(bool p_enable);
bool is_snap_controls_to_pixels_enabled() const;
void set_snap_2d_transforms_to_pixel(bool p_enable);
bool is_snap_2d_transforms_to_pixel_enabled() const;
void set_snap_2d_vertices_to_pixel(bool p_enable);
bool is_snap_2d_vertices_to_pixel_enabled() const;
void set_input_as_handled();
bool is_input_handled() const;
void set_handle_input_locally(bool p_enable);
bool is_handling_input_locally() const;
bool gui_is_dragging() const;
bool gui_is_drag_successful() const;
void gui_cancel_drag();
void gui_perform_drop_at(const Point2 &p_pos, Control *p_control = nullptr);
Control *gui_find_control(const Point2 &p_global);
void set_sdf_oversize(SDFOversize p_sdf_oversize);
SDFOversize get_sdf_oversize() const;
void set_sdf_scale(SDFScale p_sdf_scale);
SDFScale get_sdf_scale() const;
void set_default_canvas_item_texture_filter(DefaultCanvasItemTextureFilter p_filter);
DefaultCanvasItemTextureFilter get_default_canvas_item_texture_filter() const;
void set_default_canvas_item_texture_repeat(DefaultCanvasItemTextureRepeat p_repeat);
DefaultCanvasItemTextureRepeat get_default_canvas_item_texture_repeat() const;
// VRS
void set_vrs_mode(VRSMode p_vrs_mode);
VRSMode get_vrs_mode() const;
void set_vrs_update_mode(VRSUpdateMode p_vrs_update_mode);
VRSUpdateMode get_vrs_update_mode() const;
void set_vrs_texture(Ref<Texture2D> p_texture);
Ref<Texture2D> get_vrs_texture() const;
virtual DisplayServer::WindowID get_window_id() const = 0;
void set_embedding_subwindows(bool p_embed);
bool is_embedding_subwindows() const;
TypedArray<Window> get_embedded_subwindows() const;
void subwindow_set_popup_safe_rect(Window *p_window, const Rect2i &p_rect);
Rect2i subwindow_get_popup_safe_rect(Window *p_window) const;
Viewport *get_parent_viewport() const;
Window *get_base_window();
void set_canvas_cull_mask(uint32_t p_layers);
uint32_t get_canvas_cull_mask() const;
void set_canvas_cull_mask_bit(uint32_t p_layer, bool p_enable);
bool get_canvas_cull_mask_bit(uint32_t p_layer) const;
#ifdef TOOLS_ENABLED
bool is_visible_subviewport() const;
#endif // TOOLS_ENABLED
virtual bool is_size_2d_override_stretch_enabled() const { return true; }
Transform2D get_screen_transform() const;
virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const;
virtual Transform2D get_popup_base_transform_native() const { return Transform2D(); }
virtual Transform2D get_popup_base_transform() const { return Transform2D(); }
virtual Viewport *get_section_root_viewport() const { return nullptr; }
virtual bool is_attached_in_viewport() const { return false; }
virtual bool is_sub_viewport() const { return false; }
private:
// 2D audio, camera, and physics. (don't put World2D here because World2D is needed for Control nodes).
friend class AudioListener2D; // Needs _audio_listener_2d_set and _audio_listener_2d_remove
AudioListener2D *audio_listener_2d = nullptr;
void _audio_listener_2d_set(AudioListener2D *p_audio_listener);
void _audio_listener_2d_remove(AudioListener2D *p_audio_listener);
bool is_audio_listener_2d_enabled = false;
RID internal_audio_listener_2d;
friend class Camera2D; // Needs _camera_2d_set
Camera2D *camera_2d = nullptr;
void _camera_2d_set(Camera2D *p_camera_2d);
#ifndef PHYSICS_2D_DISABLED
// Collider to frame
HashMap<ObjectID, uint64_t> physics_2d_mouseover;
// Collider & shape to frame
HashMap<Pair<ObjectID, int>, uint64_t> physics_2d_shape_mouseover;
// Cleans up colliders corresponding to old frames or all of them.
void _cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference = 0);
#endif // PHYSICS_2D_DISABLED
public:
AudioListener2D *get_audio_listener_2d() const;
void set_as_audio_listener_2d(bool p_enable);
bool is_audio_listener_2d() const;
Camera2D *get_camera_2d() const;
void assign_next_enabled_camera_2d(const StringName &p_camera_group);
#ifndef _3D_DISABLED
private:
// 3D audio, camera, physics, and world.
bool use_xr = false;
friend class AudioListener3D;
AudioListener3D *audio_listener_3d = nullptr;
HashSet<AudioListener3D *> audio_listener_3d_set;
bool is_audio_listener_3d_enabled = false;
RID internal_audio_listener_3d;
void _update_audio_listener_3d();
void _listener_transform_3d_changed_notify();
void _audio_listener_3d_set(AudioListener3D *p_listener);
bool _audio_listener_3d_add(AudioListener3D *p_listener); //true if first
void _audio_listener_3d_remove(AudioListener3D *p_listener);
void _audio_listener_3d_make_next_current(AudioListener3D *p_exclude);
#ifndef PHYSICS_3D_DISABLED
void _collision_object_3d_input_event(CollisionObject3D *p_object, Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape);
#endif // PHYSICS_3D_DISABLED
struct Camera3DOverrideData {
Transform3D transform;
enum Projection {
PROJECTION_PERSPECTIVE,
PROJECTION_ORTHOGONAL
};
Projection projection = Projection::PROJECTION_PERSPECTIVE;
real_t fov = 0.0;
real_t size = 0.0;
real_t z_near = 0.0;
real_t z_far = 0.0;
RID rid;
operator bool() const {
return rid != RID();
}
} camera_3d_override;
friend class Camera3D;
Camera3D *camera_3d = nullptr;
HashSet<Camera3D *> camera_3d_set;
void _camera_3d_transform_changed_notify();
void _camera_3d_set(Camera3D *p_camera);
bool _camera_3d_add(Camera3D *p_camera); //true if first
void _camera_3d_remove(Camera3D *p_camera);
void _camera_3d_make_next_current(Camera3D *p_exclude);
Ref<World3D> world_3d;
Ref<World3D> own_world_3d;
void _own_world_3d_changed();
void _propagate_enter_world_3d(Node *p_node);
void _propagate_exit_world_3d(Node *p_node);
public:
AudioListener3D *get_audio_listener_3d() const;
void set_as_audio_listener_3d(bool p_enable);
bool is_audio_listener_3d() const;
Camera3D *get_camera_3d() const;
void enable_camera_3d_override(bool p_enable);
bool is_camera_3d_override_enabled() const;
void set_camera_3d_override_transform(const Transform3D &p_transform);
Transform3D get_camera_3d_override_transform() const;
void set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far);
void set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far);
HashMap<StringName, real_t> get_camera_3d_override_properties() const;
Vector3 camera_3d_override_project_ray_normal(const Point2 &p_pos) const;
Vector3 camera_3d_override_project_ray_origin(const Point2 &p_pos) const;
Vector3 camera_3d_override_project_local_ray_normal(const Point2 &p_pos) const;
void set_disable_3d(bool p_disable);
bool is_3d_disabled() const;
void set_world_3d(const Ref<World3D> &p_world_3d);
Ref<World3D> get_world_3d() const;
Ref<World3D> find_world_3d() const;
void set_use_own_world_3d(bool p_use_own_world_3d);
bool is_using_own_world_3d() const;
void set_use_xr(bool p_use_xr);
bool is_using_xr();
#endif // _3D_DISABLED
Viewport();
~Viewport();
};
class SubViewport : public Viewport {
GDCLASS(SubViewport, Viewport);
public:
enum ClearMode {
CLEAR_MODE_ALWAYS,
CLEAR_MODE_NEVER,
CLEAR_MODE_ONCE
};
enum UpdateMode {
UPDATE_DISABLED,
UPDATE_ONCE, //then goes to disabled
UPDATE_WHEN_VISIBLE, // default
UPDATE_WHEN_PARENT_VISIBLE,
UPDATE_ALWAYS
};
private:
UpdateMode update_mode = UPDATE_WHEN_VISIBLE;
ClearMode clear_mode = CLEAR_MODE_ALWAYS;
bool size_2d_override_stretch = false;
void _internal_set_size(const Size2i &p_size, bool p_force = false);
protected:
static void _bind_methods();
virtual DisplayServer::WindowID get_window_id() const override;
void _notification(int p_what);
public:
void set_size(const Size2i &p_size);
Size2i get_size() const;
void set_size_force(const Size2i &p_size);
void set_size_2d_override(const Size2i &p_size);
Size2i get_size_2d_override() const;
void set_size_2d_override_stretch(bool p_enable);
bool is_size_2d_override_stretch_enabled() const override;
void set_update_mode(UpdateMode p_mode);
UpdateMode get_update_mode() const;
void set_clear_mode(ClearMode p_mode);
ClearMode get_clear_mode() const;
virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override;
virtual Transform2D get_popup_base_transform() const override;
virtual Viewport *get_section_root_viewport() const override;
virtual bool is_attached_in_viewport() const override;
virtual bool is_sub_viewport() const override { return true; }
void _validate_property(PropertyInfo &p_property) const;
SubViewport();
};
VARIANT_ENUM_CAST(Viewport::Scaling3DMode);
VARIANT_ENUM_CAST(SubViewport::UpdateMode);
VARIANT_ENUM_CAST(Viewport::PositionalShadowAtlasQuadrantSubdiv);
VARIANT_ENUM_CAST(Viewport::MSAA);
VARIANT_ENUM_CAST(Viewport::AnisotropicFiltering);
VARIANT_ENUM_CAST(Viewport::ScreenSpaceAA);
VARIANT_ENUM_CAST(Viewport::DebugDraw);
VARIANT_ENUM_CAST(Viewport::SDFScale);
VARIANT_ENUM_CAST(Viewport::SDFOversize);
VARIANT_ENUM_CAST(Viewport::VRSMode);
VARIANT_ENUM_CAST(Viewport::VRSUpdateMode);
VARIANT_ENUM_CAST(SubViewport::ClearMode);
VARIANT_ENUM_CAST(Viewport::RenderInfo);
VARIANT_ENUM_CAST(Viewport::RenderInfoType);
VARIANT_ENUM_CAST(Viewport::DefaultCanvasItemTextureFilter);
VARIANT_ENUM_CAST(Viewport::DefaultCanvasItemTextureRepeat);

3506
scene/main/window.cpp Normal file

File diff suppressed because it is too large Load Diff

538
scene/main/window.h Normal file
View File

@@ -0,0 +1,538 @@
/**************************************************************************/
/* window.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/main/viewport.h"
#include "scene/resources/theme.h"
#include "servers/display_server.h"
class Font;
class Shortcut;
class StyleBox;
class ThemeOwner;
class ThemeContext;
class Window : public Viewport {
GDCLASS(Window, Viewport);
public:
// Keep synced with enum hint for `mode` property.
enum Mode {
MODE_WINDOWED = DisplayServer::WINDOW_MODE_WINDOWED,
MODE_MINIMIZED = DisplayServer::WINDOW_MODE_MINIMIZED,
MODE_MAXIMIZED = DisplayServer::WINDOW_MODE_MAXIMIZED,
MODE_FULLSCREEN = DisplayServer::WINDOW_MODE_FULLSCREEN,
MODE_EXCLUSIVE_FULLSCREEN = DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN,
};
enum Flags {
FLAG_RESIZE_DISABLED = DisplayServer::WINDOW_FLAG_RESIZE_DISABLED,
FLAG_BORDERLESS = DisplayServer::WINDOW_FLAG_BORDERLESS,
FLAG_ALWAYS_ON_TOP = DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP,
FLAG_TRANSPARENT = DisplayServer::WINDOW_FLAG_TRANSPARENT,
FLAG_NO_FOCUS = DisplayServer::WINDOW_FLAG_NO_FOCUS,
FLAG_POPUP = DisplayServer::WINDOW_FLAG_POPUP,
FLAG_EXTEND_TO_TITLE = DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE,
FLAG_MOUSE_PASSTHROUGH = DisplayServer::WINDOW_FLAG_MOUSE_PASSTHROUGH,
FLAG_SHARP_CORNERS = DisplayServer::WINDOW_FLAG_SHARP_CORNERS,
FLAG_EXCLUDE_FROM_CAPTURE = DisplayServer::WINDOW_FLAG_EXCLUDE_FROM_CAPTURE,
FLAG_POPUP_WM_HINT = DisplayServer::WINDOW_FLAG_POPUP_WM_HINT,
FLAG_MINIMIZE_DISABLED = DisplayServer::WINDOW_FLAG_MINIMIZE_DISABLED,
FLAG_MAXIMIZE_DISABLED = DisplayServer::WINDOW_FLAG_MAXIMIZE_DISABLED,
FLAG_MAX = DisplayServer::WINDOW_FLAG_MAX,
};
enum ContentScaleMode {
CONTENT_SCALE_MODE_DISABLED,
CONTENT_SCALE_MODE_CANVAS_ITEMS,
CONTENT_SCALE_MODE_VIEWPORT,
};
enum ContentScaleAspect {
CONTENT_SCALE_ASPECT_IGNORE,
CONTENT_SCALE_ASPECT_KEEP,
CONTENT_SCALE_ASPECT_KEEP_WIDTH,
CONTENT_SCALE_ASPECT_KEEP_HEIGHT,
CONTENT_SCALE_ASPECT_EXPAND,
};
enum ContentScaleStretch {
CONTENT_SCALE_STRETCH_FRACTIONAL,
CONTENT_SCALE_STRETCH_INTEGER,
};
enum LayoutDirection {
LAYOUT_DIRECTION_INHERITED,
LAYOUT_DIRECTION_APPLICATION_LOCALE,
LAYOUT_DIRECTION_LTR,
LAYOUT_DIRECTION_RTL,
LAYOUT_DIRECTION_SYSTEM_LOCALE,
LAYOUT_DIRECTION_MAX,
#ifndef DISABLE_DEPRECATED
LAYOUT_DIRECTION_LOCALE = LAYOUT_DIRECTION_APPLICATION_LOCALE,
#endif // DISABLE_DEPRECATED
};
enum {
DEFAULT_WINDOW_SIZE = 100,
};
// Keep synced with enum hint for `initial_position` property and `display/window/size/initial_position_type` project setting.
enum WindowInitialPosition {
WINDOW_INITIAL_POSITION_ABSOLUTE,
WINDOW_INITIAL_POSITION_CENTER_PRIMARY_SCREEN,
WINDOW_INITIAL_POSITION_CENTER_MAIN_WINDOW_SCREEN,
WINDOW_INITIAL_POSITION_CENTER_OTHER_SCREEN,
WINDOW_INITIAL_POSITION_CENTER_SCREEN_WITH_MOUSE_FOCUS,
WINDOW_INITIAL_POSITION_CENTER_SCREEN_WITH_KEYBOARD_FOCUS,
};
private:
DisplayServer::WindowID window_id = DisplayServer::INVALID_WINDOW_ID;
bool initialized = false;
String title;
String displayed_title;
mutable int current_screen = 0;
mutable Point2i position;
mutable Size2i size = Size2i(DEFAULT_WINDOW_SIZE, DEFAULT_WINDOW_SIZE);
mutable Size2i min_size;
mutable Size2i max_size;
mutable Vector<Vector2> mpath;
mutable Mode mode = MODE_WINDOWED;
mutable bool flags[FLAG_MAX] = {};
bool visible = true;
bool focused = false;
WindowInitialPosition initial_position = WINDOW_INITIAL_POSITION_ABSOLUTE;
bool force_native = false;
bool transient = false;
bool transient_to_focused = false;
bool exclusive = false;
bool wrap_controls = false;
bool updating_child_controls = false;
bool updating_embedded_window = false;
bool clamp_to_embedder = false;
bool unparent_when_invisible = false;
bool keep_title_visible = false;
LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED;
void _update_child_controls();
void _update_embedded_window();
Size2i content_scale_size;
ContentScaleMode content_scale_mode = CONTENT_SCALE_MODE_DISABLED;
ContentScaleAspect content_scale_aspect = CONTENT_SCALE_ASPECT_IGNORE;
ContentScaleStretch content_scale_stretch = CONTENT_SCALE_STRETCH_FRACTIONAL;
real_t content_scale_factor = 1.0;
RID accessibility_title_element;
RID accessibility_announcement_element;
String announcement;
String accessibility_name;
String accessibility_description;
void _make_window();
void _clear_window();
void _update_from_window();
void _accessibility_notify_enter(Node *p_node);
void _accessibility_notify_exit(Node *p_node);
bool _try_parent_dialog(Node *p_from_node);
Size2i max_size_used;
Size2i _clamp_limit_size(const Size2i &p_limit_size);
Size2i _clamp_window_size(const Size2i &p_size);
void _validate_limit_size();
void _update_viewport_size();
void _update_window_size();
void _propagate_window_notification(Node *p_node, int p_notification);
void _update_window_callbacks();
Window *transient_parent = nullptr;
Window *exclusive_child = nullptr;
HashSet<Window *> transient_children;
void _clear_transient();
void _make_transient();
void _set_transient_exclusive_child(bool p_clear_invalid = false);
static Window *focused_window;
ThemeOwner *theme_owner = nullptr;
Ref<Theme> theme;
StringName theme_type_variation;
bool bulk_theme_override = false;
Theme::ThemeIconMap theme_icon_override;
Theme::ThemeStyleMap theme_style_override;
Theme::ThemeFontMap theme_font_override;
Theme::ThemeFontSizeMap theme_font_size_override;
Theme::ThemeColorMap theme_color_override;
Theme::ThemeConstantMap theme_constant_override;
mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache;
mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache;
mutable HashMap<StringName, Theme::ThemeFontMap> theme_font_cache;
mutable HashMap<StringName, Theme::ThemeFontSizeMap> theme_font_size_cache;
mutable HashMap<StringName, Theme::ThemeColorMap> theme_color_cache;
mutable HashMap<StringName, Theme::ThemeConstantMap> theme_constant_cache;
void _theme_changed();
void _notify_theme_override_changed();
void _invalidate_theme_cache();
struct ThemeCache {
Ref<StyleBox> embedded_border;
Ref<StyleBox> embedded_unfocused_border;
Ref<Font> title_font;
int title_font_size = 0;
Color title_color;
int title_height = 0;
Color title_outline_modulate;
int title_outline_size = 0;
Ref<Texture2D> close;
Ref<Texture2D> close_pressed;
int close_h_offset = 0;
int close_v_offset = 0;
int resize_margin = 0;
} theme_cache;
void _settings_changed();
Viewport *embedder = nullptr;
Transform2D window_transform;
friend class Viewport; //friend back, can call the methods below
void _window_input(const Ref<InputEvent> &p_ev);
void _window_input_text(const String &p_text);
void _window_drop_files(const Vector<String> &p_files);
void _rect_changed_callback(const Rect2i &p_callback);
void _event_callback(DisplayServer::WindowEvent p_event);
virtual bool _can_consume_input_events() const override;
bool mouse_in_window = false;
void _update_mouse_over(Vector2 p_pos) override;
void _mouse_leave_viewport() override;
void _update_displayed_title();
Ref<Shortcut> debugger_stop_shortcut;
static int root_layout_direction;
protected:
virtual void _pre_popup() {} // Called after "about_to_popup", but before window is shown.
virtual Rect2i _popup_adjust_rect() const { return Rect2i(); }
virtual void _post_popup() {}
virtual void _update_theme_item_cache();
virtual void _input_from_window(const Ref<InputEvent> &p_event) {}
void _notification(int p_what);
static void _bind_methods();
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _validate_property(PropertyInfo &p_property) const;
void _accessibility_action_grab_focus(const Variant &p_data) {
grab_focus();
}
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
GDVIRTUAL0RC(Vector2, _get_contents_minimum_size)
public:
enum {
NOTIFICATION_VISIBILITY_CHANGED = 30,
NOTIFICATION_POST_POPUP = 31,
NOTIFICATION_THEME_CHANGED = 32
};
static void set_root_layout_direction(int p_root_dir);
static Window *get_from_id(DisplayServer::WindowID p_window_id);
RID get_accessibility_element() const override;
virtual RID get_focused_accessibility_element() const override;
void set_title(const String &p_title);
String get_title() const;
String get_displayed_title() const;
void set_initial_position(WindowInitialPosition p_initial_position);
WindowInitialPosition get_initial_position() const;
void set_force_native(bool p_force_native);
bool get_force_native() const;
void set_current_screen(int p_screen);
int get_current_screen() const;
void set_position(const Point2i &p_position);
Point2i get_position() const;
void move_to_center();
void set_size(const Size2i &p_size);
Size2i get_size() const;
void reset_size();
Point2i get_position_with_decorations() const;
Size2i get_size_with_decorations() const;
void set_max_size(const Size2i &p_max_size);
Size2i get_max_size() const;
void set_min_size(const Size2i &p_min_size);
Size2i get_min_size() const;
void set_mode(Mode p_mode);
Mode get_mode() const;
void set_flag(Flags p_flag, bool p_enabled);
bool get_flag(Flags p_flag) const;
bool is_popup() const;
bool is_maximize_allowed() const;
void request_attention();
#ifndef DISABLE_DEPRECATED
void move_to_foreground();
#endif // DISABLE_DEPRECATED
virtual void set_visible(bool p_visible);
bool is_visible() const;
void update_mouse_cursor_state() override;
void show();
void hide();
void set_transient(bool p_transient);
bool is_transient() const;
void set_transient_to_focused(bool p_transient_to_focused);
bool is_transient_to_focused() const;
void set_exclusive(bool p_exclusive);
bool is_exclusive() const;
void set_clamp_to_embedder(bool p_enable);
bool is_clamped_to_embedder() const;
void set_unparent_when_invisible(bool p_unparent);
bool is_in_edited_scene_root() const;
bool can_draw() const;
void set_ime_active(bool p_active);
void set_ime_position(const Point2i &p_pos);
bool is_embedded() const;
Viewport *get_embedder() const;
void set_content_scale_size(const Size2i &p_size);
Size2i get_content_scale_size() const;
void set_content_scale_mode(ContentScaleMode p_mode);
ContentScaleMode get_content_scale_mode() const;
void set_content_scale_aspect(ContentScaleAspect p_aspect);
ContentScaleAspect get_content_scale_aspect() const;
void set_content_scale_stretch(ContentScaleStretch p_stretch);
ContentScaleStretch get_content_scale_stretch() const;
void set_keep_title_visible(bool p_title_visible);
bool get_keep_title_visible() const;
void set_content_scale_factor(real_t p_factor);
real_t get_content_scale_factor() const;
void set_mouse_passthrough_polygon(const Vector<Vector2> &p_region);
Vector<Vector2> get_mouse_passthrough_polygon() const;
void set_wrap_controls(bool p_enable);
bool is_wrapping_controls() const;
void child_controls_changed();
Window *get_exclusive_child() const { return exclusive_child; }
HashSet<Window *> get_transient_children() const { return transient_children; }
Window *get_parent_visible_window() const;
Window *get_non_popup_window() const;
Viewport *get_parent_viewport() const;
virtual void popup(const Rect2i &p_screen_rect = Rect2i());
void popup_on_parent(const Rect2i &p_parent_rect);
void popup_centered(const Size2i &p_minsize = Size2i());
void popup_centered_ratio(float p_ratio = 0.8);
void popup_centered_clamped(const Size2i &p_size = Size2i(), float p_fallback_ratio = 0.75);
void popup_exclusive(Node *p_from_node, const Rect2i &p_screen_rect = Rect2i());
void popup_exclusive_on_parent(Node *p_from_node, const Rect2i &p_parent_rect);
void popup_exclusive_centered(Node *p_from_node, const Size2i &p_minsize = Size2i());
void popup_exclusive_centered_ratio(Node *p_from_node, float p_ratio = 0.8);
void popup_exclusive_centered_clamped(Node *p_from_node, const Size2i &p_size = Size2i(), float p_fallback_ratio = 0.75);
Rect2i fit_rect_in_parent(Rect2i p_rect, const Rect2i &p_parent_rect) const;
Size2 get_contents_minimum_size() const;
Size2 get_clamped_minimum_size() const;
void grab_focus();
bool has_focus() const;
bool has_focus_or_active_popup() const;
void start_drag();
void start_resize(DisplayServer::WindowResizeEdge p_edge);
Rect2i get_usable_parent_rect() const;
void set_accessibility_name(const String &p_name);
String get_accessibility_name() const;
void set_accessibility_description(const String &p_description);
String get_accessibility_description() const;
void accessibility_announcement(const String &p_announcement);
// Internationalization.
void set_layout_direction(LayoutDirection p_direction);
LayoutDirection get_layout_direction() const;
bool is_layout_rtl() const;
#ifndef DISABLE_DEPRECATED
void set_use_font_oversampling(bool p_oversampling);
bool is_using_font_oversampling() const;
void set_auto_translate(bool p_enable);
bool is_auto_translating() const;
#endif
// Theming.
void set_theme_owner_node(Node *p_node);
Node *get_theme_owner_node() const;
bool has_theme_owner_node() const;
void set_theme_context(ThemeContext *p_context, bool p_propagate = true);
void set_theme(const Ref<Theme> &p_theme);
Ref<Theme> get_theme() const;
void set_theme_type_variation(const StringName &p_theme_type);
StringName get_theme_type_variation() const;
void begin_bulk_theme_override();
void end_bulk_theme_override();
void add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon);
void add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style);
void add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font);
void add_theme_font_size_override(const StringName &p_name, int p_font_size);
void add_theme_color_override(const StringName &p_name, const Color &p_color);
void add_theme_constant_override(const StringName &p_name, int p_constant);
void remove_theme_icon_override(const StringName &p_name);
void remove_theme_style_override(const StringName &p_name);
void remove_theme_font_override(const StringName &p_name);
void remove_theme_font_size_override(const StringName &p_name);
void remove_theme_color_override(const StringName &p_name);
void remove_theme_constant_override(const StringName &p_name);
Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
int get_theme_font_size(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Color get_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
int get_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Variant get_theme_item(Theme::DataType p_data_type, const StringName &p_name, const StringName &p_theme_type = StringName()) const;
#ifdef TOOLS_ENABLED
Ref<Texture2D> get_editor_theme_icon(const StringName &p_name) const;
Ref<Texture2D> get_editor_theme_native_menu_icon(const StringName &p_name, bool p_global_menu, bool p_dark_mode) const;
#endif
bool has_theme_icon_override(const StringName &p_name) const;
bool has_theme_stylebox_override(const StringName &p_name) const;
bool has_theme_font_override(const StringName &p_name) const;
bool has_theme_font_size_override(const StringName &p_name) const;
bool has_theme_color_override(const StringName &p_name) const;
bool has_theme_constant_override(const StringName &p_name) const;
bool has_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_font_size(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
float get_theme_default_base_scale() const;
Ref<Font> get_theme_default_font() const;
int get_theme_default_font_size() const;
virtual Transform2D get_final_transform() const override;
virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override;
virtual Transform2D get_popup_base_transform() const override;
virtual Transform2D get_popup_base_transform_native() const override;
virtual Viewport *get_section_root_viewport() const override;
virtual bool is_attached_in_viewport() const override;
Rect2i get_parent_rect() const;
virtual DisplayServer::WindowID get_window_id() const override;
static Window *get_focused_window() { return focused_window; }
virtual Size2 _get_contents_minimum_size() const;
Window();
~Window();
};
VARIANT_ENUM_CAST(Window::Mode);
VARIANT_ENUM_CAST(Window::Flags);
VARIANT_ENUM_CAST(Window::ContentScaleMode);
VARIANT_ENUM_CAST(Window::ContentScaleAspect);
VARIANT_ENUM_CAST(Window::ContentScaleStretch);
VARIANT_ENUM_CAST(Window::LayoutDirection);
VARIANT_ENUM_CAST(Window::WindowInitialPosition);