summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/config/project_settings.cpp2
-rw-r--r--core/config/project_settings.h5
-rw-r--r--core/core_bind.cpp12
-rw-r--r--core/io/config_file.cpp2
-rw-r--r--core/io/dir_access.cpp4
-rw-r--r--core/io/http_client_tcp.cpp26
-rw-r--r--core/io/http_client_tcp.h1
-rw-r--r--core/io/multiplayer_api.cpp22
-rw-r--r--core/io/multiplayer_api.h6
-rw-r--r--core/io/multiplayer_replicator.cpp301
-rw-r--r--core/io/multiplayer_replicator.h38
-rw-r--r--core/io/resource_format_binary.cpp4
-rw-r--r--core/io/resource_loader.cpp2
-rw-r--r--core/math/vector2.cpp4
-rw-r--r--core/math/vector2.h1
-rw-r--r--core/string/ustring.cpp2
-rw-r--r--core/string/ustring.h2
-rw-r--r--core/templates/safe_list.h375
-rw-r--r--core/templates/vector.h2
-rw-r--r--core/variant/array.cpp6
-rw-r--r--core/variant/array.h2
-rw-r--r--core/variant/variant.cpp4
-rw-r--r--core/variant/variant_call.cpp4
-rw-r--r--core/variant/variant_op.cpp8
-rw-r--r--core/variant/variant_op.h96
-rw-r--r--core/variant/variant_utility.cpp11
-rw-r--r--doc/classes/@GlobalScope.xml17
-rw-r--r--doc/classes/Array.xml2
-rw-r--r--doc/classes/AudioStreamPlayback.xml2
-rw-r--r--doc/classes/CharacterBody2D.xml7
-rw-r--r--doc/classes/EditorPlugin.xml16
-rw-r--r--doc/classes/MultiplayerAPI.xml13
-rw-r--r--doc/classes/MultiplayerReplicator.xml54
-rw-r--r--doc/classes/Node.xml35
-rw-r--r--doc/classes/Object.xml8
-rw-r--r--doc/classes/PhysicsServer2D.xml3
-rw-r--r--doc/classes/PhysicsServer3D.xml3
-rw-r--r--doc/classes/SoftBody3D.xml23
-rw-r--r--doc/classes/String.xml2
-rw-r--r--doc/classes/TextServer.xml2
-rw-r--r--doc/classes/TextureProgressBar.xml3
-rw-r--r--doc/classes/Vector2.xml12
-rw-r--r--doc/translations/ar.po6
-rw-r--r--doc/translations/ca.po6
-rw-r--r--doc/translations/classes.pot6
-rw-r--r--doc/translations/cs.po6
-rw-r--r--doc/translations/de.po6
-rw-r--r--doc/translations/es.po10
-rw-r--r--doc/translations/fa.po6
-rw-r--r--doc/translations/fi.po6
-rw-r--r--doc/translations/fr.po6
-rw-r--r--doc/translations/id.po6
-rw-r--r--doc/translations/it.po6
-rw-r--r--doc/translations/ja.po6
-rw-r--r--doc/translations/ko.po6
-rw-r--r--doc/translations/nl.po6
-rw-r--r--doc/translations/pl.po6
-rw-r--r--doc/translations/pt_BR.po6
-rw-r--r--doc/translations/ro.po6
-rw-r--r--doc/translations/ru.po6
-rw-r--r--doc/translations/sr_Cyrl.po6
-rw-r--r--doc/translations/th.po6
-rw-r--r--doc/translations/tr.po6
-rw-r--r--doc/translations/uk.po6
-rw-r--r--doc/translations/zh_Hans.po6
-rw-r--r--doc/translations/zh_Hant.po6
-rw-r--r--drivers/unix/dir_access_unix.cpp26
-rw-r--r--drivers/unix/os_unix.cpp2
-rw-r--r--drivers/windows/dir_access_windows.cpp10
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_parser.cpp203
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_parser.h16
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.cpp541
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.h17
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_server.cpp31
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_server.h2
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_types.h10
-rw-r--r--editor/debugger/editor_debugger_node.cpp3
-rw-r--r--editor/debugger/script_editor_debugger.cpp14
-rw-r--r--editor/debugger/script_editor_debugger.h3
-rw-r--r--editor/editor_file_dialog.cpp2
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/editor_node.h1
-rw-r--r--editor/editor_properties.cpp4
-rw-r--r--editor/editor_run_native.cpp18
-rw-r--r--editor/editor_run_native.h3
-rw-r--r--editor/import/collada.cpp4
-rw-r--r--editor/import/resource_importer_shader_file.cpp2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp1
-rw-r--r--editor/plugins/script_editor_plugin.cpp32
-rw-r--r--editor/plugins/script_editor_plugin.h4
-rw-r--r--editor/plugins/script_text_editor.cpp12
-rw-r--r--editor/plugins/script_text_editor.h2
-rw-r--r--editor/plugins/text_editor.h2
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp1
-rw-r--r--main/main.cpp12
-rw-r--r--modules/gdnative/include/nativescript/godot_nativescript.h8
-rw-r--r--modules/gdnative/text/text_server_gdnative.cpp2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp6
-rw-r--r--modules/gdscript/gdscript.cpp8
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp2
-rw-r--r--modules/gdscript/gdscript_editor.cpp28
-rw-r--r--modules/gdscript/gdscript_parser.cpp55
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/features/str_preserves_case.out4
-rw-r--r--modules/minimp3/audio_stream_mp3.cpp8
-rw-r--r--modules/minimp3/audio_stream_mp3.h2
-rw-r--r--modules/mono/csharp_script.cpp7
-rw-r--r--modules/mono/editor/code_completion.cpp2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs26
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs11
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h1
-rw-r--r--modules/stb_vorbis/audio_stream_ogg_vorbis.cpp8
-rw-r--r--modules/stb_vorbis/audio_stream_ogg_vorbis.h2
-rw-r--r--modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml54
-rw-r--r--modules/visual_script/visual_script_builtin_funcs.cpp40
-rw-r--r--modules/visual_script/visual_script_builtin_funcs.h2
-rw-r--r--modules/visual_script/visual_script_editor.h2
-rw-r--r--modules/visual_script/visual_script_nodes.cpp2
-rw-r--r--modules/websocket/wsl_client.cpp28
-rw-r--r--modules/websocket/wsl_client.h2
-rw-r--r--platform/android/dir_access_jandroid.cpp2
-rw-r--r--platform/android/export/export_plugin.cpp2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java14
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.cpp32
-rw-r--r--scene/2d/audio_stream_player_2d.cpp326
-rw-r--r--scene/2d/audio_stream_player_2d.h26
-rw-r--r--scene/2d/physics_body_2d.cpp137
-rw-r--r--scene/2d/physics_body_2d.h22
-rw-r--r--scene/3d/audio_stream_player_3d.cpp661
-rw-r--r--scene/3d/audio_stream_player_3d.h42
-rw-r--r--scene/3d/physics_body_3d.cpp124
-rw-r--r--scene/3d/physics_body_3d.h10
-rw-r--r--scene/3d/soft_body_3d.cpp5
-rw-r--r--scene/3d/sprite_3d.cpp2
-rw-r--r--scene/3d/vehicle_body_3d.cpp33
-rw-r--r--scene/3d/vehicle_body_3d.h3
-rw-r--r--scene/audio/audio_stream_player.cpp237
-rw-r--r--scene/audio/audio_stream_player.h13
-rw-r--r--scene/gui/box_container.cpp4
-rw-r--r--scene/gui/color_picker.cpp16
-rw-r--r--scene/gui/dialogs.cpp6
-rw-r--r--scene/gui/file_dialog.cpp11
-rw-r--r--scene/gui/gradient_edit.cpp2
-rw-r--r--scene/gui/graph_edit.cpp5
-rw-r--r--scene/gui/item_list.cpp2
-rw-r--r--scene/gui/line_edit.cpp4
-rw-r--r--scene/gui/menu_button.cpp2
-rw-r--r--scene/gui/option_button.cpp2
-rw-r--r--scene/gui/popup.cpp2
-rw-r--r--scene/gui/popup_menu.cpp9
-rw-r--r--scene/gui/rich_text_label.cpp2
-rw-r--r--scene/gui/scroll_container.cpp7
-rw-r--r--scene/gui/spin_box.cpp4
-rw-r--r--scene/gui/tab_container.cpp19
-rw-r--r--scene/gui/text_edit.cpp16
-rw-r--r--scene/gui/texture_progress_bar.cpp57
-rw-r--r--scene/gui/texture_progress_bar.h4
-rw-r--r--scene/gui/tree.cpp45
-rw-r--r--scene/gui/video_player.cpp5
-rw-r--r--scene/main/node.cpp155
-rw-r--r--scene/main/node.h30
-rw-r--r--scene/main/viewport.cpp16
-rw-r--r--scene/resources/audio_stream_sample.cpp7
-rw-r--r--scene/resources/audio_stream_sample.h2
-rw-r--r--scene/resources/packed_scene.cpp9
-rw-r--r--scene/resources/resource_format_text.cpp4
-rw-r--r--servers/audio/audio_stream.cpp45
-rw-r--r--servers/audio/audio_stream.h16
-rw-r--r--servers/audio/effects/audio_stream_generator.cpp3
-rw-r--r--servers/audio/effects/audio_stream_generator.h2
-rw-r--r--servers/audio_server.cpp492
-rw-r--r--servers/audio_server.h85
-rw-r--r--servers/physics_2d/area_2d_sw.cpp20
-rw-r--r--servers/physics_2d/area_2d_sw.h8
-rw-r--r--servers/physics_2d/body_2d_sw.cpp105
-rw-r--r--servers/physics_2d/body_2d_sw.h100
-rw-r--r--servers/physics_2d/body_direct_state_2d_sw.cpp182
-rw-r--r--servers/physics_2d/body_direct_state_2d_sw.h91
-rw-r--r--servers/physics_2d/collision_solver_2d_sw.cpp10
-rw-r--r--servers/physics_2d/collision_solver_2d_sw.h2
-rw-r--r--servers/physics_2d/physics_server_2d_sw.cpp20
-rw-r--r--servers/physics_2d/physics_server_2d_sw.h5
-rw-r--r--servers/physics_2d/physics_server_2d_wrap_mt.h1
-rw-r--r--servers/physics_2d/shape_2d_sw.cpp6
-rw-r--r--servers/physics_2d/shape_2d_sw.h8
-rw-r--r--servers/physics_2d/space_2d_sw.cpp4
-rw-r--r--servers/physics_2d/space_2d_sw.h5
-rw-r--r--servers/physics_2d/step_2d_sw.cpp2
-rw-r--r--servers/physics_3d/area_3d_sw.cpp20
-rw-r--r--servers/physics_3d/area_3d_sw.h9
-rw-r--r--servers/physics_3d/body_3d_sw.cpp93
-rw-r--r--servers/physics_3d/body_3d_sw.h113
-rw-r--r--servers/physics_3d/body_direct_state_3d_sw.cpp182
-rw-r--r--servers/physics_3d/body_direct_state_3d_sw.h94
-rw-r--r--servers/physics_3d/collision_solver_3d_sw.cpp30
-rw-r--r--servers/physics_3d/collision_solver_3d_sw.h6
-rw-r--r--servers/physics_3d/physics_server_3d_sw.cpp20
-rw-r--r--servers/physics_3d/physics_server_3d_sw.h4
-rw-r--r--servers/physics_3d/physics_server_3d_wrap_mt.h1
-rw-r--r--servers/physics_3d/shape_3d_sw.cpp31
-rw-r--r--servers/physics_3d/shape_3d_sw.h14
-rw-r--r--servers/physics_3d/soft_body_3d_sw.cpp13
-rw-r--r--servers/physics_3d/space_3d_sw.h5
-rw-r--r--servers/physics_3d/step_3d_sw.cpp2
-rw-r--r--servers/physics_server_2d.h4
-rw-r--r--servers/physics_server_3d.h4
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp2
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp2
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp2
-rw-r--r--servers/rendering/renderer_rd/renderer_storage_rd.cpp2
-rw-r--r--servers/text_server.cpp2
-rw-r--r--tests/test_config_file.h11
-rw-r--r--tests/test_string.h2
223 files changed, 4536 insertions, 2107 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index c5e6c6d685..03892d1d4f 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1006,7 +1006,7 @@ bool ProjectSettings::has_custom_feature(const String &p_feature) const {
return custom_features.has(p_feature);
}
-Map<StringName, ProjectSettings::AutoloadInfo> ProjectSettings::get_autoload_list() const {
+OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> ProjectSettings::get_autoload_list() const {
return autoloads;
}
diff --git a/core/config/project_settings.h b/core/config/project_settings.h
index ed8fb19fa0..7e93f26f0d 100644
--- a/core/config/project_settings.h
+++ b/core/config/project_settings.h
@@ -33,6 +33,7 @@
#include "core/object/class_db.h"
#include "core/os/thread_safe.h"
+#include "core/templates/ordered_hash_map.h"
#include "core/templates/set.h"
class ProjectSettings : public Object {
@@ -91,7 +92,7 @@ protected:
Set<String> custom_features;
Map<StringName, StringName> feature_overrides;
- Map<StringName, AutoloadInfo> autoloads;
+ OrderedHashMap<StringName, AutoloadInfo> autoloads;
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -168,7 +169,7 @@ public:
bool has_custom_feature(const String &p_feature) const;
- Map<StringName, AutoloadInfo> get_autoload_list() const;
+ OrderedHashMap<StringName, AutoloadInfo> get_autoload_list() const;
void add_autoload(const AutoloadInfo &p_autoload);
void remove_autoload(const StringName &p_autoload);
bool has_autoload(const StringName &p_autoload) const;
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 83a3b80803..fd5b3bb731 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -1504,7 +1504,7 @@ String Directory::get_current_dir() {
Error Directory::make_dir(String p_dir) {
ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly.");
- if (!p_dir.is_rel_path()) {
+ if (!p_dir.is_relative_path()) {
DirAccess *d = DirAccess::create_for_path(p_dir);
Error err = d->make_dir(p_dir);
memdelete(d);
@@ -1515,7 +1515,7 @@ Error Directory::make_dir(String p_dir) {
Error Directory::make_dir_recursive(String p_dir) {
ERR_FAIL_COND_V_MSG(!d, ERR_UNCONFIGURED, "Directory is not configured properly.");
- if (!p_dir.is_rel_path()) {
+ if (!p_dir.is_relative_path()) {
DirAccess *d = DirAccess::create_for_path(p_dir);
Error err = d->make_dir_recursive(p_dir);
memdelete(d);
@@ -1526,7 +1526,7 @@ Error Directory::make_dir_recursive(String p_dir) {
bool Directory::file_exists(String p_file) {
ERR_FAIL_COND_V_MSG(!d, false, "Directory is not configured properly.");
- if (!p_file.is_rel_path()) {
+ if (!p_file.is_relative_path()) {
return FileAccess::exists(p_file);
}
@@ -1535,7 +1535,7 @@ bool Directory::file_exists(String p_file) {
bool Directory::dir_exists(String p_dir) {
ERR_FAIL_COND_V_MSG(!d, false, "Directory is not configured properly.");
- if (!p_dir.is_rel_path()) {
+ if (!p_dir.is_relative_path()) {
DirAccess *d = DirAccess::create_for_path(p_dir);
bool exists = d->dir_exists(p_dir);
memdelete(d);
@@ -1559,7 +1559,7 @@ Error Directory::rename(String p_from, String p_to) {
ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use.");
ERR_FAIL_COND_V_MSG(p_from.is_empty() || p_from == "." || p_from == "..", ERR_INVALID_PARAMETER, "Invalid path to rename.");
- if (!p_from.is_rel_path()) {
+ if (!p_from.is_relative_path()) {
DirAccess *d = DirAccess::create_for_path(p_from);
ERR_FAIL_COND_V_MSG(!d->file_exists(p_from) && !d->dir_exists(p_from), ERR_DOES_NOT_EXIST, "File or directory does not exist.");
Error err = d->rename(p_from, p_to);
@@ -1573,7 +1573,7 @@ Error Directory::rename(String p_from, String p_to) {
Error Directory::remove(String p_name) {
ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use.");
- if (!p_name.is_rel_path()) {
+ if (!p_name.is_relative_path()) {
DirAccess *d = DirAccess::create_for_path(p_name);
Error err = d->remove(p_name);
memdelete(d);
diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp
index 55793aa5a4..49fa73dab2 100644
--- a/core/io/config_file.cpp
+++ b/core/io/config_file.cpp
@@ -188,7 +188,7 @@ Error ConfigFile::_internal_save(FileAccess *file) {
for (OrderedHashMap<String, Variant>::Element F = E.get().front(); F; F = F.next()) {
String vstr;
VariantWriter::write_to_string(F.get(), vstr);
- file->store_string(F.key() + "=" + vstr + "\n");
+ file->store_string(F.key().property_name_encode() + "=" + vstr + "\n");
}
}
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index 8234adea06..3bff0a3fd5 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -135,7 +135,7 @@ Error DirAccess::make_dir_recursive(String p_dir) {
String full_dir;
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
//append current
full_dir = get_current_dir().plus_file(p_dir);
@@ -345,7 +345,7 @@ Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flag
dirs.push_back(n);
} else {
const String &rel_path = n;
- if (!n.is_rel_path()) {
+ if (!n.is_relative_path()) {
list_dir_end();
return ERR_BUG;
}
diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp
index f291086808..b3d35b3603 100644
--- a/core/io/http_client_tcp.cpp
+++ b/core/io/http_client_tcp.cpp
@@ -45,6 +45,8 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss
conn_port = p_port;
conn_host = p_host;
+ ip_candidates.clear();
+
ssl = p_ssl;
ssl_verify_host = p_verify_host;
@@ -234,6 +236,7 @@ void HTTPClientTCP::close() {
resolving = IP::RESOLVER_INVALID_ID;
}
+ ip_candidates.clear();
response_headers.clear();
response_str.clear();
body_size = -1;
@@ -256,10 +259,17 @@ Error HTTPClientTCP::poll() {
return OK; // Still resolving
case IP::RESOLVER_STATUS_DONE: {
- IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving);
- Error err = tcp_connection->connect_to_host(host, conn_port);
+ ip_candidates = IP::get_singleton()->get_resolve_item_addresses(resolving);
IP::get_singleton()->erase_resolve_item(resolving);
resolving = IP::RESOLVER_INVALID_ID;
+
+ Error err = ERR_BUG; // Should be at least one entry.
+ while (ip_candidates.size() > 0) {
+ err = tcp_connection->connect_to_host(ip_candidates.front(), conn_port);
+ if (err == OK) {
+ break;
+ }
+ }
if (err) {
status = STATUS_CANT_CONNECT;
return err;
@@ -313,6 +323,7 @@ Error HTTPClientTCP::poll() {
if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) {
// Handshake has been successful
handshaking = false;
+ ip_candidates.clear();
status = STATUS_CONNECTED;
return OK;
} else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) {
@@ -323,15 +334,24 @@ Error HTTPClientTCP::poll() {
}
// ... we will need to poll more for handshake to finish
} else {
+ ip_candidates.clear();
status = STATUS_CONNECTED;
}
return OK;
} break;
case StreamPeerTCP::STATUS_ERROR:
case StreamPeerTCP::STATUS_NONE: {
+ Error err = ERR_CANT_CONNECT;
+ while (ip_candidates.size() > 0) {
+ tcp_connection->disconnect_from_host();
+ err = tcp_connection->connect_to_host(ip_candidates.pop_front(), conn_port);
+ if (err == OK) {
+ return OK;
+ }
+ }
close();
status = STATUS_CANT_CONNECT;
- return ERR_CANT_CONNECT;
+ return err;
} break;
}
} break;
diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h
index e178399fbe..170afb551c 100644
--- a/core/io/http_client_tcp.h
+++ b/core/io/http_client_tcp.h
@@ -37,6 +37,7 @@ class HTTPClientTCP : public HTTPClient {
private:
Status status = STATUS_DISCONNECTED;
IP::ResolverID resolving = IP::RESOLVER_INVALID_ID;
+ Array ip_candidates;
int conn_port = -1;
String conn_host;
bool ssl = false;
diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp
index 0ce9a70921..c145225751 100644
--- a/core/io/multiplayer_api.cpp
+++ b/core/io/multiplayer_api.cpp
@@ -96,14 +96,11 @@ _FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, i
case MultiplayerAPI::RPC_MODE_DISABLED: {
return false;
} break;
- case MultiplayerAPI::RPC_MODE_REMOTE: {
+ case MultiplayerAPI::RPC_MODE_ANY: {
return true;
} break;
- case MultiplayerAPI::RPC_MODE_MASTER: {
- return p_node->is_network_master();
- } break;
- case MultiplayerAPI::RPC_MODE_PUPPET: {
- return !p_node->is_network_master() && p_remote_id == p_node->get_network_master();
+ case MultiplayerAPI::RPC_MODE_AUTHORITY: {
+ return !p_node->is_network_authority() && p_remote_id == p_node->get_network_authority();
} break;
}
@@ -140,6 +137,9 @@ void MultiplayerAPI::poll() {
break; // It's also possible that a packet or RPC caused a disconnection, so also check here.
}
}
+ if (network_peer.is_valid() && network_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) {
+ replicator->poll();
+ }
}
void MultiplayerAPI::clear() {
@@ -326,6 +326,9 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
case NETWORK_COMMAND_DESPAWN: {
replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false);
} break;
+ case NETWORK_COMMAND_SYNC: {
+ replicator->process_sync(p_from, p_packet, p_packet_len);
+ } break;
}
}
@@ -363,7 +366,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id,
ERR_FAIL_COND(config.name == StringName());
bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from);
- ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", master is " + itos(p_node->get_network_master()) + ".");
+ ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_network_authority()) + ".");
int argc = 0;
bool byte_only = false;
@@ -1132,9 +1135,8 @@ void MultiplayerAPI::_bind_methods() {
ADD_SIGNAL(MethodInfo("server_disconnected"));
BIND_ENUM_CONSTANT(RPC_MODE_DISABLED);
- BIND_ENUM_CONSTANT(RPC_MODE_REMOTE);
- BIND_ENUM_CONSTANT(RPC_MODE_MASTER);
- BIND_ENUM_CONSTANT(RPC_MODE_PUPPET);
+ BIND_ENUM_CONSTANT(RPC_MODE_ANY);
+ BIND_ENUM_CONSTANT(RPC_MODE_AUTHORITY);
}
MultiplayerAPI::MultiplayerAPI() {
diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h
index 5853541efa..3c96a3eed1 100644
--- a/core/io/multiplayer_api.h
+++ b/core/io/multiplayer_api.h
@@ -43,9 +43,8 @@ class MultiplayerAPI : public RefCounted {
public:
enum RPCMode {
RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default)
- RPC_MODE_REMOTE, // Using rpc() on it will call method in all remote peers
- RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote
- RPC_MODE_PUPPET, // Using rpc() on it will call method for all puppets
+ RPC_MODE_ANY, // Any peer can call this rpc()
+ RPC_MODE_AUTHORITY, // Only the node's network authority (server by default) can call this rpc()
};
struct RPCConfig {
@@ -74,6 +73,7 @@ public:
NETWORK_COMMAND_RAW,
NETWORK_COMMAND_SPAWN,
NETWORK_COMMAND_DESPAWN,
+ NETWORK_COMMAND_SYNC, // This is the max we can have. We should optmize simplify/confirm, possibly spawn/despawn.
};
enum NetworkNodeIdCompression {
diff --git a/core/io/multiplayer_replicator.cpp b/core/io/multiplayer_replicator.cpp
index ba0fe32b58..1642aab136 100644
--- a/core/io/multiplayer_replicator.cpp
+++ b/core/io/multiplayer_replicator.cpp
@@ -38,6 +38,140 @@
if (packet_cache.size() < m_amount) \
packet_cache.resize(m_amount);
+Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer) {
+ ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
+ SceneConfig &cfg = replications[p_scene_id];
+ int full_size = 0;
+ bool same_size = true;
+ int last_size = 0;
+ bool all_raw = true;
+ struct EncodeInfo {
+ int size = 0;
+ bool raw = false;
+ List<Variant> state;
+ };
+ Map<ObjectID, struct EncodeInfo> state;
+ if (tracked_objects.has(p_scene_id)) {
+ for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
+ Object *obj = ObjectDB::get_instance(obj_id);
+ if (obj) {
+ struct EncodeInfo info;
+ Error err = _get_state(cfg.sync_properties, obj, info.state);
+ ERR_CONTINUE(err);
+ err = _encode_state(info.state, nullptr, info.size, &info.raw);
+ ERR_CONTINUE(err);
+ state[obj_id] = info;
+ full_size += info.size;
+ if (last_size && info.size != last_size) {
+ same_size = false;
+ }
+ all_raw = all_raw && info.raw;
+ last_size = info.size;
+ }
+ }
+ }
+ // Default implementation do not send empty updates.
+ if (!full_size) {
+ return OK;
+ }
+#ifdef DEBUG_ENABLED
+ if (full_size > 4096 && cfg.sync_interval) {
+ WARN_PRINT_ONCE(vformat("The timed state update for scene %d is big (%d bytes) consider optimizing it", p_scene_id));
+ }
+#endif
+ if (same_size) {
+ // This is fast and small. Should we allow more than 256 objects per type?
+ // This costs us 1 byte.
+ MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + 2 + full_size);
+ } else {
+ MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + state.size() * 2 + full_size);
+ }
+ int ofs = 0;
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC + ((same_size ? 1 : 0) << MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT);
+ ofs = 1;
+ ofs += encode_uint64(p_scene_id, &ptr[ofs]);
+ ptr[ofs] = cfg.sync_recv++;
+ ofs += 1;
+ ofs += encode_uint16(state.size(), &ptr[ofs]);
+ if (same_size) {
+ ofs += encode_uint16(last_size + (all_raw ? 1 << 15 : 0), &ptr[ofs]);
+ }
+ for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
+ if (!state.has(obj_id)) {
+ continue;
+ }
+ struct EncodeInfo &info = state[obj_id];
+ Object *obj = ObjectDB::get_instance(obj_id);
+ ERR_CONTINUE(!obj);
+ int size = 0;
+ if (!same_size) {
+ // We need to encode the size of every object.
+ ofs += encode_uint16(info.size + (info.raw ? 1 << 15 : 0), &ptr[ofs]);
+ }
+ Error err = _encode_state(info.state, &ptr[ofs], size, &info.raw);
+ ERR_CONTINUE(err);
+ ofs += size;
+ }
+ Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer();
+ network_peer->set_target_peer(p_peer);
+ network_peer->set_transfer_channel(0);
+ network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_UNRELIABLE);
+ return network_peer->put_packet(ptr, ofs);
+}
+
+void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) {
+ ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received");
+ ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id));
+ SceneConfig &cfg = replications[p_id];
+ ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_network_server(), "The defualt implementation only allows sync packets from the server");
+ const bool same_size = ((p_packet[0] & 64) >> MultiplayerAPI::BYTE_ONLY_OR_NO_ARGS_SHIFT) == 1;
+ int ofs = SYNC_CMD_OFFSET;
+ int time = p_packet[ofs];
+ // Skip old update.
+ if (time < cfg.sync_recv && cfg.sync_recv - time < 127) {
+ return;
+ }
+ cfg.sync_recv = time;
+ ofs += 1;
+ int count = decode_uint16(&p_packet[ofs]);
+ ofs += 2;
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_COND(!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count);
+#else
+ if (!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count) {
+ return;
+ }
+#endif
+ int data_size = 0;
+ bool raw = false;
+ if (same_size) {
+ // This is fast and optimized.
+ data_size = decode_uint16(&p_packet[ofs]);
+ raw = (data_size & (1 << 15)) != 0;
+ data_size = data_size & ~(1 << 15);
+ ofs += 2;
+ ERR_FAIL_COND(p_packet_len - ofs < data_size * count);
+ }
+ for (const ObjectID &obj_id : tracked_objects[p_id]) {
+ Object *obj = ObjectDB::get_instance(obj_id);
+ ERR_CONTINUE(!obj);
+ if (!same_size) {
+ // This is slow and wasteful.
+ data_size = decode_uint16(&p_packet[ofs]);
+ raw = (data_size & (1 << 15)) != 0;
+ data_size = data_size & ~(1 << 15);
+ ofs += 2;
+ ERR_FAIL_COND(p_packet_len - ofs < data_size);
+ }
+ int size = 0;
+ Error err = _decode_state(cfg.sync_properties, obj, &p_packet[ofs], data_size, size, raw);
+ ofs += data_size;
+ ERR_CONTINUE(err);
+ ERR_CONTINUE(size != data_size);
+ }
+}
+
Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) {
ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
@@ -136,6 +270,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res
Node *node = scene->instantiate();
ERR_FAIL_COND(!node);
replicated_nodes[node->get_instance_id()] = p_scene_id;
+ _track(p_scene_id, node);
int size;
_decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw);
parent->_add_child_nocheck(node, name);
@@ -145,6 +280,7 @@ void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const Res
Node *node = parent->get_node(name);
ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name));
emit_signal(SNAME("despawned"), p_scene_id, node);
+ _untrack(p_scene_id, node);
replicated_nodes.erase(node->get_instance_id());
node->queue_delete();
}
@@ -197,6 +333,37 @@ void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_p
}
}
+void MultiplayerReplicator::process_sync(int p_from, const uint8_t *p_packet, int p_packet_len) {
+ ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received");
+ ResourceUID::ID id = decode_uint64(&p_packet[1]);
+ ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id));
+ const SceneConfig &cfg = replications[id];
+ if (cfg.on_sync_receive.is_valid()) {
+ Array objs;
+ if (tracked_objects.has(id)) {
+ objs.resize(tracked_objects[id].size());
+ int idx = 0;
+ for (const ObjectID &obj_id : tracked_objects[id]) {
+ objs[idx++] = ObjectDB::get_instance(obj_id);
+ }
+ }
+ PackedByteArray pba;
+ pba.resize(p_packet_len - SPAWN_CMD_OFFSET);
+ if (pba.size()) {
+ memcpy(pba.ptrw(), p_packet, p_packet_len - SPAWN_CMD_OFFSET);
+ }
+ Variant args[4] = { p_from, id, objs, pba };
+ Variant *argp[4] = { args, &args[1], &args[2], &args[3] };
+ Callable::CallError ce;
+ Variant ret;
+ cfg.on_sync_receive.call((const Variant **)argp, 4, ret, ce);
+ ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom sync function failed");
+ } else {
+ ERR_FAIL_COND_MSG(p_from != 1, "Default sync implementation only allow syncing from server to client");
+ _process_default_sync(id, p_packet, p_packet_len);
+ }
+}
+
Error MultiplayerReplicator::_get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant) {
ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object");
for (const StringName &prop : p_properties) {
@@ -306,6 +473,21 @@ Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, Replicati
return OK;
}
+Error MultiplayerReplicator::sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) {
+ ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty");
+ ERR_FAIL_COND_V(!replications.has(p_id), ERR_UNCONFIGURED);
+ SceneConfig &cfg = replications[p_id];
+ ERR_FAIL_COND_V_MSG(p_interval && cfg.mode != REPLICATION_MODE_SERVER && !p_on_send.is_valid(), ERR_INVALID_PARAMETER, "Timed updates in custom mode are only allowed if custom callbacks are also specified");
+ for (int i = 0; i < p_props.size(); i++) {
+ cfg.sync_properties.push_back(p_props[i]);
+ }
+ cfg.on_sync_send = p_on_send;
+ cfg.on_sync_receive = p_on_recv;
+ cfg.sync_interval = p_interval * 1000;
+ return OK;
+}
+
Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn) {
int data_size = 0;
int is_raw = false;
@@ -337,6 +519,7 @@ Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUI
}
Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) {
+ ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
const SceneConfig &cfg = replications[p_scene_id];
if (cfg.on_spawn_despawn_send.is_valid()) {
@@ -357,6 +540,7 @@ Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &
}
Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) {
+ ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
const SceneConfig &cfg = replications[p_scene_id];
if (cfg.on_spawn_despawn_send.is_valid()) {
@@ -408,13 +592,14 @@ Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj,
return _spawn_despawn(p_scene_id, p_obj, p_peer, false);
}
-PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj) {
+PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj, bool p_initial) {
PackedByteArray state;
ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id));
const SceneConfig &cfg = replications[p_scene_id];
int len = 0;
List<Variant> state_vars;
- Error err = _get_state(cfg.properties, p_obj, state_vars);
+ const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties;
+ Error err = _get_state(props, p_obj, state_vars);
ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state.");
err = _encode_state(state_vars, nullptr, len);
ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state.");
@@ -423,11 +608,12 @@ PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_sce
return state;
}
-Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data) {
+Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data, bool p_initial) {
ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
const SceneConfig &cfg = replications[p_scene_id];
+ const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties;
int size;
- return _decode_state(cfg.properties, p_obj, p_data.ptr(), p_data.size(), size);
+ return _decode_state(props, p_obj, p_data.ptr(), p_data.size(), size);
}
void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) {
@@ -448,12 +634,14 @@ void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node
if (p_enter) {
if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server()) {
replicated_nodes[p_node->get_instance_id()] = id;
+ _track(id, p_node);
spawn(id, p_node, 0);
}
emit_signal(SNAME("replicated_instance_added"), id, p_node);
} else {
if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_network_server() && replicated_nodes.has(p_node->get_instance_id())) {
replicated_nodes.erase(p_node->get_instance_id());
+ _untrack(id, p_node);
despawn(id, p_node, 0);
}
emit_signal(SNAME("replicated_instance_removed"), id, p_node);
@@ -471,18 +659,119 @@ void MultiplayerReplicator::spawn_all(int p_peer) {
}
}
+void MultiplayerReplicator::poll() {
+ for (KeyValue<ResourceUID::ID, SceneConfig> &E : replications) {
+ if (!E.value.sync_interval) {
+ continue;
+ }
+ if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_network_server()) {
+ continue;
+ }
+ uint64_t time = OS::get_singleton()->get_ticks_usec();
+ if (E.value.sync_last + E.value.sync_interval <= time) {
+ sync_all(E.key, 0);
+ E.value.sync_last = time;
+ }
+ // Handle wrapping.
+ if (E.value.sync_last > time) {
+ E.value.sync_last = time;
+ }
+ }
+}
+
+void MultiplayerReplicator::track(const ResourceUID::ID &p_scene_id, Object *p_obj) {
+ ERR_FAIL_COND(!replications.has(p_scene_id));
+ const SceneConfig &cfg = replications[p_scene_id];
+ ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode.");
+ _track(p_scene_id, p_obj);
+}
+
+void MultiplayerReplicator::_track(const ResourceUID::ID &p_scene_id, Object *p_obj) {
+ ERR_FAIL_COND(!p_obj);
+ ERR_FAIL_COND(!replications.has(p_scene_id));
+ if (!tracked_objects.has(p_scene_id)) {
+ tracked_objects[p_scene_id] = List<ObjectID>();
+ }
+ tracked_objects[p_scene_id].push_back(p_obj->get_instance_id());
+}
+
+void MultiplayerReplicator::untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) {
+ ERR_FAIL_COND(!replications.has(p_scene_id));
+ const SceneConfig &cfg = replications[p_scene_id];
+ ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode.");
+ _untrack(p_scene_id, p_obj);
+}
+
+void MultiplayerReplicator::_untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) {
+ ERR_FAIL_COND(!p_obj);
+ ERR_FAIL_COND(!replications.has(p_scene_id));
+ if (tracked_objects.has(p_scene_id)) {
+ tracked_objects[p_scene_id].erase(p_obj->get_instance_id());
+ }
+}
+
+Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_peer) {
+ ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
+ if (!tracked_objects.has(p_scene_id)) {
+ return OK;
+ }
+ const SceneConfig &cfg = replications[p_scene_id];
+ if (cfg.on_sync_send.is_valid()) {
+ Array objs;
+ if (tracked_objects.has(p_scene_id)) {
+ objs.resize(tracked_objects[p_scene_id].size());
+ int idx = 0;
+ for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
+ objs[idx++] = ObjectDB::get_instance(obj_id);
+ }
+ }
+ Variant args[3] = { p_scene_id, objs, p_peer };
+ Variant *argp[3] = { args, &args[1], &args[2] };
+ Callable::CallError ce;
+ Variant ret;
+ cfg.on_sync_send.call((const Variant **)argp, 3, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom sync function failed");
+ return OK;
+ } else if (cfg.sync_properties.size()) {
+ return _sync_all_default(p_scene_id, p_peer);
+ }
+ return OK;
+}
+
+Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel) {
+ ERR_FAIL_COND_V(!multiplayer->has_network_peer(), ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
+ const SceneConfig &cfg = replications[p_scene_id];
+ ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions");
+ MAKE_ROOM(SYNC_CMD_OFFSET + p_data.size());
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC;
+ encode_uint64(p_scene_id, &ptr[1]);
+ Ref<MultiplayerPeer> network_peer = multiplayer->get_network_peer();
+ network_peer->set_target_peer(p_peer_id);
+ network_peer->set_transfer_channel(p_channel);
+ network_peer->set_transfer_mode(p_transfer_mode);
+ return network_peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size());
+}
+
void MultiplayerReplicator::clear() {
+ tracked_objects.clear();
replicated_nodes.clear();
}
void MultiplayerReplicator::_bind_methods() {
ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable()));
+ ClassDB::bind_method(D_METHOD("sync_config", "scene_id", "interval", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::sync_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable()));
ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0));
ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0));
ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath()));
ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath()));
- ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object"), &MultiplayerReplicator::encode_state);
- ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data"), &MultiplayerReplicator::decode_state);
+ ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track);
+ ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack);
+ ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object", "initial"), &MultiplayerReplicator::encode_state, DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data", "initial"), &MultiplayerReplicator::decode_state, DEFVAL(true));
ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
diff --git a/core/io/multiplayer_replicator.h b/core/io/multiplayer_replicator.h
index e19dd80602..2630ad7a8a 100644
--- a/core/io/multiplayer_replicator.h
+++ b/core/io/multiplayer_replicator.h
@@ -32,6 +32,8 @@
#define MULTIPLAYER_REPLICATOR_H
#include "core/io/multiplayer_api.h"
+
+#include "core/templates/hash_map.h"
#include "core/variant/typed_array.h"
class MultiplayerReplicator : public Object {
@@ -40,6 +42,7 @@ class MultiplayerReplicator : public Object {
public:
enum {
SPAWN_CMD_OFFSET = 9,
+ SYNC_CMD_OFFSET = 9,
};
enum ReplicationMode {
@@ -50,9 +53,15 @@ public:
struct SceneConfig {
ReplicationMode mode;
+ uint64_t sync_interval = 0;
+ uint64_t sync_last = 0;
+ uint8_t sync_recv = 0;
List<StringName> properties;
+ List<StringName> sync_properties;
Callable on_spawn_despawn_send;
Callable on_spawn_despawn_receive;
+ Callable on_sync_send;
+ Callable on_sync_receive;
};
protected:
@@ -63,31 +72,52 @@ private:
Vector<uint8_t> packet_cache;
Map<ResourceUID::ID, SceneConfig> replications;
Map<ObjectID, ResourceUID::ID> replicated_nodes;
+ HashMap<ResourceUID::ID, List<ObjectID>> tracked_objects;
+ // Encoding
+ Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant);
Error _encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr);
Error _decode_state(const List<StringName> &p_cfg, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false);
- Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant);
+
+ // Spawn
Error _spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn);
Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn);
void _process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn);
Error _send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn);
+ // Sync
+ void _process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len);
+ Error _sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer);
+ void _track(const ResourceUID::ID &p_scene_id, Object *p_object);
+ void _untrack(const ResourceUID::ID &p_scene_id, Object *p_object);
+
public:
void clear();
+ // Encoding
+ PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node, bool p_initial);
+ Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data, bool p_initial);
+
+ // Spawn
Error spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable());
Error spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0);
Error despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0);
-
Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath());
Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath());
- PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node);
- Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data);
+
+ // Sync
+ Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable());
+ Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer);
+ Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, MultiplayerPeer::TransferMode p_mode, int p_channel);
+ void track(const ResourceUID::ID &p_scene_id, Object *p_object);
+ void untrack(const ResourceUID::ID &p_scene_id, Object *p_object);
// Used by MultiplayerAPI
void spawn_all(int p_peer);
void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn);
+ void process_sync(int p_from, const uint8_t *p_packet, int p_packet_len);
void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter);
+ void poll();
MultiplayerReplicator(MultiplayerAPI *p_multiplayer) {
multiplayer = p_multiplayer;
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 00d4d093da..84fd6496a7 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -335,7 +335,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
String exttype = get_unicode_string();
String path = get_unicode_string();
- if (path.find("://") == -1 && path.is_rel_path()) {
+ if (path.find("://") == -1 && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(res_path.get_base_dir().plus_file(path));
}
@@ -626,7 +626,7 @@ Error ResourceLoaderBinary::load() {
path = remaps[path];
}
- if (path.find("://") == -1 && path.is_rel_path()) {
+ if (path.find("://") == -1 && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(path.get_base_dir().plus_file(external_resources[i].path));
}
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 64237f3b15..3026236f07 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -278,7 +278,7 @@ static String _validate_local_path(const String &p_path) {
ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(p_path);
if (uid != ResourceUID::INVALID_ID) {
return ResourceUID::get_singleton()->get_id_path(uid);
- } else if (p_path.is_rel_path()) {
+ } else if (p_path.is_relative_path()) {
return "res://" + p_path;
} else {
return ProjectSettings::get_singleton()->localize_path(p_path);
diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp
index 54abc1b7f2..b53dc05a00 100644
--- a/core/math/vector2.cpp
+++ b/core/math/vector2.cpp
@@ -34,6 +34,10 @@ real_t Vector2::angle() const {
return Math::atan2(y, x);
}
+Vector2 Vector2::from_angle(const real_t p_angle) {
+ return Vector2(Math::cos(p_angle), Math::sin(p_angle));
+}
+
real_t Vector2::length() const {
return Math::sqrt(x * x + y * y);
}
diff --git a/core/math/vector2.h b/core/math/vector2.h
index 330b4741b1..332c0475fa 100644
--- a/core/math/vector2.h
+++ b/core/math/vector2.h
@@ -147,6 +147,7 @@ struct Vector2 {
bool operator>=(const Vector2 &p_vec2) const { return x == p_vec2.x ? (y >= p_vec2.y) : (x > p_vec2.x); }
real_t angle() const;
+ static Vector2 from_angle(const real_t p_angle);
_FORCE_INLINE_ Vector2 abs() const {
return Vector2(Math::abs(x), Math::abs(y));
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index a30d6b9102..a3b5356b1d 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -4324,7 +4324,7 @@ bool String::is_resource_file() const {
return begins_with("res://") && find("::") == -1;
}
-bool String::is_rel_path() const {
+bool String::is_relative_path() const {
return !is_absolute_path();
}
diff --git a/core/string/ustring.h b/core/string/ustring.h
index ffb354d6e1..6a4b40da60 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -398,7 +398,7 @@ public:
// path functions
bool is_absolute_path() const;
- bool is_rel_path() const;
+ bool is_relative_path() const;
bool is_resource_file() const;
String path_to(const String &p_path) const;
String path_to_file(const String &p_path) const;
diff --git a/core/templates/safe_list.h b/core/templates/safe_list.h
new file mode 100644
index 0000000000..d8f010663b
--- /dev/null
+++ b/core/templates/safe_list.h
@@ -0,0 +1,375 @@
+/*************************************************************************/
+/* safe_list.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 SAFE_LIST_H
+#define SAFE_LIST_H
+
+#include "core/os/memory.h"
+#include "core/typedefs.h"
+#include <functional>
+
+#if !defined(NO_THREADS)
+
+#include <atomic>
+#include <type_traits>
+
+// Design goals for these classes:
+// - Accessing this list with an iterator will never result in a use-after free,
+// even if the element being accessed has been logically removed from the list on
+// another thread.
+// - Logical deletion from the list will not result in deallocation at that time,
+// instead the node will be deallocated at a later time when it is safe to do so.
+// - No blocking synchronization primitives will be used.
+
+// This is used in very specific areas of the engine where it's critical that these guarantees are held.
+
+template <class T, class A = DefaultAllocator>
+class SafeList {
+ struct SafeListNode {
+ std::atomic<SafeListNode *> next = nullptr;
+
+ // If the node is logically deleted, this pointer will typically point
+ // to the previous list item in time that was also logically deleted.
+ std::atomic<SafeListNode *> graveyard_next = nullptr;
+
+ std::function<void(T)> deletion_fn = [](T t) { return; };
+
+ T val;
+ };
+
+ static_assert(std::atomic<T>::is_always_lock_free);
+
+ std::atomic<SafeListNode *> head = nullptr;
+ std::atomic<SafeListNode *> graveyard_head = nullptr;
+
+ std::atomic_uint active_iterator_count = 0;
+
+public:
+ class Iterator {
+ friend class SafeList;
+
+ SafeListNode *cursor;
+ SafeList *list;
+
+ Iterator(SafeListNode *p_cursor, SafeList *p_list) :
+ cursor(p_cursor), list(p_list) {
+ list->active_iterator_count++;
+ }
+
+ public:
+ Iterator(const Iterator &p_other) :
+ cursor(p_other.cursor), list(p_other.list) {
+ list->active_iterator_count++;
+ }
+
+ ~Iterator() {
+ list->active_iterator_count--;
+ }
+
+ public:
+ T &operator*() {
+ return cursor->val;
+ }
+
+ Iterator &operator++() {
+ cursor = cursor->next;
+ return *this;
+ }
+
+ // These two operators are mostly useful for comparisons to nullptr.
+ bool operator==(const void *p_other) const {
+ return cursor == p_other;
+ }
+
+ bool operator!=(const void *p_other) const {
+ return cursor != p_other;
+ }
+
+ // These two allow easy range-based for loops.
+ bool operator==(const Iterator &p_other) const {
+ return cursor == p_other.cursor;
+ }
+
+ bool operator!=(const Iterator &p_other) const {
+ return cursor != p_other.cursor;
+ }
+ };
+
+public:
+ // Calling this will cause an allocation.
+ void insert(T p_value) {
+ SafeListNode *new_node = memnew_allocator(SafeListNode, A);
+ new_node->val = p_value;
+ SafeListNode *expected_head = nullptr;
+ do {
+ expected_head = head.load();
+ new_node->next.store(expected_head);
+ } while (!head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ new_node));
+ }
+
+ Iterator find(T p_value) {
+ for (Iterator it = begin(); it != end(); ++it) {
+ if (*it == p_value) {
+ return it;
+ }
+ }
+ return end();
+ }
+
+ void erase(T p_value, std::function<void(T)> p_deletion_fn) {
+ Iterator tmp = find(p_value);
+ erase(tmp, p_deletion_fn);
+ }
+
+ void erase(T p_value) {
+ Iterator tmp = find(p_value);
+ erase(tmp, [](T t) { return; });
+ }
+
+ void erase(Iterator &p_iterator, std::function<void(T)> p_deletion_fn) {
+ p_iterator.cursor->deletion_fn = p_deletion_fn;
+ erase(p_iterator);
+ }
+
+ void erase(Iterator &p_iterator) {
+ if (find(p_iterator.cursor->val) == nullptr) {
+ // Not in the list, nothing to do.
+ return;
+ }
+ // First, remove the node from the list.
+ while (true) {
+ Iterator prev = begin();
+ SafeListNode *expected_head = prev.cursor;
+ for (; prev != end(); ++prev) {
+ if (prev.cursor && prev.cursor->next == p_iterator.cursor) {
+ break;
+ }
+ }
+ if (prev != end()) {
+ // There exists a node before this.
+ prev.cursor->next.store(p_iterator.cursor->next.load());
+ // Done.
+ break;
+ } else {
+ if (head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor->next.load())) {
+ // Successfully reassigned the head pointer before another thread changed it to something else.
+ break;
+ }
+ // Fall through upon failure, try again.
+ }
+ }
+ // Then queue it for deletion by putting it in the node graveyard.
+ // Don't touch `next` because an iterator might still be pointing at this node.
+ SafeListNode *expected_head = nullptr;
+ do {
+ expected_head = graveyard_head.load();
+ p_iterator.cursor->graveyard_next.store(expected_head);
+ } while (!graveyard_head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor));
+ }
+
+ Iterator begin() {
+ return Iterator(head.load(), this);
+ }
+
+ Iterator end() {
+ return Iterator(nullptr, this);
+ }
+
+ // Calling this will cause zero to many deallocations.
+ void maybe_cleanup() {
+ SafeListNode *cursor = nullptr;
+ SafeListNode *new_graveyard_head = nullptr;
+ do {
+ // The access order here is theoretically important.
+ cursor = graveyard_head.load();
+ if (active_iterator_count.load() != 0) {
+ // It's not safe to clean up with an active iterator, because that iterator
+ // could be pointing to an element that we want to delete.
+ return;
+ }
+ // Any iterator created after this point will never point to a deleted node.
+ // Swap it out with the current graveyard head.
+ } while (!graveyard_head.compare_exchange_strong(/* expected= */ cursor, /* new= */ new_graveyard_head));
+ // Our graveyard list is now unreachable by any active iterators,
+ // detached from the main graveyard head and ready for deletion.
+ while (cursor) {
+ SafeListNode *tmp = cursor;
+ cursor = cursor->graveyard_next;
+ tmp->deletion_fn(tmp->val);
+ memdelete_allocator<SafeListNode, A>(tmp);
+ }
+ }
+};
+
+#else // NO_THREADS
+
+// Effectively the same structure without the atomics. It's probably possible to simplify it but the semantics shouldn't differ greatly.
+template <class T, class A = DefaultAllocator>
+class SafeList {
+ struct SafeListNode {
+ SafeListNode *next = nullptr;
+
+ // If the node is logically deleted, this pointer will typically point to the previous list item in time that was also logically deleted.
+ SafeListNode *graveyard_next = nullptr;
+
+ std::function<void(T)> deletion_fn = [](T t) { return; };
+
+ T val;
+ };
+
+ SafeListNode *head = nullptr;
+ SafeListNode *graveyard_head = nullptr;
+
+ unsigned int active_iterator_count = 0;
+
+public:
+ class Iterator {
+ friend class SafeList;
+
+ SafeListNode *cursor;
+ SafeList *list;
+
+ public:
+ Iterator(SafeListNode *p_cursor, SafeList *p_list) :
+ cursor(p_cursor), list(p_list) {
+ list->active_iterator_count++;
+ }
+
+ ~Iterator() {
+ list->active_iterator_count--;
+ }
+
+ T &operator*() {
+ return cursor->val;
+ }
+
+ Iterator &operator++() {
+ cursor = cursor->next;
+ return *this;
+ }
+
+ // These two operators are mostly useful for comparisons to nullptr.
+ bool operator==(const void *p_other) const {
+ return cursor == p_other;
+ }
+
+ bool operator!=(const void *p_other) const {
+ return cursor != p_other;
+ }
+
+ // These two allow easy range-based for loops.
+ bool operator==(const Iterator &p_other) const {
+ return cursor == p_other.cursor;
+ }
+
+ bool operator!=(const Iterator &p_other) const {
+ return cursor != p_other.cursor;
+ }
+ };
+
+public:
+ // Calling this will cause an allocation.
+ void insert(T p_value) {
+ SafeListNode *new_node = memnew_allocator(SafeListNode, A);
+ new_node->val = p_value;
+ new_node->next = head;
+ head = new_node;
+ }
+
+ Iterator find(T p_value) {
+ for (Iterator it = begin(); it != end(); ++it) {
+ if (*it == p_value) {
+ return it;
+ }
+ }
+ return end();
+ }
+
+ void erase(T p_value, std::function<void(T)> p_deletion_fn) {
+ erase(find(p_value), p_deletion_fn);
+ }
+
+ void erase(T p_value) {
+ erase(find(p_value), [](T t) { return; });
+ }
+
+ void erase(Iterator p_iterator, std::function<void(T)> p_deletion_fn) {
+ p_iterator.cursor->deletion_fn = p_deletion_fn;
+ erase(p_iterator);
+ }
+
+ void erase(Iterator p_iterator) {
+ Iterator prev = begin();
+ for (; prev != end(); ++prev) {
+ if (prev.cursor && prev.cursor->next == p_iterator.cursor) {
+ break;
+ }
+ }
+ if (prev == end()) {
+ // Not in the list, nothing to do.
+ return;
+ }
+ // First, remove the node from the list.
+ prev.cursor->next = p_iterator.cursor->next;
+
+ // Then queue it for deletion by putting it in the node graveyard. Don't touch `next` because an iterator might still be pointing at this node.
+ p_iterator.cursor->graveyard_next = graveyard_head;
+ graveyard_head = p_iterator.cursor;
+ }
+
+ Iterator begin() {
+ return Iterator(head, this);
+ }
+
+ Iterator end() {
+ return Iterator(nullptr, this);
+ }
+
+ // Calling this will cause zero to many deallocations.
+ void maybe_cleanup() {
+ SafeListNode *cursor = graveyard_head;
+ if (active_iterator_count != 0) {
+ // It's not safe to clean up with an active iterator, because that iterator could be pointing to an element that we want to delete.
+ return;
+ }
+ graveyard_head = nullptr;
+ // Our graveyard list is now unreachable by any active iterators, detached from the main graveyard head and ready for deletion.
+ while (cursor) {
+ SafeListNode *tmp = cursor;
+ cursor = cursor->next;
+ tmp->deletion_fn(tmp->val);
+ memdelete_allocator<SafeListNode, A>(tmp);
+ }
+ }
+};
+
+#endif
+
+#endif // SAFE_LIST_H
diff --git a/core/templates/vector.h b/core/templates/vector.h
index 08cbef6ba4..033345d04c 100644
--- a/core/templates/vector.h
+++ b/core/templates/vector.h
@@ -229,7 +229,7 @@ public:
_FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return elem_ptr == b.elem_ptr; }
_FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return elem_ptr != b.elem_ptr; }
- ConstIterator(T *p_ptr) { elem_ptr = p_ptr; }
+ ConstIterator(const T *p_ptr) { elem_ptr = p_ptr; }
ConstIterator() {}
ConstIterator(const ConstIterator &p_it) { elem_ptr = p_it.elem_ptr; }
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 78ad796283..8373cbd4e8 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -203,9 +203,9 @@ Error Array::resize(int p_new_size) {
return _p->array.resize(p_new_size);
}
-void Array::insert(int p_pos, const Variant &p_value) {
- ERR_FAIL_COND(!_p->typed.validate(p_value, "insert"));
- _p->array.insert(p_pos, p_value);
+Error Array::insert(int p_pos, const Variant &p_value) {
+ ERR_FAIL_COND_V(!_p->typed.validate(p_value, "insert"), ERR_INVALID_PARAMETER);
+ return _p->array.insert(p_pos, p_value);
}
void Array::fill(const Variant &p_value) {
diff --git a/core/variant/array.h b/core/variant/array.h
index e9634ccece..4a1b25c4a9 100644
--- a/core/variant/array.h
+++ b/core/variant/array.h
@@ -72,7 +72,7 @@ public:
void append_array(const Array &p_array);
Error resize(int p_new_size);
- void insert(int p_pos, const Variant &p_value);
+ Error insert(int p_pos, const Variant &p_value);
void remove(int p_pos);
void fill(const Variant &p_value);
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index d538b9faff..c5cada674f 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -1627,9 +1627,9 @@ Variant::operator String() const {
String Variant::stringify(List<const void *> &stack) const {
switch (type) {
case NIL:
- return "Null";
+ return "null";
case BOOL:
- return _data._bool ? "True" : "False";
+ return _data._bool ? "true" : "false";
case INT:
return itos(_data._int);
case FLOAT:
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 382b256c08..39207df9e7 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1421,7 +1421,7 @@ static void _register_variant_builtin_methods() {
//bind_method(String, humanize_size, sarray("size"), varray());
bind_method(String, is_absolute_path, sarray(), varray());
- bind_method(String, is_rel_path, sarray(), varray());
+ bind_method(String, is_relative_path, sarray(), varray());
bind_method(String, simplify_path, sarray(), varray());
bind_method(String, get_base_dir, sarray(), varray());
bind_method(String, get_file, sarray(), varray());
@@ -1502,6 +1502,8 @@ static void _register_variant_builtin_methods() {
bind_method(Vector2, clamp, sarray("min", "max"), varray());
bind_method(Vector2, snapped, sarray("step"), varray());
+ bind_static_method(Vector2, from_angle, sarray("angle"), varray());
+
/* Vector2i */
bind_method(Vector2i, aspect, sarray(), varray());
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index 16c7428781..a245aff35b 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -171,7 +171,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorDiv<Vector2, Vector2, double>>(Variant::OP_DIVIDE, Variant::VECTOR2, Variant::FLOAT);
register_op<OperatorEvaluatorDiv<Vector2, Vector2, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR2, Variant::INT);
- register_op<OperatorEvaluatorDiv<Vector2i, Vector2i, Vector2i>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::VECTOR2I);
+ register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, Vector2i>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::VECTOR2I);
register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, double>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::FLOAT);
register_op<OperatorEvaluatorDivNZ<Vector2i, Vector2i, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR2I, Variant::INT);
@@ -183,7 +183,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorDiv<Vector3, Vector3, double>>(Variant::OP_DIVIDE, Variant::VECTOR3, Variant::FLOAT);
register_op<OperatorEvaluatorDiv<Vector3, Vector3, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR3, Variant::INT);
- register_op<OperatorEvaluatorDiv<Vector3i, Vector3i, Vector3i>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::VECTOR3I);
+ register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, Vector3i>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::VECTOR3I);
register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, double>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::FLOAT);
register_op<OperatorEvaluatorDivNZ<Vector3i, Vector3i, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR3I, Variant::INT);
@@ -195,10 +195,10 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorDiv<Color, Color, int64_t>>(Variant::OP_DIVIDE, Variant::COLOR, Variant::INT);
register_op<OperatorEvaluatorModNZ<int64_t, int64_t, int64_t>>(Variant::OP_MODULE, Variant::INT, Variant::INT);
- register_op<OperatorEvaluatorMod<Vector2i, Vector2i, Vector2i>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::VECTOR2I);
+ register_op<OperatorEvaluatorModNZ<Vector2i, Vector2i, Vector2i>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::VECTOR2I);
register_op<OperatorEvaluatorModNZ<Vector2i, Vector2i, int64_t>>(Variant::OP_MODULE, Variant::VECTOR2I, Variant::INT);
- register_op<OperatorEvaluatorMod<Vector3i, Vector3i, Vector3i>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::VECTOR3I);
+ register_op<OperatorEvaluatorModNZ<Vector3i, Vector3i, Vector3i>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::VECTOR3I);
register_op<OperatorEvaluatorModNZ<Vector3i, Vector3i, int64_t>>(Variant::OP_MODULE, Variant::VECTOR3I, Variant::INT);
register_op<OperatorEvaluatorStringModNil>(Variant::OP_MODULE, Variant::STRING, Variant::NIL);
diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h
index cbdd60f404..3c9f849a4f 100644
--- a/core/variant/variant_op.h
+++ b/core/variant/variant_op.h
@@ -168,6 +168,54 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
+template <>
+class OperatorEvaluatorDivNZ<Vector2i, Vector2i, Vector2i> {
+public:
+ static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
+ const Vector2i &a = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_left);
+ const Vector2i &b = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_right);
+ if (unlikely(b.x == 0 || b.y == 0)) {
+ r_valid = false;
+ *r_ret = "Division by zero error";
+ return;
+ }
+ *r_ret = a / b;
+ r_valid = true;
+ }
+ static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
+ VariantTypeChanger<Vector2i>::change(r_ret);
+ *VariantGetInternalPtr<Vector2i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector2i>::get_ptr(left) / *VariantGetInternalPtr<Vector2i>::get_ptr(right);
+ }
+ static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
+ PtrToArg<Vector2i>::encode(PtrToArg<Vector2i>::convert(left) / PtrToArg<Vector2i>::convert(right), r_ret);
+ }
+ static Variant::Type get_return_type() { return GetTypeInfo<Vector2i>::VARIANT_TYPE; }
+};
+
+template <>
+class OperatorEvaluatorDivNZ<Vector3i, Vector3i, Vector3i> {
+public:
+ static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
+ const Vector3i &a = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_left);
+ const Vector3i &b = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_right);
+ if (unlikely(b.x == 0 || b.y == 0 || b.z == 0)) {
+ r_valid = false;
+ *r_ret = "Division by zero error";
+ return;
+ }
+ *r_ret = a / b;
+ r_valid = true;
+ }
+ static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
+ VariantTypeChanger<Vector3i>::change(r_ret);
+ *VariantGetInternalPtr<Vector3i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector3i>::get_ptr(left) / *VariantGetInternalPtr<Vector3i>::get_ptr(right);
+ }
+ static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
+ PtrToArg<Vector3i>::encode(PtrToArg<Vector3i>::convert(left) / PtrToArg<Vector3i>::convert(right), r_ret);
+ }
+ static Variant::Type get_return_type() { return GetTypeInfo<Vector3i>::VARIANT_TYPE; }
+};
+
template <class R, class A, class B>
class OperatorEvaluatorMod {
public:
@@ -209,6 +257,54 @@ public:
static Variant::Type get_return_type() { return GetTypeInfo<R>::VARIANT_TYPE; }
};
+template <>
+class OperatorEvaluatorModNZ<Vector2i, Vector2i, Vector2i> {
+public:
+ static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
+ const Vector2i &a = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_left);
+ const Vector2i &b = *VariantGetInternalPtr<Vector2i>::get_ptr(&p_right);
+ if (unlikely(b.x == 0 || b.y == 0)) {
+ r_valid = false;
+ *r_ret = "Module by zero error";
+ return;
+ }
+ *r_ret = a % b;
+ r_valid = true;
+ }
+ static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
+ VariantTypeChanger<Vector2i>::change(r_ret);
+ *VariantGetInternalPtr<Vector2i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector2i>::get_ptr(left) % *VariantGetInternalPtr<Vector2i>::get_ptr(right);
+ }
+ static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
+ PtrToArg<Vector2i>::encode(PtrToArg<Vector2i>::convert(left) / PtrToArg<Vector2i>::convert(right), r_ret);
+ }
+ static Variant::Type get_return_type() { return GetTypeInfo<Vector2i>::VARIANT_TYPE; }
+};
+
+template <>
+class OperatorEvaluatorModNZ<Vector3i, Vector3i, Vector3i> {
+public:
+ static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
+ const Vector3i &a = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_left);
+ const Vector3i &b = *VariantGetInternalPtr<Vector3i>::get_ptr(&p_right);
+ if (unlikely(b.x == 0 || b.y == 0 || b.z == 0)) {
+ r_valid = false;
+ *r_ret = "Module by zero error";
+ return;
+ }
+ *r_ret = a % b;
+ r_valid = true;
+ }
+ static void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
+ VariantTypeChanger<Vector3i>::change(r_ret);
+ *VariantGetInternalPtr<Vector3i>::get_ptr(r_ret) = *VariantGetInternalPtr<Vector3i>::get_ptr(left) % *VariantGetInternalPtr<Vector3i>::get_ptr(right);
+ }
+ static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
+ PtrToArg<Vector3i>::encode(PtrToArg<Vector3i>::convert(left) % PtrToArg<Vector3i>::convert(right), r_ret);
+ }
+ static Variant::Type get_return_type() { return GetTypeInfo<Vector3i>::VARIANT_TYPE; }
+};
+
template <class R, class A>
class OperatorEvaluatorNeg {
public:
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index f5cb2d40d6..232054d0ca 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -267,14 +267,6 @@ struct VariantUtilityFunctions {
return Math::db2linear(db);
}
- static inline Vector2 polar2cartesian(double r, double th) {
- return Vector2(r * Math::cos(th), r * Math::sin(th));
- }
-
- static inline Vector2 cartesian2polar(double x, double y) {
- return Vector2(Math::sqrt(x * x + y * y), Math::atan2(y, x));
- }
-
static inline int64_t wrapi(int64_t value, int64_t min, int64_t max) {
return Math::wrapi(value, min, max);
}
@@ -1214,9 +1206,6 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDR(linear2db, sarray("lin"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(db2linear, sarray("db"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(polar2cartesian, sarray("r", "th"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(cartesian2polar, sarray("x", "y"), Variant::UTILITY_FUNC_TYPE_MATH);
-
FUNCBINDR(wrapi, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(wrapf, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 585b94a522..616d88c7f3 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -99,14 +99,6 @@
[b]Warning:[/b] Deserialized object can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats (remote code execution).
</description>
</method>
- <method name="cartesian2polar">
- <return type="Vector2" />
- <argument index="0" name="x" type="float" />
- <argument index="1" name="y" type="float" />
- <description>
- Converts a 2D point expressed in the cartesian coordinate system (X and Y axis) to the polar coordinate system (a distance from the origin and an angle).
- </description>
- </method>
<method name="ceil">
<return type="float" />
<argument index="0" name="x" type="float" />
@@ -521,14 +513,6 @@
[b]Warning:[/b] Due to the way it is implemented, this function returns [code]0[/code] rather than [code]1[/code] for non-positive values of [code]value[/code] (in reality, 1 is the smallest integer power of 2).
</description>
</method>
- <method name="polar2cartesian">
- <return type="Vector2" />
- <argument index="0" name="r" type="float" />
- <argument index="1" name="th" type="float" />
- <description>
- Converts a 2D point expressed in the polar coordinate system (a distance from the origin [code]r[/code] and an angle [code]th[/code]) to the cartesian coordinate system (X and Y axis).
- </description>
- </method>
<method name="posmod">
<return type="int" />
<argument index="0" name="x" type="int" />
@@ -2474,6 +2458,7 @@
<constant name="METHOD_FLAG_STATIC" value="256" enum="MethodFlags">
</constant>
<constant name="METHOD_FLAG_OBJECT_CORE" value="512" enum="MethodFlags">
+ Used internally. Allows to not dump core virtuals such as [code]_notification[/code] to the JSON API.
</constant>
<constant name="METHOD_FLAGS_DEFAULT" value="1" enum="MethodFlags">
Default method flags.
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index df0e3a0459..396490c8eb 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -302,7 +302,7 @@
</description>
</method>
<method name="insert">
- <return type="void" />
+ <return type="int" />
<argument index="0" name="position" type="int" />
<argument index="1" name="value" type="Variant" />
<description>
diff --git a/doc/classes/AudioStreamPlayback.xml b/doc/classes/AudioStreamPlayback.xml
index 09d063ed3b..25f3e076b4 100644
--- a/doc/classes/AudioStreamPlayback.xml
+++ b/doc/classes/AudioStreamPlayback.xml
@@ -26,7 +26,7 @@
</description>
</method>
<method name="_mix" qualifiers="virtual">
- <return type="void" />
+ <return type="int" />
<argument index="0" name="buffer" type="AudioFrame*" />
<argument index="1" name="rate_scale" type="float" />
<argument index="2" name="frames" type="int" />
diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml
index e5f60541b9..71e6eeab5a 100644
--- a/doc/classes/CharacterBody2D.xml
+++ b/doc/classes/CharacterBody2D.xml
@@ -152,8 +152,11 @@
<member name="motion_mode" type="int" setter="set_motion_mode" getter="get_motion_mode" enum="CharacterBody2D.MotionMode" default="0">
Sets the motion mode which defines the behaviour of [method move_and_slide]. See [enum MotionMode] constants for available modes.
</member>
- <member name="moving_platform_ignore_layers" type="int" setter="set_moving_platform_ignore_layers" getter="get_moving_platform_ignore_layers" default="0">
- Collision layers that will be excluded for detecting bodies that will act as moving platforms to be followed by the [CharacterBody2D]. By default, all touching bodies are detected and propagate their velocity. You can add excluded layers to ignore bodies that are contained in these layers.
+ <member name="moving_platform_floor_layers" type="int" setter="set_moving_platform_floor_layers" getter="get_moving_platform_floor_layers" default="4294967295">
+ Collision layers that will be included for detecting floor bodies that will act as moving platforms to be followed by the [CharacterBody2D]. By default, all floor bodies are detected and propagate their velocity.
+ </member>
+ <member name="moving_platform_wall_layers" type="int" setter="set_moving_platform_wall_layers" getter="get_moving_platform_wall_layers" default="0">
+ Collision layers that will be included for detecting wall bodies that will act as moving platforms to be followed by the [CharacterBody2D]. By default, all wall bodies are ignored.
</member>
<member name="slide_on_ceiling" type="bool" setter="set_slide_on_ceiling_enabled" getter="is_slide_on_ceiling_enabled" default="true">
If [code]true[/code], during a jump against the ceiling, the body will slide, if [code]false[/code] it will be stopped and will fall vertically.
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index e564e8045c..b8436be76a 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -56,11 +56,11 @@
Called by the engine when the 3D editor's viewport is updated. Use the [code]overlay[/code] [Control] for drawing. You can update the viewport manually by calling [method update_overlays].
[codeblocks]
[gdscript]
- func _forward_spatial_3d_over_viewport(overlay):
+ func _forward_3d_draw_over_viewport(overlay):
# Draw a circle at cursor position.
overlay.draw_circle(overlay.get_local_mouse_position(), 64)
- func _forward_spatial_gui_input(camera, event):
+ func _forward_3d_gui_input(camera, event):
if event is InputEventMouseMotion:
# Redraw viewport when cursor is moved.
update_overlays()
@@ -68,13 +68,13 @@
return false
[/gdscript]
[csharp]
- public override void ForwardSpatialDrawOverViewport(Godot.Control overlay)
+ public override void _Forward3dDrawOverViewport(Godot.Control overlay)
{
// Draw a circle at cursor position.
overlay.DrawCircle(overlay.GetLocalMousePosition(), 64, Colors.White);
}
- public override bool ForwardSpatialGuiInput(Godot.Camera3D camera, InputEvent @event)
+ public override bool _Forward3dGuiInput(Godot.Camera3D camera, InputEvent @event)
{
if (@event is InputEventMouseMotion)
{
@@ -104,12 +104,12 @@
[codeblocks]
[gdscript]
# Prevents the InputEvent to reach other Editor classes.
- func _forward_spatial_gui_input(camera, event):
+ func _forward_3d_gui_input(camera, event):
return true
[/gdscript]
[csharp]
// Prevents the InputEvent to reach other Editor classes.
- public override bool ForwardSpatialGuiInput(Camera3D camera, InputEvent @event)
+ public override bool _Forward3dGuiInput(Camera3D camera, InputEvent @event)
{
return true;
}
@@ -119,12 +119,12 @@
[codeblocks]
[gdscript]
# Consumes InputEventMouseMotion and forwards other InputEvent types.
- func _forward_spatial_gui_input(camera, event):
+ func _forward_3d_gui_input(camera, event):
return event is InputEventMouseMotion
[/gdscript]
[csharp]
// Consumes InputEventMouseMotion and forwards other InputEvent types.
- public override bool ForwardSpatialGuiInput(Camera3D camera, InputEvent @event)
+ public override bool _Forward3dGuiInput(Camera3D camera, InputEvent @event)
{
return @event is InputEventMouseMotion;
}
diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml
index 610b00efe9..70046fc3e9 100644
--- a/doc/classes/MultiplayerAPI.xml
+++ b/doc/classes/MultiplayerAPI.xml
@@ -73,7 +73,7 @@
[b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats such as remote code execution.
</member>
<member name="network_peer" type="MultiplayerPeer" setter="set_network_peer" getter="get_network_peer">
- The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_network_server]) and will set root node's network mode to master, or it will become a regular peer with root node set to puppet. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals.
+ The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the MultiplayerAPI will become a network server (check with [method is_network_server]) and will set root node's network mode to authority, or it will become a regular client peer. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to MultiplayerAPI's signals.
</member>
<member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections" default="false">
If [code]true[/code], the MultiplayerAPI's [member network_peer] refuses new incoming connections.
@@ -125,14 +125,11 @@
<constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode">
Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods.
</constant>
- <constant name="RPC_MODE_REMOTE" value="1" enum="RPCMode">
- Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on the remote end, not locally. Analogous to the [code]remote[/code] keyword. Calls and property changes are accepted from all remote peers, no matter if they are node's master or puppets.
+ <constant name="RPC_MODE_ANY" value="1" enum="RPCMode">
+ Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not.
</constant>
- <constant name="RPC_MODE_MASTER" value="2" enum="RPCMode">
- Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on the network master for this node. Analogous to the [code]master[/code] keyword. Only accepts calls or property changes from the node's network puppets, see [method Node.set_network_master].
- </constant>
- <constant name="RPC_MODE_PUPPET" value="3" enum="RPCMode">
- Used with [method Node.rpc_config] to set a method to be called or a property to be changed only on puppets for this node. Analogous to the [code]puppet[/code] keyword. Only accepts calls or property changes from the node's network master, see [method Node.set_network_master].
+ <constant name="RPC_MODE_AUTHORITY" value="2" enum="RPCMode">
+ Used with [method Node.rpc_config] to set a method to be callable remotely only by the current network authority (which is the server by default). Analogous to the [code]@rpc(auth)[/code] annotation. See [method Node.set_network_authority].
</constant>
</constants>
</class>
diff --git a/doc/classes/MultiplayerReplicator.xml b/doc/classes/MultiplayerReplicator.xml
index 15029e181f..0778a7335f 100644
--- a/doc/classes/MultiplayerReplicator.xml
+++ b/doc/classes/MultiplayerReplicator.xml
@@ -12,6 +12,7 @@
<argument index="0" name="scene_id" type="int" />
<argument index="1" name="object" type="Object" />
<argument index="2" name="data" type="PackedByteArray" />
+ <argument index="3" name="initial" type="bool" default="true" />
<description>
Decode the given [code]data[/code] representing a spawnable state into [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when a client receives a server spawn for a scene with [constant REPLICATION_MODE_SERVER]. See [method spawn_config].
Tip: You may find this function useful in servers when parsing spawn requests from clients, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM].
@@ -23,12 +24,14 @@
<argument index="1" name="object" type="Object" />
<argument index="2" name="peer_id" type="int" default="0" />
<description>
+ Request a despawn for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code]. This will either trigger the default behaviour, or invoke the custom spawn/despawn callables specified in [method spawn_config]. See [method send_despawn] for the default behavior.
</description>
</method>
<method name="encode_state">
<return type="PackedByteArray" />
<argument index="0" name="scene_id" type="int" />
<argument index="1" name="object" type="Object" />
+ <argument index="2" name="initial" type="bool" default="true" />
<description>
Encode the given [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when the server spawns scenes with [constant REPLICATION_MODE_SERVER]. See [method spawn_config].
Tip: You may find this function useful when requesting spawns from clients to server, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM].
@@ -54,12 +57,24 @@
Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_network_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead.
</description>
</method>
+ <method name="send_sync">
+ <return type="int" enum="Error" />
+ <argument index="0" name="peer_id" type="int" />
+ <argument index="1" name="scene_id" type="int" />
+ <argument index="2" name="data" type="PackedByteArray" />
+ <argument index="3" name="transfer_mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" />
+ <argument index="4" name="channel" type="int" default="0" />
+ <description>
+ Sends a sync request for the instances of the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). This function can only be called manually when overriding the send and receive sync functions (see [method sync_config]).
+ </description>
+ </method>
<method name="spawn">
<return type="int" enum="Error" />
<argument index="0" name="scene_id" type="int" />
<argument index="1" name="object" type="Object" />
<argument index="2" name="peer_id" type="int" default="0" />
<description>
+ Request a spawn for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code]. This will either trigger the default behaviour, or invoke the custom spawn/despawn callables specified in [method spawn_config]. See [method send_spawn] for the default behavior.
</description>
</method>
<method name="spawn_config">
@@ -70,10 +85,47 @@
<argument index="3" name="custom_send" type="Callable" />
<argument index="4" name="custom_receive" type="Callable" />
<description>
- Configures the MultiplayerAPI to track instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. When [code]mode[/code] is [constant REPLICATION_MODE_SERVER], the specified [code]properties[/code] will also be replicated to clients during the initial spawn.
+ Configures the MultiplayerReplicator to track instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. When [code]mode[/code] is [constant REPLICATION_MODE_SERVER], the specified [code]properties[/code] will also be replicated to clients during the initial spawn. You can optionally specify a [code]custom_send[/code] and a [code]custom_receive[/code] to override the default behaviour and customize the spawn/despawn proecess.
Tip: You can use a custom property in the scene main script to return a customly optimized state representation.
</description>
</method>
+ <method name="sync_all">
+ <return type="int" enum="Error" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="peer_id" type="int" default="0" />
+ <description>
+ Manually request a sync for all the instances of the scene identified by [code]scene_id[/code]. This function will trigger the default sync behaviour, or call your send custom send callable if specified in [method sync_config].
+ Note: The default implementation only allow syncing from server to clients.
+ </description>
+ </method>
+ <method name="sync_config">
+ <return type="int" enum="Error" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="interval" type="int" />
+ <argument index="2" name="properties" type="StringName[]" default="[]" />
+ <argument index="3" name="custom_send" type="Callable" />
+ <argument index="4" name="custom_receive" type="Callable" />
+ <description>
+ Configures the MultiplayerReplicator to sync instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication at the desired [code]interval[/code] (in milliseconds). The specified [code]properties[/code] will be part of the state sync. You can optionally specify a [code]custom_send[/code] and a [code]custom_receive[/code] to override the default behaviour and customize the syncronization proecess.
+ Tip: You can use a custom property in the scene main script to return a customly optimized state representation (having a single property that returns a PackedByteArray is higly recommended when dealing with many instances).
+ </description>
+ </method>
+ <method name="track">
+ <return type="void" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="object" type="Object" />
+ <description>
+ Track the given [code]object[/code] as an instance of the scene identified by [code]scene_id[/code]. This object will be passed to your custom sync callables (see [method sync_config]). Tracking and untracking is automatic in [constant REPLICATION_MODE_SERVER].
+ </description>
+ </method>
+ <method name="untrack">
+ <return type="void" />
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="object" type="Object" />
+ <description>
+ Untrack the given [code]object[/code]. This object will no longer be passed to your custom sync callables (see [method sync_config]). Tracking and untracking is automatic in [constant REPLICATION_MODE_SERVER].
+ </description>
+ </method>
</methods>
<signals>
<signal name="despawn_requested">
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index f5bc705ecb..7d79c50466 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -109,9 +109,11 @@
<return type="void" />
<argument index="0" name="node" type="Node" />
<argument index="1" name="legible_unique_name" type="bool" default="false" />
+ <argument index="2" name="internal" type="int" enum="Node.InternalMode" default="0" />
<description>
Adds a child node. Nodes can have any number of children, but every child must have a unique name. Child nodes are automatically deleted when the parent node is deleted, so an entire scene can be removed by deleting its topmost node.
If [code]legible_unique_name[/code] is [code]true[/code], the child node will have a human-readable name based on the name of the node being instantiated instead of its type.
+ If [code]internal[/code] is different than [constant INTERNAL_MODE_DISABLED], the child will be added as internal node. Such nodes are ignored by methods like [method get_children], unless their parameter [code]include_internal[/code] is [code]true[/code].The intended usage is to hide the internal nodes from the user, so the user won't accidentally delete or modify them. Used by some GUI nodes, e.g. [ColorPicker]. See [enum InternalMode] for available modes.
[b]Note:[/b] If the child node already has a parent, the function will fail. Use [method remove_child] first to remove the node from its current parent. For example:
[codeblocks]
[gdscript]
@@ -141,6 +143,7 @@
Adds a [code]sibling[/code] node to current's node parent, at the same level as that node, right below it.
If [code]legible_unique_name[/code] is [code]true[/code], the child node will have a human-readable name based on the name of the node being instantiated instead of its type.
Use [method add_child] instead of this method if you don't need the child node to be added below a specific node in the list of children.
+ [b]Note:[/b] If this node is internal, the new sibling will be internal too (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="add_to_group">
@@ -200,22 +203,28 @@
<method name="get_child" qualifiers="const">
<return type="Node" />
<argument index="0" name="idx" type="int" />
+ <argument index="1" name="include_internal" type="bool" default="false" />
<description>
Returns a child node by its index (see [method get_child_count]). This method is often used for iterating all children of a node.
Negative indices access the children from the last one.
+ If [code]include_internal[/code] is [code]true[/code], internal children are skipped (see [code]internal[/code] parameter in [method add_child]).
To access a child node via its name, use [method get_node].
</description>
</method>
<method name="get_child_count" qualifiers="const">
<return type="int" />
+ <argument index="0" name="include_internal" type="bool" default="false" />
<description>
Returns the number of child nodes.
+ If [code]include_internal[/code] is [code]false[/code], internal children aren't counted (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="get_children" qualifiers="const">
<return type="Node[]" />
+ <argument index="0" name="include_internal" type="bool" default="false" />
<description>
Returns an array of references to node's children.
+ If [code]include_internal[/code] is [code]false[/code], the returned array won't include internal children (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="get_editor_description" qualifiers="const">
@@ -231,14 +240,16 @@
</method>
<method name="get_index" qualifiers="const">
<return type="int" />
+ <argument index="0" name="include_internal" type="bool" default="false" />
<description>
Returns the node's order in the scene tree branch. For example, if called on the first child node the position is [code]0[/code].
+ If [code]include_internal[/code] is [code]false[/code], the index won't take internal children into account, i.e. first non-internal child will have index of 0 (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
- <method name="get_network_master" qualifiers="const">
+ <method name="get_network_authority" qualifiers="const">
<return type="int" />
<description>
- Returns the peer ID of the network master for this node. See [method set_network_master].
+ Returns the peer ID of the network authority for this node. See [method set_network_authority].
</description>
</method>
<method name="get_node" qualifiers="const">
@@ -406,10 +417,10 @@
Returns [code]true[/code] if this node is currently inside a [SceneTree].
</description>
</method>
- <method name="is_network_master" qualifiers="const">
+ <method name="is_network_authority" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if the local system is the master of this node.
+ Returns [code]true[/code] if the local system is the authority of this node.
</description>
</method>
<method name="is_physics_processing" qualifiers="const">
@@ -460,6 +471,7 @@
<argument index="1" name="to_position" type="int" />
<description>
Moves a child node to a different position (order) among the other children. Since calls, signals, etc are performed by tree order, changing the order of children nodes may be useful.
+ [b]Note:[/b] Internal children can only be moved within their expected "internal range" (see [code]internal[/code] parameter in [method add_child]).
</description>
</method>
<method name="print_stray_nodes">
@@ -576,7 +588,7 @@
<argument index="2" name="transfer_mode" type="int" enum="MultiplayerPeer.TransferMode" default="2" />
<argument index="3" name="channel" type="int" default="0" />
<description>
- Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding keywords ([code]remote[/code], [code]master[/code], [code]puppet[/code], [code]remotesync[/code], [code]mastersync[/code], [code]puppetsync[/code]). By default, methods are not exposed to networking (and RPCs).
+ Changes the RPC mode for the given [code]method[/code] to the given [code]rpc_mode[/code], optionally specifying the [code]transfer_mode[/code] and [code]channel[/code] (on supported peers). See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(auth)[/code]). By default, methods are not exposed to networking (and RPCs).
</description>
</method>
<method name="rpc_id" qualifiers="vararg">
@@ -608,12 +620,12 @@
<description>
</description>
</method>
- <method name="set_network_master">
+ <method name="set_network_authority">
<return type="void" />
<argument index="0" name="id" type="int" />
<argument index="1" name="recursive" type="bool" default="true" />
<description>
- Sets the node's network master to the peer with the given peer ID. The network master is the peer that has authority over the node on the network. Useful in conjunction with the [code]master[/code] and [code]puppet[/code] keywords. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the master for all children of this node.
+ Sets the node's network authority to the peer with the given peer ID. The network authority is the peer that has authority over the node on the network. Useful in conjunction with [method rpc_config] and the [MultiplayerAPI]. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the authority for all children of this node.
</description>
</method>
<method name="set_physics_process">
@@ -888,5 +900,14 @@
Duplicate using instancing.
An instance stays linked to the original so when the original changes, the instance changes too.
</constant>
+ <constant name="INTERNAL_MODE_DISABLED" value="0" enum="InternalMode">
+ Node will not be internal.
+ </constant>
+ <constant name="INTERNAL_MODE_FRONT" value="1" enum="InternalMode">
+ Node will be placed at the front of parent's node list, before any non-internal sibling.
+ </constant>
+ <constant name="INTERNAL_MODE_BACK" value="2" enum="InternalMode">
+ Node will be placed at the back of parent's node list, after any non-internal sibling.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index 9ad79dc17a..c9e9a0699c 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -13,15 +13,15 @@
[codeblocks]
[gdscript]
var n = Node2D.new()
- print("position" in n) # Prints "True".
- print("other_property" in n) # Prints "False".
+ print("position" in n) # Prints "true".
+ print("other_property" in n) # Prints "false".
[/gdscript]
[csharp]
var node = new Node2D();
// C# has no direct equivalent to GDScript's `in` operator here, but we
// can achieve the same behavior by performing `Get` with a null check.
- GD.Print(node.Get("position") != null); // Prints "True".
- GD.Print(node.Get("other_property") != null); // Prints "False".
+ GD.Print(node.Get("position") != null); // Prints "true".
+ GD.Print(node.Get("other_property") != null); // Prints "false".
[/csharp]
[/codeblocks]
The [code]in[/code] operator will evaluate to [code]true[/code] as long as the key exists, even if the value is [code]null[/code].
diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml
index d0ae665139..9867b98ae6 100644
--- a/doc/classes/PhysicsServer2D.xml
+++ b/doc/classes/PhysicsServer2D.xml
@@ -489,6 +489,9 @@
<argument index="2" name="userdata" type="Variant" default="null" />
<description>
Sets the function used to calculate physics for an object, if that object allows it (see [method body_set_omit_force_integration]).
+ The force integration function takes 2 arguments:
+ [code]state:[/code] [PhysicsDirectBodyState2D] used to retrieve and modify the body's state.
+ [code]userdata:[/code] Optional user data, if it was passed when calling [code]body_set_force_integration_callback[/code].
</description>
</method>
<method name="body_set_max_contacts_reported">
diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml
index c47a311161..46cbe48b28 100644
--- a/doc/classes/PhysicsServer3D.xml
+++ b/doc/classes/PhysicsServer3D.xml
@@ -478,6 +478,9 @@
<argument index="2" name="userdata" type="Variant" default="null" />
<description>
Sets the function used to calculate physics for an object, if that object allows it (see [method body_set_omit_force_integration]).
+ The force integration function takes 2 arguments:
+ [code]state:[/code] [PhysicsDirectBodyState3D] used to retrieve and modify the body's state.
+ [code]userdata:[/code] Optional user data, if it was passed when calling [code]body_set_force_integration_callback[/code].
</description>
</method>
<method name="body_set_max_contacts_reported">
diff --git a/doc/classes/SoftBody3D.xml b/doc/classes/SoftBody3D.xml
index ddfc14ceac..d5f0e3c95c 100644
--- a/doc/classes/SoftBody3D.xml
+++ b/doc/classes/SoftBody3D.xml
@@ -42,6 +42,20 @@
<description>
</description>
</method>
+ <method name="get_point_transform">
+ <return type="Vector3" />
+ <argument index="0" name="point_index" type="int" />
+ <description>
+ Returns local translation of a vertex in the surface array.
+ </description>
+ </method>
+ <method name="is_point_pinned" qualifiers="const">
+ <return type="bool" />
+ <argument index="0" name="point_index" type="int" />
+ <description>
+ Returns [code]true[/code] if vertex is set to pinned.
+ </description>
+ </method>
<method name="remove_collision_exception_with">
<return type="void" />
<argument index="0" name="body" type="Node" />
@@ -65,6 +79,15 @@
Based on [code]value[/code], enables or disables the specified layer in the [member collision_mask], given a [code]layer_number[/code] between 1 and 32.
</description>
</method>
+ <method name="set_point_pinned">
+ <return type="void" />
+ <argument index="0" name="point_index" type="int" />
+ <argument index="1" name="pinned" type="bool" />
+ <argument index="2" name="attachment_path" type="NodePath" default="NodePath(&quot;&quot;)" />
+ <description>
+ Sets the pinned state of a surface vertex. When set to [code]true[/code], the optional [code]attachment_path[/code] can define a [Node3D] the pinned vertex will be attached to.
+ </description>
+ </method>
</methods>
<members>
<member name="collision_layer" type="int" setter="set_collision_layer" getter="get_collision_layer" default="1">
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index de9eb518c6..eb6c52d662 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -248,7 +248,7 @@
Returns [code]true[/code] if the length of the string equals [code]0[/code].
</description>
</method>
- <method name="is_rel_path" qualifiers="const">
+ <method name="is_relative_path" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the string is a path to a file or directory and its starting point is implicitly defined within the context it is being used. The starting point may refer to the current directory ([code]./[/code]), or the current [Node].
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index b32168ebc5..d7af2204cf 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -551,7 +551,7 @@
<argument index="0" name="font_rid" type="RID" />
<argument index="1" name="size" type="Vector2i" />
<argument index="2" name="glyph" type="int" />
- <argument index="3" name="size" type="Vector2" />
+ <argument index="3" name="gl_size" type="Vector2" />
<description>
Sets size of the glyph.
</description>
diff --git a/doc/classes/TextureProgressBar.xml b/doc/classes/TextureProgressBar.xml
index 9da89ebe43..ee47557b39 100644
--- a/doc/classes/TextureProgressBar.xml
+++ b/doc/classes/TextureProgressBar.xml
@@ -60,6 +60,9 @@
[Texture2D] that clips based on the node's [code]value[/code] and [member fill_mode]. As [code]value[/code] increased, the texture fills up. It shows entirely when [code]value[/code] reaches [code]max_value[/code]. It doesn't show at all if [code]value[/code] is equal to [code]min_value[/code].
The [code]value[/code] property comes from [Range]. See [member Range.value], [member Range.min_value], [member Range.max_value].
</member>
+ <member name="texture_progress_offset" type="Vector2" setter="set_texture_progress_offset" getter="get_texture_progress_offset" default="Vector2(0, 0)">
+ The offset of [member texture_progress]. Useful for [member texture_over] and [member texture_under] with fancy borders, to avoid transparent margins in your progress texture.
+ </member>
<member name="texture_under" type="Texture2D" setter="set_under_texture" getter="get_under_texture">
[Texture2D] that draws under the progress bar. The bar's background.
</member>
diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml
index cb5662419e..ab4d0e181a 100644
--- a/doc/classes/Vector2.xml
+++ b/doc/classes/Vector2.xml
@@ -155,6 +155,18 @@
Returns the vector with all components rounded down (towards negative infinity).
</description>
</method>
+ <method name="from_angle" qualifiers="static">
+ <return type="Vector2" />
+ <argument index="0" name="angle" type="float" />
+ <description>
+ Creates a unit [Vector2] rotated to the given [code]angle[/code] in radians. This is equivalent to doing [code]Vector2(cos(angle), sin(angle))[/code] or [code]Vector2.RIGHT.rotated(angle)[/code].
+ [codeblock]
+ print(Vector2.from_angle(0)) # Prints (1, 0).
+ print(Vector2(1, 0).angle()) # Prints 0, which is the angle used above.
+ print(Vector2.from_angle(PI / 2)) # Prints (0, 1).
+ [/codeblock]
+ </description>
+ </method>
<method name="is_equal_approx" qualifiers="const">
<return type="bool" />
<argument index="0" name="to" type="Vector2" />
diff --git a/doc/translations/ar.po b/doc/translations/ar.po
index 4199bca6c7..b06b6f013e 100644
--- a/doc/translations/ar.po
+++ b/doc/translations/ar.po
@@ -9896,7 +9896,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33047,8 +33047,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/ca.po b/doc/translations/ca.po
index c72cba18bc..8b1b10a7db 100644
--- a/doc/translations/ca.po
+++ b/doc/translations/ca.po
@@ -9927,7 +9927,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33078,8 +33078,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/classes.pot b/doc/translations/classes.pot
index 321e67f759..375a9e850c 100644
--- a/doc/translations/classes.pot
+++ b/doc/translations/classes.pot
@@ -9897,7 +9897,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33048,8 +33048,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/cs.po b/doc/translations/cs.po
index 3584fc5062..d6d7025d92 100644
--- a/doc/translations/cs.po
+++ b/doc/translations/cs.po
@@ -10389,7 +10389,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33547,8 +33547,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/de.po b/doc/translations/de.po
index 76eff809ff..83eabb14fe 100644
--- a/doc/translations/de.po
+++ b/doc/translations/de.po
@@ -10211,7 +10211,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33464,8 +33464,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/es.po b/doc/translations/es.po
index 44b3b22597..1426b57e30 100644
--- a/doc/translations/es.po
+++ b/doc/translations/es.po
@@ -12986,7 +12986,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
"Convierte un valor [String] a un valor booleano, este método devolverá "
@@ -44845,8 +44845,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
@@ -44878,8 +44878,8 @@ msgstr ""
"usando [code]in[/code]:\n"
"[codeblock]\n"
"var nodo = Node2D.new()\n"
-"print(\"position\" in nodo) # Imprime \"True\".\n"
-"print(\"otra_propiedad\" in nodo) # Imprime \"False\".\n"
+"print(\"position\" in nodo) # Imprime \"true\".\n"
+"print(\"otra_propiedad\" in nodo) # Imprime \"false\".\n"
"[/codeblock]\n"
"El operador [code]in[/code] evaluará a [code]true[/code] siempre que la "
"clave exista, incluso si el valor es [code]null[/code].\n"
diff --git a/doc/translations/fa.po b/doc/translations/fa.po
index 2a185fadc5..7455b828de 100644
--- a/doc/translations/fa.po
+++ b/doc/translations/fa.po
@@ -9902,7 +9902,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33053,8 +33053,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/fi.po b/doc/translations/fi.po
index a5f470c60b..94cda2b9d9 100644
--- a/doc/translations/fi.po
+++ b/doc/translations/fi.po
@@ -9915,7 +9915,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33066,8 +33066,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/fr.po b/doc/translations/fr.po
index 2d9b1db565..e0aef69af3 100644
--- a/doc/translations/fr.po
+++ b/doc/translations/fr.po
@@ -10233,7 +10233,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33414,8 +33414,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/id.po b/doc/translations/id.po
index cd841fc553..fa90969628 100644
--- a/doc/translations/id.po
+++ b/doc/translations/id.po
@@ -9928,7 +9928,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33079,8 +33079,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/it.po b/doc/translations/it.po
index aa085f6158..d8b888535f 100644
--- a/doc/translations/it.po
+++ b/doc/translations/it.po
@@ -10186,7 +10186,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33345,8 +33345,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/ja.po b/doc/translations/ja.po
index ee900d58c2..fea270ad95 100644
--- a/doc/translations/ja.po
+++ b/doc/translations/ja.po
@@ -11116,7 +11116,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -34317,8 +34317,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/ko.po b/doc/translations/ko.po
index 60416fb63c..0dce30d48f 100644
--- a/doc/translations/ko.po
+++ b/doc/translations/ko.po
@@ -9904,7 +9904,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33055,8 +33055,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/nl.po b/doc/translations/nl.po
index c0dc01c653..e409bd00b4 100644
--- a/doc/translations/nl.po
+++ b/doc/translations/nl.po
@@ -9930,7 +9930,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33081,8 +33081,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/pl.po b/doc/translations/pl.po
index 2664f263cb..06f09dcf81 100644
--- a/doc/translations/pl.po
+++ b/doc/translations/pl.po
@@ -9948,7 +9948,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33100,8 +33100,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/pt_BR.po b/doc/translations/pt_BR.po
index f86bed9585..28ee6b7668 100644
--- a/doc/translations/pt_BR.po
+++ b/doc/translations/pt_BR.po
@@ -9943,7 +9943,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33094,8 +33094,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/ro.po b/doc/translations/ro.po
index b25c3911cc..8cabaeebe1 100644
--- a/doc/translations/ro.po
+++ b/doc/translations/ro.po
@@ -9904,7 +9904,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33055,8 +33055,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/ru.po b/doc/translations/ru.po
index cf5289d7f9..e515ff50ff 100644
--- a/doc/translations/ru.po
+++ b/doc/translations/ru.po
@@ -10409,7 +10409,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33592,8 +33592,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/sr_Cyrl.po b/doc/translations/sr_Cyrl.po
index 06399d5e87..655a4a825c 100644
--- a/doc/translations/sr_Cyrl.po
+++ b/doc/translations/sr_Cyrl.po
@@ -9914,7 +9914,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33065,8 +33065,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/th.po b/doc/translations/th.po
index cdf1c6d7e2..7f3ef3a1e2 100644
--- a/doc/translations/th.po
+++ b/doc/translations/th.po
@@ -9920,7 +9920,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33071,8 +33071,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/tr.po b/doc/translations/tr.po
index 646a3fb5b3..d102161901 100644
--- a/doc/translations/tr.po
+++ b/doc/translations/tr.po
@@ -9896,7 +9896,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33047,8 +33047,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/uk.po b/doc/translations/uk.po
index c2232d81ab..42daf9cd30 100644
--- a/doc/translations/uk.po
+++ b/doc/translations/uk.po
@@ -9982,7 +9982,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33133,8 +33133,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/zh_Hans.po b/doc/translations/zh_Hans.po
index 40d1eb68bc..312fbd37c0 100644
--- a/doc/translations/zh_Hans.po
+++ b/doc/translations/zh_Hans.po
@@ -10129,7 +10129,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33283,8 +33283,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/doc/translations/zh_Hant.po b/doc/translations/zh_Hant.po
index 13515dff67..09d52a63c7 100644
--- a/doc/translations/zh_Hant.po
+++ b/doc/translations/zh_Hant.po
@@ -9933,7 +9933,7 @@ msgid ""
"Cast a [String] value to a boolean value, this method will return "
"[code]false[/code] if [code]\"\"[/code] is passed in, and [code]true[/code] "
"for all non-empty strings.\n"
-"Examples: [code]bool(\"False\")[/code] returns [code]true[/code], "
+"Examples: [code]bool(\"false\")[/code] returns [code]true[/code], "
"[code]bool(\"\")[/code] returns [code]false[/code]."
msgstr ""
@@ -33084,8 +33084,8 @@ msgid ""
"code]:\n"
"[codeblock]\n"
"var n = Node2D.new()\n"
-"print(\"position\" in n) # Prints \"True\".\n"
-"print(\"other_property\" in n) # Prints \"False\".\n"
+"print(\"position\" in n) # Prints \"true\".\n"
+"print(\"other_property\" in n) # Prints \"false\".\n"
"[/codeblock]\n"
"The [code]in[/code] operator will evaluate to [code]true[/code] as long as "
"the key exists, even if the value is [code]null[/code].\n"
diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp
index a2c9bae852..1754b47c85 100644
--- a/drivers/unix/dir_access_unix.cpp
+++ b/drivers/unix/dir_access_unix.cpp
@@ -71,7 +71,7 @@ Error DirAccessUnix::list_dir_begin() {
bool DirAccessUnix::file_exists(String p_file) {
GLOBAL_LOCK_FUNCTION
- if (p_file.is_rel_path()) {
+ if (p_file.is_relative_path()) {
p_file = current_dir.plus_file(p_file);
}
@@ -90,7 +90,7 @@ bool DirAccessUnix::file_exists(String p_file) {
bool DirAccessUnix::dir_exists(String p_dir) {
GLOBAL_LOCK_FUNCTION
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
p_dir = get_current_dir().plus_file(p_dir);
}
@@ -105,7 +105,7 @@ bool DirAccessUnix::dir_exists(String p_dir) {
bool DirAccessUnix::is_readable(String p_dir) {
GLOBAL_LOCK_FUNCTION
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
p_dir = get_current_dir().plus_file(p_dir);
}
@@ -116,7 +116,7 @@ bool DirAccessUnix::is_readable(String p_dir) {
bool DirAccessUnix::is_writable(String p_dir) {
GLOBAL_LOCK_FUNCTION
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
p_dir = get_current_dir().plus_file(p_dir);
}
@@ -125,7 +125,7 @@ bool DirAccessUnix::is_writable(String p_dir) {
}
uint64_t DirAccessUnix::get_modified_time(String p_file) {
- if (p_file.is_rel_path()) {
+ if (p_file.is_relative_path()) {
p_file = current_dir.plus_file(p_file);
}
@@ -293,7 +293,7 @@ bool DirAccessUnix::drives_are_shortcuts() {
Error DirAccessUnix::make_dir(String p_dir) {
GLOBAL_LOCK_FUNCTION
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
p_dir = get_current_dir().plus_file(p_dir);
}
@@ -328,7 +328,7 @@ Error DirAccessUnix::change_dir(String p_dir) {
// try_dir is the directory we are trying to change into
String try_dir = "";
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
String next_dir = current_dir.plus_file(p_dir);
next_dir = next_dir.simplify_path();
try_dir = next_dir;
@@ -372,13 +372,13 @@ String DirAccessUnix::get_current_dir(bool p_include_drive) {
}
Error DirAccessUnix::rename(String p_path, String p_new_path) {
- if (p_path.is_rel_path()) {
+ if (p_path.is_relative_path()) {
p_path = get_current_dir().plus_file(p_path);
}
p_path = fix_path(p_path);
- if (p_new_path.is_rel_path()) {
+ if (p_new_path.is_relative_path()) {
p_new_path = get_current_dir().plus_file(p_new_path);
}
@@ -388,7 +388,7 @@ Error DirAccessUnix::rename(String p_path, String p_new_path) {
}
Error DirAccessUnix::remove(String p_path) {
- if (p_path.is_rel_path()) {
+ if (p_path.is_relative_path()) {
p_path = get_current_dir().plus_file(p_path);
}
@@ -407,7 +407,7 @@ Error DirAccessUnix::remove(String p_path) {
}
bool DirAccessUnix::is_link(String p_file) {
- if (p_file.is_rel_path()) {
+ if (p_file.is_relative_path()) {
p_file = get_current_dir().plus_file(p_file);
}
@@ -422,7 +422,7 @@ bool DirAccessUnix::is_link(String p_file) {
}
String DirAccessUnix::read_link(String p_file) {
- if (p_file.is_rel_path()) {
+ if (p_file.is_relative_path()) {
p_file = get_current_dir().plus_file(p_file);
}
@@ -439,7 +439,7 @@ String DirAccessUnix::read_link(String p_file) {
}
Error DirAccessUnix::create_link(String p_source, String p_target) {
- if (p_target.is_rel_path()) {
+ if (p_target.is_relative_path()) {
p_target = get_current_dir().plus_file(p_target);
}
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index f6a3e93b55..3032c31629 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -392,7 +392,7 @@ String OS_Unix::get_locale() const {
Error OS_Unix::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
String path = p_path;
- if (FileAccess::exists(path) && path.is_rel_path()) {
+ if (FileAccess::exists(path) && path.is_relative_path()) {
// dlopen expects a slash, in this case a leading ./ for it to be interpreted as a relative path,
// otherwise it will end up searching various system directories for the lib instead and finally failing.
path = "./" + path;
diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp
index 325bae5b56..f7a5f7279e 100644
--- a/drivers/windows/dir_access_windows.cpp
+++ b/drivers/windows/dir_access_windows.cpp
@@ -155,7 +155,7 @@ Error DirAccessWindows::make_dir(String p_dir) {
GLOBAL_LOCK_FUNCTION
p_dir = fix_path(p_dir);
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
p_dir = current_dir.plus_file(p_dir);
}
@@ -227,7 +227,7 @@ bool DirAccessWindows::file_exists(String p_file) {
bool DirAccessWindows::dir_exists(String p_dir) {
GLOBAL_LOCK_FUNCTION
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
p_dir = get_current_dir().plus_file(p_dir);
}
@@ -242,13 +242,13 @@ bool DirAccessWindows::dir_exists(String p_dir) {
}
Error DirAccessWindows::rename(String p_path, String p_new_path) {
- if (p_path.is_rel_path()) {
+ if (p_path.is_relative_path()) {
p_path = get_current_dir().plus_file(p_path);
}
p_path = fix_path(p_path);
- if (p_new_path.is_rel_path()) {
+ if (p_new_path.is_relative_path()) {
p_new_path = get_current_dir().plus_file(p_new_path);
}
@@ -281,7 +281,7 @@ Error DirAccessWindows::rename(String p_path, String p_new_path) {
}
Error DirAccessWindows::remove(String p_path) {
- if (p_path.is_rel_path()) {
+ if (p_path.is_relative_path()) {
p_path = get_current_dir().plus_file(p_path);
}
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
index 945291b163..fbd3aaa409 100644
--- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
@@ -33,12 +33,15 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_node.h"
+#include "editor/editor_run_native.h"
void DebugAdapterParser::_bind_methods() {
// Requests
ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize);
- ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::prepare_success_response);
+ ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect);
ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch);
+ ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach);
+ ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart);
ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate);
ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::prepare_success_response);
ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause);
@@ -46,10 +49,13 @@ void DebugAdapterParser::_bind_methods() {
ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads);
ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace);
ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints);
+ ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations);
ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes);
ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables);
ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next);
ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn);
+ ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate);
+ ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg);
}
Dictionary DebugAdapterParser::prepare_base_event() const {
@@ -80,13 +86,31 @@ Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params
DAP::Message message;
String error, error_desc;
switch (err_type) {
+ case DAP::ErrorType::WRONG_PATH:
+ error = "wrong_path";
+ error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\"";
+ break;
+ case DAP::ErrorType::NOT_RUNNING:
+ error = "not_running";
+ error_desc = "Can't attach to a running session since there isn't one.";
+ break;
+ case DAP::ErrorType::TIMEOUT:
+ error = "timeout";
+ error_desc = "Timeout reached while processing a request.";
+ break;
+ case DAP::ErrorType::UNKNOWN_PLATFORM:
+ error = "unknown_platform";
+ error_desc = "The specified platform is unknown.";
+ break;
+ case DAP::ErrorType::MISSING_DEVICE:
+ error = "missing_device";
+ error_desc = "There's no connected device with specified id.";
+ break;
case DAP::ErrorType::UNKNOWN:
+ default:
error = "unknown";
error_desc = "An unknown error has ocurred when processing the request.";
break;
- case DAP::ErrorType::WRONG_PATH:
- error = "wrong_path";
- error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\"";
}
message.id = err_type;
@@ -114,10 +138,35 @@ Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const
DebugAdapterProtocol::get_singleton()->notify_initialized();
+ if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) {
+ // Send all current breakpoints
+ List<String> breakpoints;
+ ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
+ for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
+ String breakpoint = E->get();
+
+ String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://"
+ int line = breakpoint.substr(path.size()).to_int();
+
+ DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true);
+ }
+ } else {
+ // Remove all current breakpoints
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints();
+ }
+
return response;
}
-Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) {
+Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const {
+ if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) {
+ EditorNode::get_singleton()->run_stop();
+ }
+
+ return prepare_success_response(p_params);
+}
+
+Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const {
Dictionary args = p_params["arguments"];
if (args.has("project") && !is_valid_path(args["project"])) {
Dictionary variables;
@@ -126,17 +175,85 @@ Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) {
return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables);
}
+ if (args.has("godot/custom_data")) {
+ DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"];
+ }
+
ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) {
dbg->debug_skip_breakpoints();
}
- EditorNode::get_singleton()->run_play();
+ String platform_string = args.get("platform", "host");
+ if (platform_string == "host") {
+ EditorNode::get_singleton()->run_play();
+ } else {
+ int device = args.get("device", -1);
+ int idx = -1;
+ if (platform_string == "android") {
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
+ if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") {
+ idx = i;
+ break;
+ }
+ }
+ } else if (platform_string == "web") {
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
+ if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "HTML5") {
+ idx = i;
+ break;
+ }
+ }
+ }
+
+ if (idx == -1) {
+ return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM);
+ }
+
+ EditorNode *editor = EditorNode::get_singleton();
+ Error err = platform_string == "android" ? editor->run_play_native(device, idx) : editor->run_play_native(-1, idx);
+ if (err) {
+ if (err == ERR_INVALID_PARAMETER && platform_string == "android") {
+ return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE);
+ } else {
+ return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
+ }
+ }
+ }
+
+ DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false;
DebugAdapterProtocol::get_singleton()->notify_process();
return prepare_success_response(p_params);
}
+Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const {
+ ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
+ if (!dbg->is_session_active()) {
+ return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING);
+ }
+
+ DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true;
+ DebugAdapterProtocol::get_singleton()->notify_process();
+ return prepare_success_response(p_params);
+}
+
+Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const {
+ // Extract embedded "arguments" so it can be given to req_launch/req_attach
+ Dictionary params = p_params, args;
+ args = params["arguments"];
+ args = args["arguments"];
+ params["arguments"] = args;
+
+ Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params);
+ if (!response["success"]) {
+ response["command"] = p_params["command"];
+ return response;
+ }
+
+ return prepare_success_response(p_params);
+}
+
Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const {
EditorNode::get_singleton()->run_stop();
@@ -205,7 +322,7 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const
return response;
}
-Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) {
+Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const {
Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;
@@ -230,14 +347,30 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) {
lines.push_back(breakpoint.line + !lines_at_one);
}
- EditorDebuggerNode::get_singleton()->set_breakpoints(ProjectSettings::get_singleton()->localize_path(source.path), lines);
Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines);
body["breakpoints"] = updated_breakpoints;
return response;
}
-Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) {
+Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const {
+ Dictionary response = prepare_success_response(p_params), body;
+ response["body"] = body;
+ Dictionary args = p_params["arguments"];
+
+ Array locations;
+ DAP::BreakpointLocation location;
+ location.line = args["line"];
+ if (args.has("endLine")) {
+ location.endLine = args["endLine"];
+ }
+ locations.push_back(location.to_json());
+
+ body["breakpoints"] = locations;
+ return response;
+}
+
+Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const {
Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;
@@ -291,7 +424,14 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
int variable_id = args["variablesReference"];
Map<int, Array>::Element *E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id);
+
if (E) {
+ if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
+ for (int i = 0; i < E->value().size(); i++) {
+ Dictionary variable = E->value()[i];
+ variable.erase("type");
+ }
+ }
body["variables"] = E ? E->value() : Array();
return response;
} else {
@@ -313,6 +453,29 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const {
return prepare_success_response(p_params);
}
+Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const {
+ Dictionary response = prepare_success_response(p_params), body;
+ response["body"] = body;
+
+ Dictionary args = p_params["arguments"];
+
+ String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]);
+ body["result"] = value;
+
+ return response;
+}
+
+Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {
+ Dictionary args = p_params["arguments"];
+
+ String msg = args["message"];
+ Array data = args["data"];
+
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data);
+
+ return prepare_success_response(p_params);
+}
+
Dictionary DebugAdapterParser::ev_initialized() const {
Dictionary event = prepare_base_event();
event["event"] = "initialized";
@@ -423,3 +586,25 @@ Dictionary DebugAdapterParser::ev_output(const String &p_message) const {
return event;
}
+
+Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const {
+ Dictionary event = prepare_base_event(), body;
+ event["event"] = "breakpoint";
+ event["body"] = body;
+
+ body["reason"] = p_enabled ? "new" : "removed";
+ body["breakpoint"] = p_breakpoint.to_json();
+
+ return event;
+}
+
+Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const {
+ Dictionary event = prepare_base_event(), body;
+ event["event"] = "godot/custom_data";
+ event["body"] = body;
+
+ body["message"] = p_msg;
+ body["data"] = p_data;
+
+ return event;
+}
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h
index b86b37d067..4c93464e39 100644
--- a/editor/debugger/debug_adapter/debug_adapter_parser.h
+++ b/editor/debugger/debug_adapter/debug_adapter_parser.h
@@ -44,7 +44,7 @@ class DebugAdapterParser : public Object {
private:
friend DebugAdapterProtocol;
- _FORCE_INLINE_ bool is_valid_path(const String &p_path) {
+ _FORCE_INLINE_ bool is_valid_path(const String &p_path) const {
return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path());
}
@@ -60,17 +60,23 @@ protected:
public:
// Requests
Dictionary req_initialize(const Dictionary &p_params) const;
- Dictionary req_launch(const Dictionary &p_params);
+ Dictionary req_launch(const Dictionary &p_params) const;
+ Dictionary req_disconnect(const Dictionary &p_params) const;
+ Dictionary req_attach(const Dictionary &p_params) const;
+ Dictionary req_restart(const Dictionary &p_params) const;
Dictionary req_terminate(const Dictionary &p_params) const;
Dictionary req_pause(const Dictionary &p_params) const;
Dictionary req_continue(const Dictionary &p_params) const;
Dictionary req_threads(const Dictionary &p_params) const;
Dictionary req_stackTrace(const Dictionary &p_params) const;
- Dictionary req_setBreakpoints(const Dictionary &p_params);
- Dictionary req_scopes(const Dictionary &p_params);
+ Dictionary req_setBreakpoints(const Dictionary &p_params) const;
+ Dictionary req_breakpointLocations(const Dictionary &p_params) const;
+ Dictionary req_scopes(const Dictionary &p_params) const;
Dictionary req_variables(const Dictionary &p_params) const;
Dictionary req_next(const Dictionary &p_params) const;
Dictionary req_stepIn(const Dictionary &p_params) const;
+ Dictionary req_evaluate(const Dictionary &p_params) const;
+ Dictionary req_godot_put_msg(const Dictionary &p_params) const;
// Events
Dictionary ev_initialized() const;
@@ -83,6 +89,8 @@ public:
Dictionary ev_stopped_step() const;
Dictionary ev_continued() const;
Dictionary ev_output(const String &p_message) const;
+ Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const;
+ Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const;
};
#endif
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
index 0482271432..2e0e6cb7c8 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
@@ -94,11 +94,17 @@ Error DAPeer::handle_data() {
String msg;
msg.parse_utf8((const char *)req_buf, req_pos);
+ // Apply a timestamp if it there's none yet
+ if (!timestamp) {
+ timestamp = OS::get_singleton()->get_ticks_msec();
+ }
+
// Response
if (DebugAdapterProtocol::get_singleton()->process_message(msg)) {
// Reset to read again
req_pos = 0;
has_header = false;
+ timestamp = 0;
}
}
return OK;
@@ -180,12 +186,460 @@ void DebugAdapterProtocol::reset_stack_info() {
variable_list.clear();
}
+int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
+ switch (p_var.get_type()) {
+ case Variant::VECTOR2:
+ case Variant::VECTOR2I: {
+ int id = variable_id++;
+ Vector2 vec = p_var;
+ DAP::Variable x, y;
+ x.name = "x";
+ y.name = "y";
+ x.type = y.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT);
+ x.value = rtos(vec.x);
+ y.value = rtos(vec.y);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::RECT2:
+ case Variant::RECT2I: {
+ int id = variable_id++;
+ Rect2 rect = p_var;
+ DAP::Variable x, y, w, h;
+ x.name = "x";
+ y.name = "y";
+ w.name = "w";
+ h.name = "h";
+ x.type = y.type = w.type = h.type = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT);
+ x.value = rtos(rect.position.x);
+ y.value = rtos(rect.position.y);
+ w.value = rtos(rect.size.x);
+ h.value = rtos(rect.size.y);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(w.to_json());
+ arr.push_back(h.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::VECTOR3:
+ case Variant::VECTOR3I: {
+ int id = variable_id++;
+ Vector3 vec = p_var;
+ DAP::Variable x, y, z;
+ x.name = "x";
+ y.name = "y";
+ z.name = "z";
+ x.type = y.type = z.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT);
+ x.value = rtos(vec.x);
+ y.value = rtos(vec.y);
+ z.value = rtos(vec.z);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(z.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::TRANSFORM2D: {
+ int id = variable_id++;
+ Transform2D transform = p_var;
+ DAP::Variable x, y, origin;
+ x.name = "x";
+ y.name = "y";
+ origin.name = "origin";
+ x.type = y.type = origin.type = Variant::get_type_name(Variant::VECTOR2);
+ x.value = transform.elements[0];
+ y.value = transform.elements[1];
+ origin.value = transform.elements[2];
+ x.variablesReference = parse_variant(transform.elements[0]);
+ y.variablesReference = parse_variant(transform.elements[1]);
+ origin.variablesReference = parse_variant(transform.elements[2]);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(origin.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PLANE: {
+ int id = variable_id++;
+ Plane plane = p_var;
+ DAP::Variable d, normal;
+ d.name = "d";
+ normal.name = "normal";
+ d.type = Variant::get_type_name(Variant::FLOAT);
+ normal.type = Variant::get_type_name(Variant::VECTOR3);
+ d.value = rtos(plane.d);
+ normal.value = plane.normal;
+ normal.variablesReference = parse_variant(plane.normal);
+
+ Array arr;
+ arr.push_back(d.to_json());
+ arr.push_back(normal.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::QUATERNION: {
+ int id = variable_id++;
+ Quaternion quat = p_var;
+ DAP::Variable x, y, z, w;
+ x.name = "x";
+ y.name = "y";
+ z.name = "z";
+ w.name = "w";
+ x.type = y.type = z.type = w.type = Variant::get_type_name(Variant::FLOAT);
+ x.value = rtos(quat.x);
+ y.value = rtos(quat.y);
+ z.value = rtos(quat.z);
+ w.value = rtos(quat.w);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(z.to_json());
+ arr.push_back(w.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::AABB: {
+ int id = variable_id++;
+ AABB aabb = p_var;
+ DAP::Variable position, size;
+ position.name = "position";
+ size.name = "size";
+ position.type = size.type = Variant::get_type_name(Variant::VECTOR3);
+ position.value = aabb.position;
+ size.value = aabb.size;
+ position.variablesReference = parse_variant(aabb.position);
+ size.variablesReference = parse_variant(aabb.size);
+
+ Array arr;
+ arr.push_back(position.to_json());
+ arr.push_back(size.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::BASIS: {
+ int id = variable_id++;
+ Basis basis = p_var;
+ DAP::Variable x, y, z;
+ x.name = "x";
+ y.name = "y";
+ z.name = "z";
+ x.type = y.type = z.type = Variant::get_type_name(Variant::VECTOR2);
+ x.value = basis.elements[0];
+ y.value = basis.elements[1];
+ z.value = basis.elements[2];
+ x.variablesReference = parse_variant(basis.elements[0]);
+ y.variablesReference = parse_variant(basis.elements[1]);
+ z.variablesReference = parse_variant(basis.elements[2]);
+
+ Array arr;
+ arr.push_back(x.to_json());
+ arr.push_back(y.to_json());
+ arr.push_back(z.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::TRANSFORM3D: {
+ int id = variable_id++;
+ Transform3D transform = p_var;
+ DAP::Variable basis, origin;
+ basis.name = "basis";
+ origin.name = "origin";
+ basis.type = Variant::get_type_name(Variant::BASIS);
+ origin.type = Variant::get_type_name(Variant::VECTOR3);
+ basis.value = transform.basis;
+ origin.value = transform.origin;
+ basis.variablesReference = parse_variant(transform.basis);
+ origin.variablesReference = parse_variant(transform.origin);
+
+ Array arr;
+ arr.push_back(basis.to_json());
+ arr.push_back(origin.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::COLOR: {
+ int id = variable_id++;
+ Color color = p_var;
+ DAP::Variable r, g, b, a;
+ r.name = "r";
+ g.name = "g";
+ b.name = "b";
+ a.name = "a";
+ r.type = g.type = b.type = a.type = Variant::get_type_name(Variant::FLOAT);
+ r.value = rtos(color.r);
+ g.value = rtos(color.g);
+ b.value = rtos(color.b);
+ a.value = rtos(color.a);
+
+ Array arr;
+ arr.push_back(r.to_json());
+ arr.push_back(g.to_json());
+ arr.push_back(b.to_json());
+ arr.push_back(a.to_json());
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::ARRAY: {
+ int id = variable_id++;
+ Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(array[i].get_type());
+ var.value = array[i];
+ var.variablesReference = parse_variant(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::DICTIONARY: {
+ int id = variable_id++;
+ Dictionary dictionary = p_var;
+ Array arr;
+
+ for (int i = 0; i < dictionary.size(); i++) {
+ DAP::Variable var;
+ var.name = dictionary.get_key_at_index(i);
+ Variant value = dictionary.get_value_at_index(i);
+ var.type = Variant::get_type_name(value.get_type());
+ var.value = value;
+ var.variablesReference = parse_variant(value);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_BYTE_ARRAY: {
+ int id = variable_id++;
+ PackedByteArray array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "byte";
+ var.value = itos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_INT32_ARRAY: {
+ int id = variable_id++;
+ PackedInt32Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "int";
+ var.value = itos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_INT64_ARRAY: {
+ int id = variable_id++;
+ PackedInt64Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "long";
+ var.value = itos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_FLOAT32_ARRAY: {
+ int id = variable_id++;
+ PackedFloat32Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "float";
+ var.value = rtos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_FLOAT64_ARRAY: {
+ int id = variable_id++;
+ PackedFloat64Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = "double";
+ var.value = rtos(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_STRING_ARRAY: {
+ int id = variable_id++;
+ PackedStringArray array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(Variant::STRING);
+ var.value = array[i];
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_VECTOR2_ARRAY: {
+ int id = variable_id++;
+ PackedVector2Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(Variant::VECTOR2);
+ var.value = array[i];
+ var.variablesReference = parse_variant(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_VECTOR3_ARRAY: {
+ int id = variable_id++;
+ PackedVector2Array array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(Variant::VECTOR3);
+ var.value = array[i];
+ var.variablesReference = parse_variant(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ case Variant::PACKED_COLOR_ARRAY: {
+ int id = variable_id++;
+ PackedColorArray array = p_var;
+ DAP::Variable size;
+ size.name = "size";
+ size.type = Variant::get_type_name(Variant::INT);
+ size.value = itos(array.size());
+
+ Array arr;
+ arr.push_back(size.to_json());
+
+ for (int i = 0; i < array.size(); i++) {
+ DAP::Variable var;
+ var.name = itos(i);
+ var.type = Variant::get_type_name(Variant::COLOR);
+ var.value = array[i];
+ var.variablesReference = parse_variant(array[i]);
+ arr.push_back(var.to_json());
+ }
+ variable_list.insert(id, arr);
+ return id;
+ }
+ default:
+ // Simple atomic stuff, or too complex to be manipulated
+ return 0;
+ }
+}
+
bool DebugAdapterProtocol::process_message(const String &p_text) {
JSON json;
ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Mal-formed message!");
Dictionary params = json.get_data();
bool completed = true;
+ if (OS::get_singleton()->get_ticks_msec() - _current_peer->timestamp > _request_timeout) {
+ Dictionary response = parser->prepare_error_response(params, DAP::ErrorType::TIMEOUT);
+ _current_peer->res_queue.push_front(response);
+ return true;
+ }
+
// Append "req_" to any command received; prevents name clash with existing functions, and possibly exploiting
String command = "req_" + (String)params["command"];
if (parser->has_method(command)) {
@@ -211,7 +665,7 @@ void DebugAdapterProtocol::notify_initialized() {
}
void DebugAdapterProtocol::notify_process() {
- String launch_mode = _current_request.is_empty() ? "launch" : _current_request;
+ String launch_mode = _current_peer->attached ? "attach" : "launch";
Dictionary event = parser->ev_process(launch_mode);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
@@ -222,7 +676,7 @@ void DebugAdapterProtocol::notify_process() {
void DebugAdapterProtocol::notify_terminated() {
Dictionary event = parser->ev_terminated();
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
- if (_current_request == "launch" && _current_peer == E->get()) {
+ if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) {
continue;
}
E->get()->res_queue.push_back(event);
@@ -232,7 +686,7 @@ void DebugAdapterProtocol::notify_terminated() {
void DebugAdapterProtocol::notify_exited(const int &p_exitcode) {
Dictionary event = parser->ev_exited(p_exitcode);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
- if (_current_request == "launch" && _current_peer == E->get()) {
+ if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) {
continue;
}
E->get()->res_queue.push_back(event);
@@ -286,25 +740,46 @@ void DebugAdapterProtocol::notify_output(const String &p_message) {
}
}
+void DebugAdapterProtocol::notify_custom_data(const String &p_msg, const Array &p_data) {
+ Dictionary event = parser->ev_custom_data(p_msg, p_data);
+ for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
+ Ref<DAPeer> peer = E->get();
+ if (peer->supportsCustomData) {
+ peer->res_queue.push_back(event);
+ }
+ }
+}
+
+void DebugAdapterProtocol::notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) {
+ Dictionary event = parser->ev_breakpoint(p_breakpoint, p_enabled);
+ for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
+ if (_current_request == "setBreakpoints" && E->get() == _current_peer) {
+ continue;
+ }
+ E->get()->res_queue.push_back(event);
+ }
+}
+
Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array &p_lines) {
Array updated_breakpoints;
+ // Add breakpoints
for (int i = 0; i < p_lines.size(); i++) {
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true);
DAP::Breakpoint breakpoint;
- breakpoint.verified = true;
- breakpoint.source.path = p_path;
- breakpoint.source.compute_checksums();
breakpoint.line = p_lines[i];
+ breakpoint.source.path = p_path;
- List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
- if (E) {
- breakpoint.id = E->get().id;
- } else {
- breakpoint.id = breakpoint_id++;
- breakpoint_list.push_back(breakpoint);
- }
+ ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array());
+ updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json());
+ }
- updated_breakpoints.push_back(breakpoint.to_json());
+ // Remove breakpoints
+ for (List<DAP::Breakpoint>::Element *E = breakpoint_list.front(); E; E = E->next()) {
+ DAP::Breakpoint b = E->get();
+ if (b.source.path == p_path && !p_lines.has(b.line)) {
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false);
+ }
}
return updated_breakpoints;
@@ -347,6 +822,29 @@ void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool
_processing_stackdump = p_has_stackdump;
}
+void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) {
+ DAP::Breakpoint breakpoint;
+ breakpoint.verified = true;
+ breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path);
+ breakpoint.source.compute_checksums();
+ breakpoint.line = p_line;
+
+ if (p_enabled) {
+ // Add the breakpoint
+ breakpoint.id = breakpoint_id++;
+ breakpoint_list.push_back(breakpoint);
+ } else {
+ // Remove the breakpoint
+ List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
+ if (E) {
+ breakpoint.id = E->get().id;
+ breakpoint_list.erase(E);
+ }
+ }
+
+ notify_breakpoint(breakpoint, p_enabled);
+}
+
void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
if (_processing_breakpoint && !p_stack_dump.is_empty()) {
// Find existing breakpoint
@@ -424,11 +922,21 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) {
variable.name = stack_var.name;
variable.value = stack_var.value;
variable.type = Variant::get_type_name(stack_var.value.get_type());
+ variable.variablesReference = parse_variant(stack_var.value);
variable_list.find(variable_id)->value().push_back(variable.to_json());
_remaining_vars--;
}
+void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_data) {
+ // Ignore data that is already handled by DAP
+ if (p_msg == "debug_enter" || p_msg == "debug_exit" || p_msg == "stack_dump" || p_msg == "stack_frame_vars" || p_msg == "stack_frame_var" || p_msg == "output" || p_msg == "request_quit") {
+ return;
+ }
+
+ notify_custom_data(p_msg, p_data);
+}
+
void DebugAdapterProtocol::poll() {
if (server->is_connection_available()) {
on_client_connected();
@@ -459,6 +967,8 @@ void DebugAdapterProtocol::poll() {
}
Error DebugAdapterProtocol::start(int p_port, const IPAddress &p_bind_ip) {
+ _request_timeout = (uint64_t)_EDITOR_GET("network/debug_adapter/request_timeout");
+ _sync_breakpoints = (bool)_EDITOR_GET("network/debug_adapter/sync_breakpoints");
_initialized = true;
return server->listen(p_port, p_bind_ip);
}
@@ -484,12 +994,15 @@ DebugAdapterProtocol::DebugAdapterProtocol() {
node->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused));
EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton();
+ debugger_node->connect("breakpoint_toggled", callable_mp(this, &DebugAdapterProtocol::on_debug_breakpoint_toggled));
+
debugger_node->get_default_debugger()->connect("stopped", callable_mp(this, &DebugAdapterProtocol::on_debug_stopped));
debugger_node->get_default_debugger()->connect("output", callable_mp(this, &DebugAdapterProtocol::on_debug_output));
debugger_node->get_default_debugger()->connect("breaked", callable_mp(this, &DebugAdapterProtocol::on_debug_breaked));
debugger_node->get_default_debugger()->connect("stack_dump", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_dump));
debugger_node->get_default_debugger()->connect("stack_frame_vars", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_vars));
debugger_node->get_default_debugger()->connect("stack_frame_var", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_var));
+ debugger_node->get_default_debugger()->connect("debug_data", callable_mp(this, &DebugAdapterProtocol::on_debug_data));
}
DebugAdapterProtocol::~DebugAdapterProtocol() {
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h
index 6b542033ed..d4291992bf 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.h
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h
@@ -52,12 +52,17 @@ struct DAPeer : RefCounted {
int content_length = 0;
List<Dictionary> res_queue;
int seq = 0;
+ uint64_t timestamp = 0;
// Client specific info
bool linesStartAt1 = false;
bool columnsStartAt1 = false;
bool supportsVariableType = false;
bool supportsInvalidatedEvent = false;
+ bool supportsCustomData = false;
+
+ // Internal client info
+ bool attached = false;
Error handle_data();
Error send_data();
@@ -82,20 +87,26 @@ private:
void on_debug_stopped();
void on_debug_output(const String &p_message);
void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump);
+ void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled);
void on_debug_stack_dump(const Array &p_stack_dump);
void on_debug_stack_frame_vars(const int &p_size);
void on_debug_stack_frame_var(const Array &p_data);
+ void on_debug_data(const String &p_msg, const Array &p_data);
void reset_current_info();
void reset_ids();
void reset_stack_info();
+ int parse_variant(const Variant &p_var);
+
bool _initialized = false;
bool _processing_breakpoint = false;
bool _stepping = false;
bool _processing_stackdump = false;
int _remaining_vars = 0;
int _current_frame = 0;
+ uint64_t _request_timeout = 1000;
+ bool _sync_breakpoints = false;
String _current_request;
Ref<DAPeer> _current_peer;
@@ -108,6 +119,8 @@ private:
Map<int, Array> variable_list;
public:
+ friend class DebugAdapterServer;
+
_FORCE_INLINE_ static DebugAdapterProtocol *get_singleton() { return singleton; }
_FORCE_INLINE_ bool is_active() const { return _initialized && clients.size() > 0; }
@@ -126,8 +139,10 @@ public:
void notify_stopped_step();
void notify_continued();
void notify_output(const String &p_message);
+ void notify_custom_data(const String &p_msg, const Array &p_data);
+ void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled);
- Array update_breakpoints(const String &p_path, const Array &p_breakpoints);
+ Array update_breakpoints(const String &p_path, const Array &p_lines);
void poll();
Error start(int p_port, const IPAddress &p_bind_ip);
diff --git a/editor/debugger/debug_adapter/debug_adapter_server.cpp b/editor/debugger/debug_adapter/debug_adapter_server.cpp
index f9092a1791..4775e2c8b0 100644
--- a/editor/debugger/debug_adapter/debug_adapter_server.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_server.cpp
@@ -36,7 +36,8 @@
DebugAdapterServer::DebugAdapterServer() {
_EDITOR_DEF("network/debug_adapter/remote_port", remote_port);
- _EDITOR_DEF("network/debug_adapter/use_thread", use_thread);
+ _EDITOR_DEF("network/debug_adapter/request_timeout", protocol._request_timeout);
+ _EDITOR_DEF("network/debug_adapter/sync_breakpoints", protocol._sync_breakpoints);
}
void DebugAdapterServer::_notification(int p_what) {
@@ -50,16 +51,17 @@ void DebugAdapterServer::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
// The main loop can be run again during request processing, which modifies internal state of the protocol.
// Thus, "polling" is needed to prevent it from parsing other requests while the current one isn't finished.
- if (started && !use_thread && !polling) {
+ if (started && !polling) {
polling = true;
protocol.poll();
polling = false;
}
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+ protocol._request_timeout = EditorSettings::get_singleton()->get("network/debug_adapter/request_timeout");
+ protocol._sync_breakpoints = EditorSettings::get_singleton()->get("network/debug_adapter/sync_breakpoints");
int remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port");
- bool use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread");
- if (remote_port != this->remote_port || use_thread != this->use_thread) {
+ if (remote_port != this->remote_port) {
this->stop();
this->start();
}
@@ -67,35 +69,16 @@ void DebugAdapterServer::_notification(int p_what) {
}
}
-void DebugAdapterServer::thread_func(void *p_userdata) {
- DebugAdapterServer *self = static_cast<DebugAdapterServer *>(p_userdata);
- while (self->thread_running) {
- // Poll 20 times per second
- self->protocol.poll();
- OS::get_singleton()->delay_usec(50000);
- }
-}
-
void DebugAdapterServer::start() {
remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port");
- use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread");
if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) {
EditorNode::get_log()->add_message("--- Debug adapter server started ---", EditorLog::MSG_TYPE_EDITOR);
- if (use_thread) {
- thread_running = true;
- thread.start(DebugAdapterServer::thread_func, this);
- }
- set_process_internal(!use_thread);
+ set_process_internal(true);
started = true;
}
}
void DebugAdapterServer::stop() {
- if (use_thread) {
- ERR_FAIL_COND(!thread.is_started());
- thread_running = false;
- thread.wait_to_finish();
- }
protocol.stop();
started = false;
EditorNode::get_log()->add_message("--- Debug adapter server stopped ---", EditorLog::MSG_TYPE_EDITOR);
diff --git a/editor/debugger/debug_adapter/debug_adapter_server.h b/editor/debugger/debug_adapter/debug_adapter_server.h
index f8a38965cc..c449403cc2 100644
--- a/editor/debugger/debug_adapter/debug_adapter_server.h
+++ b/editor/debugger/debug_adapter/debug_adapter_server.h
@@ -39,11 +39,9 @@ class DebugAdapterServer : public EditorPlugin {
DebugAdapterProtocol protocol;
- Thread thread;
int remote_port = 6006;
bool thread_running = false;
bool started = false;
- bool use_thread = false;
bool polling = false;
static void thread_func(void *p_userdata);
diff --git a/editor/debugger/debug_adapter/debug_adapter_types.h b/editor/debugger/debug_adapter/debug_adapter_types.h
index aa9ab1adcd..5156c91d14 100644
--- a/editor/debugger/debug_adapter/debug_adapter_types.h
+++ b/editor/debugger/debug_adapter/debug_adapter_types.h
@@ -38,7 +38,11 @@ namespace DAP {
enum ErrorType {
UNKNOWN,
- WRONG_PATH
+ WRONG_PATH,
+ NOT_RUNNING,
+ TIMEOUT,
+ UNKNOWN_PLATFORM,
+ MISSING_DEVICE
};
struct Checksum {
@@ -118,10 +122,14 @@ struct Breakpoint {
struct BreakpointLocation {
int line;
+ int endLine = -1;
_FORCE_INLINE_ Dictionary to_json() const {
Dictionary dict;
dict["line"] = line;
+ if (endLine >= 0) {
+ dict["endLine"] = endLine;
+ }
return dict;
}
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index a9cb1a0131..07c02eb022 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -164,6 +164,7 @@ void EditorDebuggerNode::_bind_methods() {
ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script")));
ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug")));
+ ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled")));
}
EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() {
@@ -487,6 +488,8 @@ void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
dbg->set_breakpoint(p_path, p_line, p_enabled);
});
+
+ emit_signal("breakpoint_toggled", p_path, p_line, p_enabled);
}
void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) {
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 2a5013893f..2f5bde64a9 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -296,6 +296,7 @@ Size2 ScriptEditorDebugger::get_minimum_size() const {
}
void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) {
+ emit_signal(SNAME("debug_data"), p_msg, p_data);
if (p_msg == "debug_enter") {
_put_msg("get_stack_dump", Array());
@@ -872,6 +873,16 @@ void ScriptEditorDebugger::_clear_execution() {
inspector->clear_stack_variables();
}
+void ScriptEditorDebugger::_set_breakpoint(const String &p_file, const int &p_line, const bool &p_enabled) {
+ Ref<Script> script = ResourceLoader::load(p_file);
+ emit_signal("set_breakpoint", script, p_line - 1, p_enabled);
+ script.unref();
+}
+
+void ScriptEditorDebugger::_clear_breakpoints() {
+ emit_signal("clear_breakpoints");
+}
+
void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
error_count = 0;
warning_count = 0;
@@ -1503,6 +1514,9 @@ void ScriptEditorDebugger::_bind_methods() {
ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump")));
ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars")));
ADD_SIGNAL(MethodInfo("stack_frame_var", PropertyInfo(Variant::ARRAY, "data")));
+ ADD_SIGNAL(MethodInfo("debug_data", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::ARRAY, "data")));
+ ADD_SIGNAL(MethodInfo("set_breakpoint", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled")));
+ ADD_SIGNAL(MethodInfo("clear_breakpoints"));
}
void ScriptEditorDebugger::add_debugger_plugin(const Ref<Script> &p_script) {
diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h
index 499dda86da..1c1c0fd3e5 100644
--- a/editor/debugger/script_editor_debugger.h
+++ b/editor/debugger/script_editor_debugger.h
@@ -205,6 +205,9 @@ private:
void _clear_execution();
void _stop_and_notify();
+ void _set_breakpoint(const String &p_path, const int &p_line, const bool &p_enabled);
+ void _clear_breakpoints();
+
protected:
void _notification(int p_what);
static void _bind_methods();
diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp
index 1e9d579708..bf95e6cf62 100644
--- a/editor/editor_file_dialog.cpp
+++ b/editor/editor_file_dialog.cpp
@@ -958,7 +958,7 @@ String EditorFileDialog::get_current_path() const {
}
void EditorFileDialog::set_current_dir(const String &p_dir) {
- if (p_dir.is_rel_path()) {
+ if (p_dir.is_relative_path()) {
dir_access->change_dir(OS::get_singleton()->get_resource_dir());
}
dir_access->change_dir(p_dir);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index b1c546a8c0..9c7bddf037 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -4770,6 +4770,10 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
return true;
}
+Error EditorNode::run_play_native(int p_idx, int p_platform) {
+ return run_native->run_native(p_idx, p_platform);
+}
+
void EditorNode::run_play() {
_menu_option_confirm(RUN_STOP, true);
_run(false);
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 51d01d07ef..03c18a8972 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -898,6 +898,7 @@ public:
bool ensure_main_scene(bool p_from_native);
+ Error run_play_native(int p_idx, int p_platform);
void run_play();
void run_play_current();
void run_play_custom(const String &p_custom);
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index cb7fffe592..9507833746 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -68,9 +68,9 @@ void EditorPropertyText::_text_changed(const String &p_string) {
}
if (string_name) {
- emit_changed(get_edited_property(), StringName(p_string), "", true);
+ emit_changed(get_edited_property(), StringName(p_string));
} else {
- emit_changed(get_edited_property(), p_string, "", true);
+ emit_changed(get_edited_property(), p_string);
}
}
diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp
index e115cd77e1..5828549bdc 100644
--- a/editor/editor_run_native.cpp
+++ b/editor/editor_run_native.cpp
@@ -52,8 +52,8 @@ void EditorRunNative::_notification(int p_what) {
small_icon.instantiate();
small_icon->create_from_image(im);
MenuButton *mb = memnew(MenuButton);
- mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::_run_native), varray(i));
- mb->connect("pressed", callable_mp(this, &EditorRunNative::_run_native), varray(-1, i));
+ mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::run_native), varray(i));
+ mb->connect("pressed", callable_mp(this, &EditorRunNative::run_native), varray(-1, i));
mb->set_icon(small_icon);
add_child(mb);
menus[i] = mb;
@@ -93,22 +93,22 @@ void EditorRunNative::_notification(int p_what) {
}
}
-void EditorRunNative::_run_native(int p_idx, int p_platform) {
+Error EditorRunNative::run_native(int p_idx, int p_platform) {
if (!EditorNode::get_singleton()->ensure_main_scene(true)) {
resume_idx = p_idx;
resume_platform = p_platform;
- return;
+ return OK;
}
Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(p_platform);
- ERR_FAIL_COND(eep.is_null());
+ ERR_FAIL_COND_V(eep.is_null(), ERR_UNAVAILABLE);
if (p_idx == -1) {
if (eep->get_options_count() == 1) {
menus[p_platform]->get_popup()->hide();
p_idx = 0;
} else {
- return;
+ return ERR_INVALID_PARAMETER;
}
}
@@ -124,7 +124,7 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) {
if (preset.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("No runnable export preset found for this platform.\nPlease add a runnable preset in the Export menu or define an existing preset as runnable."));
- return;
+ return ERR_UNAVAILABLE;
}
emit_signal(SNAME("native_run"), preset);
@@ -149,11 +149,11 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) {
flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION;
}
- eep->run(preset, p_idx, flags);
+ return eep->run(preset, p_idx, flags);
}
void EditorRunNative::resume_run_native() {
- _run_native(resume_idx, resume_platform);
+ run_native(resume_idx, resume_platform);
}
void EditorRunNative::_bind_methods() {
diff --git a/editor/editor_run_native.h b/editor/editor_run_native.h
index 3516f668c6..97f6fc005a 100644
--- a/editor/editor_run_native.h
+++ b/editor/editor_run_native.h
@@ -43,13 +43,12 @@ class EditorRunNative : public HBoxContainer {
int resume_idx;
int resume_platform;
- void _run_native(int p_idx, int p_platform);
-
protected:
static void _bind_methods();
void _notification(int p_what);
public:
+ Error run_native(int p_idx, int p_platform);
bool is_deploy_debug_remote_enabled() const;
void resume_run_native();
diff --git a/editor/import/collada.cpp b/editor/import/collada.cpp
index 71930e1e59..b6d0927ce6 100644
--- a/editor/import/collada.cpp
+++ b/editor/import/collada.cpp
@@ -287,7 +287,7 @@ void Collada::_parse_image(XMLParser &parser) {
if (state.version < State::Version(1, 4, 0)) {
/* <1.4 */
String path = parser.get_attribute_value("source").strip_edges();
- if (path.find("://") == -1 && path.is_rel_path()) {
+ if (path.find("://") == -1 && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
image.path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path.uri_decode()));
}
@@ -300,7 +300,7 @@ void Collada::_parse_image(XMLParser &parser) {
parser.read();
String path = parser.get_node_data().strip_edges().uri_decode();
- if (path.find("://") == -1 && path.is_rel_path()) {
+ if (path.find("://") == -1 && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path));
diff --git a/editor/import/resource_importer_shader_file.cpp b/editor/import/resource_importer_shader_file.cpp
index 4d92490675..c01d8068da 100644
--- a/editor/import/resource_importer_shader_file.cpp
+++ b/editor/import/resource_importer_shader_file.cpp
@@ -78,7 +78,7 @@ static String _include_function(const String &p_path, void *userpointer) {
String *base_path = (String *)userpointer;
String include = p_path;
- if (include.is_rel_path()) {
+ if (include.is_relative_path()) {
include = base_path->plus_file(include);
}
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index ec3f899e78..d96cc1cd18 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -4195,6 +4195,7 @@ void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) {
p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
if (p_zoom == zoom) {
+ zoom_widget->set_zoom(p_zoom);
return;
}
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 3d2b3f4478..7ef5993ec5 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -37,6 +37,7 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_node.h"
#include "editor/editor_run_script.h"
#include "editor/editor_scale.h"
@@ -115,9 +116,9 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
}
/* Autoloads. */
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->value();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.value();
if (info.is_singleton) {
highlighter->add_keyword_color(info.name, usertype_color);
}
@@ -479,6 +480,29 @@ void ScriptEditor::_clear_execution(REF p_script) {
}
}
+void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) {
+ Ref<Script> script = Object::cast_to<Script>(*p_script);
+ if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) {
+ if (edit(p_script, p_line, 0, false)) {
+ editor->push_item(p_script.ptr());
+
+ ScriptEditorBase *se = _get_current_editor();
+ if (se) {
+ se->set_breakpoint(p_line, p_enabled);
+ }
+ }
+ }
+}
+
+void ScriptEditor::_clear_breakpoints() {
+ for (int i = 0; i < tab_container->get_child_count(); i++) {
+ ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i));
+ if (se) {
+ se->clear_breakpoints();
+ }
+ }
+}
+
ScriptEditorBase *ScriptEditor::_get_current_editor() const {
int selected = tab_container->get_current_tab();
if (selected < 0 || selected >= tab_container->get_child_count()) {
@@ -3484,6 +3508,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution));
debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution));
debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked));
+ debugger->get_default_debugger()->connect("set_breakpoint", callable_mp(this, &ScriptEditor::_set_breakpoint));
+ debugger->get_default_debugger()->connect("clear_breakpoints", callable_mp(this, &ScriptEditor::_clear_breakpoints));
menu_hb->add_spacer();
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index a57aeea5c0..e2420b4623 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -155,6 +155,8 @@ public:
virtual void tag_saved_version() = 0;
virtual void reload(bool p_soft) {}
virtual Array get_breakpoints() = 0;
+ virtual void set_breakpoint(int p_line, bool p_enabled) = 0;
+ virtual void clear_breakpoints() = 0;
virtual void add_callback(const String &p_function, PackedStringArray p_args) = 0;
virtual void update_settings() = 0;
virtual void set_debugger_active(bool p_active) = 0;
@@ -374,6 +376,8 @@ class ScriptEditor : public PanelContainer {
void _breaked(bool p_breaked, bool p_can_debug);
void _update_window_menu();
void _script_created(Ref<Script> p_script);
+ void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled);
+ void _clear_breakpoints();
ScriptEditorBase *_get_current_editor() const;
Array _get_open_script_editors() const;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 5f48106afc..c44760807f 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -831,7 +831,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
if (info.is_singleton) {
EditorNode::get_singleton()->load_scene(info.path);
}
- } else if (p_symbol.is_rel_path()) {
+ } else if (p_symbol.is_relative_path()) {
// Every symbol other than absolute path is relative path so keep this condition at last.
String path = _get_absolute_path(p_symbol);
if (FileAccess::exists(path)) {
@@ -858,7 +858,7 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
ScriptLanguage::LookupResult result;
if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) {
text_edit->set_symbol_lookup_word_as_valid(true);
- } else if (p_symbol.is_rel_path()) {
+ } else if (p_symbol.is_relative_path()) {
String path = _get_absolute_path(p_symbol);
if (FileAccess::exists(path)) {
text_edit->set_symbol_lookup_word_as_valid(true);
@@ -1370,6 +1370,14 @@ Array ScriptTextEditor::get_breakpoints() {
return code_editor->get_text_editor()->get_breakpointed_lines();
}
+void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) {
+ code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled);
+}
+
+void ScriptTextEditor::clear_breakpoints() {
+ code_editor->get_text_editor()->clear_breakpointed_lines();
+}
+
void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) {
code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this);
}
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 1ca6f56ea1..4208d67f17 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -225,6 +225,8 @@ public:
virtual void reload(bool p_soft) override;
virtual Array get_breakpoints() override;
+ virtual void set_breakpoint(int p_line, bool p_enabled) override;
+ virtual void clear_breakpoints() override;
virtual void add_callback(const String &p_function, PackedStringArray p_args) override;
virtual void update_settings() override;
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index 839e1c5f7a..6bf0042393 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -121,6 +121,8 @@ public:
virtual void set_edit_state(const Variant &p_state) override;
virtual Vector<String> get_functions() override;
virtual Array get_breakpoints() override;
+ virtual void set_breakpoint(int p_line, bool p_enabled) override{};
+ virtual void clear_breakpoints() override{};
virtual void goto_line(int p_line, bool p_with_error = false) override;
void goto_line_selection(int p_line, int p_begin, int p_end);
virtual void set_executing_line(int p_line) override;
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 5b1da11f12..50808a25af 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -2547,6 +2547,7 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa
}
}
}
+ _member_cancel();
VisualShaderNodeUniform *uniform = Object::cast_to<VisualShaderNodeUniform>(vsnode.ptr());
if (uniform) {
diff --git a/main/main.cpp b/main/main.cpp
index a90dc121f3..9076d110bd 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1967,7 +1967,7 @@ bool Main::start() {
for (int i = 0; i < _doc_data_class_path_count; i++) {
// Custom modules are always located by absolute path.
String path = _doc_data_class_paths[i].path;
- if (path.is_rel_path()) {
+ if (path.is_relative_path()) {
path = doc_tool_path.plus_file(path);
}
String name = _doc_data_class_paths[i].name;
@@ -2125,11 +2125,11 @@ bool Main::start() {
if (!project_manager && !editor) { // game
if (game_path != "" || script != "") {
//autoload
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
//first pass, add the constants so they exist before any script is loaded
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->get();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.get();
if (info.is_singleton) {
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
@@ -2140,8 +2140,8 @@ bool Main::start() {
//second pass, load into global constants
List<Node *> to_add;
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->get();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.get();
RES res = ResourceLoader::load(info.path);
ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path);
diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h
index c97f5f0389..09eac2492f 100644
--- a/modules/gdnative/include/nativescript/godot_nativescript.h
+++ b/modules/gdnative/include/nativescript/godot_nativescript.h
@@ -39,12 +39,8 @@ extern "C" {
typedef enum {
GODOT_METHOD_RPC_MODE_DISABLED,
- GODOT_METHOD_RPC_MODE_REMOTE,
- GODOT_METHOD_RPC_MODE_MASTER,
- GODOT_METHOD_RPC_MODE_PUPPET,
- GODOT_METHOD_RPC_MODE_REMOTESYNC,
- GODOT_METHOD_RPC_MODE_MASTERSYNC,
- GODOT_METHOD_RPC_MODE_PUPPETSYNC,
+ GODOT_METHOD_RPC_MODE_ANY,
+ GODOT_METHOD_RPC_MODE_AUTHORITY,
} godot_nativescript_method_rpc_mode;
typedef enum {
diff --git a/modules/gdnative/text/text_server_gdnative.cpp b/modules/gdnative/text/text_server_gdnative.cpp
index 195c32c3f8..39db8ae636 100644
--- a/modules/gdnative/text/text_server_gdnative.cpp
+++ b/modules/gdnative/text/text_server_gdnative.cpp
@@ -383,7 +383,7 @@ Vector2 TextServerGDNative::font_get_glyph_size(RID p_font_rid, const Vector2i &
void TextServerGDNative::font_set_glyph_size(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) {
ERR_FAIL_COND(interface == nullptr);
- interface->font_set_glyph_size(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph, (const godot_vector2 *)&p_size);
+ interface->font_set_glyph_size(data, (godot_rid *)&p_font_rid, (const godot_vector2i *)&p_size, p_glyph, (const godot_vector2 *)&p_gl_size);
}
Rect2 TextServerGDNative::font_get_glyph_uv_rect(RID p_font_rid, const Vector2i &p_size, int32_t p_glyph) const {
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 8b9bd2659d..ab441d194a 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -471,9 +471,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
/* Autoloads. */
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->value();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.value();
if (info.is_singleton) {
keywords[info.name] = usertype_color;
}
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 4589cf1a36..7943cf7469 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -625,9 +625,9 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc
String path = "";
if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) {
path = c->extends_path;
- if (path.is_rel_path()) {
+ if (path.is_relative_path()) {
String base = get_path();
- if (base == "" || base.is_rel_path()) {
+ if (base == "" || base.is_relative_path()) {
ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data());
} else {
path = base.get_base_dir().plus_file(path);
@@ -2059,7 +2059,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
if (r_icon_path) {
if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
*r_icon_path = c->icon_path;
- } else if (c->icon_path.is_rel_path()) {
+ } else if (c->icon_path.is_relative_path()) {
*r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path();
}
}
@@ -2087,7 +2087,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
break;
}
String subpath = subclass->extends_path;
- if (subpath.is_rel_path()) {
+ if (subpath.is_relative_path()) {
subpath = path.get_base_dir().plus_file(subpath).simplify_path();
}
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 6b7403d854..6f76ca05dc 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -2727,7 +2727,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
} else {
p_preload->resolved_path = p_preload->path->reduced_value;
// TODO: Save this as script dependency.
- if (p_preload->resolved_path.is_rel_path()) {
+ if (p_preload->resolved_path.is_relative_path()) {
p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path);
}
p_preload->resolved_path = p_preload->resolved_path.simplify_path();
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 372a726d71..70e18c6e6c 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -731,9 +731,9 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
}
// Autoload singletons
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->get();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.get();
if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") {
continue;
}
@@ -1086,12 +1086,12 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool
kwa++;
}
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) {
- if (!E->value().is_singleton) {
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ if (!E.value().is_singleton) {
continue;
}
- ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ ScriptCodeCompletionOption option(E.key(), ScriptCodeCompletionOption::KIND_CONSTANT);
r_result.insert(option.display, option);
}
@@ -1359,12 +1359,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]);
found = true;
} else {
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- String name = E->key();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ String name = E.key();
if (name == which) {
- String script = E->value().path;
+ String script = E.value().path;
if (!script.begins_with("res://")) {
script = "res://" + script;
@@ -2660,10 +2660,10 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
// Get autoloads.
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- String path = "/root/" + E->key();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ String path = "/root/" + E.key();
ScriptCodeCompletionOption option(path.quote(quote_style), ScriptCodeCompletionOption::KIND_NODE_PATH);
options.insert(option.display, option);
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index af872e49e2..f4c721e00a 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -133,7 +133,7 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
// Networking.
- register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>, 4, true);
+ register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_AUTHORITY>, 4, true);
// TODO: Warning annotations.
}
@@ -2365,6 +2365,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
}
assignment->assignee = p_previous_operand;
assignment->assigned_value = parse_expression(false);
+ if (assignment->assigned_value == nullptr) {
+ push_error(R"(Expected an expression after "=".)");
+ }
#ifdef DEBUG_ENABLED
if (has_operator && source_variable != nullptr && source_variable->assignments == 0) {
@@ -2377,7 +2380,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) {
AwaitNode *await = alloc_node<AwaitNode>();
- await->to_await = parse_precedence(PREC_AWAIT, false);
+ ExpressionNode *element = parse_precedence(PREC_AWAIT, false);
+ if (element == nullptr) {
+ push_error(R"(Expected signal or coroutine after "await".)");
+ }
+ await->to_await = element;
current_function->is_coroutine = true;
@@ -3392,43 +3399,35 @@ bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Nod
MultiplayerAPI::RPCConfig rpc_config;
rpc_config.rpc_mode = t_mode;
- for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
- if (i == 0) {
+ if (p_annotation->resolved_arguments.size()) {
+ int last = p_annotation->resolved_arguments.size() - 1;
+ if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) {
+ rpc_config.channel = p_annotation->resolved_arguments[last].operator int();
+ last -= 1;
+ }
+ if (last > 3) {
+ push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation);
+ return false;
+ }
+ for (int i = last; i >= 0; i--) {
String mode = p_annotation->resolved_arguments[i].operator String();
if (mode == "any") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE;
- } else if (mode == "master") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_MASTER;
- } else if (mode == "puppet") {
- rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_PUPPET;
- } else {
- push_error(R"(Invalid RPC mode. Must be one of: 'any', 'master', or 'puppet')", p_annotation);
- return false;
- }
- } else if (i == 1) {
- String sync = p_annotation->resolved_arguments[i].operator String();
- if (sync == "sync") {
+ rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_ANY;
+ } else if (mode == "auth") {
+ rpc_config.rpc_mode = MultiplayerAPI::RPC_MODE_AUTHORITY;
+ } else if (mode == "sync") {
rpc_config.sync = true;
- } else if (sync == "nosync") {
+ } else if (mode == "nosync") {
rpc_config.sync = false;
- } else {
- push_error(R"(Invalid RPC sync mode. Must be one of: 'sync' or 'nosync')", p_annotation);
- return false;
- }
- } else if (i == 2) {
- String mode = p_annotation->resolved_arguments[i].operator String();
- if (mode == "reliable") {
+ } else if (mode == "reliable") {
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
} else if (mode == "unreliable") {
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
} else if (mode == "ordered") {
rpc_config.transfer_mode = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
} else {
- push_error(R"(Invalid RPC transfer mode. Must be one of: 'reliable', 'unreliable', 'ordered')", p_annotation);
- return false;
+ push_error(R"(Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).)", p_annotation);
}
- } else if (i == 3) {
- rpc_config.channel = p_annotation->resolved_arguments[i].operator int();
}
}
switch (p_node->type) {
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 03a48bf071..6225e5d1eb 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -48,11 +48,11 @@
namespace GDScriptTests {
void init_autoloads() {
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
// First pass, add the constants so they exist before any script is loaded.
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->get();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.get();
if (info.is_singleton) {
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
@@ -62,8 +62,8 @@ void init_autoloads() {
}
// Second pass, load into global constants.
- for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
- const ProjectSettings::AutoloadInfo &info = E->get();
+ for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) {
+ const ProjectSettings::AutoloadInfo &info = E.get();
if (!info.is_singleton) {
// Skip non-singletons since we don't have a scene tree here anyway.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out
index dc4348d9c3..2c8cc9c03f 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out
@@ -1,3 +1,3 @@
GDTEST_OK
Name is equal
-True
+true
diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out
index 75b002aa62..ad6beeb646 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out
@@ -1,3 +1,3 @@
GDTEST_OK
-True
+true
OK
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd
new file mode 100644
index 0000000000..17c65ad60a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd
@@ -0,0 +1,3 @@
+func test():
+ var a = 0
+ a =
diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out
new file mode 100644
index 0000000000..1369a7a0dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected an expression after "=".
diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd
new file mode 100644
index 0000000000..9e48a7f0da
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd
@@ -0,0 +1,7 @@
+func test():
+ var null_var = null
+ var true_var: bool = true
+ var false_var: bool = false
+ print(str(null_var))
+ print(str(true_var))
+ print(str(false_var))
diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out
new file mode 100644
index 0000000000..abba38e87c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+null
+true
+false
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 2cc974322d..fb741f6266 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -37,11 +37,13 @@
#include "core/io/file_access.h"
-void AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
- ERR_FAIL_COND(!active);
+int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+ ERR_FAIL_COND_V(!active, 0);
int todo = p_frames;
+ int frames_mixed_this_step = p_frames;
+
while (todo && active) {
mp3dec_frame_info_t frame_info;
mp3d_sample_t *buf_frame = nullptr;
@@ -60,6 +62,7 @@ void AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
seek(mp3_stream->loop_offset);
loops++;
} else {
+ frames_mixed_this_step = p_frames - todo;
//fill remainder with silence
for (int i = p_frames - todo; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
@@ -69,6 +72,7 @@ void AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
}
}
}
+ return frames_mixed_this_step;
}
float AudioStreamPlaybackMP3::get_stream_sampling_rate() {
diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h
index ce001fc418..5dd88779f8 100644
--- a/modules/minimp3/audio_stream_mp3.h
+++ b/modules/minimp3/audio_stream_mp3.h
@@ -51,7 +51,7 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled {
Ref<AudioStreamMP3> mp3_stream;
protected:
- virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override;
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
virtual float get_stream_sampling_rate() override;
public:
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 1f7f1390ea..978093b7d5 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -3466,13 +3466,10 @@ int CSharpScript::get_member_line(const StringName &p_member) const {
MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const {
if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) {
- return MultiplayerAPI::RPC_MODE_REMOTE;
- }
- if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) {
- return MultiplayerAPI::RPC_MODE_MASTER;
+ return MultiplayerAPI::RPC_MODE_ANY;
}
if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) {
- return MultiplayerAPI::RPC_MODE_PUPPET;
+ return MultiplayerAPI::RPC_MODE_AUTHORITY;
}
return MultiplayerAPI::RPC_MODE_DISABLED;
diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp
index b7b36a92d8..d911f6461c 100644
--- a/modules/mono/editor/code_completion.cpp
+++ b/modules/mono/editor/code_completion.cpp
@@ -121,7 +121,7 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr
case CompletionKind::NODE_PATHS: {
{
// AutoLoads
- Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+ OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) {
const ProjectSettings::AutoloadInfo &info = E.value;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
index 6cec8773b2..8aaa9e849d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
@@ -6,8 +6,5 @@ namespace Godot
public class RemoteAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method)]
- public class MasterAttribute : Attribute {}
-
- [AttributeUsage(AttributeTargets.Method)]
public class PuppetAttribute : Attribute {}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
index 213f84ad73..c8dd0ac9ce 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
@@ -110,19 +110,6 @@ namespace Godot
}
/// <summary>
- /// Converts a 2D point expressed in the cartesian coordinate
- /// system (X and Y axis) to the polar coordinate system
- /// (a distance from the origin and an angle).
- /// </summary>
- /// <param name="x">The input X coordinate.</param>
- /// <param name="y">The input Y coordinate.</param>
- /// <returns>A <see cref="Vector2"/> with X representing the distance and Y representing the angle.</returns>
- public static Vector2 Cartesian2Polar(real_t x, real_t y)
- {
- return new Vector2(Sqrt(x * x + y * y), Atan2(y, x));
- }
-
- /// <summary>
/// Rounds `s` upward (towards positive infinity).
/// </summary>
/// <param name="s">The number to ceil.</param>
@@ -436,19 +423,6 @@ namespace Godot
}
/// <summary>
- /// Converts a 2D point expressed in the polar coordinate
- /// system (a distance from the origin `r` and an angle `th`)
- /// to the cartesian coordinate system (X and Y axis).
- /// </summary>
- /// <param name="r">The distance from the origin.</param>
- /// <param name="th">The angle of the point.</param>
- /// <returns>A <see cref="Vector2"/> representing the cartesian coordinate.</returns>
- public static Vector2 Polar2Cartesian(real_t r, real_t th)
- {
- return new Vector2(r * Cos(th), r * Sin(th));
- }
-
- /// <summary>
/// Performs a canonical Modulus operation, where the output is on the range `[0, b)`.
/// </summary>
/// <param name="a">The dividend, the primary input.</param>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index e619ad74e9..bc598248bc 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
@@ -588,7 +588,7 @@ namespace Godot
/// <summary>
/// If the string is a path to a file or directory, return <see langword="true"/> if the path is relative.
/// </summary>
- public static bool IsRelPath(this string instance)
+ public static bool IsRelativePath(this string instance)
{
return !IsAbsolutePath(instance);
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
index 8bb5e90a68..b4c4ddabb9 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
@@ -603,6 +603,17 @@ namespace Godot
y = v.y;
}
+ /// <summary>
+ /// Creates a unit Vector2 rotated to the given angle. This is equivalent to doing
+ /// `Vector2(Mathf.Cos(angle), Mathf.Sin(angle))` or `Vector2.Right.Rotated(angle)`.
+ /// </summary>
+ /// <param name="angle">Angle of the vector, in radians.</param>
+ /// <returns>The resulting vector.</returns>
+ public static Vector2 FromAngle(real_t angle)
+ {
+ return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
+ }
+
public static Vector2 operator +(Vector2 left, Vector2 right)
{
left.x += right.x;
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index 0b36796d98..8b215a66c2 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -141,7 +141,6 @@ void CachedData::clear_godot_api_cache() {
class_SignalAttribute = nullptr;
class_ToolAttribute = nullptr;
class_RemoteAttribute = nullptr;
- class_MasterAttribute = nullptr;
class_PuppetAttribute = nullptr;
class_GodotMethodAttribute = nullptr;
field_GodotMethodAttribute_methodName = nullptr;
@@ -267,7 +266,6 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute));
CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute));
CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute));
- CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute));
CACHE_CLASS_AND_CHECK(PuppetAttribute, GODOT_API_CLASS(PuppetAttribute));
CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute));
CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName"));
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 6d49da0af3..fd28bbda14 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -112,7 +112,6 @@ struct CachedData {
GDMonoClass *class_SignalAttribute;
GDMonoClass *class_ToolAttribute;
GDMonoClass *class_RemoteAttribute;
- GDMonoClass *class_MasterAttribute;
GDMonoClass *class_PuppetAttribute;
GDMonoClass *class_GodotMethodAttribute;
GDMonoField *field_GodotMethodAttribute_methodName;
diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp
index 768b419348..3a938200e9 100644
--- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp
@@ -32,13 +32,15 @@
#include "core/io/file_access.h"
-void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) {
- ERR_FAIL_COND(!active);
+int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+ ERR_FAIL_COND_V(!active, 0);
int todo = p_frames;
int start_buffer = 0;
+ int frames_mixed_this_step = p_frames;
+
while (todo && active) {
float *buffer = (float *)p_buffer;
if (start_buffer > 0) {
@@ -64,6 +66,7 @@ void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_fra
// we still have buffer to fill, start from this element in the next iteration.
start_buffer = p_frames - todo;
} else {
+ frames_mixed_this_step = p_frames - todo;
for (int i = p_frames - todo; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
@@ -72,6 +75,7 @@ void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_fra
}
}
}
+ return frames_mixed_this_step;
}
float AudioStreamPlaybackOGGVorbis::get_stream_sampling_rate() {
diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.h b/modules/stb_vorbis/audio_stream_ogg_vorbis.h
index 2bd70a2722..756c241d1f 100644
--- a/modules/stb_vorbis/audio_stream_ogg_vorbis.h
+++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.h
@@ -52,7 +52,7 @@ class AudioStreamPlaybackOGGVorbis : public AudioStreamPlaybackResampled {
Ref<AudioStreamOGGVorbis> vorbis_stream;
protected:
- virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override;
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
virtual float get_stream_sampling_rate() override;
public:
diff --git a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml
index 195d766c1d..55d0b392fa 100644
--- a/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml
+++ b/modules/visual_script/doc_classes/VisualScriptBuiltinFunc.xml
@@ -138,81 +138,75 @@
<constant name="MATH_DB2LINEAR" value="40" enum="BuiltinFunc">
Convert the input from decibel volume to linear volume.
</constant>
- <constant name="MATH_POLAR2CARTESIAN" value="41" enum="BuiltinFunc">
- Converts a 2D point expressed in the polar coordinate system (a distance from the origin [code]r[/code] and an angle [code]th[/code]) to the cartesian coordinate system (X and Y axis).
+ <constant name="MATH_WRAP" value="41" enum="BuiltinFunc">
</constant>
- <constant name="MATH_CARTESIAN2POLAR" value="42" enum="BuiltinFunc">
- Converts a 2D point expressed in the cartesian coordinate system (X and Y axis) to the polar coordinate system (a distance from the origin and an angle).
+ <constant name="MATH_WRAPF" value="42" enum="BuiltinFunc">
</constant>
- <constant name="MATH_WRAP" value="43" enum="BuiltinFunc">
- </constant>
- <constant name="MATH_WRAPF" value="44" enum="BuiltinFunc">
- </constant>
- <constant name="LOGIC_MAX" value="45" enum="BuiltinFunc">
+ <constant name="LOGIC_MAX" value="43" enum="BuiltinFunc">
Return the greater of the two numbers, also known as their maximum.
</constant>
- <constant name="LOGIC_MIN" value="46" enum="BuiltinFunc">
+ <constant name="LOGIC_MIN" value="44" enum="BuiltinFunc">
Return the lesser of the two numbers, also known as their minimum.
</constant>
- <constant name="LOGIC_CLAMP" value="47" enum="BuiltinFunc">
+ <constant name="LOGIC_CLAMP" value="45" enum="BuiltinFunc">
Return the input clamped inside the given range, ensuring the result is never outside it. Equivalent to [code]min(max(input, range_low), range_high)[/code].
</constant>
- <constant name="LOGIC_NEAREST_PO2" value="48" enum="BuiltinFunc">
+ <constant name="LOGIC_NEAREST_PO2" value="46" enum="BuiltinFunc">
Return the nearest power of 2 to the input.
</constant>
- <constant name="OBJ_WEAKREF" value="49" enum="BuiltinFunc">
+ <constant name="OBJ_WEAKREF" value="47" enum="BuiltinFunc">
Create a [WeakRef] from the input.
</constant>
- <constant name="TYPE_CONVERT" value="50" enum="BuiltinFunc">
+ <constant name="TYPE_CONVERT" value="48" enum="BuiltinFunc">
Convert between types.
</constant>
- <constant name="TYPE_OF" value="51" enum="BuiltinFunc">
+ <constant name="TYPE_OF" value="49" enum="BuiltinFunc">
Return the type of the input as an integer. Check [enum Variant.Type] for the integers that might be returned.
</constant>
- <constant name="TYPE_EXISTS" value="52" enum="BuiltinFunc">
+ <constant name="TYPE_EXISTS" value="50" enum="BuiltinFunc">
Checks if a type is registered in the [ClassDB].
</constant>
- <constant name="TEXT_CHAR" value="53" enum="BuiltinFunc">
+ <constant name="TEXT_CHAR" value="51" enum="BuiltinFunc">
Return a character with the given ascii value.
</constant>
- <constant name="TEXT_STR" value="54" enum="BuiltinFunc">
+ <constant name="TEXT_STR" value="52" enum="BuiltinFunc">
Convert the input to a string.
</constant>
- <constant name="TEXT_PRINT" value="55" enum="BuiltinFunc">
+ <constant name="TEXT_PRINT" value="53" enum="BuiltinFunc">
Print the given string to the output window.
</constant>
- <constant name="TEXT_PRINTERR" value="56" enum="BuiltinFunc">
+ <constant name="TEXT_PRINTERR" value="54" enum="BuiltinFunc">
Print the given string to the standard error output.
</constant>
- <constant name="TEXT_PRINTRAW" value="57" enum="BuiltinFunc">
+ <constant name="TEXT_PRINTRAW" value="55" enum="BuiltinFunc">
Print the given string to the standard output, without adding a newline.
</constant>
- <constant name="VAR_TO_STR" value="58" enum="BuiltinFunc">
+ <constant name="VAR_TO_STR" value="56" enum="BuiltinFunc">
Serialize a [Variant] to a string.
</constant>
- <constant name="STR_TO_VAR" value="59" enum="BuiltinFunc">
+ <constant name="STR_TO_VAR" value="57" enum="BuiltinFunc">
Deserialize a [Variant] from a string serialized using [constant VAR_TO_STR].
</constant>
- <constant name="VAR_TO_BYTES" value="60" enum="BuiltinFunc">
+ <constant name="VAR_TO_BYTES" value="58" enum="BuiltinFunc">
Serialize a [Variant] to a [PackedByteArray].
</constant>
- <constant name="BYTES_TO_VAR" value="61" enum="BuiltinFunc">
+ <constant name="BYTES_TO_VAR" value="59" enum="BuiltinFunc">
Deserialize a [Variant] from a [PackedByteArray] serialized using [constant VAR_TO_BYTES].
</constant>
- <constant name="MATH_SMOOTHSTEP" value="62" enum="BuiltinFunc">
+ <constant name="MATH_SMOOTHSTEP" value="60" enum="BuiltinFunc">
Return a number smoothly interpolated between the first two inputs, based on the third input. Similar to [constant MATH_LERP], but interpolates faster at the beginning and slower at the end. Using Hermite interpolation formula:
[codeblock]
var t = clamp((weight - from) / (to - from), 0.0, 1.0)
return t * t * (3.0 - 2.0 * t)
[/codeblock]
</constant>
- <constant name="MATH_POSMOD" value="63" enum="BuiltinFunc">
+ <constant name="MATH_POSMOD" value="61" enum="BuiltinFunc">
</constant>
- <constant name="MATH_LERP_ANGLE" value="64" enum="BuiltinFunc">
+ <constant name="MATH_LERP_ANGLE" value="62" enum="BuiltinFunc">
</constant>
- <constant name="TEXT_ORD" value="65" enum="BuiltinFunc">
+ <constant name="TEXT_ORD" value="63" enum="BuiltinFunc">
</constant>
- <constant name="FUNC_MAX" value="66" enum="BuiltinFunc">
+ <constant name="FUNC_MAX" value="64" enum="BuiltinFunc">
Represents the size of the [enum BuiltinFunc] enum.
</constant>
</constants>
diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp
index c61c3ae272..2bd7220d15 100644
--- a/modules/visual_script/visual_script_builtin_funcs.cpp
+++ b/modules/visual_script/visual_script_builtin_funcs.cpp
@@ -79,8 +79,6 @@ const char *VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX
"rad2deg",
"linear2db",
"db2linear",
- "polar2cartesian",
- "cartesian2polar",
"wrapi",
"wrapf",
"max",
@@ -194,8 +192,6 @@ int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) {
case MATH_SNAPPED:
case MATH_RANDF_RANGE:
case MATH_RANDI_RANGE:
- case MATH_POLAR2CARTESIAN:
- case MATH_CARTESIAN2POLAR:
case LOGIC_MAX:
case LOGIC_MIN:
case TYPE_CONVERT:
@@ -381,20 +377,6 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const
case MATH_DB2LINEAR: {
return PropertyInfo(Variant::FLOAT, "db");
} break;
- case MATH_POLAR2CARTESIAN: {
- if (p_idx == 0) {
- return PropertyInfo(Variant::FLOAT, "r");
- } else {
- return PropertyInfo(Variant::FLOAT, "th");
- }
- } break;
- case MATH_CARTESIAN2POLAR: {
- if (p_idx == 0) {
- return PropertyInfo(Variant::FLOAT, "x");
- } else {
- return PropertyInfo(Variant::FLOAT, "y");
- }
- } break;
case MATH_WRAP: {
if (p_idx == 0) {
return PropertyInfo(Variant::INT, "value");
@@ -553,10 +535,6 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons
case MATH_DB2LINEAR: {
t = Variant::FLOAT;
} break;
- case MATH_POLAR2CARTESIAN:
- case MATH_CARTESIAN2POLAR: {
- t = Variant::VECTOR2;
- } break;
case MATH_WRAP: {
t = Variant::INT;
} break;
@@ -874,20 +852,6 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in
VALIDATE_ARG_NUM(0);
*r_return = Math::db2linear((double)*p_inputs[0]);
} break;
- case VisualScriptBuiltinFunc::MATH_POLAR2CARTESIAN: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
- double r = *p_inputs[0];
- double th = *p_inputs[1];
- *r_return = Vector2(r * Math::cos(th), r * Math::sin(th));
- } break;
- case VisualScriptBuiltinFunc::MATH_CARTESIAN2POLAR: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
- double x = *p_inputs[0];
- double y = *p_inputs[1];
- *r_return = Vector2(Math::sqrt(x * x + y * y), Math::atan2(y, x));
- } break;
case VisualScriptBuiltinFunc::MATH_WRAP: {
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
@@ -1229,8 +1193,6 @@ void VisualScriptBuiltinFunc::_bind_methods() {
BIND_ENUM_CONSTANT(MATH_RAD2DEG);
BIND_ENUM_CONSTANT(MATH_LINEAR2DB);
BIND_ENUM_CONSTANT(MATH_DB2LINEAR);
- BIND_ENUM_CONSTANT(MATH_POLAR2CARTESIAN);
- BIND_ENUM_CONSTANT(MATH_CARTESIAN2POLAR);
BIND_ENUM_CONSTANT(MATH_WRAP);
BIND_ENUM_CONSTANT(MATH_WRAPF);
BIND_ENUM_CONSTANT(LOGIC_MAX);
@@ -1320,8 +1282,6 @@ void register_visual_script_builtin_func_node() {
VisualScriptLanguage::singleton->add_register_func("functions/built_in/rad2deg", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_RAD2DEG>);
VisualScriptLanguage::singleton->add_register_func("functions/built_in/linear2db", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_LINEAR2DB>);
VisualScriptLanguage::singleton->add_register_func("functions/built_in/db2linear", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DB2LINEAR>);
- VisualScriptLanguage::singleton->add_register_func("functions/built_in/polar2cartesian", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_POLAR2CARTESIAN>);
- VisualScriptLanguage::singleton->add_register_func("functions/built_in/cartesian2polar", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_CARTESIAN2POLAR>);
VisualScriptLanguage::singleton->add_register_func("functions/built_in/wrapi", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_WRAP>);
VisualScriptLanguage::singleton->add_register_func("functions/built_in/wrapf", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_WRAPF>);
diff --git a/modules/visual_script/visual_script_builtin_funcs.h b/modules/visual_script/visual_script_builtin_funcs.h
index f59a7a0f0c..26abc1e479 100644
--- a/modules/visual_script/visual_script_builtin_funcs.h
+++ b/modules/visual_script/visual_script_builtin_funcs.h
@@ -79,8 +79,6 @@ public:
MATH_RAD2DEG,
MATH_LINEAR2DB,
MATH_DB2LINEAR,
- MATH_POLAR2CARTESIAN,
- MATH_CARTESIAN2POLAR,
MATH_WRAP,
MATH_WRAPF,
LOGIC_MAX,
diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h
index 7dfb4fa270..ab32aae7aa 100644
--- a/modules/visual_script/visual_script_editor.h
+++ b/modules/visual_script/visual_script_editor.h
@@ -311,6 +311,8 @@ public:
virtual void tag_saved_version() override;
virtual void reload(bool p_soft) override;
virtual Array get_breakpoints() override;
+ virtual void set_breakpoint(int p_line, bool p_enable) override{};
+ virtual void clear_breakpoints() override{};
virtual void add_callback(const String &p_function, PackedStringArray p_args) override;
virtual void update_settings() override;
virtual bool show_members_overview() override;
diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp
index 480136736a..44fc91d8f5 100644
--- a/modules/visual_script/visual_script_nodes.cpp
+++ b/modules/visual_script/visual_script_nodes.cpp
@@ -163,7 +163,7 @@ void VisualScriptFunction::_get_property_list(List<PropertyInfo> *p_list) const
p_list->push_back(PropertyInfo(Variant::INT, "stack/size", PROPERTY_HINT_RANGE, "1,100000"));
}
p_list->push_back(PropertyInfo(Variant::BOOL, "stack/stackless"));
- p_list->push_back(PropertyInfo(Variant::INT, "rpc/mode", PROPERTY_HINT_ENUM, "Disabled,Remote,Master,Puppet,Remote Sync,Master Sync,Puppet Sync"));
+ p_list->push_back(PropertyInfo(Variant::INT, "rpc/mode", PROPERTY_HINT_ENUM, "Disabled,Any,Authority"));
}
int VisualScriptFunction::get_output_sequence_port_count() const {
diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp
index 49997b42d3..26c0176ea4 100644
--- a/modules/websocket/wsl_client.cpp
+++ b/modules/websocket/wsl_client.cpp
@@ -161,22 +161,28 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER);
_peer = Ref<WSLPeer>(memnew(WSLPeer));
- IPAddress addr;
- if (!p_host.is_valid_ip_address()) {
- addr = IP::get_singleton()->resolve_hostname(p_host);
+ if (p_host.is_valid_ip_address()) {
+ ip_candidates.clear();
+ ip_candidates.push_back(IPAddress(p_host));
} else {
- addr = p_host;
+ ip_candidates = IP::get_singleton()->resolve_hostname_addresses(p_host);
}
- ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(ip_candidates.is_empty(), ERR_INVALID_PARAMETER);
String port = "";
if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) {
port = ":" + itos(p_port);
}
- Error err = _tcp->connect_to_host(addr, p_port);
+ Error err = ERR_BUG; // Should be at least one entry.
+ while (ip_candidates.size() > 0) {
+ err = _tcp->connect_to_host(ip_candidates.pop_front(), p_port);
+ if (err == OK) {
+ break;
+ }
+ }
if (err != OK) {
_tcp->disconnect_from_host();
_on_error();
@@ -185,6 +191,7 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
_connection = _tcp;
_use_ssl = p_ssl;
_host = p_host;
+ _port = p_port;
// Strip edges from protocols.
_protocols.resize(p_protocols.size());
String *pw = _protocols.ptrw();
@@ -244,6 +251,7 @@ void WSLClient::poll() {
_on_error();
break;
case StreamPeerTCP::STATUS_CONNECTED: {
+ ip_candidates.clear();
Ref<StreamPeerSSL> ssl;
if (_use_ssl) {
if (_connection == _tcp) {
@@ -274,6 +282,12 @@ void WSLClient::poll() {
_do_handshake();
} break;
case StreamPeerTCP::STATUS_ERROR:
+ while (ip_candidates.size() > 0) {
+ _tcp->disconnect_from_host();
+ if (_tcp->connect_to_host(ip_candidates.pop_front(), _port) == OK) {
+ return;
+ }
+ }
disconnect_from_host();
_on_error();
break;
@@ -315,6 +329,8 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) {
memset(_resp_buf, 0, sizeof(_resp_buf));
_resp_pos = 0;
+
+ ip_candidates.clear();
}
IPAddress WSLClient::get_connected_host() const {
diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h
index 849639ee8b..3972977910 100644
--- a/modules/websocket/wsl_client.h
+++ b/modules/websocket/wsl_client.h
@@ -63,6 +63,8 @@ private:
String _key;
String _host;
+ int _port;
+ Array ip_candidates;
Vector<String> _protocols;
bool _use_ssl = false;
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 0bae090702..0eeee8215d 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -161,7 +161,7 @@ bool DirAccessJAndroid::dir_exists(String p_dir) {
if (current_dir == "")
sd = p_dir;
else {
- if (p_dir.is_rel_path())
+ if (p_dir.is_relative_path())
sd = current_dir.plus_file(p_dir);
else
sd = fix_path(p_dir);
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index b220b64df1..6b4a326ba7 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -2614,7 +2614,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
String export_filename = p_path.get_file();
String export_path = p_path.get_base_dir();
- if (export_path.is_rel_path()) {
+ if (export_path.is_relative_path()) {
export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path);
}
export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 896b169953..70bc73b9ad 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -295,7 +295,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
View pluginView = plugin.onMainCreate(activity);
if (pluginView != null) {
- containerLayout.addView(pluginView);
+ if (plugin.shouldBeOnTop()) {
+ containerLayout.addView(pluginView);
+ } else {
+ containerLayout.addView(pluginView, 0);
+ }
}
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
index 2dc8359615..4536c21ed3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -191,6 +191,9 @@ public abstract class GodotPlugin {
* The plugin can return a non-null {@link View} layout in order to add it to the Godot view
* hierarchy.
*
+ * Use shouldBeOnTop() to set whether the plugin's {@link View} should be added on top or behind
+ * the main Godot view.
+ *
* @see Activity#onCreate(Bundle)
* @return the plugin's view to be included; null if no views should be included.
*/
@@ -311,6 +314,17 @@ public abstract class GodotPlugin {
}
/**
+ * Returns whether the plugin's {@link View} returned in onMainCreate() should be placed on
+ * top of the main Godot view.
+ *
+ * Returning false causes the plugin's {@link View} to be placed behind, which can be useful
+ * when used with transparency in order to let the Godot view handle inputs.
+ */
+ public boolean shouldBeOnTop() {
+ return true;
+ }
+
+ /**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp
index 54f541f607..c50195639c 100644
--- a/platform/javascript/api/javascript_tools_editor_plugin.cpp
+++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp
@@ -35,6 +35,7 @@
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
+#include "core/os/time.h"
#include "editor/editor_node.h"
#include <emscripten/emscripten.h>
@@ -58,23 +59,40 @@ JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) {
void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) {
if (!Engine::get_singleton() || !Engine::get_singleton()->is_editor_hint()) {
- WARN_PRINT("Project download is only available in Editor mode");
+ ERR_PRINT("Downloading the project as a ZIP archive is only available in Editor mode.");
return;
}
String resource_path = ProjectSettings::get_singleton()->get_resource_path();
FileAccess *src_f;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
- zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, nullptr, &io);
- String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/";
+
+ // Name the downloded ZIP file to contain the project name and download date for easier organization.
+ // Replace characters not allowed (or risky) in Windows file names with safe characters.
+ // In the project name, all invalid characters become an empty string so that a name
+ // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge".
+ const String project_name_safe =
+ GLOBAL_GET("application/config/name").to_lower().replace(" ", "_");
+ const String datetime_safe =
+ Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_");
+ const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip"));
+ const String output_path = String("/tmp").plus_file(output_name);
+
+ zipFile zip = zipOpen2(output_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io);
+ const String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/";
_zip_recursive(resource_path, base_path, zip);
zipClose(zip, nullptr);
- FileAccess *f = FileAccess::open("/tmp/project.zip", FileAccess::READ);
- ERR_FAIL_COND_MSG(!f, "Unable to create zip file");
+ FileAccess *f = FileAccess::open(output_path, FileAccess::READ);
+ ERR_FAIL_COND_MSG(!f, "Unable to create ZIP file.");
Vector<uint8_t> buf;
buf.resize(f->get_length());
f->get_buffer(buf.ptrw(), buf.size());
- godot_js_os_download_buffer(buf.ptr(), buf.size(), "project.zip", "application/zip");
+ godot_js_os_download_buffer(buf.ptr(), buf.size(), output_name.utf8().get_data(), "application/zip");
+
+ f->close();
+ memdelete(f);
+ // Remove the temporary file since it was sent to the user's native filesystem as a download.
+ DirAccess::remove_file_or_error(output_path);
}
void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) {
@@ -108,7 +126,7 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z
void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) {
DirAccess *dir = DirAccess::open(p_path);
if (!dir) {
- WARN_PRINT("Unable to open dir for zipping: " + p_path);
+ WARN_PRINT("Unable to open directory for zipping: " + p_path);
return;
}
dir->list_dir_begin();
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index 8a4d42fd1f..ea491e8b0e 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -33,125 +33,17 @@
#include "scene/2d/area_2d.h"
#include "scene/main/window.h"
-void AudioStreamPlayer2D::_mix_audio() {
- if (!stream_playback.is_valid() || !active.is_set() ||
- (stream_paused && !stream_paused_fade_out)) {
- return;
- }
-
- if (setseek.get() >= 0.0) {
- stream_playback->start(setseek.get());
- setseek.set(-1.0); //reset seek
- }
-
- //get data
- AudioFrame *buffer = mix_buffer.ptrw();
- int buffer_size = mix_buffer.size();
-
- if (stream_paused_fade_out) {
- // Short fadeout ramp
- buffer_size = MIN(buffer_size, 128);
- }
-
- stream_playback->mix(buffer, pitch_scale, buffer_size);
-
- //write all outputs
- int oc = output_count.get();
- for (int i = 0; i < oc; i++) {
- Output current = outputs[i];
-
- //see if current output exists, to keep volume ramp
- bool found = false;
- for (int j = i; j < prev_output_count; j++) {
- if (prev_outputs[j].viewport == current.viewport) {
- if (j != i) {
- SWAP(prev_outputs[j], prev_outputs[i]);
- }
- found = true;
- break;
- }
- }
-
- if (!found) {
- //create new if was not used before
- if (prev_output_count < MAX_OUTPUTS) {
- prev_outputs[prev_output_count] = prev_outputs[i]; //may be owned by another viewport
- prev_output_count++;
- }
- prev_outputs[i] = current;
- }
-
- //mix!
- AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol;
- AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol;
- AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size);
- AudioFrame vol = vol_prev;
-
- int cc = AudioServer::get_singleton()->get_channel_count();
-
- if (cc == 1) {
- if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, 0)) {
- continue; //may have been removed
- }
-
- AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, 0);
-
- for (int j = 0; j < buffer_size; j++) {
- target[j] += buffer[j] * vol;
- vol += vol_inc;
- }
-
- } else {
- AudioFrame *targets[4];
- bool valid = true;
-
- for (int k = 0; k < cc; k++) {
- if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, k)) {
- valid = false; //may have been removed
- break;
- }
-
- targets[k] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k);
- }
-
- if (!valid) {
- continue;
- }
-
- for (int j = 0; j < buffer_size; j++) {
- AudioFrame frame = buffer[j] * vol;
- for (int k = 0; k < cc; k++) {
- targets[k][j] += frame;
- }
- vol += vol_inc;
- }
- }
-
- prev_outputs[i] = current;
- }
-
- prev_output_count = oc;
-
- //stream is no longer active, disable this.
- if (!stream_playback->is_playing()) {
- active.clear();
- }
-
- output_ready.clear();
- stream_paused_fade_in = false;
- stream_paused_fade_out = false;
-}
-
void AudioStreamPlayer2D::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) {
- AudioServer::get_singleton()->add_callback(_mix_audios, this);
+ AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this);
if (autoplay && !Engine::get_singleton()->is_editor_hint()) {
play();
}
}
if (p_what == NOTIFICATION_EXIT_TREE) {
- AudioServer::get_singleton()->remove_callback(_mix_audios, this);
+ stop();
+ AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this);
}
if (p_what == NOTIFICATION_PAUSED) {
@@ -168,109 +60,120 @@ void AudioStreamPlayer2D::_notification(int p_what) {
if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
//update anything related to position first, if possible of course
- if (!output_ready.is_set()) {
- Ref<World2D> world_2d = get_world_2d();
- ERR_FAIL_COND(world_2d.is_null());
+ if (!stream_playback.is_valid()) {
+ return;
+ }
+ if (setplay.get() >= 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) {
+ _update_panning();
+ if (setplay.get() >= 0) {
+ active.set();
+ AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get());
+ setplay.set(-1);
+ }
+ }
+
+ // Stop playing if no longer active.
+ if (active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) {
+ active.clear();
+ set_physics_process_internal(false);
+ emit_signal(SNAME("finished"));
+ }
+ }
+}
- int new_output_count = 0;
+StringName AudioStreamPlayer2D::_get_actual_bus() {
+ if (!stream_playback.is_valid()) {
+ return SNAME("Master");
+ }
- Vector2 global_pos = get_global_position();
+ Vector2 global_pos = get_global_position();
- int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus);
+ //check if any area is diverting sound into a bus
+ Ref<World2D> world_2d = get_world_2d();
+ ERR_FAIL_COND_V(world_2d.is_null(), SNAME("Master"));
- //check if any area is diverting sound into a bus
+ PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
+ PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS];
- PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
+ int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true);
- PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS];
+ for (int i = 0; i < areas; i++) {
+ Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider);
+ if (!area2d) {
+ continue;
+ }
- int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true);
+ if (!area2d->is_overriding_audio_bus()) {
+ continue;
+ }
- for (int i = 0; i < areas; i++) {
- Area2D *area2d = Object::cast_to<Area2D>(sr[i].collider);
- if (!area2d) {
- continue;
- }
+ return area2d->get_audio_bus_name();
+ }
+ return default_bus;
+}
- if (!area2d->is_overriding_audio_bus()) {
- continue;
- }
+void AudioStreamPlayer2D::_update_panning() {
+ if (!stream_playback.is_valid()) {
+ return;
+ }
- StringName bus_name = area2d->get_audio_bus_name();
- bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name);
- break;
- }
+ last_mix_count = AudioServer::get_singleton()->get_mix_count();
- const Set<Viewport *> viewports = world_2d->get_viewports();
+ Ref<World2D> world_2d = get_world_2d();
+ ERR_FAIL_COND(world_2d.is_null());
- for (Set<Viewport *>::Element *E = viewports.front(); E; E = E->next()) {
- Viewport *vp = E->get();
- if (vp->is_audio_listener_2d()) {
- //compute matrix to convert to screen
- Transform2D to_screen = vp->get_global_canvas_transform() * vp->get_canvas_transform();
- Vector2 screen_size = vp->get_visible_rect().size;
+ Vector2 global_pos = get_global_position();
- //screen in global is used for attenuation
- Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5);
+ Set<Viewport *> viewports = world_2d->get_viewports();
+ viewports.insert(get_viewport()); // TODO: This is a mediocre workaround for #50958. Remove when that bug is fixed!
- float dist = global_pos.distance_to(screen_in_global); //distance to screen center
+ volume_vector.resize(4);
+ volume_vector.write[0] = AudioFrame(0, 0);
+ volume_vector.write[1] = AudioFrame(0, 0);
+ volume_vector.write[2] = AudioFrame(0, 0);
+ volume_vector.write[3] = AudioFrame(0, 0);
- if (dist > max_distance) {
- continue; //can't hear this sound in this viewport
- }
+ for (Viewport *vp : viewports) {
+ if (!vp->is_audio_listener_2d()) {
+ continue;
+ }
+ //compute matrix to convert to screen
+ Transform2D to_screen = vp->get_global_canvas_transform() * vp->get_canvas_transform();
+ Vector2 screen_size = vp->get_visible_rect().size;
- float multiplier = Math::pow(1.0f - dist / max_distance, attenuation);
- multiplier *= Math::db2linear(volume_db); //also apply player volume!
+ //screen in global is used for attenuation
+ Vector2 screen_in_global = to_screen.affine_inverse().xform(screen_size * 0.5);
- //point in screen is used for panning
- Vector2 point_in_screen = to_screen.xform(global_pos);
+ float dist = global_pos.distance_to(screen_in_global); //distance to screen center
- float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0);
+ if (dist > max_distance) {
+ continue; //can't hear this sound in this viewport
+ }
- float l = 1.0 - pan;
- float r = pan;
+ float multiplier = Math::pow(1.0f - dist / max_distance, attenuation);
+ multiplier *= Math::db2linear(volume_db); //also apply player volume!
- outputs[new_output_count].vol = AudioFrame(l, r) * multiplier;
- outputs[new_output_count].bus_index = bus_index;
- outputs[new_output_count].viewport = vp; //keep pointer only for reference
- new_output_count++;
- if (new_output_count == MAX_OUTPUTS) {
- break;
- }
- }
- }
+ //point in screen is used for panning
+ Vector2 point_in_screen = to_screen.xform(global_pos);
- output_count.set(new_output_count);
- output_ready.set();
- }
+ float pan = CLAMP(point_in_screen.x / screen_size.width, 0.0, 1.0);
- //start playing if requested
- if (setplay.get() >= 0.0) {
- setseek.set(setplay.get());
- active.set();
- setplay.set(-1);
- }
+ float l = 1.0 - pan;
+ float r = pan;
- //stop playing if no longer active
- if (!active.is_set()) {
- set_physics_process_internal(false);
- emit_signal(SNAME("finished"));
- }
+ volume_vector.write[0] = AudioFrame(l, r) * multiplier;
}
+
+ AudioServer::get_singleton()->set_playback_bus_exclusive(stream_playback, _get_actual_bus(), volume_vector);
}
void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) {
- AudioServer::get_singleton()->lock();
-
- mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size());
-
if (stream_playback.is_valid()) {
- stream_playback.unref();
- stream.unref();
- active.clear();
- setseek.set(-1);
+ stop();
}
+ stream_playback.unref();
+ stream.unref();
if (p_stream.is_valid()) {
stream_playback = p_stream->instance_playback();
if (stream_playback.is_valid()) {
@@ -280,8 +183,6 @@ void AudioStreamPlayer2D::set_stream(Ref<AudioStream> p_stream) {
}
}
- AudioServer::get_singleton()->unlock();
-
if (p_stream.is_valid() && stream_playback.is_null()) {
stream.unref();
}
@@ -302,6 +203,9 @@ float AudioStreamPlayer2D::get_volume_db() const {
void AudioStreamPlayer2D::set_pitch_scale(float p_pitch_scale) {
ERR_FAIL_COND(p_pitch_scale <= 0.0);
pitch_scale = p_pitch_scale;
+ if (stream_playback.is_valid()) {
+ AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, p_pitch_scale);
+ }
}
float AudioStreamPlayer2D::get_pitch_scale() const {
@@ -309,27 +213,26 @@ float AudioStreamPlayer2D::get_pitch_scale() const {
}
void AudioStreamPlayer2D::play(float p_from_pos) {
- if (!is_playing()) {
- // Reset the prev_output_count if the stream is stopped
- prev_output_count = 0;
+ stop();
+ if (stream.is_valid()) {
+ stream_playback = stream->instance_playback();
}
-
if (stream_playback.is_valid()) {
setplay.set(p_from_pos);
- output_ready.clear();
set_physics_process_internal(true);
}
}
void AudioStreamPlayer2D::seek(float p_seconds) {
- if (stream_playback.is_valid()) {
- setseek.set(p_seconds);
+ if (stream_playback.is_valid() && active.is_set()) {
+ play(p_seconds);
}
}
void AudioStreamPlayer2D::stop() {
if (stream_playback.is_valid()) {
active.clear();
+ AudioServer::get_singleton()->stop_playback_stream(stream_playback);
set_physics_process_internal(false);
setplay.set(-1);
}
@@ -337,7 +240,7 @@ void AudioStreamPlayer2D::stop() {
bool AudioStreamPlayer2D::is_playing() const {
if (stream_playback.is_valid()) {
- return active.is_set() || setplay.get() >= 0;
+ return AudioServer::get_singleton()->is_playback_active(stream_playback);
}
return false;
@@ -345,30 +248,23 @@ bool AudioStreamPlayer2D::is_playing() const {
float AudioStreamPlayer2D::get_playback_position() {
if (stream_playback.is_valid()) {
- float ss = setseek.get();
- if (ss >= 0.0) {
- return ss;
- }
- return stream_playback->get_playback_position();
+ return AudioServer::get_singleton()->get_playback_position(stream_playback);
}
return 0;
}
void AudioStreamPlayer2D::set_bus(const StringName &p_bus) {
- //if audio is active, must lock this
- AudioServer::get_singleton()->lock();
- bus = p_bus;
- AudioServer::get_singleton()->unlock();
+ default_bus = p_bus; // This will be pushed to the audio server during the next physics timestep, which is fast enough.
}
StringName AudioStreamPlayer2D::get_bus() const {
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {
- if (AudioServer::get_singleton()->get_bus_name(i) == bus) {
- return bus;
+ if (AudioServer::get_singleton()->get_bus_name(i) == default_bus) {
+ return default_bus;
}
}
- return "Master";
+ return SNAME("Master");
}
void AudioStreamPlayer2D::set_autoplay(bool p_enable) {
@@ -388,7 +284,11 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) {
}
bool AudioStreamPlayer2D::_is_active() const {
- return active.is_set();
+ if (stream_playback.is_valid()) {
+ // TODO make sure this doesn't change any behavior w.r.t. pauses. Is a paused stream active?
+ return AudioServer::get_singleton()->is_playback_active(stream_playback);
+ }
+ return false;
}
void AudioStreamPlayer2D::_validate_property(PropertyInfo &property) const {
@@ -436,15 +336,17 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const {
}
void AudioStreamPlayer2D::set_stream_paused(bool p_pause) {
- if (p_pause != stream_paused) {
- stream_paused = p_pause;
- stream_paused_fade_in = !p_pause;
- stream_paused_fade_out = p_pause;
+ // TODO this does not have perfect recall, fix that maybe? If the stream isn't set, we can't persist this bool.
+ if (stream_playback.is_valid()) {
+ AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause);
}
}
bool AudioStreamPlayer2D::get_stream_paused() const {
- return stream_paused;
+ if (stream_playback.is_valid()) {
+ return AudioServer::get_singleton()->is_playback_paused(stream_playback);
+ }
+ return false;
}
Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() {
diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h
index cf05a49b00..6428fbe017 100644
--- a/scene/2d/audio_stream_player_2d.h
+++ b/scene/2d/audio_stream_player_2d.h
@@ -51,38 +51,30 @@ private:
Viewport *viewport = nullptr; //pointer only used for reference to previous mix
};
- Output outputs[MAX_OUTPUTS];
- SafeNumeric<int> output_count;
- SafeFlag output_ready;
-
- //these are used by audio thread to have a reference of previous volumes (for ramping volume and avoiding clicks)
- Output prev_outputs[MAX_OUTPUTS];
- int prev_output_count = 0;
-
Ref<AudioStreamPlayback> stream_playback;
Ref<AudioStream> stream;
- Vector<AudioFrame> mix_buffer;
- SafeNumeric<float> setseek{ -1.0 };
SafeFlag active;
SafeNumeric<float> setplay{ -1.0 };
+ Vector<AudioFrame> volume_vector;
+
+ uint64_t last_mix_count = -1;
+
float volume_db = 0.0;
float pitch_scale = 1.0;
bool autoplay = false;
- bool stream_paused = false;
- bool stream_paused_fade_in = false;
- bool stream_paused_fade_out = false;
- StringName bus;
-
- void _mix_audio();
- static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_mix_audio(); }
+ StringName default_bus = "Master";
void _set_playing(bool p_enable);
bool _is_active() const;
+ StringName _get_actual_bus();
+ void _update_panning();
void _bus_layout_changed();
+ static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_update_panning(); }
+
uint32_t area_mask = 1;
float max_distance = 2000.0;
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index 8a9d28ae43..3518d434c3 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -259,15 +259,17 @@ bool StaticBody2D::is_sync_to_physics_enabled() const {
return sync_to_physics;
}
-void StaticBody2D::_direct_state_changed(Object *p_state) {
+void StaticBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) {
+ StaticBody2D *body = (StaticBody2D *)p_instance;
+ body->_body_state_changed(p_state);
+}
+
+void StaticBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
if (!sync_to_physics) {
return;
}
- PhysicsDirectBodyState2D *state = Object::cast_to<PhysicsDirectBodyState2D>(p_state);
- ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState2D object as argument");
-
- last_valid_transform = state->get_transform();
+ last_valid_transform = p_state->get_transform();
set_notify_local_transform(false);
set_global_transform(last_valid_transform);
set_notify_local_transform(true);
@@ -293,7 +295,7 @@ void StaticBody2D::_notification(int p_what) {
// Used by sync to physics, send the new transform to the physics...
Transform2D new_transform = get_global_transform();
- real_t delta_time = get_physics_process_delta_time();
+ double delta_time = get_physics_process_delta_time();
new_transform.translate(constant_linear_velocity * delta_time);
new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time);
@@ -316,7 +318,7 @@ void StaticBody2D::_notification(int p_what) {
Transform2D new_transform = get_global_transform();
- real_t delta_time = get_physics_process_delta_time();
+ double delta_time = get_physics_process_delta_time();
new_transform.translate(constant_linear_velocity * delta_time);
new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time);
@@ -379,11 +381,11 @@ void StaticBody2D::_update_kinematic_motion() {
#endif
if (kinematic_motion && sync_to_physics) {
- PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &StaticBody2D::_direct_state_changed));
+ PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback);
set_only_update_transform_changes(true);
set_notify_local_transform(true);
} else {
- PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable());
+ PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr);
set_only_update_transform_changes(false);
set_notify_local_transform(false);
}
@@ -511,26 +513,26 @@ struct _RigidBody2DInOut {
int local_shape = 0;
};
-void RigidBody2D::_direct_state_changed(Object *p_state) {
-#ifdef DEBUG_ENABLED
- state = Object::cast_to<PhysicsDirectBodyState2D>(p_state);
- ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState2D object as argument");
-#else
- state = (PhysicsDirectBodyState2D *)p_state; //trust it
-#endif
+void RigidBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) {
+ RigidBody2D *body = (RigidBody2D *)p_instance;
+ body->_body_state_changed(p_state);
+}
+void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
set_block_transform_notify(true); // don't want notify (would feedback loop)
if (mode != MODE_KINEMATIC) {
- set_global_transform(state->get_transform());
+ set_global_transform(p_state->get_transform());
}
- linear_velocity = state->get_linear_velocity();
- angular_velocity = state->get_angular_velocity();
- if (sleeping != state->is_sleeping()) {
- sleeping = state->is_sleeping();
+
+ linear_velocity = p_state->get_linear_velocity();
+ angular_velocity = p_state->get_angular_velocity();
+
+ if (sleeping != p_state->is_sleeping()) {
+ sleeping = p_state->is_sleeping();
emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed);
}
- GDVIRTUAL_CALL(_integrate_forces, state);
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
set_block_transform_notify(false); // want it back
@@ -546,20 +548,18 @@ void RigidBody2D::_direct_state_changed(Object *p_state) {
}
}
- _RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(state->get_contact_count() * sizeof(_RigidBody2DInOut));
+ _RigidBody2DInOut *toadd = (_RigidBody2DInOut *)alloca(p_state->get_contact_count() * sizeof(_RigidBody2DInOut));
int toadd_count = 0; //state->get_contact_count();
RigidBody2D_RemoveAction *toremove = (RigidBody2D_RemoveAction *)alloca(rc * sizeof(RigidBody2D_RemoveAction));
int toremove_count = 0;
//put the ones to add
- for (int i = 0; i < state->get_contact_count(); i++) {
- RID rid = state->get_contact_collider(i);
- ObjectID obj = state->get_contact_collider_id(i);
- int local_shape = state->get_contact_local_shape(i);
- int shape = state->get_contact_collider_shape(i);
-
- //bool found=false;
+ for (int i = 0; i < p_state->get_contact_count(); i++) {
+ RID rid = p_state->get_contact_collider(i);
+ ObjectID obj = p_state->get_contact_collider_id(i);
+ int local_shape = p_state->get_contact_local_shape(i);
+ int shape = p_state->get_contact_collider_shape(i);
Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.find(obj);
if (!E) {
@@ -612,8 +612,6 @@ void RigidBody2D::_direct_state_changed(Object *p_state) {
contact_monitor->locked = false;
}
-
- state = nullptr;
}
void RigidBody2D::set_mode(Mode p_mode) {
@@ -709,25 +707,15 @@ real_t RigidBody2D::get_angular_damp() const {
}
void RigidBody2D::set_axis_velocity(const Vector2 &p_axis) {
- Vector2 v = state ? state->get_linear_velocity() : linear_velocity;
Vector2 axis = p_axis.normalized();
- v -= axis * axis.dot(v);
- v += p_axis;
- if (state) {
- set_linear_velocity(v);
- } else {
- PhysicsServer2D::get_singleton()->body_set_axis_velocity(get_rid(), p_axis);
- linear_velocity = v;
- }
+ linear_velocity -= axis * axis.dot(linear_velocity);
+ linear_velocity += p_axis;
+ PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity);
}
void RigidBody2D::set_linear_velocity(const Vector2 &p_velocity) {
linear_velocity = p_velocity;
- if (state) {
- state->set_linear_velocity(linear_velocity);
- } else {
- PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity);
- }
+ PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity);
}
Vector2 RigidBody2D::get_linear_velocity() const {
@@ -736,11 +724,7 @@ Vector2 RigidBody2D::get_linear_velocity() const {
void RigidBody2D::set_angular_velocity(real_t p_velocity) {
angular_velocity = p_velocity;
- if (state) {
- state->set_angular_velocity(angular_velocity);
- } else {
- PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity);
- }
+ PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity);
}
real_t RigidBody2D::get_angular_velocity() const {
@@ -1019,7 +1003,7 @@ void RigidBody2D::_bind_methods() {
RigidBody2D::RigidBody2D() :
PhysicsBody2D(PhysicsServer2D::BODY_MODE_DYNAMIC) {
- PhysicsServer2D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &RigidBody2D::_direct_state_changed));
+ PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback);
}
RigidBody2D::~RigidBody2D() {
@@ -1045,14 +1029,19 @@ void RigidBody2D::_reload_physics_characteristics() {
bool CharacterBody2D::move_and_slide() {
// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky.
- float delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time();
+ double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time();
Vector2 current_platform_velocity = platform_velocity;
if ((on_floor || on_wall) && platform_rid.is_valid()) {
- bool excluded = (moving_platform_ignore_layers & platform_layer) != 0;
+ bool excluded = false;
+ if (on_floor) {
+ excluded = (moving_platform_floor_layers & platform_layer) == 0;
+ } else if (on_wall) {
+ excluded = (moving_platform_wall_layers & platform_layer) == 0;
+ }
if (!excluded) {
- // This approach makes sure there is less delay between the actual body velocity and the one we saved.
+ //this approach makes sure there is less delay between the actual body velocity and the one we saved
PhysicsDirectBodyState2D *bs = PhysicsServer2D::get_singleton()->body_get_direct_state(platform_rid);
if (bs) {
Transform2D gt = get_global_transform();
@@ -1095,7 +1084,7 @@ bool CharacterBody2D::move_and_slide() {
return motion_results.size() > 0;
}
-void CharacterBody2D::_move_and_slide_grounded(real_t p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) {
+void CharacterBody2D::_move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity) {
Vector2 motion = linear_velocity * p_delta;
Vector2 motion_slide_up = motion.slide(up_direction);
@@ -1239,7 +1228,7 @@ void CharacterBody2D::_move_and_slide_grounded(real_t p_delta, bool p_was_on_flo
}
}
-void CharacterBody2D::_move_and_slide_free(real_t p_delta) {
+void CharacterBody2D::_move_and_slide_free(double p_delta) {
Vector2 motion = linear_velocity * p_delta;
platform_rid = RID();
@@ -1290,7 +1279,6 @@ void CharacterBody2D::_snap_on_floor(bool was_on_floor, bool vel_dir_facing_up)
if (result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) {
on_floor = true;
floor_normal = result.collision_normal;
- platform_velocity = result.collider_velocity;
_set_platform_data(result);
if (floor_stop_on_slope) {
@@ -1332,19 +1320,21 @@ void CharacterBody2D::_set_collision_direction(const PhysicsServer2D::MotionResu
if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor
on_floor = true;
floor_normal = p_result.collision_normal;
- platform_velocity = p_result.collider_velocity;
_set_platform_data(p_result);
} else if (motion_mode == MOTION_MODE_GROUNDED && p_result.get_angle(-up_direction) <= floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling
on_ceiling = true;
} else {
on_wall = true;
- platform_velocity = p_result.collider_velocity;
- _set_platform_data(p_result);
+ // Don't apply wall velocity when the collider is a CharacterBody2D.
+ if (Object::cast_to<CharacterBody2D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) {
+ _set_platform_data(p_result);
+ }
}
}
void CharacterBody2D::_set_platform_data(const PhysicsServer2D::MotionResult &p_result) {
platform_rid = p_result.collider;
+ platform_velocity = p_result.collider_velocity;
platform_layer = 0;
CollisionObject2D *collision_object = Object::cast_to<CollisionObject2D>(ObjectDB::get_instance(p_result.collider_id));
if (collision_object) {
@@ -1468,12 +1458,20 @@ void CharacterBody2D::set_slide_on_ceiling_enabled(bool p_enabled) {
slide_on_ceiling = p_enabled;
}
-uint32_t CharacterBody2D::get_moving_platform_ignore_layers() const {
- return moving_platform_ignore_layers;
+uint32_t CharacterBody2D::get_moving_platform_floor_layers() const {
+ return moving_platform_floor_layers;
+}
+
+void CharacterBody2D::set_moving_platform_floor_layers(uint32_t p_exclude_layers) {
+ moving_platform_floor_layers = p_exclude_layers;
+}
+
+uint32_t CharacterBody2D::get_moving_platform_wall_layers() const {
+ return moving_platform_wall_layers;
}
-void CharacterBody2D::set_moving_platform_ignore_layers(uint32_t p_exclude_layers) {
- moving_platform_ignore_layers = p_exclude_layers;
+void CharacterBody2D::set_moving_platform_wall_layers(uint32_t p_exclude_layers) {
+ moving_platform_wall_layers = p_exclude_layers;
}
void CharacterBody2D::set_motion_mode(MotionMode p_mode) {
@@ -1558,8 +1556,10 @@ void CharacterBody2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_slide_on_ceiling_enabled", "enabled"), &CharacterBody2D::set_slide_on_ceiling_enabled);
ClassDB::bind_method(D_METHOD("is_slide_on_ceiling_enabled"), &CharacterBody2D::is_slide_on_ceiling_enabled);
- ClassDB::bind_method(D_METHOD("set_moving_platform_ignore_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_ignore_layers);
- ClassDB::bind_method(D_METHOD("get_moving_platform_ignore_layers"), &CharacterBody2D::get_moving_platform_ignore_layers);
+ ClassDB::bind_method(D_METHOD("set_moving_platform_floor_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_floor_layers);
+ ClassDB::bind_method(D_METHOD("get_moving_platform_floor_layers"), &CharacterBody2D::get_moving_platform_floor_layers);
+ ClassDB::bind_method(D_METHOD("set_moving_platform_wall_layers", "exclude_layer"), &CharacterBody2D::set_moving_platform_wall_layers);
+ ClassDB::bind_method(D_METHOD("get_moving_platform_wall_layers"), &CharacterBody2D::get_moving_platform_wall_layers);
ClassDB::bind_method(D_METHOD("get_max_slides"), &CharacterBody2D::get_max_slides);
ClassDB::bind_method(D_METHOD("set_max_slides", "max_slides"), &CharacterBody2D::set_max_slides);
@@ -1601,7 +1601,8 @@ void CharacterBody2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_max_angle", PROPERTY_HINT_RANGE, "0,180,0.1,radians"), "set_floor_max_angle", "get_floor_max_angle");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "floor_snap_length", PROPERTY_HINT_RANGE, "0,1000,0.1"), "set_floor_snap_length", "get_floor_snap_length");
ADD_GROUP("Moving platform", "moving_platform");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_ignore_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_ignore_layers", "get_moving_platform_ignore_layers");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_floor_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_floor_layers", "get_moving_platform_floor_layers");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "moving_platform_wall_layers", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_moving_platform_wall_layers", "get_moving_platform_wall_layers");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision/safe_margin", PROPERTY_HINT_RANGE, "0.001,256,0.001"), "set_safe_margin", "get_safe_margin");
BIND_ENUM_CONSTANT(MOTION_MODE_GROUNDED);
diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h
index 885f0ace05..1bf53ea53c 100644
--- a/scene/2d/physics_body_2d.h
+++ b/scene/2d/physics_body_2d.h
@@ -73,7 +73,8 @@ class StaticBody2D : public PhysicsBody2D {
Transform2D last_valid_transform;
- void _direct_state_changed(Object *p_state);
+ static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state);
+ void _body_state_changed(PhysicsDirectBodyState2D *p_state);
protected:
void _notification(int p_what);
@@ -124,7 +125,6 @@ public:
private:
bool can_sleep = true;
- PhysicsDirectBodyState2D *state = nullptr;
Mode mode = MODE_DYNAMIC;
real_t mass = 1.0;
@@ -183,7 +183,9 @@ private:
void _body_exit_tree(ObjectID p_id);
void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape);
- void _direct_state_changed(Object *p_state);
+
+ static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state);
+ void _body_state_changed(PhysicsDirectBodyState2D *p_state);
protected:
void _notification(int p_what);
@@ -310,7 +312,8 @@ private:
float floor_snap_length = 0;
real_t free_mode_min_slide_angle = Math::deg2rad((real_t)15.0);
Vector2 up_direction = Vector2(0.0, -1.0);
- uint32_t moving_platform_ignore_layers = 0;
+ uint32_t moving_platform_floor_layers = UINT32_MAX;
+ uint32_t moving_platform_wall_layers = 0;
Vector2 linear_velocity;
Vector2 floor_normal;
@@ -350,14 +353,17 @@ private:
real_t get_free_mode_min_slide_angle() const;
void set_free_mode_min_slide_angle(real_t p_radians);
- uint32_t get_moving_platform_ignore_layers() const;
- void set_moving_platform_ignore_layers(const uint32_t p_exclude_layer);
+ uint32_t get_moving_platform_floor_layers() const;
+ void set_moving_platform_floor_layers(const uint32_t p_exclude_layer);
+
+ uint32_t get_moving_platform_wall_layers() const;
+ void set_moving_platform_wall_layers(const uint32_t p_exclude_layer);
void set_motion_mode(MotionMode p_mode);
MotionMode get_motion_mode() const;
- void _move_and_slide_free(real_t p_delta);
- void _move_and_slide_grounded(real_t p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity);
+ void _move_and_slide_free(double p_delta);
+ void _move_and_slide_grounded(double p_delta, bool p_was_on_floor, const Vector2 &p_prev_platform_velocity);
Ref<KinematicCollision2D> _get_slide_collision(int p_bounce);
Ref<KinematicCollision2D> _get_last_slide_collision();
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 3b44580264..16d5b9da3e 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -95,7 +95,7 @@ static const Vector3 speaker_directions[7] = {
Vector3(1.0, 0.0, 0.0).normalized(), // side-right
};
-void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tightness, AudioStreamPlayer3D::Output &output) {
+void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output) {
unsigned int speaker_count = 0; // only main speakers (no LFE)
switch (AudioServer::get_singleton()->get_speaker_mode()) {
case AudioServer::SPEAKER_MODE_STEREO:
@@ -118,182 +118,94 @@ void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tig
switch (AudioServer::get_singleton()->get_speaker_mode()) {
case AudioServer::SPEAKER_SURROUND_71:
- output.vol[3].l = volumes[5]; // side-left
- output.vol[3].r = volumes[6]; // side-right
+ output.write[3].l = volumes[5]; // side-left
+ output.write[3].r = volumes[6]; // side-right
[[fallthrough]];
case AudioServer::SPEAKER_SURROUND_51:
- output.vol[2].l = volumes[3]; // rear-left
- output.vol[2].r = volumes[4]; // rear-right
+ output.write[2].l = volumes[3]; // rear-left
+ output.write[2].r = volumes[4]; // rear-right
[[fallthrough]];
case AudioServer::SPEAKER_SURROUND_31:
- output.vol[1].r = 1.0; // LFE - always full power
- output.vol[1].l = volumes[2]; // center
+ output.write[1].r = 1.0; // LFE - always full power
+ output.write[1].l = volumes[2]; // center
[[fallthrough]];
case AudioServer::SPEAKER_MODE_STEREO:
- output.vol[0].r = volumes[1]; // front-right
- output.vol[0].l = volumes[0]; // front-left
+ output.write[0].r = volumes[1]; // front-right
+ output.write[0].l = volumes[0]; // front-left
break;
}
}
-void AudioStreamPlayer3D::_mix_audio() {
- if (!stream_playback.is_valid() || !active.is_set() ||
- (stream_paused && !stream_paused_fade_out)) {
- return;
- }
+void AudioStreamPlayer3D::_calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol) {
+ reverb_vol.resize(4);
+ reverb_vol.write[0] = AudioFrame(0, 0);
+ reverb_vol.write[1] = AudioFrame(0, 0);
+ reverb_vol.write[2] = AudioFrame(0, 0);
+ reverb_vol.write[3] = AudioFrame(0, 0);
- bool started = false;
- if (setseek.get() >= 0.0) {
- stream_playback->start(setseek.get());
- setseek.set(-1.0); //reset seek
- started = true;
- }
+ float uniformity = area->get_reverb_uniformity();
+ float area_send = area->get_reverb_amount();
- //get data
- AudioFrame *buffer = mix_buffer.ptrw();
- int buffer_size = mix_buffer.size();
+ if (uniformity > 0.0) {
+ float distance = listener_area_pos.length();
+ float attenuation = Math::db2linear(_get_attenuation_db(distance));
- if (stream_paused_fade_out) {
- // Short fadeout ramp
- buffer_size = MIN(buffer_size, 128);
- }
+ // Determine the fraction of sound that would come from each speaker if they were all driven uniformly.
+ float center_val[3] = { 0.5f, 0.25f, 0.16666f };
+ int channel_count = AudioServer::get_singleton()->get_channel_count();
+ AudioFrame center_frame(center_val[channel_count - 1], center_val[channel_count - 1]);
- // Mix if we're not paused or we're fading out
- if ((output_count.get() > 0 || out_of_range_mode == OUT_OF_RANGE_MIX)) {
- float output_pitch_scale = 0.0;
- if (output_count.get()) {
- //used for doppler, not realistic but good enough
- for (int i = 0; i < output_count.get(); i++) {
- output_pitch_scale += outputs[i].pitch_scale;
- }
- output_pitch_scale /= float(output_count.get());
- } else {
- output_pitch_scale = 1.0;
- }
+ if (attenuation < 1.0) {
+ //pan the uniform sound
+ Vector3 rev_pos = listener_area_pos;
+ rev_pos.y = 0;
+ rev_pos.normalize();
- stream_playback->mix(buffer, pitch_scale * output_pitch_scale, buffer_size);
- }
-
- //write all outputs
- for (int i = 0; i < output_count.get(); i++) {
- Output current = outputs[i];
-
- //see if current output exists, to keep volume ramp
- bool found = false;
- for (int j = i; j < prev_output_count; j++) {
- if (prev_outputs[j].viewport == current.viewport) {
- if (j != i) {
- SWAP(prev_outputs[j], prev_outputs[i]);
- }
- found = true;
- break;
+ if (channel_count >= 1) {
+ // Stereo pair
+ float c = rev_pos.x * 0.5 + 0.5;
+ reverb_vol.write[0].l = 1.0 - c;
+ reverb_vol.write[0].r = c;
}
- }
- bool interpolate_filter = !started;
+ if (channel_count >= 3) {
+ // Center pair + Side pair
+ float xl = Vector3(-1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5;
+ float xr = Vector3(1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5;
- if (!found) {
- //create new if was not used before
- if (prev_output_count < MAX_OUTPUTS) {
- prev_outputs[prev_output_count] = prev_outputs[i]; //may be owned by another viewport
- prev_output_count++;
+ reverb_vol.write[1].l = xl;
+ reverb_vol.write[1].r = xr;
+ reverb_vol.write[2].l = 1.0 - xr;
+ reverb_vol.write[2].r = 1.0 - xl;
}
- prev_outputs[i] = current;
- interpolate_filter = false;
- }
-
- //mix!
-
- int buffers = AudioServer::get_singleton()->get_channel_count();
-
- for (int k = 0; k < buffers; k++) {
- AudioFrame target_volume = stream_paused_fade_out ? AudioFrame(0.f, 0.f) : current.vol[k];
- AudioFrame vol_prev = stream_paused_fade_in ? AudioFrame(0.f, 0.f) : prev_outputs[i].vol[k];
- AudioFrame vol_inc = (target_volume - vol_prev) / float(buffer_size);
- AudioFrame vol = vol_prev;
- if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.bus_index, k)) {
- continue; //may have been deleted, will be updated on process
+ if (channel_count >= 4) {
+ // Rear pair
+ // FIXME: Not sure what math should be done here
+ float c = rev_pos.x * 0.5 + 0.5;
+ reverb_vol.write[3].l = 1.0 - c;
+ reverb_vol.write[3].r = c;
}
- AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k);
- current.filter.set_mode(AudioFilterSW::HIGHSHELF);
- current.filter.set_sampling_rate(AudioServer::get_singleton()->get_mix_rate());
- current.filter.set_cutoff(attenuation_filter_cutoff_hz);
- current.filter.set_resonance(1);
- current.filter.set_stages(1);
- current.filter.set_gain(current.filter_gain);
-
- if (interpolate_filter) {
- current.filter_process[k * 2 + 0] = prev_outputs[i].filter_process[k * 2 + 0];
- current.filter_process[k * 2 + 1] = prev_outputs[i].filter_process[k * 2 + 1];
-
- current.filter_process[k * 2 + 0].set_filter(&current.filter, false);
- current.filter_process[k * 2 + 1].set_filter(&current.filter, false);
-
- current.filter_process[k * 2 + 0].update_coeffs(buffer_size);
- current.filter_process[k * 2 + 1].update_coeffs(buffer_size);
- for (int j = 0; j < buffer_size; j++) {
- AudioFrame f = buffer[j] * vol;
- current.filter_process[k * 2 + 0].process_one_interp(f.l);
- current.filter_process[k * 2 + 1].process_one_interp(f.r);
-
- target[j] += f;
- vol += vol_inc;
- }
- } else {
- current.filter_process[k * 2 + 0].set_filter(&current.filter);
- current.filter_process[k * 2 + 1].set_filter(&current.filter);
-
- current.filter_process[k * 2 + 0].update_coeffs();
- current.filter_process[k * 2 + 1].update_coeffs();
- for (int j = 0; j < buffer_size; j++) {
- AudioFrame f = buffer[j] * vol;
- current.filter_process[k * 2 + 0].process_one(f.l);
- current.filter_process[k * 2 + 1].process_one(f.r);
-
- target[j] += f;
- vol += vol_inc;
- }
+ for (int i = 0; i < channel_count; i++) {
+ reverb_vol.write[i] = reverb_vol[i].lerp(center_frame, attenuation);
}
-
- if (current.reverb_bus_index >= 0) {
- if (!AudioServer::get_singleton()->thread_has_channel_mix_buffer(current.reverb_bus_index, k)) {
- continue; //may have been deleted, will be updated on process
- }
-
- AudioFrame *rtarget = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.reverb_bus_index, k);
-
- if (current.reverb_bus_index == prev_outputs[i].reverb_bus_index) {
- AudioFrame rvol_inc = (current.reverb_vol[k] - prev_outputs[i].reverb_vol[k]) / float(buffer_size);
- AudioFrame rvol = prev_outputs[i].reverb_vol[k];
-
- for (int j = 0; j < buffer_size; j++) {
- rtarget[j] += buffer[j] * rvol;
- rvol += rvol_inc;
- }
- } else {
- AudioFrame rvol = current.reverb_vol[k];
- for (int j = 0; j < buffer_size; j++) {
- rtarget[j] += buffer[j] * rvol;
- }
- }
+ } else {
+ for (int i = 0; i < channel_count; i++) {
+ reverb_vol.write[i] = center_frame;
}
}
- prev_outputs[i] = current;
- }
-
- prev_output_count = output_count.get();
+ for (int i = 0; i < channel_count; i++) {
+ reverb_vol.write[i] = direct_path_vol[i].lerp(reverb_vol[i] * attenuation, uniformity);
+ reverb_vol.write[i] *= area_send;
+ }
- //stream is no longer active, disable this.
- if (!stream_playback->is_playing()) {
- active.clear();
+ } else {
+ for (int i = 0; i < 4; i++) {
+ reverb_vol.write[i] = direct_path_vol[i] * area_send;
+ }
}
-
- output_ready.clear();
- stream_paused_fade_in = false;
- stream_paused_fade_out = false;
}
float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const {
@@ -329,14 +241,15 @@ float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const {
void AudioStreamPlayer3D::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) {
velocity_tracker->reset(get_global_transform().origin);
- AudioServer::get_singleton()->add_callback(_mix_audios, this);
+ AudioServer::get_singleton()->add_listener_changed_callback(_listener_changed_cb, this);
if (autoplay && !Engine::get_singleton()->is_editor_hint()) {
play();
}
}
if (p_what == NOTIFICATION_EXIT_TREE) {
- AudioServer::get_singleton()->remove_callback(_mix_audios, this);
+ stop();
+ AudioServer::get_singleton()->remove_listener_changed_callback(_listener_changed_cb, this);
}
if (p_what == NOTIFICATION_PAUSED) {
@@ -359,268 +272,216 @@ void AudioStreamPlayer3D::_notification(int p_what) {
if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
//update anything related to position first, if possible of course
- if (!output_ready.is_set()) {
- Vector3 linear_velocity;
+ if (!stream_playback.is_valid()) {
+ return;
+ }
+ //start playing if requested
- //compute linear velocity for doppler
- if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
- linear_velocity = velocity_tracker->get_tracked_linear_velocity();
- }
+ if (setplay.get() >= 0) {
+ Vector<AudioFrame> volume_vector = _update_panning();
+ AudioServer::get_singleton()->start_playback_stream(stream_playback, _get_actual_bus(), volume_vector, setplay.get());
+ active.set();
+ setplay.set(-1);
+ }
+
+ if (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) {
+ _update_panning();
+ last_mix_count = AudioServer::get_singleton()->get_mix_count();
+ }
+
+ // Stop playing if no longer active.
+ if (!active.is_set()) {
+ set_physics_process_internal(false);
+ emit_signal(SNAME("finished"));
+ }
+ }
+}
+
+Area3D *AudioStreamPlayer3D::_get_overriding_area() {
+ //check if any area is diverting sound into a bus
+ Ref<World3D> world_3d = get_world_3d();
+ ERR_FAIL_COND_V(world_3d.is_null(), nullptr);
+
+ Vector3 global_pos = get_global_transform().origin;
- Ref<World3D> world_3d = get_world_3d();
- ERR_FAIL_COND(world_3d.is_null());
+ PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space());
- int new_output_count = 0;
+ PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS];
- Vector3 global_pos = get_global_transform().origin;
+ int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true);
- int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus);
+ for (int i = 0; i < areas; i++) {
+ if (!sr[i].collider) {
+ continue;
+ }
+
+ Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider);
+ if (!tarea) {
+ continue;
+ }
+
+ if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) {
+ continue;
+ }
+
+ return tarea;
+ }
+ return nullptr;
+}
- //check if any area is diverting sound into a bus
+StringName AudioStreamPlayer3D::_get_actual_bus() {
+ if (!stream_playback.is_valid()) {
+ return SNAME("Master");
+ }
+ Area3D *overriding_area = _get_overriding_area();
+ if (overriding_area && overriding_area->is_overriding_audio_bus() && !overriding_area->is_using_reverb_bus()) {
+ return overriding_area->get_audio_bus_name();
+ }
+ return bus;
+}
- PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space());
+Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() {
+ Vector<AudioFrame> output_volume_vector;
+ output_volume_vector.resize(4);
+ for (AudioFrame &frame : output_volume_vector) {
+ frame = AudioFrame(0, 0);
+ }
- PhysicsDirectSpaceState3D::ShapeResult sr[MAX_INTERSECT_AREAS];
+ ERR_FAIL_COND_V(stream_playback.is_null(), output_volume_vector);
- int areas = space_state->intersect_point(global_pos, sr, MAX_INTERSECT_AREAS, Set<RID>(), area_mask, false, true);
- Area3D *area = nullptr;
+ Vector3 linear_velocity;
- for (int i = 0; i < areas; i++) {
- if (!sr[i].collider) {
- continue;
- }
+ //compute linear velocity for doppler
+ if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
+ linear_velocity = velocity_tracker->get_tracked_linear_velocity();
+ }
- Area3D *tarea = Object::cast_to<Area3D>(sr[i].collider);
- if (!tarea) {
- continue;
- }
+ Vector3 global_pos = get_global_transform().origin;
- if (!tarea->is_overriding_audio_bus() && !tarea->is_using_reverb_bus()) {
- continue;
- }
+ Ref<World3D> world_3d = get_world_3d();
+ ERR_FAIL_COND_V(world_3d.is_null(), output_volume_vector);
- area = tarea;
- break;
+ Set<Camera3D *> cameras = world_3d->get_cameras();
+ cameras.insert(get_viewport()->get_camera_3d());
+
+ PhysicsDirectSpaceState3D *space_state = PhysicsServer3D::get_singleton()->space_get_direct_state(world_3d->get_space());
+
+ for (Camera3D *camera : cameras) {
+ Viewport *vp = camera->get_viewport();
+ if (!vp->is_audio_listener_3d()) {
+ continue;
+ }
+
+ bool listener_is_camera = true;
+ Node3D *listener_node = camera;
+
+ Listener3D *listener = vp->get_listener_3d();
+ if (listener) {
+ listener_node = listener;
+ listener_is_camera = false;
+ }
+
+ Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos);
+
+ float dist = local_pos.length();
+
+ Vector3 area_sound_pos;
+ Vector3 listener_area_pos;
+
+ Area3D *area = _get_overriding_area();
+
+ if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) {
+ area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), listener_node->get_global_transform().origin);
+ listener_area_pos = listener_node->get_global_transform().affine_inverse().xform(area_sound_pos);
+ }
+
+ if (max_distance > 0) {
+ float total_max = max_distance;
+
+ if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) {
+ total_max = MAX(total_max, listener_area_pos.length());
}
+ if (total_max > max_distance) {
+ continue; //can't hear this sound in this listener
+ }
+ }
+
+ float multiplier = Math::db2linear(_get_attenuation_db(dist));
+ if (max_distance > 0) {
+ multiplier *= MAX(0, 1.0 - (dist / max_distance));
+ }
- for (const Set<Camera3D *>::Element *E = world_3d->get_cameras().front(); E; E = E->next()) {
- Camera3D *camera = E->get();
- Viewport *vp = camera->get_viewport();
- if (!vp->is_audio_listener_3d()) {
- continue;
- }
-
- bool listener_is_camera = true;
- Node3D *listener_node = camera;
-
- Listener3D *listener = vp->get_listener_3d();
- if (listener) {
- listener_node = listener;
- listener_is_camera = false;
- }
-
- Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos);
-
- float dist = local_pos.length();
-
- Vector3 area_sound_pos;
- Vector3 listener_area_pos;
-
- if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) {
- area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), listener_node->get_global_transform().origin);
- listener_area_pos = listener_node->to_local(area_sound_pos);
- }
-
- if (max_distance > 0) {
- float total_max = max_distance;
-
- if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) {
- total_max = MAX(total_max, listener_area_pos.length());
- }
- if (total_max > max_distance) {
- continue; //can't hear this sound in this listener
- }
- }
-
- float multiplier = Math::db2linear(_get_attenuation_db(dist));
- if (max_distance > 0) {
- multiplier *= MAX(0, 1.0 - (dist / max_distance));
- }
-
- Output output;
- output.bus_index = bus_index;
- output.reverb_bus_index = -1; //no reverb by default
- output.viewport = vp;
-
- float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db;
-
- if (emission_angle_enabled) {
- Vector3 listenertopos = global_pos - listener_node->get_global_transform().origin;
- float c = listenertopos.normalized().dot(get_global_transform().basis.get_axis(2).normalized()); //it's z negative
- float angle = Math::rad2deg(Math::acos(c));
- if (angle > emission_angle) {
- db_att -= -emission_angle_filter_attenuation_db;
- }
- }
-
- output.filter_gain = Math::db2linear(db_att);
-
- //TODO: The lower the second parameter (tightness) the more the sound will "enclose" the listener (more undirected / playing from
- // speakers not facing the source) - this could be made distance dependent.
- _calc_output_vol(local_pos.normalized(), 4.0, output);
-
- unsigned int cc = AudioServer::get_singleton()->get_channel_count();
- for (unsigned int k = 0; k < cc; k++) {
- output.vol[k] *= multiplier;
- }
-
- bool filled_reverb = false;
- int vol_index_max = AudioServer::get_singleton()->get_speaker_mode() + 1;
-
- if (area) {
- if (area->is_overriding_audio_bus()) {
- //override audio bus
- StringName bus_name = area->get_audio_bus_name();
- output.bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name);
- }
-
- if (area->is_using_reverb_bus()) {
- filled_reverb = true;
- StringName bus_name = area->get_reverb_bus();
- output.reverb_bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus_name);
-
- float uniformity = area->get_reverb_uniformity();
- float area_send = area->get_reverb_amount();
-
- if (uniformity > 0.0) {
- float distance = listener_area_pos.length();
- float attenuation = Math::db2linear(_get_attenuation_db(distance));
-
- //float dist_att_db = -20 * Math::log(dist + 0.00001); //logarithmic attenuation, like in real life
-
- float center_val[3] = { 0.5f, 0.25f, 0.16666f };
- AudioFrame center_frame(center_val[vol_index_max - 1], center_val[vol_index_max - 1]);
-
- if (attenuation < 1.0) {
- //pan the uniform sound
- Vector3 rev_pos = listener_area_pos;
- rev_pos.y = 0;
- rev_pos.normalize();
-
- if (cc >= 1) {
- // Stereo pair
- float c = rev_pos.x * 0.5 + 0.5;
- output.reverb_vol[0].l = 1.0 - c;
- output.reverb_vol[0].r = c;
- }
-
- if (cc >= 3) {
- // Center pair + Side pair
- float xl = Vector3(-1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5;
- float xr = Vector3(1, 0, -1).normalized().dot(rev_pos) * 0.5 + 0.5;
-
- output.reverb_vol[1].l = xl;
- output.reverb_vol[1].r = xr;
- output.reverb_vol[2].l = 1.0 - xr;
- output.reverb_vol[2].r = 1.0 - xl;
- }
-
- if (cc >= 4) {
- // Rear pair
- // FIXME: Not sure what math should be done here
- float c = rev_pos.x * 0.5 + 0.5;
- output.reverb_vol[3].l = 1.0 - c;
- output.reverb_vol[3].r = c;
- }
-
- for (int i = 0; i < vol_index_max; i++) {
- output.reverb_vol[i] = output.reverb_vol[i].lerp(center_frame, attenuation);
- }
- } else {
- for (int i = 0; i < vol_index_max; i++) {
- output.reverb_vol[i] = center_frame;
- }
- }
-
- for (int i = 0; i < vol_index_max; i++) {
- output.reverb_vol[i] = output.vol[i].lerp(output.reverb_vol[i] * attenuation, uniformity);
- output.reverb_vol[i] *= area_send;
- }
-
- } else {
- for (int i = 0; i < vol_index_max; i++) {
- output.reverb_vol[i] = output.vol[i] * area_send;
- }
- }
- }
- }
-
- if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
- Vector3 listener_velocity;
-
- if (listener_is_camera) {
- listener_velocity = camera->get_doppler_tracked_velocity();
- }
-
- Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity);
-
- if (local_velocity == Vector3()) {
- output.pitch_scale = 1.0;
- } else {
- float approaching = local_pos.normalized().dot(local_velocity.normalized());
- float velocity = local_velocity.length();
- float speed_of_sound = 343.0;
-
- output.pitch_scale = speed_of_sound / (speed_of_sound + velocity * approaching);
- output.pitch_scale = CLAMP(output.pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff
- }
-
- } else {
- output.pitch_scale = 1.0;
- }
-
- if (!filled_reverb) {
- for (int i = 0; i < vol_index_max; i++) {
- output.reverb_vol[i] = AudioFrame(0, 0);
- }
- }
-
- outputs[new_output_count] = output;
- new_output_count++;
- if (new_output_count == MAX_OUTPUTS) {
- break;
- }
+ float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db;
+
+ if (emission_angle_enabled) {
+ Vector3 listenertopos = global_pos - listener_node->get_global_transform().origin;
+ float c = listenertopos.normalized().dot(get_global_transform().basis.get_axis(2).normalized()); //it's z negative
+ float angle = Math::rad2deg(Math::acos(c));
+ if (angle > emission_angle) {
+ db_att -= -emission_angle_filter_attenuation_db;
}
+ }
+
+ AudioServer::get_singleton()->set_playback_highshelf_params(stream_playback, Math::db2linear(db_att), attenuation_filter_cutoff_hz);
+ //TODO: The lower the second parameter (tightness) the more the sound will "enclose" the listener (more undirected / playing from
+ // speakers not facing the source) - this could be made distance dependent.
+ _calc_output_vol(local_pos.normalized(), 4.0, output_volume_vector);
- output_count.set(new_output_count);
- output_ready.set();
+ for (unsigned int k = 0; k < 4; k++) {
+ output_volume_vector.write[k] = multiplier * output_volume_vector[k];
}
- //start playing if requested
- if (setplay.get() >= 0.0) {
- setseek.set(setplay.get());
- active.set();
- setplay.set(-1);
+ Map<StringName, Vector<AudioFrame>> bus_volumes;
+ if (area) {
+ if (area->is_overriding_audio_bus()) {
+ //override audio bus
+ bus_volumes[area->get_audio_bus_name()] = output_volume_vector;
+ }
+
+ if (area->is_using_reverb_bus()) {
+ StringName reverb_bus_name = area->get_reverb_bus();
+ Vector<AudioFrame> reverb_vol;
+ _calc_reverb_vol(area, listener_area_pos, output_volume_vector, reverb_vol);
+ bus_volumes[reverb_bus_name] = reverb_vol;
+ }
+ } else {
+ bus_volumes[bus] = output_volume_vector;
}
+ AudioServer::get_singleton()->set_playback_bus_volumes_linear(stream_playback, bus_volumes);
- //stop playing if no longer active
- if (!active.is_set()) {
- set_physics_process_internal(false);
- emit_signal(SNAME("finished"));
+ if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
+ Vector3 listener_velocity;
+
+ if (listener_is_camera) {
+ listener_velocity = camera->get_doppler_tracked_velocity();
+ }
+
+ Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity);
+
+ if (local_velocity == Vector3()) {
+ AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale);
+ } else {
+ float approaching = local_pos.normalized().dot(local_velocity.normalized());
+ float velocity = local_velocity.length();
+ float speed_of_sound = 343.0;
+
+ float doppler_pitch_scale = pitch_scale * speed_of_sound / (speed_of_sound + velocity * approaching);
+ doppler_pitch_scale = CLAMP(doppler_pitch_scale, (1 / 8.0), 8.0); //avoid crazy stuff
+
+ AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, doppler_pitch_scale);
+ }
+ } else {
+ AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale);
}
}
+ return output_volume_vector;
}
void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) {
- AudioServer::get_singleton()->lock();
-
- mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size());
-
if (stream_playback.is_valid()) {
+ stop();
stream_playback.unref();
stream.unref();
- active.clear();
- setseek.set(-1);
}
if (p_stream.is_valid()) {
@@ -632,8 +493,6 @@ void AudioStreamPlayer3D::set_stream(Ref<AudioStream> p_stream) {
}
}
- AudioServer::get_singleton()->unlock();
-
if (p_stream.is_valid() && stream_playback.is_null()) {
stream.unref();
}
@@ -677,27 +536,22 @@ float AudioStreamPlayer3D::get_pitch_scale() const {
}
void AudioStreamPlayer3D::play(float p_from_pos) {
- if (!is_playing()) {
- // Reset the prev_output_count if the stream is stopped
- prev_output_count = 0;
- }
-
if (stream_playback.is_valid()) {
setplay.set(p_from_pos);
- output_ready.clear();
set_physics_process_internal(true);
}
}
void AudioStreamPlayer3D::seek(float p_seconds) {
- if (stream_playback.is_valid()) {
- setseek.set(p_seconds);
+ if (stream_playback.is_valid() && active.is_set()) {
+ play(p_seconds);
}
}
void AudioStreamPlayer3D::stop() {
if (stream_playback.is_valid()) {
active.clear();
+ AudioServer::get_singleton()->stop_playback_stream(stream_playback);
set_physics_process_internal(false);
setplay.set(-1);
}
@@ -713,11 +567,7 @@ bool AudioStreamPlayer3D::is_playing() const {
float AudioStreamPlayer3D::get_playback_position() {
if (stream_playback.is_valid()) {
- float ss = setseek.get();
- if (ss >= 0.0) {
- return ss;
- }
- return stream_playback->get_playback_position();
+ return AudioServer::get_singleton()->get_playback_position(stream_playback);
}
return 0;
@@ -736,7 +586,7 @@ StringName AudioStreamPlayer3D::get_bus() const {
return bus;
}
}
- return "Master";
+ return SNAME("Master");
}
void AudioStreamPlayer3D::set_autoplay(bool p_enable) {
@@ -879,15 +729,16 @@ AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking()
}
void AudioStreamPlayer3D::set_stream_paused(bool p_pause) {
- if (p_pause != stream_paused) {
- stream_paused = p_pause;
- stream_paused_fade_in = !stream_paused;
- stream_paused_fade_out = stream_paused;
+ if (stream_playback.is_valid()) {
+ AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause);
}
}
bool AudioStreamPlayer3D::get_stream_paused() const {
- return stream_paused;
+ if (stream_playback.is_valid()) {
+ return AudioServer::get_singleton()->is_playback_paused(stream_playback);
+ }
+ return false;
}
Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() {
diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h
index f86e19403c..f915abad2b 100644
--- a/scene/3d/audio_stream_player_3d.h
+++ b/scene/3d/audio_stream_player_3d.h
@@ -31,10 +31,12 @@
#ifndef AUDIO_STREAM_PLAYER_3D_H
#define AUDIO_STREAM_PLAYER_3D_H
+#include "scene/3d/area_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/velocity_tracker_3d.h"
#include "servers/audio/audio_filter_sw.h"
#include "servers/audio/audio_stream.h"
+#include "servers/audio_server.h"
class Camera3D;
class AudioStreamPlayer3D : public Node3D {
@@ -66,31 +68,9 @@ private:
};
- struct Output {
- AudioFilterSW filter;
- AudioFilterSW::Processor filter_process[8];
- AudioFrame vol[4];
- float filter_gain = 0.0;
- float pitch_scale = 0.0;
- int bus_index = -1;
- int reverb_bus_index = -1;
- AudioFrame reverb_vol[4];
- Viewport *viewport = nullptr; //pointer only used for reference to previous mix
- };
-
- Output outputs[MAX_OUTPUTS];
- SafeNumeric<int> output_count;
- SafeFlag output_ready;
-
- //these are used by audio thread to have a reference of previous volumes (for ramping volume and avoiding clicks)
- Output prev_outputs[MAX_OUTPUTS];
- int prev_output_count = 0;
-
Ref<AudioStreamPlayback> stream_playback;
Ref<AudioStream> stream;
- Vector<AudioFrame> mix_buffer;
- SafeNumeric<float> setseek{ -1.0 };
SafeFlag active;
SafeNumeric<float> setplay{ -1.0 };
@@ -100,17 +80,21 @@ private:
float max_db = 3.0;
float pitch_scale = 1.0;
bool autoplay = false;
- bool stream_paused = false;
- bool stream_paused_fade_in = false;
- bool stream_paused_fade_out = false;
- StringName bus;
+ StringName bus = "Master";
+
+ uint64_t last_mix_count = -1;
+
+ static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output);
+
+ void _calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol);
- static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Output &output);
- void _mix_audio();
- static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->_mix_audio(); }
+ static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->_update_panning(); }
void _set_playing(bool p_enable);
bool _is_active() const;
+ StringName _get_actual_bus();
+ Area3D *_get_overriding_area();
+ Vector<AudioFrame> _update_panning();
void _bus_layout_changed();
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index a8a755972f..0356994cdb 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -283,18 +283,20 @@ bool StaticBody3D::is_sync_to_physics_enabled() const {
return sync_to_physics;
}
-void StaticBody3D::_direct_state_changed(Object *p_state) {
- PhysicsDirectBodyState3D *state = Object::cast_to<PhysicsDirectBodyState3D>(p_state);
- ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument");
+void StaticBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) {
+ StaticBody3D *body = (StaticBody3D *)p_instance;
+ body->_body_state_changed(p_state);
+}
- linear_velocity = state->get_linear_velocity();
- angular_velocity = state->get_angular_velocity();
+void StaticBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
+ linear_velocity = p_state->get_linear_velocity();
+ angular_velocity = p_state->get_angular_velocity();
if (!sync_to_physics) {
return;
}
- last_valid_transform = state->get_transform();
+ last_valid_transform = p_state->get_transform();
set_notify_local_transform(false);
set_global_transform(last_valid_transform);
set_notify_local_transform(true);
@@ -347,7 +349,7 @@ void StaticBody3D::_notification(int p_what) {
// Used by sync to physics, send the new transform to the physics...
Transform3D new_transform = get_global_transform();
- real_t delta_time = get_physics_process_delta_time();
+ double delta_time = get_physics_process_delta_time();
new_transform.origin += constant_linear_velocity * delta_time;
real_t ang_vel = constant_angular_velocity.length();
@@ -378,7 +380,7 @@ void StaticBody3D::_notification(int p_what) {
Transform3D new_transform = get_global_transform();
- real_t delta_time = get_physics_process_delta_time();
+ double delta_time = get_physics_process_delta_time();
new_transform.origin += constant_linear_velocity * delta_time;
real_t ang_vel = constant_angular_velocity.length();
@@ -458,13 +460,13 @@ void StaticBody3D::_update_kinematic_motion() {
bool needs_physics_process = false;
if (kinematic_motion) {
- PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &StaticBody3D::_direct_state_changed));
+ PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, &StaticBody3D::_body_state_changed_callback);
if (!constant_angular_velocity.is_equal_approx(Vector3()) || !constant_linear_velocity.is_equal_approx(Vector3())) {
needs_physics_process = true;
}
} else {
- PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable());
+ PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr);
}
set_physics_process_internal(needs_physics_process);
@@ -582,25 +584,26 @@ struct _RigidBodyInOut {
int local_shape = 0;
};
-void RigidBody3D::_direct_state_changed(Object *p_state) {
-#ifdef DEBUG_ENABLED
- state = Object::cast_to<PhysicsDirectBodyState3D>(p_state);
- ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument");
-#else
- state = (PhysicsDirectBodyState3D *)p_state; //trust it
-#endif
+void RigidBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) {
+ RigidBody3D *body = (RigidBody3D *)p_instance;
+ body->_body_state_changed(p_state);
+}
+void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
set_ignore_transform_notification(true);
- set_global_transform(state->get_transform());
- linear_velocity = state->get_linear_velocity();
- angular_velocity = state->get_angular_velocity();
- inverse_inertia_tensor = state->get_inverse_inertia_tensor();
- if (sleeping != state->is_sleeping()) {
- sleeping = state->is_sleeping();
+ set_global_transform(p_state->get_transform());
+
+ linear_velocity = p_state->get_linear_velocity();
+ angular_velocity = p_state->get_angular_velocity();
+
+ inverse_inertia_tensor = p_state->get_inverse_inertia_tensor();
+
+ if (sleeping != p_state->is_sleeping()) {
+ sleeping = p_state->is_sleeping();
emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed);
}
- GDVIRTUAL_CALL(_integrate_forces, state);
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
set_ignore_transform_notification(false);
_on_transform_changed();
@@ -617,18 +620,18 @@ void RigidBody3D::_direct_state_changed(Object *p_state) {
}
}
- _RigidBodyInOut *toadd = (_RigidBodyInOut *)alloca(state->get_contact_count() * sizeof(_RigidBodyInOut));
+ _RigidBodyInOut *toadd = (_RigidBodyInOut *)alloca(p_state->get_contact_count() * sizeof(_RigidBodyInOut));
int toadd_count = 0; //state->get_contact_count();
RigidBody3D_RemoveAction *toremove = (RigidBody3D_RemoveAction *)alloca(rc * sizeof(RigidBody3D_RemoveAction));
int toremove_count = 0;
//put the ones to add
- for (int i = 0; i < state->get_contact_count(); i++) {
- RID rid = state->get_contact_collider(i);
- ObjectID obj = state->get_contact_collider_id(i);
- int local_shape = state->get_contact_local_shape(i);
- int shape = state->get_contact_collider_shape(i);
+ for (int i = 0; i < p_state->get_contact_count(); i++) {
+ RID rid = p_state->get_contact_collider(i);
+ ObjectID obj = p_state->get_contact_collider_id(i);
+ int local_shape = p_state->get_contact_local_shape(i);
+ int shape = p_state->get_contact_collider_shape(i);
//bool found=false;
@@ -683,8 +686,6 @@ void RigidBody3D::_direct_state_changed(Object *p_state) {
contact_monitor->locked = false;
}
-
- state = nullptr;
}
void RigidBody3D::_notification(int p_what) {
@@ -787,25 +788,15 @@ real_t RigidBody3D::get_angular_damp() const {
}
void RigidBody3D::set_axis_velocity(const Vector3 &p_axis) {
- Vector3 v = state ? state->get_linear_velocity() : linear_velocity;
Vector3 axis = p_axis.normalized();
- v -= axis * axis.dot(v);
- v += p_axis;
- if (state) {
- set_linear_velocity(v);
- } else {
- PhysicsServer3D::get_singleton()->body_set_axis_velocity(get_rid(), p_axis);
- linear_velocity = v;
- }
+ linear_velocity -= axis * axis.dot(linear_velocity);
+ linear_velocity += p_axis;
+ PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity);
}
void RigidBody3D::set_linear_velocity(const Vector3 &p_velocity) {
linear_velocity = p_velocity;
- if (state) {
- state->set_linear_velocity(linear_velocity);
- } else {
- PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity);
- }
+ PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, linear_velocity);
}
Vector3 RigidBody3D::get_linear_velocity() const {
@@ -814,11 +805,7 @@ Vector3 RigidBody3D::get_linear_velocity() const {
void RigidBody3D::set_angular_velocity(const Vector3 &p_velocity) {
angular_velocity = p_velocity;
- if (state) {
- state->set_angular_velocity(angular_velocity);
- } else {
- PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity);
- }
+ PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity);
}
Vector3 RigidBody3D::get_angular_velocity() const {
@@ -1055,7 +1042,7 @@ void RigidBody3D::_bind_methods() {
RigidBody3D::RigidBody3D() :
PhysicsBody3D(PhysicsServer3D::BODY_MODE_DYNAMIC) {
- PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &RigidBody3D::_direct_state_changed));
+ PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, &RigidBody3D::_body_state_changed_callback);
}
RigidBody3D::~RigidBody3D() {
@@ -1083,7 +1070,7 @@ bool CharacterBody3D::move_and_slide() {
bool was_on_floor = on_floor;
// Hack in order to work with calling from _process as well as from _physics_process; calling from thread is risky
- float delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time();
+ double delta = Engine::get_singleton()->is_in_physics_frame() ? get_physics_process_delta_time() : get_process_delta_time();
for (int i = 0; i < 3; i++) {
if (locked_axis & (1 << i)) {
@@ -1229,8 +1216,11 @@ void CharacterBody3D::_set_collision_direction(const PhysicsServer3D::MotionResu
on_ceiling = true;
} else {
on_wall = true;
- on_floor_body = p_result.collider;
- floor_velocity = p_result.collider_velocity;
+ // Don't apply wall velocity when the collider is a CharacterBody3D.
+ if (Object::cast_to<CharacterBody3D>(ObjectDB::get_instance(p_result.collider_id)) == nullptr) {
+ on_floor_body = p_result.collider;
+ floor_velocity = p_result.collider_velocity;
+ }
}
}
}
@@ -2249,23 +2239,19 @@ void PhysicalBone3D::_notification(int p_what) {
}
}
-void PhysicalBone3D::_direct_state_changed(Object *p_state) {
+void PhysicalBone3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) {
+ PhysicalBone3D *bone = (PhysicalBone3D *)p_instance;
+ bone->_body_state_changed(p_state);
+}
+
+void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
if (!simulate_physics || !_internal_simulate_physics) {
return;
}
- /// Update bone transform
-
- PhysicsDirectBodyState3D *state;
-
-#ifdef DEBUG_ENABLED
- state = Object::cast_to<PhysicsDirectBodyState3D>(p_state);
- ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument");
-#else
- state = (PhysicsDirectBodyState3D *)p_state; //trust it
-#endif
+ /// Update bone transform.
- Transform3D global_transform(state->get_transform());
+ Transform3D global_transform(p_state->get_transform());
set_ignore_transform_notification(true);
set_global_transform(global_transform);
@@ -2727,7 +2713,7 @@ void PhysicalBone3D::_start_physics_simulation() {
set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC);
PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer());
PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask());
- PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &PhysicalBone3D::_direct_state_changed));
+ PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, &PhysicalBone3D::_body_state_changed_callback);
set_as_top_level(true);
_internal_simulate_physics = true;
}
@@ -2746,7 +2732,7 @@ void PhysicalBone3D::_stop_physics_simulation() {
PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), 0);
}
if (_internal_simulate_physics) {
- PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), Callable());
+ PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr);
parent_skeleton->set_bone_global_pose_override(bone_id, Transform3D(), 0.0, false);
set_as_top_level(false);
_internal_simulate_physics = false;
diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h
index 17e688451d..8c09f77846 100644
--- a/scene/3d/physics_body_3d.h
+++ b/scene/3d/physics_body_3d.h
@@ -86,7 +86,8 @@ class StaticBody3D : public PhysicsBody3D {
Transform3D last_valid_transform;
- void _direct_state_changed(Object *p_state);
+ static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state);
+ void _body_state_changed(PhysicsDirectBodyState3D *p_state);
protected:
void _notification(int p_what);
@@ -136,7 +137,6 @@ public:
protected:
bool can_sleep = true;
- PhysicsDirectBodyState3D *state = nullptr;
Mode mode = MODE_DYNAMIC;
real_t mass = 1.0;
@@ -197,7 +197,8 @@ protected:
void _body_exit_tree(ObjectID p_id);
void _body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_local_shape);
- virtual void _direct_state_changed(Object *p_state);
+ static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state);
+ virtual void _body_state_changed(PhysicsDirectBodyState3D *p_state);
void _notification(int p_what);
static void _bind_methods();
@@ -525,7 +526,8 @@ protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _notification(int p_what);
- void _direct_state_changed(Object *p_state);
+ static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state);
+ void _body_state_changed(PhysicsDirectBodyState3D *p_state);
static void _bind_methods();
diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp
index 28ff3e9412..7eb189e890 100644
--- a/scene/3d/soft_body_3d.cpp
+++ b/scene/3d/soft_body_3d.cpp
@@ -355,6 +355,11 @@ void SoftBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_drag_coefficient", "drag_coefficient"), &SoftBody3D::set_drag_coefficient);
ClassDB::bind_method(D_METHOD("get_drag_coefficient"), &SoftBody3D::get_drag_coefficient);
+ ClassDB::bind_method(D_METHOD("get_point_transform", "point_index"), &SoftBody3D::get_point_transform);
+
+ ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::pin_point, DEFVAL(NodePath()));
+ ClassDB::bind_method(D_METHOD("is_point_pinned", "point_index"), &SoftBody3D::is_point_pinned);
+
ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &SoftBody3D::set_ray_pickable);
ClassDB::bind_method(D_METHOD("is_ray_pickable"), &SoftBody3D::is_ray_pickable);
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index a28382f4cb..b9a2736918 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -1177,7 +1177,7 @@ void AnimatedSprite3D::stop() {
}
bool AnimatedSprite3D::is_playing() const {
- return is_processing();
+ return playing;
}
void AnimatedSprite3D::_reset_timeout() {
diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp
index 92c0e09947..daeea81891 100644
--- a/scene/3d/vehicle_body_3d.cpp
+++ b/scene/3d/vehicle_body_3d.cpp
@@ -802,24 +802,21 @@ void VehicleBody3D::_update_friction(PhysicsDirectBodyState3D *s) {
}
}
-void VehicleBody3D::_direct_state_changed(Object *p_state) {
- RigidBody3D::_direct_state_changed(p_state);
+void VehicleBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
+ RigidBody3D::_body_state_changed(p_state);
- state = Object::cast_to<PhysicsDirectBodyState3D>(p_state);
- ERR_FAIL_NULL_MSG(state, "Method '_direct_state_changed' must receive a valid PhysicsDirectBodyState3D object as argument");
-
- real_t step = state->get_step();
+ real_t step = p_state->get_step();
for (int i = 0; i < wheels.size(); i++) {
- _update_wheel(i, state);
+ _update_wheel(i, p_state);
}
for (int i = 0; i < wheels.size(); i++) {
- _ray_cast(i, state);
- wheels[i]->set_transform(state->get_transform().inverse() * wheels[i]->m_worldTransform);
+ _ray_cast(i, p_state);
+ wheels[i]->set_transform(p_state->get_transform().inverse() * wheels[i]->m_worldTransform);
}
- _update_suspension(state);
+ _update_suspension(p_state);
for (int i = 0; i < wheels.size(); i++) {
//apply suspension force
@@ -831,20 +828,20 @@ void VehicleBody3D::_direct_state_changed(Object *p_state) {
suspensionForce = wheel.m_maxSuspensionForce;
}
Vector3 impulse = wheel.m_raycastInfo.m_contactNormalWS * suspensionForce * step;
- Vector3 relative_position = wheel.m_raycastInfo.m_contactPointWS - state->get_transform().origin;
+ Vector3 relative_position = wheel.m_raycastInfo.m_contactPointWS - p_state->get_transform().origin;
- state->apply_impulse(impulse, relative_position);
+ p_state->apply_impulse(impulse, relative_position);
}
- _update_friction(state);
+ _update_friction(p_state);
for (int i = 0; i < wheels.size(); i++) {
VehicleWheel3D &wheel = *wheels[i];
- Vector3 relpos = wheel.m_raycastInfo.m_hardPointWS - state->get_transform().origin;
- Vector3 vel = state->get_linear_velocity() + (state->get_angular_velocity()).cross(relpos); // * mPos);
+ Vector3 relpos = wheel.m_raycastInfo.m_hardPointWS - p_state->get_transform().origin;
+ Vector3 vel = p_state->get_linear_velocity() + (p_state->get_angular_velocity()).cross(relpos); // * mPos);
if (wheel.m_raycastInfo.m_isInContact) {
- const Transform3D &chassisWorldTransform = state->get_transform();
+ const Transform3D &chassisWorldTransform = p_state->get_transform();
Vector3 fwd(
chassisWorldTransform.basis[0][Vector3::AXIS_Z],
@@ -864,8 +861,6 @@ void VehicleBody3D::_direct_state_changed(Object *p_state) {
wheel.m_deltaRotation *= real_t(0.99); //damping of rotation when not in contact
}
-
- state = nullptr;
}
void VehicleBody3D::set_engine_force(real_t p_engine_force) {
@@ -926,7 +921,5 @@ void VehicleBody3D::_bind_methods() {
VehicleBody3D::VehicleBody3D() {
exclude.insert(get_rid());
- //PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &VehicleBody3D::_direct_state_changed));
-
set_mass(40);
}
diff --git a/scene/3d/vehicle_body_3d.h b/scene/3d/vehicle_body_3d.h
index 2c10205ea3..f29c3d89b7 100644
--- a/scene/3d/vehicle_body_3d.h
+++ b/scene/3d/vehicle_body_3d.h
@@ -192,7 +192,8 @@ class VehicleBody3D : public RigidBody3D {
static void _bind_methods();
- void _direct_state_changed(Object *p_state) override;
+ static void _body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state);
+ virtual void _body_state_changed(PhysicsDirectBodyState3D *p_state) override;
public:
void set_engine_force(real_t p_engine_force);
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index 9383355292..f7b7604fd5 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -31,119 +31,18 @@
#include "audio_stream_player.h"
#include "core/config/engine.h"
-
-void AudioStreamPlayer::_mix_to_bus(const AudioFrame *p_frames, int p_amount) {
- int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus);
-
- AudioFrame *targets[4] = { nullptr, nullptr, nullptr, nullptr };
-
- if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) {
- targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 0);
- } else {
- switch (mix_target) {
- case MIX_TARGET_STEREO: {
- targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 0);
- } break;
- case MIX_TARGET_SURROUND: {
- for (int i = 0; i < AudioServer::get_singleton()->get_channel_count(); i++) {
- targets[i] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, i);
- }
- } break;
- case MIX_TARGET_CENTER: {
- targets[0] = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 1);
- } break;
- }
- }
-
- for (int c = 0; c < 4; c++) {
- if (!targets[c]) {
- break;
- }
- for (int i = 0; i < p_amount; i++) {
- targets[c][i] += p_frames[i];
- }
- }
-}
-
-void AudioStreamPlayer::_mix_internal(bool p_fadeout) {
- //get data
- AudioFrame *buffer = mix_buffer.ptrw();
- int buffer_size = mix_buffer.size();
-
- if (p_fadeout) {
- // Short fadeout ramp
- buffer_size = MIN(buffer_size, 128);
- }
-
- stream_playback->mix(buffer, pitch_scale, buffer_size);
-
- //multiply volume interpolating to avoid clicks if this changes
- float target_volume = p_fadeout ? -80.0 : volume_db;
- float vol = Math::db2linear(mix_volume_db);
- float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size);
-
- for (int i = 0; i < buffer_size; i++) {
- buffer[i] *= vol;
- vol += vol_inc;
- }
-
- //set volume for next mix
- mix_volume_db = target_volume;
-
- _mix_to_bus(buffer, buffer_size);
-}
-
-void AudioStreamPlayer::_mix_audio() {
- if (use_fadeout) {
- _mix_to_bus(fadeout_buffer.ptr(), fadeout_buffer.size());
- use_fadeout = false;
- }
-
- if (!stream_playback.is_valid() || !active.is_set() ||
- (stream_paused && !stream_paused_fade)) {
- return;
- }
-
- if (stream_paused) {
- if (stream_paused_fade && stream_playback->is_playing()) {
- _mix_internal(true);
- stream_paused_fade = false;
- }
- return;
- }
-
- if (setstop.is_set()) {
- _mix_internal(true);
- stream_playback->stop();
- setstop.clear();
- }
-
- if (setseek.get() >= 0.0 && !stop_has_priority.is_set()) {
- if (stream_playback->is_playing()) {
- //fade out to avoid pops
- _mix_internal(true);
- }
-
- stream_playback->start(setseek.get());
- setseek.set(-1.0); //reset seek
- mix_volume_db = volume_db; //reset ramp
- }
-
- stop_has_priority.clear();
-
- _mix_internal(false);
-}
+#include "core/math/audio_frame.h"
+#include "servers/audio_server.h"
void AudioStreamPlayer::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) {
- AudioServer::get_singleton()->add_callback(_mix_audios, this);
if (autoplay && !Engine::get_singleton()->is_editor_hint()) {
play();
}
}
if (p_what == NOTIFICATION_INTERNAL_PROCESS) {
- if (!active.is_set() || (setseek.get() < 0 && !stream_playback->is_playing())) {
+ if (stream_playback.is_valid() && active.is_set() && !AudioServer::get_singleton()->is_playback_active(stream_playback)) {
active.clear();
set_process_internal(false);
emit_signal(SNAME("finished"));
@@ -151,7 +50,9 @@ void AudioStreamPlayer::_notification(int p_what) {
}
if (p_what == NOTIFICATION_EXIT_TREE) {
- AudioServer::get_singleton()->remove_callback(_mix_audios, this);
+ if (stream_playback.is_valid()) {
+ AudioServer::get_singleton()->stop_playback_stream(stream_playback);
+ }
}
if (p_what == NOTIFICATION_PAUSED) {
@@ -167,38 +68,10 @@ void AudioStreamPlayer::_notification(int p_what) {
}
void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) {
- AudioServer::get_singleton()->lock();
-
- if (active.is_set() && stream_playback.is_valid() && !stream_paused) {
- //changing streams out of the blue is not a great idea, but at least
- //let's try to somehow avoid a click
-
- AudioFrame *buffer = fadeout_buffer.ptrw();
- int buffer_size = fadeout_buffer.size();
-
- stream_playback->mix(buffer, pitch_scale, buffer_size);
-
- //multiply volume interpolating to avoid clicks if this changes
- float target_volume = -80.0;
- float vol = Math::db2linear(mix_volume_db);
- float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size);
-
- for (int i = 0; i < buffer_size; i++) {
- buffer[i] *= vol;
- vol += vol_inc;
- }
-
- use_fadeout = true;
- }
-
- mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size());
-
if (stream_playback.is_valid()) {
+ stop();
stream_playback.unref();
stream.unref();
- active.clear();
- setseek.set(-1);
- setstop.clear();
}
if (p_stream.is_valid()) {
@@ -210,8 +83,6 @@ void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) {
}
}
- AudioServer::get_singleton()->unlock();
-
if (p_stream.is_valid() && stream_playback.is_null()) {
stream.unref();
}
@@ -223,6 +94,8 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const {
void AudioStreamPlayer::set_volume_db(float p_volume) {
volume_db = p_volume;
+
+ AudioServer::get_singleton()->set_playback_all_bus_volumes_linear(stream_playback, _get_volume_vector());
}
float AudioStreamPlayer::get_volume_db() const {
@@ -232,6 +105,8 @@ float AudioStreamPlayer::get_volume_db() const {
void AudioStreamPlayer::set_pitch_scale(float p_pitch_scale) {
ERR_FAIL_COND(p_pitch_scale <= 0.0);
pitch_scale = p_pitch_scale;
+
+ AudioServer::get_singleton()->set_playback_pitch_scale(stream_playback, pitch_scale);
}
float AudioStreamPlayer::get_pitch_scale() const {
@@ -239,31 +114,32 @@ float AudioStreamPlayer::get_pitch_scale() const {
}
void AudioStreamPlayer::play(float p_from_pos) {
+ stop();
+ if (stream.is_valid()) {
+ stream_playback = stream->instance_playback();
+ }
if (stream_playback.is_valid()) {
- //mix_volume_db = volume_db; do not reset volume ramp here, can cause clicks
- setseek.set(p_from_pos);
- stop_has_priority.clear();
+ AudioServer::get_singleton()->start_playback_stream(stream_playback, bus, _get_volume_vector(), p_from_pos);
active.set();
- set_process_internal(true);
}
}
void AudioStreamPlayer::seek(float p_seconds) {
- if (stream_playback.is_valid()) {
- setseek.set(p_seconds);
+ if (stream_playback.is_valid() && active.is_set()) {
+ play(p_seconds);
}
}
void AudioStreamPlayer::stop() {
- if (stream_playback.is_valid() && active.is_set()) {
- setstop.set();
- stop_has_priority.set();
+ if (stream_playback.is_valid()) {
+ active.clear();
+ AudioServer::get_singleton()->stop_playback_stream(stream_playback);
}
}
bool AudioStreamPlayer::is_playing() const {
if (stream_playback.is_valid()) {
- return active.is_set() && !setstop.is_set(); //&& stream_playback->is_playing();
+ return AudioServer::get_singleton()->is_playback_active(stream_playback);
}
return false;
@@ -271,26 +147,22 @@ bool AudioStreamPlayer::is_playing() const {
float AudioStreamPlayer::get_playback_position() {
if (stream_playback.is_valid()) {
- float ss = setseek.get();
- if (ss >= 0.0) {
- return ss;
- }
- return stream_playback->get_playback_position();
+ return AudioServer::get_singleton()->get_playback_position(stream_playback);
}
return 0;
}
void AudioStreamPlayer::set_bus(const StringName &p_bus) {
- //if audio is active, must lock this
- AudioServer::get_singleton()->lock();
bus = p_bus;
- AudioServer::get_singleton()->unlock();
+ if (stream_playback.is_valid()) {
+ AudioServer::get_singleton()->set_playback_bus_exclusive(stream_playback, p_bus, _get_volume_vector());
+ }
}
StringName AudioStreamPlayer::get_bus() const {
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {
- if (AudioServer::get_singleton()->get_bus_name(i) == bus) {
+ if (AudioServer::get_singleton()->get_bus_name(i) == String(bus)) {
return bus;
}
}
@@ -322,18 +194,61 @@ void AudioStreamPlayer::_set_playing(bool p_enable) {
}
bool AudioStreamPlayer::_is_active() const {
- return active.is_set();
+ if (stream_playback.is_valid()) {
+ return AudioServer::get_singleton()->is_playback_active(stream_playback);
+ }
+ return false;
}
void AudioStreamPlayer::set_stream_paused(bool p_pause) {
- if (p_pause != stream_paused) {
- stream_paused = p_pause;
- stream_paused_fade = p_pause;
+ // TODO this does not have perfect recall, fix that maybe? If the stream isn't set, we can't persist this bool.
+ if (stream_playback.is_valid()) {
+ AudioServer::get_singleton()->set_playback_paused(stream_playback, p_pause);
}
}
bool AudioStreamPlayer::get_stream_paused() const {
- return stream_paused;
+ if (stream_playback.is_valid()) {
+ return AudioServer::get_singleton()->is_playback_paused(stream_playback);
+ }
+ return false;
+}
+
+Vector<AudioFrame> AudioStreamPlayer::_get_volume_vector() {
+ Vector<AudioFrame> volume_vector;
+ // We need at most four stereo pairs (for 7.1 systems).
+ volume_vector.resize(4);
+
+ // Initialize the volume vector to zero.
+ for (AudioFrame &channel_volume_db : volume_vector) {
+ channel_volume_db = AudioFrame(0, 0);
+ }
+
+ float volume_linear = Math::db2linear(volume_db);
+
+ // Set the volume vector up according to the speaker mode and mix target.
+ // TODO do we need to scale the volume down when we output to more channels?
+ if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) {
+ volume_vector.write[0] = AudioFrame(volume_linear, volume_linear);
+ } else {
+ switch (mix_target) {
+ case MIX_TARGET_STEREO: {
+ volume_vector.write[0] = AudioFrame(volume_linear, volume_linear);
+ } break;
+ case MIX_TARGET_SURROUND: {
+ // TODO Make sure this is right.
+ volume_vector.write[0] = AudioFrame(volume_linear, volume_linear);
+ volume_vector.write[1] = AudioFrame(volume_linear, /* LFE= */ 1.0f);
+ volume_vector.write[2] = AudioFrame(volume_linear, volume_linear);
+ volume_vector.write[3] = AudioFrame(volume_linear, volume_linear);
+ } break;
+ case MIX_TARGET_CENTER: {
+ // TODO Make sure this is right.
+ volume_vector.write[1] = AudioFrame(volume_linear, /* LFE= */ 1.0f);
+ } break;
+ }
+ }
+ return volume_vector;
}
void AudioStreamPlayer::_validate_property(PropertyInfo &property) const {
@@ -410,8 +325,6 @@ void AudioStreamPlayer::_bind_methods() {
}
AudioStreamPlayer::AudioStreamPlayer() {
- fadeout_buffer.resize(512);
-
AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer::_bus_layout_changed));
}
diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h
index aa8d088be5..76b6698c9d 100644
--- a/scene/audio/audio_stream_player.h
+++ b/scene/audio/audio_stream_player.h
@@ -48,22 +48,13 @@ public:
private:
Ref<AudioStreamPlayback> stream_playback;
Ref<AudioStream> stream;
- Vector<AudioFrame> mix_buffer;
- Vector<AudioFrame> fadeout_buffer;
- bool use_fadeout = false;
- SafeNumeric<float> setseek{ -1.0 };
SafeFlag active;
- SafeFlag setstop;
- SafeFlag stop_has_priority;
- float mix_volume_db = 0.0;
float pitch_scale = 1.0;
float volume_db = 0.0;
bool autoplay = false;
- bool stream_paused = false;
- bool stream_paused_fade = false;
- StringName bus;
+ StringName bus = "Master";
MixTarget mix_target = MIX_TARGET_STEREO;
@@ -77,6 +68,8 @@ private:
void _bus_layout_changed();
void _mix_to_bus(const AudioFrame *p_frames, int p_amount);
+ Vector<AudioFrame> _get_volume_vector();
+
protected:
void _validate_property(PropertyInfo &property) const override;
void _notification(int p_what);
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index a2f1d2b15a..cf2df4e1a2 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -351,11 +351,11 @@ MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control
Label *l = memnew(Label);
l->set_theme_type_variation("HeaderSmall");
l->set_text(p_label);
- add_child(l);
+ add_child(l, false, INTERNAL_MODE_FRONT);
MarginContainer *mc = memnew(MarginContainer);
mc->add_theme_constant_override("margin_left", 0);
mc->add_child(p_control);
- add_child(mc);
+ add_child(mc, false, INTERNAL_MODE_FRONT);
if (p_expand) {
mc->set_v_size_flags(SIZE_EXPAND_FILL);
}
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 0dddb2b09a..1afb0b8e9d 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -1139,7 +1139,7 @@ void ColorPicker::_bind_methods() {
ColorPicker::ColorPicker() :
BoxContainer(true) {
HBoxContainer *hb_edit = memnew(HBoxContainer);
- add_child(hb_edit);
+ add_child(hb_edit, false, INTERNAL_MODE_FRONT);
hb_edit->set_v_size_flags(SIZE_EXPAND_FILL);
hb_edit->add_child(uv_edit);
@@ -1151,7 +1151,7 @@ ColorPicker::ColorPicker() :
uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, uv_edit));
HBoxContainer *hb_smpl = memnew(HBoxContainer);
- add_child(hb_smpl);
+ add_child(hb_smpl, false, INTERNAL_MODE_FRONT);
hb_smpl->add_child(sample);
sample->set_h_size_flags(SIZE_EXPAND_FILL);
@@ -1165,12 +1165,12 @@ ColorPicker::ColorPicker() :
btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed));
VBoxContainer *vbl = memnew(VBoxContainer);
- add_child(vbl);
+ add_child(vbl, false, INTERNAL_MODE_FRONT);
- add_child(memnew(HSeparator));
+ add_child(memnew(HSeparator), false, INTERNAL_MODE_FRONT);
VBoxContainer *vbr = memnew(VBoxContainer);
- add_child(vbr);
+ add_child(vbr, false, INTERNAL_MODE_FRONT);
vbr->set_h_size_flags(SIZE_EXPAND_FILL);
for (int i = 0; i < 4; i++) {
@@ -1273,11 +1273,11 @@ ColorPicker::ColorPicker() :
set_pick_color(Color(1, 1, 1));
- add_child(preset_separator);
+ add_child(preset_separator, false, INTERNAL_MODE_FRONT);
preset_container->set_h_size_flags(SIZE_EXPAND_FILL);
preset_container->set_columns(preset_column_count);
- add_child(preset_container);
+ add_child(preset_container, false, INTERNAL_MODE_FRONT);
btn_add_preset->set_icon_align(Button::ALIGN_CENTER);
btn_add_preset->set_tooltip(RTR("Add current color as a preset."));
@@ -1405,7 +1405,7 @@ void ColorPickerButton::_update_picker() {
picker = memnew(ColorPicker);
picker->set_anchors_and_offsets_preset(PRESET_WIDE);
popup->add_child(picker);
- add_child(popup);
+ add_child(popup, false, INTERNAL_MODE_FRONT);
picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed));
popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup));
popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed));
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index da858e8e83..5d98aaa698 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -319,7 +319,7 @@ AcceptDialog::AcceptDialog() {
set_clamp_to_embedder(true);
bg = memnew(Panel);
- add_child(bg);
+ add_child(bg, false, INTERNAL_MODE_FRONT);
hbc = memnew(HBoxContainer);
@@ -331,9 +331,9 @@ AcceptDialog::AcceptDialog() {
label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
label->set_begin(Point2(margin, margin));
label->set_end(Point2(-margin, -button_margin - 10));
- add_child(label);
+ add_child(label, false, INTERNAL_MODE_FRONT);
- add_child(hbc);
+ add_child(hbc, false, INTERNAL_MODE_FRONT);
hbc->add_spacer();
ok = memnew(Button);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 2512b24623..973b72973d 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -924,7 +924,7 @@ FileDialog::FileDialog() {
show_hidden_files = default_show_hidden_files;
vbox = memnew(VBoxContainer);
- add_child(vbox);
+ add_child(vbox, false, INTERNAL_MODE_FRONT);
vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed));
mode = FILE_MODE_SAVE_FILE;
@@ -1023,8 +1023,7 @@ FileDialog::FileDialog() {
filter->connect("item_selected", callable_mp(this, &FileDialog::_filter_selected));
confirm_save = memnew(ConfirmationDialog);
- // confirm_save->set_as_top_level(true);
- add_child(confirm_save);
+ add_child(confirm_save, false, INTERNAL_MODE_FRONT);
confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed));
@@ -1036,16 +1035,16 @@ FileDialog::FileDialog() {
makedirname = memnew(LineEdit);
makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
makevb->add_margin_child(TTRC("Name:"), makedirname);
- add_child(makedialog);
+ add_child(makedialog, false, INTERNAL_MODE_FRONT);
makedialog->register_text_enter(makedirname);
makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm));
mkdirerr = memnew(AcceptDialog);
mkdirerr->set_text(TTRC("Could not create folder."));
- add_child(mkdirerr);
+ add_child(mkdirerr, false, INTERNAL_MODE_FRONT);
exterr = memnew(AcceptDialog);
exterr->set_text(TTRC("Must use a valid extension."));
- add_child(exterr);
+ add_child(exterr, false, INTERNAL_MODE_FRONT);
update_filters();
update_dir();
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index bbab3008e2..56b8a936e1 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -48,7 +48,7 @@ GradientEdit::GradientEdit() {
picker = memnew(ColorPicker);
popup->add_child(picker);
- add_child(popup);
+ add_child(popup, false, INTERNAL_MODE_FRONT);
}
int GradientEdit::_get_point_from_pos(int x) {
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 0281bc7efb..cabae9feb2 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -366,7 +366,6 @@ void GraphEdit::_graph_node_raised(Node *p_gn) {
}
move_child(connections_layer, first_not_comment);
- top_layer->raise();
emit_signal(SNAME("node_selected"), p_gn);
}
@@ -2246,14 +2245,14 @@ GraphEdit::GraphEdit() {
zoom_max = (1 * Math::pow(zoom_step, 4));
top_layer = memnew(GraphEditFilter(this));
- add_child(top_layer);
+ add_child(top_layer, false, INTERNAL_MODE_BACK);
top_layer->set_mouse_filter(MOUSE_FILTER_PASS);
top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw));
top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input));
connections_layer = memnew(Control);
- add_child(connections_layer);
+ add_child(connections_layer, false, INTERNAL_MODE_FRONT);
connections_layer->connect("draw", callable_mp(this, &GraphEdit::_connections_layer_draw));
connections_layer->set_name("CLAYER");
connections_layer->set_disable_visibility_clip(true); // so it can draw freely and be offset
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 8297de9f30..d10ad90c1f 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -1656,7 +1656,7 @@ void ItemList::_bind_methods() {
ItemList::ItemList() {
scroll_bar = memnew(VScrollBar);
- add_child(scroll_bar);
+ add_child(scroll_bar, false, INTERNAL_MODE_FRONT);
scroll_bar->connect("value_changed", callable_mp(this, &ItemList::_scroll_changed));
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index d524e78caa..3605842224 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -2220,7 +2220,7 @@ void LineEdit::_bind_methods() {
void LineEdit::_ensure_menu() {
if (!menu) {
menu = memnew(PopupMenu);
- add_child(menu);
+ add_child(menu, false, INTERNAL_MODE_FRONT);
menu_dir = memnew(PopupMenu);
menu_dir->set_name("DirMenu");
@@ -2305,7 +2305,7 @@ LineEdit::LineEdit() {
set_mouse_filter(MOUSE_FILTER_STOP);
caret_blink_timer = memnew(Timer);
- add_child(caret_blink_timer);
+ add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT);
caret_blink_timer->set_wait_time(0.65);
caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret));
set_caret_blink_enabled(false);
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index e312aaed57..737ba84617 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -172,7 +172,7 @@ MenuButton::MenuButton() {
popup = memnew(PopupMenu);
popup->hide();
- add_child(popup);
+ add_child(popup, false, INTERNAL_MODE_FRONT);
popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(true));
popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(false));
}
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index cd55f258b3..d16e96dbec 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -351,7 +351,7 @@ OptionButton::OptionButton() {
popup = memnew(PopupMenu);
popup->hide();
- add_child(popup);
+ add_child(popup, false, INTERNAL_MODE_FRONT);
popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected));
popup->connect("id_focused", callable_mp(this, &OptionButton::_focused));
popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false));
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index f7e7e1cd60..e9414598a2 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -255,5 +255,5 @@ void PopupPanel::_notification(int p_what) {
PopupPanel::PopupPanel() {
panel = memnew(Panel);
- add_child(panel);
+ add_child(panel, false, INTERNAL_MODE_FRONT);
}
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index f75d6755a8..c0a559e624 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -228,6 +228,7 @@ void PopupMenu::_activate_submenu(int p_over) {
// Set autohide areas
PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup);
if (submenu_pum) {
+ submenu_pum->take_mouse_focus();
// Make the position of the parent popup relative to submenu popup
this_rect.position = this_rect.position - submenu_pum->get_position();
@@ -1689,7 +1690,7 @@ PopupMenu::PopupMenu() {
// Margin Container
margin_container = memnew(MarginContainer);
margin_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
- add_child(margin_container);
+ add_child(margin_container, false, INTERNAL_MODE_FRONT);
margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background));
// Scroll Container
@@ -1703,7 +1704,7 @@ PopupMenu::PopupMenu() {
control->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- scroll_container->add_child(control);
+ scroll_container->add_child(control, false, INTERNAL_MODE_FRONT);
control->connect("draw", callable_mp(this, &PopupMenu::_draw_items));
connect("window_input", callable_mp(this, &PopupMenu::gui_input));
@@ -1712,13 +1713,13 @@ PopupMenu::PopupMenu() {
submenu_timer->set_wait_time(0.3);
submenu_timer->set_one_shot(true);
submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
- add_child(submenu_timer);
+ add_child(submenu_timer, false, INTERNAL_MODE_FRONT);
minimum_lifetime_timer = memnew(Timer);
minimum_lifetime_timer->set_wait_time(0.3);
minimum_lifetime_timer->set_one_shot(true);
minimum_lifetime_timer->connect("timeout", callable_mp(this, &PopupMenu::_minimum_lifetime_timeout));
- add_child(minimum_lifetime_timer);
+ add_child(minimum_lifetime_timer, false, INTERNAL_MODE_FRONT);
}
PopupMenu::~PopupMenu() {
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index b7519af4aa..fbc67d8a24 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -4361,7 +4361,7 @@ RichTextLabel::RichTextLabel() {
current_frame = main;
vscroll = memnew(VScrollBar);
- add_child(vscroll);
+ add_child(vscroll, false, INTERNAL_MODE_FRONT);
vscroll->set_drag_node(String(".."));
vscroll->set_step(1);
vscroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index b5daa8b3f1..0c051f61e2 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -228,9 +228,6 @@ void ScrollContainer::_update_scrollbar_position() {
v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
- h_scroll->raise();
- v_scroll->raise();
-
_updating_scrollbars = false;
}
@@ -618,12 +615,12 @@ void ScrollContainer::_bind_methods() {
ScrollContainer::ScrollContainer() {
h_scroll = memnew(HScrollBar);
h_scroll->set_name("_h_scroll");
- add_child(h_scroll);
+ add_child(h_scroll, false, INTERNAL_MODE_BACK);
h_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved));
v_scroll = memnew(VScrollBar);
v_scroll->set_name("_v_scroll");
- add_child(v_scroll);
+ add_child(v_scroll, false, INTERNAL_MODE_BACK);
v_scroll->connect("value_changed", callable_mp(this, &ScrollContainer::_scroll_moved));
deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone");
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 65a3eb3adf..1074d0d8a0 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -278,7 +278,7 @@ void SpinBox::_bind_methods() {
SpinBox::SpinBox() {
line_edit = memnew(LineEdit);
- add_child(line_edit);
+ add_child(line_edit, false, INTERNAL_MODE_FRONT);
line_edit->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
@@ -291,5 +291,5 @@ SpinBox::SpinBox() {
range_click_timer = memnew(Timer);
range_click_timer->connect("timeout", callable_mp(this, &SpinBox::_range_click_timeout));
- add_child(range_click_timer);
+ add_child(range_click_timer, false, INTERNAL_MODE_FRONT);
}
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 5884d2a854..845a2ec6c7 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -434,14 +434,14 @@ void TabContainer::_notification(int p_what) {
}
int tab_width = tab_widths[i];
- if (get_tab_disabled(index)) {
+ if (index == current) {
+ x_current = x;
+ } else if (get_tab_disabled(index)) {
if (rtl) {
_draw_tab(tab_disabled, font_disabled_color, index, size.width - (tabs_ofs_cache + x) - tab_width);
} else {
_draw_tab(tab_disabled, font_disabled_color, index, tabs_ofs_cache + x);
}
- } else if (index == current) {
- x_current = x;
} else {
if (rtl) {
_draw_tab(tab_unselected, font_unselected_color, index, size.width - (tabs_ofs_cache + x) - tab_width);
@@ -459,12 +459,13 @@ void TabContainer::_notification(int p_what) {
panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height));
}
- // Draw selected tab in front. only draw selected tab when it's in visible range.
+ // Draw selected tab in front. Only draw selected tab when it's in visible range.
if (tabs.size() > 0 && current - first_tab_cache < tab_widths.size() && current >= first_tab_cache) {
+ Ref<StyleBox> current_style_box = get_tab_disabled(current) ? tab_disabled : tab_selected;
if (rtl) {
- _draw_tab(tab_selected, font_selected_color, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]);
+ _draw_tab(current_style_box, font_selected_color, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]);
} else {
- _draw_tab(tab_selected, font_selected_color, current, tabs_ofs_cache + x_current);
+ _draw_tab(current_style_box, font_selected_color, current, tabs_ofs_cache + x_current);
}
}
@@ -640,8 +641,8 @@ void TabContainer::_on_mouse_exited() {
int TabContainer::_get_tab_width(int p_index) const {
ERR_FAIL_INDEX_V(p_index, get_tab_count(), 0);
- Control *control = Object::cast_to<Control>(_get_tabs()[p_index]);
- if (!control || control->is_set_as_top_level() || get_tab_hidden(p_index)) {
+ Control *control = get_tab_control(p_index);
+ if (!control || get_tab_hidden(p_index)) {
return 0;
}
@@ -905,7 +906,7 @@ void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) {
if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) {
Control *moving_tabc = from_tabc->get_tab_control(tab_from_id);
from_tabc->remove_child(moving_tabc);
- add_child(moving_tabc);
+ add_child(moving_tabc, false, INTERNAL_MODE_FRONT);
if (hover_now < 0) {
hover_now = get_tab_count() - 1;
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index a731f80741..7e64c13b37 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -5004,7 +5004,7 @@ void TextEdit::_paste_internal() {
void TextEdit::_generate_context_menu() {
if (!menu) {
menu = memnew(PopupMenu);
- add_child(menu);
+ add_child(menu, false, INTERNAL_MODE_FRONT);
menu_dir = memnew(PopupMenu);
menu_dir->set_name("DirMenu");
@@ -5012,7 +5012,7 @@ void TextEdit::_generate_context_menu() {
menu_dir->add_radio_check_item(RTR("Auto-detect direction"), MENU_DIR_AUTO);
menu_dir->add_radio_check_item(RTR("Left-to-right"), MENU_DIR_LTR);
menu_dir->add_radio_check_item(RTR("Right-to-left"), MENU_DIR_RTL);
- menu->add_child(menu_dir);
+ menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
menu_ctl = memnew(PopupMenu);
menu_ctl->set_name("CTLMenu");
@@ -5034,7 +5034,7 @@ void TextEdit::_generate_context_menu() {
menu_ctl->add_item(RTR("Zero width non-joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
menu_ctl->add_item(RTR("Word joiner (WJ)"), MENU_INSERT_WJ);
menu_ctl->add_item(RTR("Soft hyphen (SHY)"), MENU_INSERT_SHY);
- menu->add_child(menu_ctl);
+ menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
@@ -5948,8 +5948,8 @@ TextEdit::TextEdit() {
h_scroll = memnew(HScrollBar);
v_scroll = memnew(VScrollBar);
- add_child(h_scroll);
- add_child(v_scroll);
+ add_child(h_scroll, false, INTERNAL_MODE_FRONT);
+ add_child(v_scroll, false, INTERNAL_MODE_FRONT);
h_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved));
v_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved));
@@ -5958,19 +5958,19 @@ TextEdit::TextEdit() {
/* Caret. */
caret_blink_timer = memnew(Timer);
- add_child(caret_blink_timer);
+ add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT);
caret_blink_timer->set_wait_time(0.65);
caret_blink_timer->connect("timeout", callable_mp(this, &TextEdit::_toggle_draw_caret));
set_caret_blink_enabled(false);
/* Selection. */
click_select_held = memnew(Timer);
- add_child(click_select_held);
+ add_child(click_select_held, false, INTERNAL_MODE_FRONT);
click_select_held->set_wait_time(0.05);
click_select_held->connect("timeout", callable_mp(this, &TextEdit::_click_selection_held));
idle_detect = memnew(Timer);
- add_child(idle_detect);
+ add_child(idle_detect, false, INTERNAL_MODE_FRONT);
idle_detect->set_one_shot(true);
idle_detect->set_wait_time(GLOBAL_GET("gui/timers/text_edit_idle_detect_sec"));
idle_detect->connect("timeout", callable_mp(this, &TextEdit::_push_current_op));
diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index 5e5dec3579..286f01ee33 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -100,6 +100,15 @@ Ref<Texture2D> TextureProgressBar::get_progress_texture() const {
return progress;
}
+void TextureProgressBar::set_progress_offset(Point2 p_offset) {
+ progress_offset = p_offset;
+ update();
+}
+
+Point2 TextureProgressBar::get_progress_offset() const {
+ return progress_offset;
+}
+
void TextureProgressBar::set_tint_under(const Color &p_tint) {
tint_under = p_tint;
update();
@@ -360,6 +369,9 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
}
}
+ if (p_texture == progress) {
+ dst_rect.position += progress_offset;
+ }
p_texture->get_rect_region(dst_rect, src_rect, dst_rect, src_rect);
RID ci = get_canvas_item();
@@ -403,20 +415,24 @@ void TextureProgressBar::_notification(int p_what) {
Size2 s = progress->get_size();
switch (mode) {
case FILL_LEFT_TO_RIGHT: {
- Rect2 region = Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y));
- draw_texture_rect_region(progress, region, region, tint_progress);
+ Rect2 region = Rect2(progress_offset, Size2(s.x * get_as_ratio(), s.y));
+ Rect2 source = Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y));
+ draw_texture_rect_region(progress, region, source, tint_progress);
} break;
case FILL_RIGHT_TO_LEFT: {
- Rect2 region = Rect2(Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y));
- draw_texture_rect_region(progress, region, region, tint_progress);
+ Rect2 region = Rect2(progress_offset + Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y));
+ Rect2 source = Rect2(Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y));
+ draw_texture_rect_region(progress, region, source, tint_progress);
} break;
case FILL_TOP_TO_BOTTOM: {
- Rect2 region = Rect2(Point2(), Size2(s.x, s.y * get_as_ratio()));
- draw_texture_rect_region(progress, region, region, tint_progress);
+ Rect2 region = Rect2(progress_offset + Point2(), Size2(s.x, s.y * get_as_ratio()));
+ Rect2 source = Rect2(Point2(), Size2(s.x, s.y * get_as_ratio()));
+ draw_texture_rect_region(progress, region, source, tint_progress);
} break;
case FILL_BOTTOM_TO_TOP: {
- Rect2 region = Rect2(Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio()));
- draw_texture_rect_region(progress, region, region, tint_progress);
+ Rect2 region = Rect2(progress_offset + Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio()));
+ Rect2 source = Rect2(Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio()));
+ draw_texture_rect_region(progress, region, source, tint_progress);
} break;
case FILL_CLOCKWISE:
case FILL_COUNTER_CLOCKWISE:
@@ -427,8 +443,9 @@ void TextureProgressBar::_notification(int p_what) {
float val = get_as_ratio() * rad_max_degrees / 360;
if (val == 1) {
- Rect2 region = Rect2(Point2(), s);
- draw_texture_rect(progress, region, false, tint_progress);
+ Rect2 region = Rect2(progress_offset, s);
+ Rect2 source = Rect2(Point2(), s);
+ draw_texture_rect_region(progress, region, source, tint_progress);
} else if (val != 0) {
Array pts;
float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1;
@@ -454,14 +471,14 @@ void TextureProgressBar::_notification(int p_what) {
Vector<Point2> uvs;
Vector<Point2> points;
uvs.push_back(get_relative_center());
- points.push_back(Point2(s.x * get_relative_center().x, s.y * get_relative_center().y));
+ points.push_back(progress_offset + Point2(s.x * get_relative_center().x, s.y * get_relative_center().y));
for (int i = 0; i < pts.size(); i++) {
Point2 uv = unit_val_to_uv(pts[i]);
if (uvs.find(uv) >= 0) {
continue;
}
uvs.push_back(uv);
- points.push_back(Point2(uv.x * s.x, uv.y * s.y));
+ points.push_back(progress_offset + Point2(uv.x * s.x, uv.y * s.y));
}
Vector<Color> colors;
colors.push_back(tint_progress);
@@ -484,17 +501,19 @@ void TextureProgressBar::_notification(int p_what) {
}
} break;
case FILL_BILINEAR_LEFT_AND_RIGHT: {
- Rect2 region = Rect2(Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y));
- draw_texture_rect_region(progress, region, region, tint_progress);
+ Rect2 region = Rect2(progress_offset + Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y));
+ Rect2 source = Rect2(Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y));
+ draw_texture_rect_region(progress, region, source, tint_progress);
} break;
case FILL_BILINEAR_TOP_AND_BOTTOM: {
- Rect2 region = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio()));
- draw_texture_rect_region(progress, region, region, tint_progress);
+ Rect2 region = Rect2(progress_offset + Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio()));
+ Rect2 source = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio()));
+ draw_texture_rect_region(progress, region, source, tint_progress);
} break;
case FILL_MODE_MAX:
break;
default:
- draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress);
+ draw_texture_rect_region(progress, Rect2(progress_offset, Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress);
}
}
if (over.is_valid()) {
@@ -585,6 +604,9 @@ void TextureProgressBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tint_over", "tint"), &TextureProgressBar::set_tint_over);
ClassDB::bind_method(D_METHOD("get_tint_over"), &TextureProgressBar::get_tint_over);
+ ClassDB::bind_method(D_METHOD("set_texture_progress_offset", "offset"), &TextureProgressBar::set_progress_offset);
+ ClassDB::bind_method(D_METHOD("get_texture_progress_offset"), &TextureProgressBar::get_progress_offset);
+
ClassDB::bind_method(D_METHOD("set_radial_initial_angle", "mode"), &TextureProgressBar::set_radial_initial_angle);
ClassDB::bind_method(D_METHOD("get_radial_initial_angle"), &TextureProgressBar::get_radial_initial_angle);
@@ -604,6 +626,7 @@ void TextureProgressBar::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_under_texture", "get_under_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_over_texture", "get_over_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_progress_texture", "get_progress_texture");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_progress_offset"), "set_texture_progress_offset", "get_texture_progress_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
ADD_GROUP("Tint", "tint_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under");
diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h
index d147c43a26..c508f41387 100644
--- a/scene/gui/texture_progress_bar.h
+++ b/scene/gui/texture_progress_bar.h
@@ -61,6 +61,9 @@ public:
void set_fill_mode(int p_fill);
int get_fill_mode();
+ void set_progress_offset(Point2 p_offset);
+ Point2 get_progress_offset() const;
+
void set_radial_initial_angle(float p_angle);
float get_radial_initial_angle();
@@ -100,6 +103,7 @@ public:
private:
FillMode mode = FILL_LEFT_TO_RIGHT;
+ Point2 progress_offset;
float rad_init_angle = 0.0;
float rad_max_degrees = 360.0;
Point2 rad_center_off;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index bf6709c4dc..cb990892ed 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1717,24 +1717,30 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
- if (drop_mode_flags && drop_mode_over == p_item) {
+ if (drop_mode_flags && drop_mode_over) {
Rect2 r = cell_rect;
- bool has_parent = p_item->get_first_child() != nullptr;
if (rtl) {
r.position.x = get_size().width - r.position.x - r.size.x;
}
-
- if (drop_mode_section == -1 || has_parent || drop_mode_section == 0) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color);
- }
-
- if (drop_mode_section == 0) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), cache.drop_position_color);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), cache.drop_position_color);
- }
-
- if ((drop_mode_section == 1 && !has_parent) || drop_mode_section == 0) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), cache.drop_position_color);
+ if (drop_mode_over == p_item) {
+ if (drop_mode_section == 0 || drop_mode_section == -1) {
+ // Line above.
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color);
+ }
+ if (drop_mode_section == 0) {
+ // Side lines.
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), cache.drop_position_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), cache.drop_position_color);
+ }
+ if (drop_mode_section == 0 || (drop_mode_section == 1 && (!p_item->get_first_child() || p_item->is_collapsed()))) {
+ // Line below.
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), cache.drop_position_color);
+ }
+ } else if (drop_mode_over == p_item->get_parent()) {
+ if (drop_mode_section == 1 && !p_item->get_prev() /* && !drop_mode_over->is_collapsed() */) { // The drop_mode_over shouldn't ever be collapsed in here, otherwise we would be drawing a child of a collapsed item.
+ // Line above.
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color);
+ }
}
}
@@ -4789,12 +4795,11 @@ Tree::Tree() {
popup_menu = memnew(PopupMenu);
popup_menu->hide();
- add_child(popup_menu);
- // popup_menu->set_as_top_level(true);
+ add_child(popup_menu, false, INTERNAL_MODE_FRONT);
popup_editor = memnew(Popup);
popup_editor->set_wrap_controls(true);
- add_child(popup_editor);
+ add_child(popup_editor, false, INTERNAL_MODE_FRONT);
popup_editor_vb = memnew(VBoxContainer);
popup_editor->add_child(popup_editor_vb);
popup_editor_vb->add_theme_constant_override("separation", 0);
@@ -4812,12 +4817,12 @@ Tree::Tree() {
h_scroll = memnew(HScrollBar);
v_scroll = memnew(VScrollBar);
- add_child(h_scroll);
- add_child(v_scroll);
+ add_child(h_scroll, false, INTERNAL_MODE_FRONT);
+ add_child(v_scroll, false, INTERNAL_MODE_FRONT);
range_click_timer = memnew(Timer);
range_click_timer->connect("timeout", callable_mp(this, &Tree::_range_click_timeout));
- add_child(range_click_timer);
+ add_child(range_click_timer, false, INTERNAL_MODE_FRONT);
h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp
index 229b5384ea..8734037a57 100644
--- a/scene/gui/video_player.cpp
+++ b/scene/gui/video_player.cpp
@@ -129,7 +129,7 @@ void VideoPlayer::_mix_audio() {
void VideoPlayer::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_ENTER_TREE: {
- AudioServer::get_singleton()->add_callback(_mix_audios, this);
+ AudioServer::get_singleton()->add_mix_callback(_mix_audios, this);
if (stream.is_valid() && autoplay && !Engine::get_singleton()->is_editor_hint()) {
play();
@@ -138,8 +138,7 @@ void VideoPlayer::_notification(int p_notification) {
} break;
case NOTIFICATION_EXIT_TREE: {
- AudioServer::get_singleton()->remove_callback(_mix_audios, this);
-
+ AudioServer::get_singleton()->remove_mix_callback(_mix_audios, this);
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index cafa4fa2d2..f2a2648140 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -48,6 +48,7 @@
#include <stdint.h>
VARIANT_ENUM_CAST(Node::ProcessMode);
+VARIANT_ENUM_CAST(Node::InternalMode);
int Node::orphan_node_count = 0;
@@ -291,14 +292,40 @@ void Node::_propagate_exit_tree() {
void Node::move_child(Node *p_child, int p_pos) {
ERR_FAIL_NULL(p_child);
- ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1, vformat("Invalid new child position: %d.", p_pos));
ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node.");
+
+ // We need to check whether node is internal and move it only in the relevant node range.
+ if (p_child->_is_internal_front()) {
+ ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_front, vformat("Invalid new child position: %d. Child is internal.", p_pos));
+ _move_child(p_child, p_pos);
+ } else if (p_child->_is_internal_back()) {
+ ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_back, vformat("Invalid new child position: %d. Child is internal.", p_pos));
+ _move_child(p_child, data.children.size() - data.internal_children_back + p_pos);
+ } else {
+ ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1 - data.internal_children_front - data.internal_children_back, vformat("Invalid new child position: %d.", p_pos));
+ _move_child(p_child, p_pos + data.internal_children_front);
+ }
+}
+
+void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) {
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, move_child() failed. Consider using call_deferred(\"move_child\") instead (or \"popup\" if this is from a popup).");
// Specifying one place beyond the end
// means the same as moving to the last position
- if (p_pos == data.children.size()) {
- p_pos--;
+ if (!p_ignore_end) { // p_ignore_end is a little hack to make back internal children work properly.
+ if (p_child->_is_internal_front()) {
+ if (p_pos == data.internal_children_front) {
+ p_pos--;
+ }
+ } else if (p_child->_is_internal_back()) {
+ if (p_pos == data.children.size()) {
+ p_pos--;
+ }
+ } else {
+ if (p_pos == data.children.size() - data.internal_children_back) {
+ p_pos--;
+ }
+ }
}
if (p_child->data.pos == p_pos) {
@@ -339,7 +366,14 @@ void Node::raise() {
return;
}
- data.parent->move_child(this, data.parent->data.children.size() - 1);
+ // Internal children move within a different index range.
+ if (_is_internal_front()) {
+ data.parent->move_child(this, data.parent->data.internal_children_front - 1);
+ } else if (_is_internal_back()) {
+ data.parent->move_child(this, data.parent->data.internal_children_back - 1);
+ } else {
+ data.parent->move_child(this, data.parent->get_child_count(false) - 1);
+ }
}
void Node::add_child_notify(Node *p_child) {
@@ -483,24 +517,24 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int
}
}
-void Node::set_network_master(int p_peer_id, bool p_recursive) {
- data.network_master = p_peer_id;
+void Node::set_network_authority(int p_peer_id, bool p_recursive) {
+ data.network_authority = p_peer_id;
if (p_recursive) {
for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->set_network_master(p_peer_id, true);
+ data.children[i]->set_network_authority(p_peer_id, true);
}
}
}
-int Node::get_network_master() const {
- return data.network_master;
+int Node::get_network_authority() const {
+ return data.network_authority;
}
-bool Node::is_network_master() const {
+bool Node::is_network_authority() const {
ERR_FAIL_COND_V(!is_inside_tree(), false);
- return get_multiplayer()->get_network_unique_id() == data.network_master;
+ return get_multiplayer()->get_network_unique_id() == data.network_authority;
}
/***** RPC CONFIG ********/
@@ -1058,6 +1092,10 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
p_child->data.pos = data.children.size();
data.children.push_back(p_child);
p_child->data.parent = this;
+
+ if (data.internal_children_back > 0) {
+ _move_child(p_child, data.children.size() - data.internal_children_back - 1);
+ }
p_child->notification(NOTIFICATION_PARENTED);
if (data.tree) {
@@ -1070,7 +1108,7 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
add_child_notify(p_child);
}
-void Node::add_child(Node *p_child, bool p_legible_unique_name) {
+void Node::add_child(Node *p_child, bool p_legible_unique_name, InternalMode p_internal) {
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND_MSG(p_child == this, vformat("Can't add child '%s' to itself.", p_child->get_name())); // adding to itself!
ERR_FAIL_COND_MSG(p_child->data.parent, vformat("Can't add child '%s' to '%s', already has a parent '%s'.", p_child->get_name(), get_name(), p_child->data.parent->get_name())); //Fail if node has a parent
@@ -1079,19 +1117,35 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) {
#endif
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_node() failed. Consider using call_deferred(\"add_child\", child) instead.");
- /* Validate name */
_validate_child_name(p_child, p_legible_unique_name);
-
_add_child_nocheck(p_child, p_child->data.name);
+
+ if (p_internal == INTERNAL_MODE_FRONT) {
+ _move_child(p_child, data.internal_children_front);
+ data.internal_children_front++;
+ } else if (p_internal == INTERNAL_MODE_BACK) {
+ if (data.internal_children_back > 0) {
+ _move_child(p_child, data.children.size() - 1, true);
+ }
+ data.internal_children_back++;
+ }
}
void Node::add_sibling(Node *p_sibling, bool p_legible_unique_name) {
ERR_FAIL_NULL(p_sibling);
+ ERR_FAIL_NULL(data.parent);
ERR_FAIL_COND_MSG(p_sibling == this, vformat("Can't add sibling '%s' to itself.", p_sibling->get_name())); // adding to itself!
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_sibling() failed. Consider using call_deferred(\"add_sibling\", sibling) instead.");
- get_parent()->add_child(p_sibling, p_legible_unique_name);
- get_parent()->move_child(p_sibling, this->get_index() + 1);
+ InternalMode internal = INTERNAL_MODE_DISABLED;
+ if (_is_internal_front()) { // The sibling will have the same internal status.
+ internal = INTERNAL_MODE_FRONT;
+ } else if (_is_internal_back()) {
+ internal = INTERNAL_MODE_BACK;
+ }
+
+ data.parent->add_child(p_sibling, p_legible_unique_name, internal);
+ data.parent->_move_child(p_sibling, get_index() + 1);
}
void Node::_propagate_validate_owner() {
@@ -1145,7 +1199,12 @@ void Node::remove_child(Node *p_child) {
ERR_FAIL_COND_MSG(idx == -1, vformat("Cannot remove child node '%s' as it is not a child of this node.", p_child->get_name()));
//ERR_FAIL_COND( p_child->data.blocked > 0 );
- //if (data.scene) { does not matter
+ // If internal child, update the counter.
+ if (p_child->_is_internal_front()) {
+ data.internal_children_front--;
+ } else if (p_child->_is_internal_back()) {
+ data.internal_children_back--;
+ }
p_child->_set_tree(nullptr);
//}
@@ -1175,17 +1234,29 @@ void Node::remove_child(Node *p_child) {
}
}
-int Node::get_child_count() const {
- return data.children.size();
+int Node::get_child_count(bool p_include_internal) const {
+ if (p_include_internal) {
+ return data.children.size();
+ } else {
+ return data.children.size() - data.internal_children_front - data.internal_children_back;
+ }
}
-Node *Node::get_child(int p_index) const {
- if (p_index < 0) {
- p_index += data.children.size();
+Node *Node::get_child(int p_index, bool p_include_internal) const {
+ if (p_include_internal) {
+ if (p_index < 0) {
+ p_index += data.children.size();
+ }
+ ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr);
+ return data.children[p_index];
+ } else {
+ if (p_index < 0) {
+ p_index += data.children.size() - data.internal_children_front - data.internal_children_back;
+ }
+ ERR_FAIL_INDEX_V(p_index, data.children.size() - data.internal_children_front - data.internal_children_back, nullptr);
+ p_index += data.internal_children_front;
+ return data.children[p_index];
}
- ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr);
-
- return data.children[p_index];
}
Node *Node::_get_child_by_name(const StringName &p_name) const {
@@ -1717,7 +1788,13 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) {
data.blocked--;
}
-int Node::get_index() const {
+int Node::get_index(bool p_include_internal) const {
+ // p_include_internal = false doesn't make sense if the node is internal.
+ ERR_FAIL_COND_V_MSG(!p_include_internal && (_is_internal_front() || _is_internal_back()), -1, "Node is internal. Can't get index with 'include_internal' being false.");
+
+ if (data.parent && !p_include_internal) {
+ return data.pos - data.parent->data.internal_children_front;
+ }
return data.pos;
}
@@ -2429,12 +2506,12 @@ void Node::queue_delete() {
}
}
-TypedArray<Node> Node::_get_children() const {
+TypedArray<Node> Node::_get_children(bool p_include_internal) const {
TypedArray<Node> arr;
- int cc = get_child_count();
+ int cc = get_child_count(p_include_internal);
arr.resize(cc);
for (int i = 0; i < cc; i++) {
- arr[i] = get_child(i);
+ arr[i] = get_child(i, p_include_internal);
}
return arr;
@@ -2581,11 +2658,11 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_name", "name"), &Node::set_name);
ClassDB::bind_method(D_METHOD("get_name"), &Node::get_name);
- ClassDB::bind_method(D_METHOD("add_child", "node", "legible_unique_name"), &Node::add_child, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("add_child", "node", "legible_unique_name", "internal"), &Node::add_child, DEFVAL(false), DEFVAL(0));
ClassDB::bind_method(D_METHOD("remove_child", "node"), &Node::remove_child);
- ClassDB::bind_method(D_METHOD("get_child_count"), &Node::get_child_count);
- ClassDB::bind_method(D_METHOD("get_children"), &Node::_get_children);
- ClassDB::bind_method(D_METHOD("get_child", "idx"), &Node::get_child);
+ ClassDB::bind_method(D_METHOD("get_child_count", "include_internal"), &Node::get_child_count, DEFVAL(false)); // Note that the default value bound for include_internal is false, while the method is declared with true. This is because internal nodes are irrelevant for GDSCript.
+ ClassDB::bind_method(D_METHOD("get_children", "include_internal"), &Node::_get_children, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("get_child", "idx", "include_internal"), &Node::get_child, DEFVAL(false));
ClassDB::bind_method(D_METHOD("has_node", "path"), &Node::has_node);
ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node);
ClassDB::bind_method(D_METHOD("get_node_or_null", "path"), &Node::get_node_or_null);
@@ -2609,7 +2686,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_owner", "owner"), &Node::set_owner);
ClassDB::bind_method(D_METHOD("get_owner"), &Node::get_owner);
ClassDB::bind_method(D_METHOD("remove_and_skip"), &Node::remove_and_skip);
- ClassDB::bind_method(D_METHOD("get_index"), &Node::get_index);
+ ClassDB::bind_method(D_METHOD("get_index", "include_internal"), &Node::get_index, DEFVAL(false));
ClassDB::bind_method(D_METHOD("print_tree"), &Node::print_tree);
ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty);
ClassDB::bind_method(D_METHOD("set_filename", "filename"), &Node::set_filename);
@@ -2661,10 +2738,10 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("request_ready"), &Node::request_ready);
- ClassDB::bind_method(D_METHOD("set_network_master", "id", "recursive"), &Node::set_network_master, DEFVAL(true));
- ClassDB::bind_method(D_METHOD("get_network_master"), &Node::get_network_master);
+ ClassDB::bind_method(D_METHOD("set_network_authority", "id", "recursive"), &Node::set_network_authority, DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("get_network_authority"), &Node::get_network_authority);
- ClassDB::bind_method(D_METHOD("is_network_master"), &Node::is_network_master);
+ ClassDB::bind_method(D_METHOD("is_network_authority"), &Node::is_network_authority);
ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer);
ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_multiplayer);
@@ -2747,6 +2824,10 @@ void Node::_bind_methods() {
BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS);
BIND_ENUM_CONSTANT(DUPLICATE_USE_INSTANCING);
+ BIND_ENUM_CONSTANT(INTERNAL_MODE_DISABLED);
+ BIND_ENUM_CONSTANT(INTERNAL_MODE_FRONT);
+ BIND_ENUM_CONSTANT(INTERNAL_MODE_BACK);
+
ADD_SIGNAL(MethodInfo("ready"));
ADD_SIGNAL(MethodInfo("renamed"));
ADD_SIGNAL(MethodInfo("tree_entered"));
diff --git a/scene/main/node.h b/scene/main/node.h
index a07accba2f..d0246ebe84 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -73,6 +73,12 @@ public:
NAME_CASING_SNAKE_CASE
};
+ enum InternalMode {
+ INTERNAL_MODE_DISABLED,
+ INTERNAL_MODE_FRONT,
+ INTERNAL_MODE_BACK,
+ };
+
struct Comparator {
bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); }
};
@@ -97,6 +103,8 @@ private:
Node *parent = nullptr;
Node *owner = nullptr;
Vector<Node *> children;
+ int internal_children_front = 0;
+ int internal_children_back = 0;
int pos = -1;
int depth = -1;
int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
@@ -119,7 +127,7 @@ private:
ProcessMode process_mode = PROCESS_MODE_INHERIT;
Node *process_owner = nullptr;
- int network_master = 1; // Server by default.
+ int network_authority = 1; // Server by default.
Vector<MultiplayerAPI::RPCConfig> rpc_methods;
// Variables used to properly sort the node when processing, ignored otherwise.
@@ -172,12 +180,15 @@ private:
void _duplicate_signals(const Node *p_original, Node *p_copy) const;
Node *_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap = nullptr) const;
- TypedArray<Node> _get_children() const;
+ TypedArray<Node> _get_children(bool p_include_internal = true) const;
Array _get_groups() const;
Variant _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Variant _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
+ _FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.pos < data.parent->data.internal_children_front; }
+ _FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.pos >= data.parent->data.children.size() - data.parent->data.internal_children_back; }
+
friend class SceneTree;
void _set_tree(SceneTree *p_tree);
@@ -284,12 +295,12 @@ public:
StringName get_name() const;
void set_name(const String &p_name);
- void add_child(Node *p_child, bool p_legible_unique_name = false);
+ void add_child(Node *p_child, bool p_legible_unique_name = false, InternalMode p_internal = INTERNAL_MODE_DISABLED);
void add_sibling(Node *p_sibling, bool p_legible_unique_name = false);
void remove_child(Node *p_child);
- int get_child_count() const;
- Node *get_child(int p_index) const;
+ int get_child_count(bool p_include_internal = true) const;
+ Node *get_child(int p_index, 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;
@@ -327,6 +338,7 @@ public:
int get_persistent_group_count() const;
void move_child(Node *p_child, int p_pos);
+ void _move_child(Node *p_child, int p_pos, bool p_ignore_end = false);
void raise();
void set_owner(Node *p_owner);
@@ -334,7 +346,7 @@ public:
void get_owned_by(Node *p_by, List<Node *> *p_owned);
void remove_and_skip();
- int get_index() const;
+ int get_index(bool p_include_internal = true) const;
Ref<Tween> create_tween();
@@ -450,9 +462,9 @@ public:
bool is_displayed_folded() const;
/* NETWORK */
- void set_network_master(int p_peer_id, bool p_recursive = true);
- int get_network_master() const;
- bool is_network_master() const;
+ void set_network_authority(int p_peer_id, bool p_recursive = true);
+ int get_network_authority() const;
+ bool is_network_authority() const;
uint16_t rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_rpc_mode, MultiplayerPeer::TransferMode p_transfer_mode, int p_channel = 0); // config a local method for RPC
Vector<MultiplayerAPI::RPCConfig> get_node_rpc_methods() const;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 801c7cf86e..8ec8c193fb 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -46,12 +46,14 @@
#include "scene/gui/control.h"
#include "scene/gui/label.h"
#include "scene/gui/popup.h"
+#include "scene/gui/popup_menu.h"
#include "scene/main/canvas_layer.h"
#include "scene/main/window.h"
#include "scene/resources/mesh.h"
#include "scene/resources/text_line.h"
#include "scene/resources/world_2d.h"
#include "scene/scene_string_names.h"
+#include "servers/audio_server.h"
void ViewportTexture::setup_local_to_scene() {
if (vp) {
@@ -820,12 +822,7 @@ Rect2 Viewport::get_visible_rect() const {
}
void Viewport::_update_listener_2d() {
- /*
- if (is_inside_tree() && audio_listener_3d && (!get_parent() || (Object::cast_to<Control>(get_parent()) && Object::cast_to<Control>(get_parent())->is_visible_in_tree())))
- SpatialSound2DServer::get_singleton()->listener_set_space(internal_listener_2d, find_world_2d()->get_sound_space());
- else
- SpatialSound2DServer::get_singleton()->listener_set_space(internal_listener_2d, RID());
-*/
+ AudioServer::get_singleton()->notify_listener_changed();
}
void Viewport::set_as_audio_listener_2d(bool p_enable) {
@@ -1105,6 +1102,12 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Cont
while (p_control) {
tooltip = p_control->get_tooltip(pos);
+ //Temporary solution for PopupMenus
+ PopupMenu *menu = Object::cast_to<PopupMenu>(this);
+ if (menu) {
+ tooltip = menu->get_tooltip(pos);
+ }
+
if (r_tooltip_owner) {
*r_tooltip_owner = p_control;
}
@@ -3072,6 +3075,7 @@ bool Viewport::is_audio_listener_3d() const {
}
void Viewport::_update_listener_3d() {
+ AudioServer::get_singleton()->notify_listener_changed();
}
void Viewport::_listener_transform_3d_changed_notify() {
diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp
index ef070589e4..2ab9b7b5a4 100644
--- a/scene/resources/audio_stream_sample.cpp
+++ b/scene/resources/audio_stream_sample.cpp
@@ -221,12 +221,12 @@ void AudioStreamPlaybackSample::do_resample(const Depth *p_src, AudioFrame *p_ds
}
}
-void AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
if (!base->data || !active) {
for (int i = 0; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
- return;
+ return 0;
}
int len = base->data_bytes;
@@ -395,12 +395,15 @@ void AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, in
}
if (todo) {
+ int mixed_frames = p_frames - todo;
//bit was missing from mix
int todo_ofs = p_frames - todo;
for (int i = todo_ofs; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
+ return mixed_frames;
}
+ return p_frames;
}
AudioStreamPlaybackSample::AudioStreamPlaybackSample() {}
diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h
index 70b8ba79ad..8bf3d29123 100644
--- a/scene/resources/audio_stream_sample.h
+++ b/scene/resources/audio_stream_sample.h
@@ -73,7 +73,7 @@ public:
virtual float get_playback_position() const override;
virtual void seek(float p_time) override;
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
AudioStreamPlaybackSample();
};
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 54bfc427c4..e74f759855 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -379,10 +379,17 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map
return OK;
}
+ bool is_editable_instance = false;
+
// save the child instantiated scenes that are chosen as editable, so they can be restored
// upon load back
if (p_node != p_owner && p_node->get_filename() != String() && p_owner->is_editable_instance(p_node)) {
editable_instances.push_back(p_owner->get_path_to(p_node));
+ // Node is the root of an editable instance.
+ is_editable_instance = true;
+ } else if (p_node->get_owner() && p_node->get_owner() != p_owner && p_owner->is_editable_instance(p_node->get_owner())) {
+ // Node is part of an editable instance.
+ is_editable_instance = true;
}
NodeData nd;
@@ -610,7 +617,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map
// Save the right type. If this node was created by an instance
// then flag that the node should not be created but reused
- if (pack_state_stack.is_empty()) {
+ if (pack_state_stack.is_empty() && !is_editable_instance) {
//this node is not part of an instancing process, so save the type
nd.type = _nm_get_string(p_node->get_class(), name_map);
} else {
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index dbe118a262..b863a309c0 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -424,7 +424,7 @@ Error ResourceLoaderText::load() {
}
}
- if (path.find("://") == -1 && path.is_rel_path()) {
+ if (path.find("://") == -1 && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().plus_file(path));
}
@@ -768,7 +768,7 @@ void ResourceLoaderText::get_dependencies(FileAccess *p_f, List<String> *p_depen
}
}
- if (!using_uid && path.find("://") == -1 && path.is_rel_path()) {
+ if (!using_uid && path.find("://") == -1 && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().plus_file(path));
}
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 5544a09ac0..e1b391b823 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -74,11 +74,13 @@ void AudioStreamPlayback::seek(float p_time) {
}
}
-void AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
- if (GDVIRTUAL_CALL(_mix, p_buffer, p_rate_scale, p_frames)) {
- return;
+int AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+ int ret;
+ if (GDVIRTUAL_CALL(_mix, p_buffer, p_rate_scale, p_frames, ret)) {
+ return ret;
}
WARN_PRINT_ONCE("AudioStreamPlayback::mix unimplemented!");
+ return 0;
}
void AudioStreamPlayback::_bind_methods() {
@@ -103,12 +105,14 @@ void AudioStreamPlaybackResampled::_begin_resample() {
mix_offset = 0;
}
-void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+int AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
float target_rate = AudioServer::get_singleton()->get_mix_rate();
float playback_speed_scale = AudioServer::get_singleton()->get_playback_speed_scale();
uint64_t mix_increment = uint64_t(((get_stream_sampling_rate() * p_rate_scale * playback_speed_scale) / double(target_rate)) * double(FP_LEN));
+ int mixed_frames_total = p_frames;
+
for (int i = 0; i < p_frames; i++) {
uint32_t idx = CUBIC_INTERP_HISTORY + uint32_t(mix_offset >> FP_BITS);
//standard cubic interpolation (great quality/performance ratio)
@@ -119,6 +123,11 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
AudioFrame y2 = internal_buffer[idx - 1];
AudioFrame y3 = internal_buffer[idx - 0];
+ if (idx <= internal_buffer_end && idx >= internal_buffer_end && mixed_frames_total == p_frames) {
+ // The internal buffer ends somewhere in this range, and we haven't yet recorded the number of good frames we have.
+ mixed_frames_total = i;
+ }
+
float mu2 = mu * mu;
AudioFrame a0 = 3 * y1 - 3 * y2 + y3 - y0;
AudioFrame a1 = 2 * y0 - 5 * y1 + 4 * y2 - y3;
@@ -135,7 +144,14 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
internal_buffer[2] = internal_buffer[INTERNAL_BUFFER_LEN + 2];
internal_buffer[3] = internal_buffer[INTERNAL_BUFFER_LEN + 3];
if (is_playing()) {
- _mix_internal(internal_buffer + 4, INTERNAL_BUFFER_LEN);
+ int mixed_frames = _mix_internal(internal_buffer + 4, INTERNAL_BUFFER_LEN);
+ if (mixed_frames != INTERNAL_BUFFER_LEN) {
+ // internal_buffer[mixed_frames] is the first frame of silence.
+ internal_buffer_end = mixed_frames;
+ } else {
+ // The internal buffer does not contain the first frame of silence.
+ internal_buffer_end = -1;
+ }
} else {
//fill with silence, not playing
for (int j = 0; j < INTERNAL_BUFFER_LEN; ++j) {
@@ -145,6 +161,7 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
mix_offset -= (INTERNAL_BUFFER_LEN << FP_BITS);
}
}
+ return mixed_frames_total;
}
////////////////////////////////
@@ -210,7 +227,7 @@ void AudioStreamMicrophone::_bind_methods() {
AudioStreamMicrophone::AudioStreamMicrophone() {
}
-void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+int AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_frames) {
AudioDriver::get_singleton()->lock();
Vector<int32_t> buf = AudioDriver::get_singleton()->get_input_buffer();
@@ -221,6 +238,8 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr
unsigned int input_position = AudioDriver::get_singleton()->get_input_position();
#endif
+ int mixed_frames = p_frames;
+
if (playback_delay > input_size) {
for (int i = 0; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0.0f, 0.0f);
@@ -240,6 +259,9 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr
p_buffer[i] = AudioFrame(l, r);
} else {
+ if (mixed_frames == p_frames) {
+ mixed_frames = i;
+ }
p_buffer[i] = AudioFrame(0.0f, 0.0f);
}
}
@@ -252,10 +274,12 @@ void AudioStreamPlaybackMicrophone::_mix_internal(AudioFrame *p_buffer, int p_fr
#endif
AudioDriver::get_singleton()->unlock();
+
+ return mixed_frames;
}
-void AudioStreamPlaybackMicrophone::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
- AudioStreamPlaybackResampled::mix(p_buffer, p_rate_scale, p_frames);
+int AudioStreamPlaybackMicrophone::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+ return AudioStreamPlaybackResampled::mix(p_buffer, p_rate_scale, p_frames);
}
float AudioStreamPlaybackMicrophone::get_stream_sampling_rate() {
@@ -428,13 +452,14 @@ void AudioStreamPlaybackRandomPitch::seek(float p_time) {
}
}
-void AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+int AudioStreamPlaybackRandomPitch::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
if (playing.is_valid()) {
- playing->mix(p_buffer, p_rate_scale * pitch_scale, p_frames);
+ return playing->mix(p_buffer, p_rate_scale * pitch_scale, p_frames);
} else {
for (int i = 0; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
+ return p_frames;
}
}
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index 25f0017211..922335508e 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -51,7 +51,7 @@ protected:
GDVIRTUAL0RC(int, _get_loop_count)
GDVIRTUAL0RC(float, _get_playback_position)
GDVIRTUAL1(_seek, float)
- GDVIRTUAL3(_mix, GDNativePtr<AudioFrame>, float, int)
+ GDVIRTUAL3R(int, _mix, GDNativePtr<AudioFrame>, float, int)
public:
virtual void start(float p_from_pos = 0.0);
virtual void stop();
@@ -62,7 +62,7 @@ public:
virtual float get_playback_position() const;
virtual void seek(float p_time);
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
};
class AudioStreamPlaybackResampled : public AudioStreamPlayback {
@@ -77,15 +77,17 @@ class AudioStreamPlaybackResampled : public AudioStreamPlayback {
};
AudioFrame internal_buffer[INTERNAL_BUFFER_LEN + CUBIC_INTERP_HISTORY];
+ unsigned int internal_buffer_end = -1;
uint64_t mix_offset;
protected:
void _begin_resample();
- virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) = 0;
+ // Returns the number of frames that were mixed.
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) = 0;
virtual float get_stream_sampling_rate() = 0;
public:
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
AudioStreamPlaybackResampled() { mix_offset = 0; }
};
@@ -140,11 +142,11 @@ class AudioStreamPlaybackMicrophone : public AudioStreamPlaybackResampled {
Ref<AudioStreamMicrophone> microphone;
protected:
- virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override;
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
virtual float get_stream_sampling_rate() override;
public:
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
virtual void start(float p_from_pos = 0.0) override;
virtual void stop() override;
@@ -208,7 +210,7 @@ public:
virtual float get_playback_position() const override;
virtual void seek(float p_time) override;
- virtual void mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
+ virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
~AudioStreamPlaybackRandomPitch();
};
diff --git a/servers/audio/effects/audio_stream_generator.cpp b/servers/audio/effects/audio_stream_generator.cpp
index bced2997ce..edb5c6d2dd 100644
--- a/servers/audio/effects/audio_stream_generator.cpp
+++ b/servers/audio/effects/audio_stream_generator.cpp
@@ -138,7 +138,7 @@ void AudioStreamGeneratorPlayback::clear_buffer() {
mixed = 0;
}
-void AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+int AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) {
int read_amount = buffer.data_left();
if (p_frames < read_amount) {
read_amount = p_frames;
@@ -156,6 +156,7 @@ void AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_fra
}
mixed += p_frames / generator->get_mix_rate();
+ return read_amount < p_frames ? read_amount : p_frames;
}
float AudioStreamGeneratorPlayback::get_stream_sampling_rate() {
diff --git a/servers/audio/effects/audio_stream_generator.h b/servers/audio/effects/audio_stream_generator.h
index 5d46771f4d..6bec744081 100644
--- a/servers/audio/effects/audio_stream_generator.h
+++ b/servers/audio/effects/audio_stream_generator.h
@@ -67,7 +67,7 @@ class AudioStreamGeneratorPlayback : public AudioStreamPlaybackResampled {
AudioStreamGenerator *generator;
protected:
- virtual void _mix_internal(AudioFrame *p_buffer, int p_frames) override;
+ virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
virtual float get_stream_sampling_rate() override;
static void _bind_methods();
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 4c54188cb2..81735d522f 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -32,13 +32,19 @@
#include "core/config/project_settings.h"
#include "core/debugger/engine_debugger.h"
+#include "core/error/error_macros.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
+#include "core/math/audio_frame.h"
#include "core/os/os.h"
+#include "core/string/string_name.h"
+#include "core/templates/pair.h"
#include "scene/resources/audio_stream_sample.h"
#include "servers/audio/audio_driver_dummy.h"
#include "servers/audio/effects/audio_effect_compressor.h"
+#include <cstring>
+
#ifdef TOOLS_ENABLED
#define MARK_EDITED set_edited(true);
#else
@@ -234,6 +240,7 @@ AudioDriver *AudioDriverManager::get_driver(int p_driver) {
//////////////////////////////////////////////
void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) {
+ mix_count++;
int todo = p_frames;
#ifdef DEBUG_ENABLED
@@ -331,10 +338,156 @@ void AudioServer::_mix_step() {
bus->soloed = false;
}
}
+ for (CallbackItem *ci : mix_callback_list) {
+ ci->callback(ci->userdata);
+ }
+
+ for (AudioStreamPlaybackListNode *playback : playback_list) {
+ // Paused streams are no-ops. Don't even mix audio from the stream playback.
+ if (playback->state.load() == AudioStreamPlaybackListNode::PAUSED) {
+ continue;
+ }
+
+ bool fading_out = playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION || playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE;
+
+ AudioFrame *buf = mix_buffer.ptrw();
+
+ // Copy the lookeahead buffer into the mix buffer.
+ for (int i = 0; i < LOOKAHEAD_BUFFER_SIZE; i++) {
+ buf[i] = playback->lookahead[i];
+ }
+
+ // Mix the audio stream
+ unsigned int mixed_frames = playback->stream_playback->mix(&buf[LOOKAHEAD_BUFFER_SIZE], playback->pitch_scale.get(), buffer_size);
+
+ if (mixed_frames != buffer_size) {
+ // We know we have at least the size of our lookahead buffer for fade-out purposes.
+
+ float fadeout_base = 0.87;
+ float fadeout_coefficient = 1;
+ static_assert(LOOKAHEAD_BUFFER_SIZE == 32, "Update fadeout_base and comment here if you change LOOKAHEAD_BUFFER_SIZE.");
+ // 0.87 ^ 32 = 0.0116. There might still be a pop but it'll be way better than if we didn't do this.
+ for (unsigned int idx = mixed_frames; idx < buffer_size; idx++) {
+ fadeout_coefficient *= fadeout_base;
+ buf[idx] *= fadeout_coefficient;
+ }
+ AudioStreamPlaybackListNode::PlaybackState new_state;
+ new_state = AudioStreamPlaybackListNode::AWAITING_DELETION;
+ playback->state.store(new_state);
+ } else {
+ // Move the last little bit of what we just mixed into our lookahead buffer.
+ for (int i = 0; i < LOOKAHEAD_BUFFER_SIZE; i++) {
+ playback->lookahead[i] = buf[buffer_size + i];
+ }
+ }
+
+ ERR_FAIL_COND(playback->bus_details.load() == nullptr);
+ // By putting null into the bus details pointers, we're taking ownership of their memory for the duration of this mix.
+ AudioStreamPlaybackBusDetails *bus_details = nullptr;
+ {
+ std::atomic<AudioStreamPlaybackBusDetails *> bus_details_atomic = nullptr;
+ bus_details = playback->bus_details.exchange(bus_details_atomic);
+ }
+ ERR_FAIL_COND(bus_details == nullptr);
+ AudioStreamPlaybackBusDetails *prev_bus_details = playback->prev_bus_details;
+
+ // Mix to any active buses.
+ for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) {
+ if (!bus_details->bus_active[idx]) {
+ continue;
+ }
+ int bus_idx = thread_find_bus_index(bus_details->bus[idx]);
+
+ int prev_bus_idx = -1;
+ for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) {
+ if (!prev_bus_details->bus_active[search_idx]) {
+ continue;
+ }
+ if (prev_bus_details->bus[search_idx].hash() == bus_details->bus[idx].hash()) {
+ prev_bus_idx = search_idx;
+ }
+ }
+
+ for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) {
+ AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx);
+ if (fading_out) {
+ bus_details->volume[idx][channel_idx] = AudioFrame(0, 0);
+ }
+ AudioFrame channel_vol = bus_details->volume[idx][channel_idx];
+
+ AudioFrame prev_channel_vol = AudioFrame(0, 0);
+ if (prev_bus_idx != -1) {
+ prev_channel_vol = prev_bus_details->volume[prev_bus_idx][channel_idx];
+ }
+ _mix_step_for_channel(channel_buf, buf, prev_channel_vol, channel_vol, playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]);
+ }
+ }
+
+ // Now go through and fade-out any buses that were being played to previously that we missed by going through current data.
+ for (int idx = 0; idx < MAX_BUSES_PER_PLAYBACK; idx++) {
+ if (!prev_bus_details->bus_active[idx]) {
+ continue;
+ }
+ int bus_idx = thread_find_bus_index(prev_bus_details->bus[idx]);
+
+ int current_bus_idx = -1;
+ for (int search_idx = 0; search_idx < MAX_BUSES_PER_PLAYBACK; search_idx++) {
+ if (bus_details->bus[search_idx] == prev_bus_details->bus[idx]) {
+ current_bus_idx = search_idx;
+ }
+ }
+ if (current_bus_idx != -1) {
+ // If we found a corresponding bus in the current bus assignments, we've already mixed to this bus.
+ continue;
+ }
+
+ for (int channel_idx = 0; channel_idx < channel_count; channel_idx++) {
+ AudioFrame *channel_buf = thread_get_channel_mix_buffer(bus_idx, channel_idx);
+ AudioFrame prev_channel_vol = prev_bus_details->volume[idx][channel_idx];
+ // Fade out to silence
+ _mix_step_for_channel(channel_buf, buf, prev_channel_vol, AudioFrame(0, 0), playback->attenuation_filter_cutoff_hz.get(), playback->highshelf_gain.get(), &playback->filter_process[channel_idx * 2], &playback->filter_process[channel_idx * 2 + 1]);
+ }
+ }
+
+ // Copy the bus details we mixed with to the previous bus details to maintain volume ramps.
+ std::copy(std::begin(bus_details->bus_active), std::end(bus_details->bus_active), std::begin(prev_bus_details->bus_active));
+ std::copy(std::begin(bus_details->bus), std::end(bus_details->bus), std::begin(prev_bus_details->bus));
+ for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) {
+ std::copy(std::begin(bus_details->volume[bus_idx]), std::end(bus_details->volume[bus_idx]), std::begin(prev_bus_details->volume[bus_idx]));
+ }
+
+ AudioStreamPlaybackBusDetails *bus_details_expected = nullptr;
+ // Only put the bus details pointer back if it hasn't been updated already.
+ if (!playback->bus_details.compare_exchange_strong(/* expected= */ bus_details_expected, /* new= */ bus_details)) {
+ // If it *has* been updated already, queue the old one for deletion.
+ bus_details_graveyard.insert(bus_details);
+ }
- //make callbacks for mixing the audio
- for (Set<CallbackItem>::Element *E = callbacks.front(); E; E = E->next()) {
- E->get().callback(E->get().userdata);
+ switch (playback->state.load()) {
+ case AudioStreamPlaybackListNode::AWAITING_DELETION:
+ case AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION:
+ playback_list.erase(playback, [](AudioStreamPlaybackListNode *p) {
+ if (p->prev_bus_details)
+ delete p->prev_bus_details;
+ if (p->bus_details)
+ delete p->bus_details;
+ p->stream_playback.unref();
+ delete p;
+ });
+ break;
+ case AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE: {
+ // Pause the stream.
+ AudioStreamPlaybackListNode::PlaybackState old_state, new_state;
+ do {
+ old_state = playback->state.load();
+ new_state = AudioStreamPlaybackListNode::PAUSED;
+ } while (!playback->state.compare_exchange_strong(/* expected= */ old_state, new_state));
+ } break;
+ case AudioStreamPlaybackListNode::PLAYING:
+ case AudioStreamPlaybackListNode::PAUSED:
+ // No-op!
+ break;
+ }
}
for (int i = buses.size() - 1; i >= 0; i--) {
@@ -464,6 +617,53 @@ void AudioServer::_mix_step() {
to_mix = buffer_size;
}
+void AudioServer::_mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_source_buf, AudioFrame p_vol_start, AudioFrame p_vol_final, float p_attenuation_filter_cutoff_hz, float p_highshelf_gain, AudioFilterSW::Processor *p_processor_l, AudioFilterSW::Processor *p_processor_r) {
+ if (p_highshelf_gain != 0) {
+ AudioFilterSW filter;
+ filter.set_mode(AudioFilterSW::HIGHSHELF);
+ filter.set_sampling_rate(AudioServer::get_singleton()->get_mix_rate());
+ filter.set_cutoff(p_attenuation_filter_cutoff_hz);
+ filter.set_resonance(1);
+ filter.set_stages(1);
+ filter.set_gain(p_highshelf_gain);
+
+ ERR_FAIL_COND(p_processor_l == nullptr);
+ ERR_FAIL_COND(p_processor_r == nullptr);
+
+ bool is_just_started = p_vol_start.l == 0 && p_vol_start.r == 0;
+ p_processor_l->set_filter(&filter, /* clear_history= */ is_just_started);
+ p_processor_l->update_coeffs(buffer_size);
+ p_processor_r->set_filter(&filter, /* clear_history= */ is_just_started);
+ p_processor_r->update_coeffs(buffer_size);
+
+ for (unsigned int frame_idx = 0; frame_idx < buffer_size; frame_idx++) {
+ // Make this buffer size invariant if buffer_size ever becomes a project setting.
+ float lerp_param = (float)frame_idx / buffer_size;
+ AudioFrame vol = p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start;
+ AudioFrame mixed = vol * p_source_buf[frame_idx];
+ p_processor_l->process_one_interp(mixed.l);
+ p_processor_r->process_one_interp(mixed.r);
+ p_out_buf[frame_idx] += mixed;
+ }
+
+ } else {
+ for (unsigned int frame_idx = 0; frame_idx < buffer_size; frame_idx++) {
+ // Make this buffer size invariant if buffer_size ever becomes a project setting.
+ float lerp_param = (float)frame_idx / buffer_size;
+ p_out_buf[frame_idx] += (p_vol_final * lerp_param + (1 - lerp_param) * p_vol_start) * p_source_buf[frame_idx];
+ }
+ }
+}
+
+AudioServer::AudioStreamPlaybackListNode *AudioServer::_find_playback_list_node(Ref<AudioStreamPlayback> p_playback) {
+ for (AudioStreamPlaybackListNode *playback_list_node : playback_list) {
+ if (playback_list_node->stream_playback == p_playback) {
+ return playback_list_node;
+ }
+ }
+ return nullptr;
+}
+
bool AudioServer::thread_has_channel_mix_buffer(int p_bus, int p_buffer) const {
if (p_bus < 0 || p_bus >= buses.size()) {
return false;
@@ -923,9 +1123,216 @@ float AudioServer::get_playback_speed_scale() const {
return playback_speed_scale;
}
+void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ Map<StringName, Vector<AudioFrame>> map;
+ map[p_bus] = p_volume_db_vector;
+
+ start_playback_stream(p_playback, map, p_start_time);
+}
+
+void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode();
+ playback_node->stream_playback = p_playback;
+ playback_node->stream_playback->start(p_start_time);
+
+ AudioStreamPlaybackBusDetails *new_bus_details = new AudioStreamPlaybackBusDetails();
+ int idx = 0;
+ for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) {
+ ERR_FAIL_COND(pair.value.size() < channel_count);
+ ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS);
+
+ new_bus_details->bus_active[idx] = true;
+ new_bus_details->bus[idx] = pair.key;
+ for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) {
+ new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx];
+ }
+ }
+ playback_node->bus_details = new_bus_details;
+ playback_node->prev_bus_details = new AudioStreamPlaybackBusDetails();
+
+ playback_node->setseek.set(-1);
+ playback_node->pitch_scale.set(1);
+ playback_node->highshelf_gain.set(0);
+ playback_node->attenuation_filter_cutoff_hz.set(0);
+
+ memset(playback_node->prev_bus_details->volume, 0, sizeof(playback_node->prev_bus_details->volume));
+
+ for (AudioFrame &frame : playback_node->lookahead) {
+ frame = AudioFrame(0, 0);
+ }
+
+ playback_node->state.store(AudioStreamPlaybackListNode::PLAYING);
+
+ playback_list.insert(playback_node);
+}
+
+void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+
+ AudioStreamPlaybackListNode::PlaybackState new_state, old_state;
+ do {
+ old_state = playback_node->state.load();
+ new_state = AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION;
+
+ } while (!playback_node->state.compare_exchange_strong(old_state, new_state));
+}
+
+void AudioServer::set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes) {
+ ERR_FAIL_COND(p_volumes.size() != MAX_CHANNELS_PER_BUS);
+
+ Map<StringName, Vector<AudioFrame>> map;
+ map[p_bus] = p_volumes;
+
+ set_playback_bus_volumes_linear(p_playback, map);
+}
+
+void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes) {
+ ERR_FAIL_COND(p_bus_volumes.size() > MAX_BUSES_PER_PLAYBACK);
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+ AudioStreamPlaybackBusDetails *old_bus_details, *new_bus_details = new AudioStreamPlaybackBusDetails();
+
+ int idx = 0;
+ for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) {
+ ERR_FAIL_COND(pair.value.size() < channel_count);
+ ERR_FAIL_COND(pair.value.size() != MAX_CHANNELS_PER_BUS);
+
+ new_bus_details->bus_active[idx] = true;
+ new_bus_details->bus[idx] = pair.key;
+ for (int channel_idx = 0; channel_idx < MAX_CHANNELS_PER_BUS; channel_idx++) {
+ new_bus_details->volume[idx][channel_idx] = pair.value[channel_idx];
+ }
+ }
+
+ do {
+ old_bus_details = playback_node->bus_details.load();
+ } while (!playback_node->bus_details.compare_exchange_strong(old_bus_details, new_bus_details));
+
+ bus_details_graveyard.insert(old_bus_details);
+}
+
+void AudioServer::set_playback_all_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Vector<AudioFrame> p_volumes) {
+ ERR_FAIL_COND(p_playback.is_null());
+ ERR_FAIL_COND(p_volumes.size() != MAX_CHANNELS_PER_BUS);
+
+ Map<StringName, Vector<AudioFrame>> map;
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+ for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) {
+ if (playback_node->bus_details.load()->bus_active[bus_idx]) {
+ map[playback_node->bus_details.load()->bus[bus_idx]] = p_volumes;
+ }
+ }
+
+ set_playback_bus_volumes_linear(p_playback, map);
+}
+
+void AudioServer::set_playback_pitch_scale(Ref<AudioStreamPlayback> p_playback, float p_pitch_scale) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+
+ playback_node->pitch_scale.set(p_pitch_scale);
+}
+
+void AudioServer::set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool p_paused) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+ if (!p_paused && playback_node->state == AudioStreamPlaybackListNode::PLAYING) {
+ return; // No-op.
+ }
+ if (p_paused && (playback_node->state == AudioStreamPlaybackListNode::PAUSED || playback_node->state == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE)) {
+ return; // No-op.
+ }
+
+ AudioStreamPlaybackListNode::PlaybackState new_state, old_state;
+ do {
+ old_state = playback_node->state.load();
+ new_state = p_paused ? AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE : AudioStreamPlaybackListNode::PLAYING;
+ } while (!playback_node->state.compare_exchange_strong(old_state, new_state));
+}
+
+void AudioServer::set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playback, float p_gain, float p_attenuation_cutoff_hz) {
+ ERR_FAIL_COND(p_playback.is_null());
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return;
+ }
+
+ playback_node->attenuation_filter_cutoff_hz.set(p_attenuation_cutoff_hz);
+ playback_node->highshelf_gain.set(p_gain);
+}
+
+bool AudioServer::is_playback_active(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND_V(p_playback.is_null(), false);
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return false;
+ }
+
+ return playback_node->state.load() == AudioStreamPlaybackListNode::PLAYING;
+}
+
+float AudioServer::get_playback_position(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND_V(p_playback.is_null(), 0);
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return 0;
+ }
+
+ return playback_node->stream_playback->get_playback_position();
+}
+
+bool AudioServer::is_playback_paused(Ref<AudioStreamPlayback> p_playback) {
+ ERR_FAIL_COND_V(p_playback.is_null(), false);
+
+ AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
+ if (!playback_node) {
+ return false;
+ }
+
+ return playback_node->state.load() == AudioStreamPlaybackListNode::PAUSED || playback_node->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE;
+}
+
+uint64_t AudioServer::get_mix_count() const {
+ return mix_count;
+}
+
+void AudioServer::notify_listener_changed() {
+ for (CallbackItem *ci : listener_changed_callback_list) {
+ ci->callback(ci->userdata);
+ }
+}
+
void AudioServer::init_channels_and_buffers() {
channel_count = get_channel_count();
temp_buffer.resize(channel_count);
+ mix_buffer.resize(buffer_size + LOOKAHEAD_BUFFER_SIZE);
for (int i = 0; i < temp_buffer.size(); i++) {
temp_buffer.write[i].resize(buffer_size);
@@ -943,7 +1350,7 @@ void AudioServer::init() {
channel_disable_threshold_db = GLOBAL_DEF_RST("audio/buses/channel_disable_threshold_db", -60.0);
channel_disable_frames = float(GLOBAL_DEF_RST("audio/buses/channel_disable_time", 2.0)) * get_mix_rate();
ProjectSettings::get_singleton()->set_custom_property_info("audio/buses/channel_disable_time", PropertyInfo(Variant::FLOAT, "audio/buses/channel_disable_time", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"));
- buffer_size = 1024; //hardcoded for now
+ buffer_size = 512; //hardcoded for now
init_channels_and_buffers();
@@ -1030,9 +1437,17 @@ void AudioServer::update() {
prof_time = 0;
#endif
- for (Set<CallbackItem>::Element *E = update_callbacks.front(); E; E = E->next()) {
- E->get().callback(E->get().userdata);
+ for (CallbackItem *ci : update_callback_list) {
+ ci->callback(ci->userdata);
}
+ mix_callback_list.maybe_cleanup();
+ update_callback_list.maybe_cleanup();
+ listener_changed_callback_list.maybe_cleanup();
+ playback_list.maybe_cleanup();
+ for (AudioStreamPlaybackBusDetails *bus_details : bus_details_graveyard) {
+ bus_details_graveyard.erase(bus_details, [](AudioStreamPlaybackBusDetails *d) { delete d; });
+ }
+ bus_details_graveyard.maybe_cleanup();
}
void AudioServer::load_default_bus_layout() {
@@ -1098,40 +1513,49 @@ double AudioServer::get_time_since_last_mix() const {
AudioServer *AudioServer::singleton = nullptr;
-void AudioServer::add_callback(AudioCallback p_callback, void *p_userdata) {
- lock();
- CallbackItem ci;
- ci.callback = p_callback;
- ci.userdata = p_userdata;
- callbacks.insert(ci);
- unlock();
+void AudioServer::add_update_callback(AudioCallback p_callback, void *p_userdata) {
+ CallbackItem *ci = new CallbackItem();
+ ci->callback = p_callback;
+ ci->userdata = p_userdata;
+ update_callback_list.insert(ci);
}
-void AudioServer::remove_callback(AudioCallback p_callback, void *p_userdata) {
- lock();
- CallbackItem ci;
- ci.callback = p_callback;
- ci.userdata = p_userdata;
- callbacks.erase(ci);
- unlock();
+void AudioServer::remove_update_callback(AudioCallback p_callback, void *p_userdata) {
+ for (CallbackItem *ci : update_callback_list) {
+ if (ci->callback == p_callback && ci->userdata == p_userdata) {
+ update_callback_list.erase(ci, [](CallbackItem *c) { delete c; });
+ }
+ }
}
-void AudioServer::add_update_callback(AudioCallback p_callback, void *p_userdata) {
- lock();
- CallbackItem ci;
- ci.callback = p_callback;
- ci.userdata = p_userdata;
- update_callbacks.insert(ci);
- unlock();
+void AudioServer::add_mix_callback(AudioCallback p_callback, void *p_userdata) {
+ CallbackItem *ci = new CallbackItem();
+ ci->callback = p_callback;
+ ci->userdata = p_userdata;
+ mix_callback_list.insert(ci);
}
-void AudioServer::remove_update_callback(AudioCallback p_callback, void *p_userdata) {
- lock();
- CallbackItem ci;
- ci.callback = p_callback;
- ci.userdata = p_userdata;
- update_callbacks.erase(ci);
- unlock();
+void AudioServer::remove_mix_callback(AudioCallback p_callback, void *p_userdata) {
+ for (CallbackItem *ci : mix_callback_list) {
+ if (ci->callback == p_callback && ci->userdata == p_userdata) {
+ mix_callback_list.erase(ci, [](CallbackItem *c) { delete c; });
+ }
+ }
+}
+
+void AudioServer::add_listener_changed_callback(AudioCallback p_callback, void *p_userdata) {
+ CallbackItem *ci = new CallbackItem();
+ ci->callback = p_callback;
+ ci->userdata = p_userdata;
+ listener_changed_callback_list.insert(ci);
+}
+
+void AudioServer::remove_listener_changed_callback(AudioCallback p_callback, void *p_userdata) {
+ for (CallbackItem *ci : listener_changed_callback_list) {
+ if (ci->callback == p_callback && ci->userdata == p_userdata) {
+ listener_changed_callback_list.erase(ci, [](CallbackItem *c) { delete c; });
+ }
+ }
}
void AudioServer::set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout) {
diff --git a/servers/audio_server.h b/servers/audio_server.h
index 7974c4a2ad..affcb3df7b 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -34,12 +34,17 @@
#include "core/math/audio_frame.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
+#include "core/templates/safe_list.h"
#include "core/variant/variant.h"
#include "servers/audio/audio_effect.h"
+#include "servers/audio/audio_filter_sw.h"
+
+#include <atomic>
class AudioDriverDummy;
class AudioStream;
class AudioStreamSample;
+class AudioStreamPlayback;
class AudioDriver {
static AudioDriver *singleton;
@@ -155,7 +160,10 @@ public:
};
enum {
- AUDIO_DATA_INVALID_ID = -1
+ AUDIO_DATA_INVALID_ID = -1,
+ MAX_CHANNELS_PER_BUS = 4,
+ MAX_BUSES_PER_PLAYBACK = 6,
+ LOOKAHEAD_BUFFER_SIZE = 32,
};
typedef void (*AudioCallback)(void *p_userdata);
@@ -219,7 +227,43 @@ private:
int index_cache;
};
+ struct AudioStreamPlaybackBusDetails {
+ bool bus_active[MAX_BUSES_PER_PLAYBACK] = { false, false, false, false, false, false };
+ StringName bus[MAX_BUSES_PER_PLAYBACK];
+ AudioFrame volume[MAX_BUSES_PER_PLAYBACK][MAX_CHANNELS_PER_BUS];
+ };
+
+ struct AudioStreamPlaybackListNode {
+ enum PlaybackState {
+ PAUSED = 0, // Paused. Keep this stream playback around though so it can be restarted.
+ PLAYING = 1, // Playing. Fading may still be necessary if volume changes!
+ FADE_OUT_TO_PAUSE = 2, // About to pause.
+ FADE_OUT_TO_DELETION = 3, // About to stop.
+ AWAITING_DELETION = 4,
+ };
+ // If zero or positive, a place in the stream to seek to during the next mix.
+ SafeNumeric<float> setseek;
+ SafeNumeric<float> pitch_scale;
+ SafeNumeric<float> highshelf_gain;
+ SafeNumeric<float> attenuation_filter_cutoff_hz; // This isn't used unless highshelf_gain is nonzero.
+ AudioFilterSW::Processor filter_process[8];
+ // Updating this ref after the list node is created breaks consistency guarantees, don't do it!
+ Ref<AudioStreamPlayback> stream_playback;
+ // Playback state determines the fate of a particular AudioStreamListNode during the mix step. Must be atomically replaced.
+ std::atomic<PlaybackState> state = AWAITING_DELETION;
+ // This data should only ever be modified by an atomic replacement of the pointer.
+ std::atomic<AudioStreamPlaybackBusDetails *> bus_details = nullptr;
+ // Previous bus details should only be accessed on the audio thread.
+ AudioStreamPlaybackBusDetails *prev_bus_details = nullptr;
+ // The next few samples are stored here so we have some time to fade audio out if it ends abruptly at the beginning of the next mix.
+ AudioFrame lookahead[LOOKAHEAD_BUFFER_SIZE];
+ };
+
+ SafeList<AudioStreamPlaybackListNode *> playback_list;
+ SafeList<AudioStreamPlaybackBusDetails *> bus_details_graveyard;
+
Vector<Vector<AudioFrame>> temp_buffer; //temp_buffer for each level
+ Vector<AudioFrame> mix_buffer;
Vector<Bus *> buses;
Map<StringName, Bus *> bus_map;
@@ -230,18 +274,19 @@ private:
void init_channels_and_buffers();
void _mix_step();
+ void _mix_step_for_channel(AudioFrame *p_out_buf, AudioFrame *p_source_buf, AudioFrame p_vol_start, AudioFrame p_vol_final, float p_attenuation_filter_cutoff_hz, float p_highshelf_gain, AudioFilterSW::Processor *p_processor_l, AudioFilterSW::Processor *p_processor_r);
+
+ // Should only be called on the main thread.
+ AudioStreamPlaybackListNode *_find_playback_list_node(Ref<AudioStreamPlayback> p_playback);
struct CallbackItem {
AudioCallback callback;
void *userdata;
-
- bool operator<(const CallbackItem &p_item) const {
- return (callback == p_item.callback ? userdata < p_item.userdata : callback < p_item.callback);
- }
};
- Set<CallbackItem> callbacks;
- Set<CallbackItem> update_callbacks;
+ SafeList<CallbackItem *> update_callback_list;
+ SafeList<CallbackItem *> mix_callback_list;
+ SafeList<CallbackItem *> listener_changed_callback_list;
friend class AudioDriver;
void _driver_process(int p_frames, int32_t *p_buffer);
@@ -319,6 +364,25 @@ public:
void set_playback_speed_scale(float p_scale);
float get_playback_speed_scale() const;
+ void start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0);
+ void start_playback_stream(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0);
+ void stop_playback_stream(Ref<AudioStreamPlayback> p_playback);
+
+ void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes);
+ void set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Map<StringName, Vector<AudioFrame>> p_bus_volumes);
+ void set_playback_all_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Vector<AudioFrame> p_volumes);
+ void set_playback_pitch_scale(Ref<AudioStreamPlayback> p_playback, float p_pitch_scale);
+ void set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool p_paused);
+ void set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playback, float p_gain, float p_attenuation_cutoff_hz);
+
+ bool is_playback_active(Ref<AudioStreamPlayback> p_playback);
+ float get_playback_position(Ref<AudioStreamPlayback> p_playback);
+ bool is_playback_paused(Ref<AudioStreamPlayback> p_playback);
+
+ uint64_t get_mix_count() const;
+
+ void notify_listener_changed();
+
virtual void init();
virtual void finish();
virtual void update();
@@ -340,12 +404,15 @@ public:
virtual double get_time_to_next_mix() const;
virtual double get_time_since_last_mix() const;
- void add_callback(AudioCallback p_callback, void *p_userdata);
- void remove_callback(AudioCallback p_callback, void *p_userdata);
+ void add_listener_changed_callback(AudioCallback p_callback, void *p_userdata);
+ void remove_listener_changed_callback(AudioCallback p_callback, void *p_userdata);
void add_update_callback(AudioCallback p_callback, void *p_userdata);
void remove_update_callback(AudioCallback p_callback, void *p_userdata);
+ void add_mix_callback(AudioCallback p_callback, void *p_userdata);
+ void remove_mix_callback(AudioCallback p_callback, void *p_userdata);
+
void set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout);
Ref<AudioBusLayout> generate_bus_layout() const;
diff --git a/servers/physics_2d/area_2d_sw.cpp b/servers/physics_2d/area_2d_sw.cpp
index 532cb259b3..663a47f273 100644
--- a/servers/physics_2d/area_2d_sw.cpp
+++ b/servers/physics_2d/area_2d_sw.cpp
@@ -274,6 +274,26 @@ void Area2DSW::call_queries() {
}
}
+void Area2DSW::compute_gravity(const Vector2 &p_position, Vector2 &r_gravity) const {
+ if (is_gravity_point()) {
+ const real_t gravity_distance_scale = get_gravity_distance_scale();
+ Vector2 v = get_transform().xform(get_gravity_vector()) - p_position;
+ if (gravity_distance_scale > 0) {
+ const real_t v_length = v.length();
+ if (v_length > 0) {
+ const real_t v_scaled = v_length * gravity_distance_scale;
+ r_gravity = (v.normalized() * (get_gravity() / (v_scaled * v_scaled)));
+ } else {
+ r_gravity = Vector2();
+ }
+ } else {
+ r_gravity = v.normalized() * get_gravity();
+ }
+ } else {
+ r_gravity = get_gravity_vector() * get_gravity();
+ }
+}
+
Area2DSW::Area2DSW() :
CollisionObject2DSW(TYPE_AREA),
monitor_query_list(this),
diff --git a/servers/physics_2d/area_2d_sw.h b/servers/physics_2d/area_2d_sw.h
index 3bf603b30d..d9147d6f1d 100644
--- a/servers/physics_2d/area_2d_sw.h
+++ b/servers/physics_2d/area_2d_sw.h
@@ -34,7 +34,6 @@
#include "collision_object_2d_sw.h"
#include "core/templates/self_list.h"
#include "servers/physics_server_2d.h"
-//#include "servers/physics_3d/query_sw.h"
class Space2DSW;
class Body2DSW;
@@ -94,17 +93,12 @@ class Area2DSW : public CollisionObject2DSW {
Map<BodyKey, BodyState> monitored_bodies;
Map<BodyKey, BodyState> monitored_areas;
- //virtual void shape_changed_notify(Shape2DSW *p_shape);
- //virtual void shape_deleted_notify(Shape2DSW *p_shape);
Set<Constraint2DSW *> constraints;
virtual void _shapes_changed();
void _queue_monitor_update();
public:
- //_FORCE_INLINE_ const Matrix32& get_inverse_transform() const { return inverse_transform; }
- //_FORCE_INLINE_ SpaceSW* get_owner() { return owner; }
-
void set_monitor_callback(ObjectID p_id, const StringName &p_method);
_FORCE_INLINE_ bool has_monitor_callback() const { return monitor_callback_id.is_valid(); }
@@ -161,6 +155,8 @@ public:
void call_queries();
+ void compute_gravity(const Vector2 &p_position, Vector2 &r_gravity) const;
+
Area2DSW();
~Area2DSW();
};
diff --git a/servers/physics_2d/body_2d_sw.cpp b/servers/physics_2d/body_2d_sw.cpp
index 7aa2f9b7de..64f71fd25c 100644
--- a/servers/physics_2d/body_2d_sw.cpp
+++ b/servers/physics_2d/body_2d_sw.cpp
@@ -29,8 +29,9 @@
/*************************************************************************/
#include "body_2d_sw.h"
+
#include "area_2d_sw.h"
-#include "physics_server_2d_sw.h"
+#include "body_direct_state_2d_sw.h"
#include "space_2d_sw.h"
void Body2DSW::_update_inertia() {
@@ -352,17 +353,10 @@ void Body2DSW::set_space(Space2DSW *p_space) {
first_integration = false;
}
-void Body2DSW::_compute_area_gravity_and_dampenings(const Area2DSW *p_area) {
- if (p_area->is_gravity_point()) {
- if (p_area->get_gravity_distance_scale() > 0) {
- Vector2 v = p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin();
- gravity += v.normalized() * (p_area->get_gravity() / Math::pow(v.length() * p_area->get_gravity_distance_scale() + 1, 2));
- } else {
- gravity += (p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin()).normalized() * p_area->get_gravity();
- }
- } else {
- gravity += p_area->get_gravity_vector() * p_area->get_gravity();
- }
+void Body2DSW::_compute_area_gravity_and_damping(const Area2DSW *p_area) {
+ Vector2 area_gravity;
+ p_area->compute_gravity(get_transform().get_origin(), area_gravity);
+ gravity += area_gravity;
area_linear_damp += p_area->get_linear_damp();
area_angular_damp += p_area->get_angular_damp();
@@ -391,7 +385,7 @@ void Body2DSW::integrate_forces(real_t p_step) {
switch (mode) {
case PhysicsServer2D::AREA_SPACE_OVERRIDE_COMBINE:
case PhysicsServer2D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: {
- _compute_area_gravity_and_dampenings(aa[i].area);
+ _compute_area_gravity_and_damping(aa[i].area);
stopped = mode == PhysicsServer2D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE;
} break;
case PhysicsServer2D::AREA_SPACE_OVERRIDE_REPLACE:
@@ -399,7 +393,7 @@ void Body2DSW::integrate_forces(real_t p_step) {
gravity = Vector2(0, 0);
area_angular_damp = 0;
area_linear_damp = 0;
- _compute_area_gravity_and_dampenings(aa[i].area);
+ _compute_area_gravity_and_damping(aa[i].area);
stopped = mode == PhysicsServer2D::AREA_SPACE_OVERRIDE_REPLACE;
} break;
default: {
@@ -408,7 +402,7 @@ void Body2DSW::integrate_forces(real_t p_step) {
}
}
if (!stopped) {
- _compute_area_gravity_and_dampenings(def_area);
+ _compute_area_gravity_and_damping(def_area);
}
gravity *= gravity_scale;
@@ -502,7 +496,7 @@ void Body2DSW::integrate_velocities(real_t p_step) {
return;
}
- if (fi_callback) {
+ if (fi_callback_data || body_state_callback) {
get_space()->body_add_to_state_query_list(&direct_state_query_list);
}
@@ -554,27 +548,27 @@ void Body2DSW::wakeup_neighbours() {
}
void Body2DSW::call_queries() {
- if (fi_callback) {
- PhysicsDirectBodyState2DSW *dbs = PhysicsDirectBodyState2DSW::singleton;
- dbs->body = this;
-
- Variant v = dbs;
- const Variant *vp[2] = { &v, &fi_callback->callback_udata };
-
- Object *obj = fi_callback->callable.get_object();
- if (!obj) {
+ if (fi_callback_data) {
+ if (!fi_callback_data->callable.get_object()) {
set_force_integration_callback(Callable());
} else {
+ Variant direct_state_variant = get_direct_state();
+ const Variant *vp[2] = { &direct_state_variant, &fi_callback_data->udata };
+
Callable::CallError ce;
Variant rv;
- if (fi_callback->callback_udata.get_type() != Variant::NIL) {
- fi_callback->callable.call(vp, 2, rv, ce);
+ if (fi_callback_data->udata.get_type() != Variant::NIL) {
+ fi_callback_data->callable.call(vp, 2, rv, ce);
} else {
- fi_callback->callable.call(vp, 1, rv, ce);
+ fi_callback_data->callable.call(vp, 1, rv, ce);
}
}
}
+
+ if (body_state_callback) {
+ (body_state_callback)(body_state_callback_instance, get_direct_state());
+ }
}
bool Body2DSW::sleep_test(real_t p_step) {
@@ -594,17 +588,30 @@ bool Body2DSW::sleep_test(real_t p_step) {
}
}
+void Body2DSW::set_state_sync_callback(void *p_instance, PhysicsServer2D::BodyStateCallback p_callback) {
+ body_state_callback_instance = p_instance;
+ body_state_callback = p_callback;
+}
+
void Body2DSW::set_force_integration_callback(const Callable &p_callable, const Variant &p_udata) {
- if (fi_callback) {
- memdelete(fi_callback);
- fi_callback = nullptr;
+ if (p_callable.get_object()) {
+ if (!fi_callback_data) {
+ fi_callback_data = memnew(ForceIntegrationCallbackData);
+ }
+ fi_callback_data->callable = p_callable;
+ fi_callback_data->udata = p_udata;
+ } else if (fi_callback_data) {
+ memdelete(fi_callback_data);
+ fi_callback_data = nullptr;
}
+}
- if (p_callable.get_object()) {
- fi_callback = memnew(ForceIntegrationCallback);
- fi_callback->callable = p_callable;
- fi_callback->callback_udata = p_udata;
+PhysicsDirectBodyState2DSW *Body2DSW::get_direct_state() {
+ if (!direct_state) {
+ direct_state = memnew(PhysicsDirectBodyState2DSW);
+ direct_state->body = this;
}
+ return direct_state;
}
Body2DSW::Body2DSW() :
@@ -639,33 +646,13 @@ Body2DSW::Body2DSW() :
still_time = 0;
continuous_cd_mode = PhysicsServer2D::CCD_MODE_DISABLED;
can_sleep = true;
- fi_callback = nullptr;
}
Body2DSW::~Body2DSW() {
- if (fi_callback) {
- memdelete(fi_callback);
- }
-}
-
-PhysicsDirectBodyState2DSW *PhysicsDirectBodyState2DSW::singleton = nullptr;
-
-PhysicsDirectSpaceState2D *PhysicsDirectBodyState2DSW::get_space_state() {
- return body->get_space()->get_direct_state();
-}
-
-Variant PhysicsDirectBodyState2DSW::get_contact_collider_shape_metadata(int p_contact_idx) const {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Variant());
-
- if (!PhysicsServer2DSW::singletonsw->body_owner.owns(body->contacts[p_contact_idx].collider)) {
- return Variant();
+ if (fi_callback_data) {
+ memdelete(fi_callback_data);
}
- Body2DSW *other = PhysicsServer2DSW::singletonsw->body_owner.getornull(body->contacts[p_contact_idx].collider);
-
- int sidx = body->contacts[p_contact_idx].collider_shape;
- if (sidx < 0 || sidx >= other->get_shape_count()) {
- return Variant();
+ if (direct_state) {
+ memdelete(direct_state);
}
-
- return other->get_shape_metadata(sidx);
}
diff --git a/servers/physics_2d/body_2d_sw.h b/servers/physics_2d/body_2d_sw.h
index 74bef433dc..9cd53ceca1 100644
--- a/servers/physics_2d/body_2d_sw.h
+++ b/servers/physics_2d/body_2d_sw.h
@@ -38,6 +38,7 @@
#include "core/templates/vset.h"
class Constraint2DSW;
+class PhysicsDirectBodyState2DSW;
class Body2DSW : public CollisionObject2DSW {
PhysicsServer2D::BodyMode mode;
@@ -116,22 +117,30 @@ class Body2DSW : public CollisionObject2DSW {
Vector<Contact> contacts; //no contacts by default
int contact_count;
- struct ForceIntegrationCallback {
+ void *body_state_callback_instance = nullptr;
+ PhysicsServer2D::BodyStateCallback body_state_callback = nullptr;
+
+ struct ForceIntegrationCallbackData {
Callable callable;
- Variant callback_udata;
+ Variant udata;
};
- ForceIntegrationCallback *fi_callback;
+ ForceIntegrationCallbackData *fi_callback_data = nullptr;
+
+ PhysicsDirectBodyState2DSW *direct_state = nullptr;
uint64_t island_step;
- _FORCE_INLINE_ void _compute_area_gravity_and_dampenings(const Area2DSW *p_area);
+ _FORCE_INLINE_ void _compute_area_gravity_and_damping(const Area2DSW *p_area);
friend class PhysicsDirectBodyState2DSW; // i give up, too many functions to expose
public:
+ void set_state_sync_callback(void *p_instance, PhysicsServer2D::BodyStateCallback p_callback);
void set_force_integration_callback(const Callable &p_callable, const Variant &p_udata = Variant());
+ PhysicsDirectBodyState2DSW *get_direct_state();
+
_FORCE_INLINE_ void add_area(Area2DSW *p_area) {
int index = areas.find(AreaCMP(p_area));
if (index > -1) {
@@ -332,87 +341,4 @@ void Body2DSW::add_contact(const Vector2 &p_local_pos, const Vector2 &p_local_no
c[idx].collider_velocity_at_pos = p_collider_velocity_at_pos;
}
-class PhysicsDirectBodyState2DSW : public PhysicsDirectBodyState2D {
- GDCLASS(PhysicsDirectBodyState2DSW, PhysicsDirectBodyState2D);
-
-public:
- static PhysicsDirectBodyState2DSW *singleton;
- Body2DSW *body;
- real_t step;
-
- virtual Vector2 get_total_gravity() const override { return body->gravity; } // get gravity vector working on this body space/area
- virtual real_t get_total_angular_damp() const override { return body->area_angular_damp; } // get density of this body space/area
- virtual real_t get_total_linear_damp() const override { return body->area_linear_damp; } // get density of this body space/area
-
- virtual real_t get_inverse_mass() const override { return body->get_inv_mass(); } // get the mass
- virtual real_t get_inverse_inertia() const override { return body->get_inv_inertia(); } // get density of this body space
-
- virtual void set_linear_velocity(const Vector2 &p_velocity) override { body->set_linear_velocity(p_velocity); }
- virtual Vector2 get_linear_velocity() const override { return body->get_linear_velocity(); }
-
- virtual void set_angular_velocity(real_t p_velocity) override { body->set_angular_velocity(p_velocity); }
- virtual real_t get_angular_velocity() const override { return body->get_angular_velocity(); }
-
- virtual void set_transform(const Transform2D &p_transform) override { body->set_state(PhysicsServer2D::BODY_STATE_TRANSFORM, p_transform); }
- virtual Transform2D get_transform() const override { return body->get_transform(); }
-
- virtual Vector2 get_velocity_at_local_position(const Vector2 &p_position) const override { return body->get_velocity_in_local_point(p_position); }
-
- virtual void add_central_force(const Vector2 &p_force) override { body->add_central_force(p_force); }
- virtual void add_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()) override { body->add_force(p_force, p_position); }
- virtual void add_torque(real_t p_torque) override { body->add_torque(p_torque); }
- virtual void apply_central_impulse(const Vector2 &p_impulse) override { body->apply_central_impulse(p_impulse); }
- virtual void apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) override { body->apply_impulse(p_impulse, p_position); }
- virtual void apply_torque_impulse(real_t p_torque) override { body->apply_torque_impulse(p_torque); }
-
- virtual void set_sleep_state(bool p_enable) override { body->set_active(!p_enable); }
- virtual bool is_sleeping() const override { return !body->is_active(); }
-
- virtual int get_contact_count() const override { return body->contact_count; }
-
- virtual Vector2 get_contact_local_position(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2());
- return body->contacts[p_contact_idx].local_pos;
- }
- virtual Vector2 get_contact_local_normal(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2());
- return body->contacts[p_contact_idx].local_normal;
- }
- virtual int get_contact_local_shape(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1);
- return body->contacts[p_contact_idx].local_shape;
- }
-
- virtual RID get_contact_collider(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID());
- return body->contacts[p_contact_idx].collider;
- }
- virtual Vector2 get_contact_collider_position(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2());
- return body->contacts[p_contact_idx].collider_pos;
- }
- virtual ObjectID get_contact_collider_id(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID());
- return body->contacts[p_contact_idx].collider_instance_id;
- }
- virtual int get_contact_collider_shape(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0);
- return body->contacts[p_contact_idx].collider_shape;
- }
- virtual Variant get_contact_collider_shape_metadata(int p_contact_idx) const override;
-
- virtual Vector2 get_contact_collider_velocity_at_position(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2());
- return body->contacts[p_contact_idx].collider_velocity_at_pos;
- }
-
- virtual PhysicsDirectSpaceState2D *get_space_state() override;
-
- virtual real_t get_step() const override { return step; }
- PhysicsDirectBodyState2DSW() {
- singleton = this;
- body = nullptr;
- }
-};
-
#endif // BODY_2D_SW_H
diff --git a/servers/physics_2d/body_direct_state_2d_sw.cpp b/servers/physics_2d/body_direct_state_2d_sw.cpp
new file mode 100644
index 0000000000..1f68cca843
--- /dev/null
+++ b/servers/physics_2d/body_direct_state_2d_sw.cpp
@@ -0,0 +1,182 @@
+/*************************************************************************/
+/* body_direct_state_2d_sw.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "body_direct_state_2d_sw.h"
+
+#include "body_2d_sw.h"
+#include "physics_server_2d_sw.h"
+#include "space_2d_sw.h"
+
+Vector2 PhysicsDirectBodyState2DSW::get_total_gravity() const {
+ return body->gravity;
+}
+
+real_t PhysicsDirectBodyState2DSW::get_total_angular_damp() const {
+ return body->area_angular_damp;
+}
+
+real_t PhysicsDirectBodyState2DSW::get_total_linear_damp() const {
+ return body->area_linear_damp;
+}
+
+real_t PhysicsDirectBodyState2DSW::get_inverse_mass() const {
+ return body->get_inv_mass();
+}
+
+real_t PhysicsDirectBodyState2DSW::get_inverse_inertia() const {
+ return body->get_inv_inertia();
+}
+
+void PhysicsDirectBodyState2DSW::set_linear_velocity(const Vector2 &p_velocity) {
+ body->set_linear_velocity(p_velocity);
+}
+
+Vector2 PhysicsDirectBodyState2DSW::get_linear_velocity() const {
+ return body->get_linear_velocity();
+}
+
+void PhysicsDirectBodyState2DSW::set_angular_velocity(real_t p_velocity) {
+ body->set_angular_velocity(p_velocity);
+}
+
+real_t PhysicsDirectBodyState2DSW::get_angular_velocity() const {
+ return body->get_angular_velocity();
+}
+
+void PhysicsDirectBodyState2DSW::set_transform(const Transform2D &p_transform) {
+ body->set_state(PhysicsServer2D::BODY_STATE_TRANSFORM, p_transform);
+}
+
+Transform2D PhysicsDirectBodyState2DSW::get_transform() const {
+ return body->get_transform();
+}
+
+Vector2 PhysicsDirectBodyState2DSW::get_velocity_at_local_position(const Vector2 &p_position) const {
+ return body->get_velocity_in_local_point(p_position);
+}
+
+void PhysicsDirectBodyState2DSW::add_central_force(const Vector2 &p_force) {
+ body->add_central_force(p_force);
+}
+
+void PhysicsDirectBodyState2DSW::add_force(const Vector2 &p_force, const Vector2 &p_position) {
+ body->add_force(p_force, p_position);
+}
+
+void PhysicsDirectBodyState2DSW::add_torque(real_t p_torque) {
+ body->add_torque(p_torque);
+}
+
+void PhysicsDirectBodyState2DSW::apply_central_impulse(const Vector2 &p_impulse) {
+ body->apply_central_impulse(p_impulse);
+}
+
+void PhysicsDirectBodyState2DSW::apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position) {
+ body->apply_impulse(p_impulse, p_position);
+}
+
+void PhysicsDirectBodyState2DSW::apply_torque_impulse(real_t p_torque) {
+ body->apply_torque_impulse(p_torque);
+}
+
+void PhysicsDirectBodyState2DSW::set_sleep_state(bool p_enable) {
+ body->set_active(!p_enable);
+}
+
+bool PhysicsDirectBodyState2DSW::is_sleeping() const {
+ return !body->is_active();
+}
+
+int PhysicsDirectBodyState2DSW::get_contact_count() const {
+ return body->contact_count;
+}
+
+Vector2 PhysicsDirectBodyState2DSW::get_contact_local_position(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2());
+ return body->contacts[p_contact_idx].local_pos;
+}
+
+Vector2 PhysicsDirectBodyState2DSW::get_contact_local_normal(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2());
+ return body->contacts[p_contact_idx].local_normal;
+}
+
+int PhysicsDirectBodyState2DSW::get_contact_local_shape(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1);
+ return body->contacts[p_contact_idx].local_shape;
+}
+
+RID PhysicsDirectBodyState2DSW::get_contact_collider(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID());
+ return body->contacts[p_contact_idx].collider;
+}
+Vector2 PhysicsDirectBodyState2DSW::get_contact_collider_position(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2());
+ return body->contacts[p_contact_idx].collider_pos;
+}
+
+ObjectID PhysicsDirectBodyState2DSW::get_contact_collider_id(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID());
+ return body->contacts[p_contact_idx].collider_instance_id;
+}
+
+int PhysicsDirectBodyState2DSW::get_contact_collider_shape(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0);
+ return body->contacts[p_contact_idx].collider_shape;
+}
+
+Vector2 PhysicsDirectBodyState2DSW::get_contact_collider_velocity_at_position(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector2());
+ return body->contacts[p_contact_idx].collider_velocity_at_pos;
+}
+
+Variant PhysicsDirectBodyState2DSW::get_contact_collider_shape_metadata(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Variant());
+
+ if (!PhysicsServer2DSW::singletonsw->body_owner.owns(body->contacts[p_contact_idx].collider)) {
+ return Variant();
+ }
+ Body2DSW *other = PhysicsServer2DSW::singletonsw->body_owner.getornull(body->contacts[p_contact_idx].collider);
+
+ int sidx = body->contacts[p_contact_idx].collider_shape;
+ if (sidx < 0 || sidx >= other->get_shape_count()) {
+ return Variant();
+ }
+
+ return other->get_shape_metadata(sidx);
+}
+
+PhysicsDirectSpaceState2D *PhysicsDirectBodyState2DSW::get_space_state() {
+ return body->get_space()->get_direct_state();
+}
+
+real_t PhysicsDirectBodyState2DSW::get_step() const {
+ return body->get_space()->get_last_step();
+}
diff --git a/servers/physics_2d/body_direct_state_2d_sw.h b/servers/physics_2d/body_direct_state_2d_sw.h
new file mode 100644
index 0000000000..aef9186086
--- /dev/null
+++ b/servers/physics_2d/body_direct_state_2d_sw.h
@@ -0,0 +1,91 @@
+/*************************************************************************/
+/* body_direct_state_2d_sw.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 BODY_DIRECT_STATE_2D_SW_H
+#define BODY_DIRECT_STATE_2D_SW_H
+
+#include "servers/physics_server_2d.h"
+
+class Body2DSW;
+
+class PhysicsDirectBodyState2DSW : public PhysicsDirectBodyState2D {
+ GDCLASS(PhysicsDirectBodyState2DSW, PhysicsDirectBodyState2D);
+
+public:
+ Body2DSW *body = nullptr;
+
+ virtual Vector2 get_total_gravity() const override;
+ virtual real_t get_total_angular_damp() const override;
+ virtual real_t get_total_linear_damp() const override;
+
+ virtual real_t get_inverse_mass() const override;
+ virtual real_t get_inverse_inertia() const override;
+
+ virtual void set_linear_velocity(const Vector2 &p_velocity) override;
+ virtual Vector2 get_linear_velocity() const override;
+
+ virtual void set_angular_velocity(real_t p_velocity) override;
+ virtual real_t get_angular_velocity() const override;
+
+ virtual void set_transform(const Transform2D &p_transform) override;
+ virtual Transform2D get_transform() const override;
+
+ virtual Vector2 get_velocity_at_local_position(const Vector2 &p_position) const override;
+
+ virtual void add_central_force(const Vector2 &p_force) override;
+ virtual void add_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()) override;
+ virtual void add_torque(real_t p_torque) override;
+ virtual void apply_central_impulse(const Vector2 &p_impulse) override;
+ virtual void apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) override;
+ virtual void apply_torque_impulse(real_t p_torque) override;
+
+ virtual void set_sleep_state(bool p_enable) override;
+ virtual bool is_sleeping() const override;
+
+ virtual int get_contact_count() const override;
+
+ virtual Vector2 get_contact_local_position(int p_contact_idx) const override;
+ virtual Vector2 get_contact_local_normal(int p_contact_idx) const override;
+ virtual int get_contact_local_shape(int p_contact_idx) const override;
+
+ virtual RID get_contact_collider(int p_contact_idx) const override;
+ virtual Vector2 get_contact_collider_position(int p_contact_idx) const override;
+ virtual ObjectID get_contact_collider_id(int p_contact_idx) const override;
+ virtual int get_contact_collider_shape(int p_contact_idx) const override;
+ virtual Variant get_contact_collider_shape_metadata(int p_contact_idx) const override;
+
+ virtual Vector2 get_contact_collider_velocity_at_position(int p_contact_idx) const override;
+
+ virtual PhysicsDirectSpaceState2D *get_space_state() override;
+
+ virtual real_t get_step() const override;
+};
+
+#endif // BODY_2D_SW_H
diff --git a/servers/physics_2d/collision_solver_2d_sw.cpp b/servers/physics_2d/collision_solver_2d_sw.cpp
index f7a8593c50..ae50615953 100644
--- a/servers/physics_2d/collision_solver_2d_sw.cpp
+++ b/servers/physics_2d/collision_solver_2d_sw.cpp
@@ -149,20 +149,20 @@ struct _ConcaveCollisionInfo2D {
Vector2 *sep_axis;
};
-void CollisionSolver2DSW::concave_callback(void *p_userdata, Shape2DSW *p_convex) {
+bool CollisionSolver2DSW::concave_callback(void *p_userdata, Shape2DSW *p_convex) {
_ConcaveCollisionInfo2D &cinfo = *(_ConcaveCollisionInfo2D *)(p_userdata);
cinfo.aabb_tests++;
- if (!cinfo.result_callback && cinfo.collided) {
- return; //already collided and no contacts requested, don't test anymore
- }
bool collided = collision_solver(cinfo.shape_A, *cinfo.transform_A, cinfo.motion_A, p_convex, *cinfo.transform_B, cinfo.motion_B, cinfo.result_callback, cinfo.userdata, cinfo.swap_result, cinfo.sep_axis, cinfo.margin_A, cinfo.margin_B);
if (!collided) {
- return;
+ return false;
}
cinfo.collided = true;
cinfo.collisions++;
+
+ // Stop at first collision if contacts are not needed.
+ return !cinfo.result_callback;
}
bool CollisionSolver2DSW::solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis, real_t p_margin_A, real_t p_margin_B) {
diff --git a/servers/physics_2d/collision_solver_2d_sw.h b/servers/physics_2d/collision_solver_2d_sw.h
index 0678e3526c..62fccc4ff3 100644
--- a/servers/physics_2d/collision_solver_2d_sw.h
+++ b/servers/physics_2d/collision_solver_2d_sw.h
@@ -39,7 +39,7 @@ public:
private:
static bool solve_static_world_margin(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
- static void concave_callback(void *p_userdata, Shape2DSW *p_convex);
+ static bool concave_callback(void *p_userdata, Shape2DSW *p_convex);
static bool solve_concave(const Shape2DSW *p_shape_A, const Transform2D &p_transform_A, const Vector2 &p_motion_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, const Vector2 &p_motion_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis = nullptr, real_t p_margin_A = 0, real_t p_margin_B = 0);
static bool solve_separation_ray(const Shape2DSW *p_shape_A, const Vector2 &p_motion_A, const Transform2D &p_transform_A, const Shape2DSW *p_shape_B, const Transform2D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, Vector2 *r_sep_axis = nullptr, real_t p_margin = 0);
diff --git a/servers/physics_2d/physics_server_2d_sw.cpp b/servers/physics_2d/physics_server_2d_sw.cpp
index 813e5ddc17..85b5e40752 100644
--- a/servers/physics_2d/physics_server_2d_sw.cpp
+++ b/servers/physics_2d/physics_server_2d_sw.cpp
@@ -30,6 +30,7 @@
#include "physics_server_2d_sw.h"
+#include "body_direct_state_2d_sw.h"
#include "broad_phase_2d_bvh.h"
#include "collision_solver_2d_sw.h"
#include "core/config/project_settings.h"
@@ -926,6 +927,12 @@ int PhysicsServer2DSW::body_get_max_contacts_reported(RID p_body) const {
return body->get_max_contacts_reported();
}
+void PhysicsServer2DSW::body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) {
+ Body2DSW *body = body_owner.getornull(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_state_sync_callback(p_instance, p_callback);
+}
+
void PhysicsServer2DSW::body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata) {
Body2DSW *body = body_owner.getornull(p_body);
ERR_FAIL_COND(!body);
@@ -960,17 +967,13 @@ bool PhysicsServer2DSW::body_test_motion(RID p_body, const Transform2D &p_from,
PhysicsDirectBodyState2D *PhysicsServer2DSW::body_get_direct_state(RID p_body) {
ERR_FAIL_COND_V_MSG((using_threads && !doing_sync), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification.");
- if (!body_owner.owns(p_body)) {
- return nullptr;
- }
-
Body2DSW *body = body_owner.getornull(p_body);
ERR_FAIL_COND_V(!body, nullptr);
+
ERR_FAIL_COND_V(!body->get_space(), nullptr);
ERR_FAIL_COND_V_MSG(body->get_space()->is_locked(), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification.");
- direct_state->body = body;
- return direct_state;
+ return body->get_direct_state();
}
/* JOINT API */
@@ -1234,10 +1237,8 @@ void PhysicsServer2DSW::set_collision_iterations(int p_iterations) {
void PhysicsServer2DSW::init() {
doing_sync = false;
- last_step = 0.001;
iterations = 8; // 8?
stepper = memnew(Step2DSW);
- direct_state = memnew(PhysicsDirectBodyState2DSW);
};
void PhysicsServer2DSW::step(real_t p_step) {
@@ -1247,8 +1248,6 @@ void PhysicsServer2DSW::step(real_t p_step) {
_update_shapes();
- last_step = p_step;
- PhysicsDirectBodyState2DSW::singleton->step = p_step;
island_count = 0;
active_objects = 0;
collision_pairs = 0;
@@ -1320,7 +1319,6 @@ void PhysicsServer2DSW::end_sync() {
void PhysicsServer2DSW::finish() {
memdelete(stepper);
- memdelete(direct_state);
};
void PhysicsServer2DSW::_update_shapes() {
diff --git a/servers/physics_2d/physics_server_2d_sw.h b/servers/physics_2d/physics_server_2d_sw.h
index c5b6d550d7..ef1306cf59 100644
--- a/servers/physics_2d/physics_server_2d_sw.h
+++ b/servers/physics_2d/physics_server_2d_sw.h
@@ -46,7 +46,6 @@ class PhysicsServer2DSW : public PhysicsServer2D {
bool active;
int iterations;
bool doing_sync;
- real_t last_step;
int island_count;
int active_objects;
@@ -59,8 +58,6 @@ class PhysicsServer2DSW : public PhysicsServer2D {
Step2DSW *stepper;
Set<const Space2DSW *> active_spaces;
- PhysicsDirectBodyState2DSW *direct_state;
-
mutable RID_PtrOwner<Shape2DSW, true> shape_owner;
mutable RID_PtrOwner<Space2DSW, true> space_owner;
mutable RID_PtrOwner<Area2DSW, true> area_owner;
@@ -242,7 +239,9 @@ public:
virtual void body_set_max_contacts_reported(RID p_body, int p_contacts) override;
virtual int body_get_max_contacts_reported(RID p_body) const override;
+ virtual void body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) override;
virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) override;
+
virtual bool body_collide_shape(RID p_body, int p_body_shape, RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, Vector2 *r_results, int p_result_max, int &r_result_count) override;
virtual void body_set_pickable(RID p_body, bool p_pickable) override;
diff --git a/servers/physics_2d/physics_server_2d_wrap_mt.h b/servers/physics_2d/physics_server_2d_wrap_mt.h
index 05d4ac64b0..6761e37daa 100644
--- a/servers/physics_2d/physics_server_2d_wrap_mt.h
+++ b/servers/physics_2d/physics_server_2d_wrap_mt.h
@@ -245,6 +245,7 @@ public:
FUNC2(body_set_omit_force_integration, RID, bool);
FUNC1RC(bool, body_is_omitting_force_integration, RID);
+ FUNC3(body_set_state_sync_callback, RID, void *, BodyStateCallback);
FUNC3(body_set_force_integration_callback, RID, const Callable &, const Variant &);
bool body_collide_shape(RID p_body, int p_body_shape, RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, Vector2 *r_results, int p_result_max, int &r_result_count) override {
diff --git a/servers/physics_2d/shape_2d_sw.cpp b/servers/physics_2d/shape_2d_sw.cpp
index 757b2e0583..064c4afe52 100644
--- a/servers/physics_2d/shape_2d_sw.cpp
+++ b/servers/physics_2d/shape_2d_sw.cpp
@@ -921,7 +921,7 @@ Variant ConcavePolygonShape2DSW::get_data() const {
return rsegments;
}
-void ConcavePolygonShape2DSW::cull(const Rect2 &p_local_aabb, Callback p_callback, void *p_userdata) const {
+void ConcavePolygonShape2DSW::cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const {
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * bvh_depth);
enum {
@@ -969,7 +969,9 @@ void ConcavePolygonShape2DSW::cull(const Rect2 &p_local_aabb, Callback p_callbac
SegmentShape2DSW ss(a, b, (b - a).orthogonal().normalized());
- p_callback(p_userdata, &ss);
+ if (p_callback(p_userdata, &ss)) {
+ return;
+ }
stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node;
} else {
diff --git a/servers/physics_2d/shape_2d_sw.h b/servers/physics_2d/shape_2d_sw.h
index bc321adb42..1185d343ee 100644
--- a/servers/physics_2d/shape_2d_sw.h
+++ b/servers/physics_2d/shape_2d_sw.h
@@ -465,9 +465,11 @@ public:
class ConcaveShape2DSW : public Shape2DSW {
public:
virtual bool is_concave() const override { return true; }
- typedef void (*Callback)(void *p_userdata, Shape2DSW *p_convex);
- virtual void cull(const Rect2 &p_local_aabb, Callback p_callback, void *p_userdata) const = 0;
+ // Returns true to stop the query.
+ typedef bool (*QueryCallback)(void *p_userdata, Shape2DSW *p_convex);
+
+ virtual void cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const = 0;
};
class ConcavePolygonShape2DSW : public ConcaveShape2DSW {
@@ -525,7 +527,7 @@ public:
virtual void set_data(const Variant &p_data) override;
virtual Variant get_data() const override;
- virtual void cull(const Rect2 &p_local_aabb, Callback p_callback, void *p_userdata) const override;
+ virtual void cull(const Rect2 &p_local_aabb, QueryCallback p_callback, void *p_userdata) const override;
DEFAULT_PROJECT_RANGE_CAST
};
diff --git a/servers/physics_2d/space_2d_sw.cpp b/servers/physics_2d/space_2d_sw.cpp
index c0493749e0..6c23f49928 100644
--- a/servers/physics_2d/space_2d_sw.cpp
+++ b/servers/physics_2d/space_2d_sw.cpp
@@ -634,7 +634,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co
//fix for moving platforms (kinematic and dynamic), margin is increased by how much it moved in the given direction
Vector2 lv = b->get_linear_velocity();
//compute displacement from linear velocity
- Vector2 motion = lv * PhysicsDirectBodyState2DSW::singleton->step;
+ Vector2 motion = lv * last_step;
real_t motion_len = motion.length();
motion.normalize();
cbk.valid_depth += motion_len * MAX(motion.dot(-cbk.valid_dir), 0.0);
@@ -926,7 +926,7 @@ bool Space2DSW::test_body_motion(Body2DSW *p_body, const Transform2D &p_from, co
//fix for moving platforms (kinematic and dynamic), margin is increased by how much it moved in the given direction
Vector2 lv = b->get_linear_velocity();
//compute displacement from linear velocity
- Vector2 motion = lv * PhysicsDirectBodyState2DSW::singleton->step;
+ Vector2 motion = lv * last_step;
real_t motion_len = motion.length();
motion.normalize();
rcd.valid_depth += motion_len * MAX(motion.dot(-rcd.valid_dir), 0.0);
diff --git a/servers/physics_2d/space_2d_sw.h b/servers/physics_2d/space_2d_sw.h
index 392df9b025..5de071a475 100644
--- a/servers/physics_2d/space_2d_sw.h
+++ b/servers/physics_2d/space_2d_sw.h
@@ -117,6 +117,8 @@ private:
bool locked;
+ real_t last_step = 0.001;
+
int island_count;
int active_objects;
int collision_pairs;
@@ -172,6 +174,9 @@ public:
void lock();
void unlock();
+ real_t get_last_step() const { return last_step; }
+ void set_last_step(real_t p_step) { last_step = p_step; }
+
void set_param(PhysicsServer2D::SpaceParameter p_param, real_t p_value);
real_t get_param(PhysicsServer2D::SpaceParameter p_param) const;
diff --git a/servers/physics_2d/step_2d_sw.cpp b/servers/physics_2d/step_2d_sw.cpp
index 8b30160cc1..0306ec5050 100644
--- a/servers/physics_2d/step_2d_sw.cpp
+++ b/servers/physics_2d/step_2d_sw.cpp
@@ -129,6 +129,8 @@ void Step2DSW::step(Space2DSW *p_space, real_t p_delta, int p_iterations) {
p_space->setup(); //update inertias, etc
+ p_space->set_last_step(p_delta);
+
iterations = p_iterations;
delta = p_delta;
diff --git a/servers/physics_3d/area_3d_sw.cpp b/servers/physics_3d/area_3d_sw.cpp
index c292abad48..c9e8bcb8ca 100644
--- a/servers/physics_3d/area_3d_sw.cpp
+++ b/servers/physics_3d/area_3d_sw.cpp
@@ -304,6 +304,26 @@ void Area3DSW::call_queries() {
}
}
+void Area3DSW::compute_gravity(const Vector3 &p_position, Vector3 &r_gravity) const {
+ if (is_gravity_point()) {
+ const real_t gravity_distance_scale = get_gravity_distance_scale();
+ Vector3 v = get_transform().xform(get_gravity_vector()) - p_position;
+ if (gravity_distance_scale > 0) {
+ const real_t v_length = v.length();
+ if (v_length > 0) {
+ const real_t v_scaled = v_length * gravity_distance_scale;
+ r_gravity = (v.normalized() * (get_gravity() / (v_scaled * v_scaled)));
+ } else {
+ r_gravity = Vector3();
+ }
+ } else {
+ r_gravity = v.normalized() * get_gravity();
+ }
+ } else {
+ r_gravity = get_gravity_vector() * get_gravity();
+ }
+}
+
Area3DSW::Area3DSW() :
CollisionObject3DSW(TYPE_AREA),
monitor_query_list(this),
diff --git a/servers/physics_3d/area_3d_sw.h b/servers/physics_3d/area_3d_sw.h
index 814472885c..d5f1e60119 100644
--- a/servers/physics_3d/area_3d_sw.h
+++ b/servers/physics_3d/area_3d_sw.h
@@ -34,7 +34,6 @@
#include "collision_object_3d_sw.h"
#include "core/templates/self_list.h"
#include "servers/physics_server_3d.h"
-//#include "servers/physics_3d/query_sw.h"
class Space3DSW;
class Body3DSW;
@@ -101,18 +100,12 @@ class Area3DSW : public CollisionObject3DSW {
Map<BodyKey, BodyState> monitored_bodies;
Map<BodyKey, BodyState> monitored_areas;
- //virtual void shape_changed_notify(ShapeSW *p_shape);
- //virtual void shape_deleted_notify(ShapeSW *p_shape);
-
Set<Constraint3DSW *> constraints;
virtual void _shapes_changed();
void _queue_monitor_update();
public:
- //_FORCE_INLINE_ const Transform& get_inverse_transform() const { return inverse_transform; }
- //_FORCE_INLINE_ SpaceSW* get_owner() { return owner; }
-
void set_monitor_callback(ObjectID p_id, const StringName &p_method);
_FORCE_INLINE_ bool has_monitor_callback() const { return monitor_callback_id.is_valid(); }
@@ -184,6 +177,8 @@ public:
void call_queries();
+ void compute_gravity(const Vector3 &p_position, Vector3 &r_gravity) const;
+
Area3DSW();
~Area3DSW();
};
diff --git a/servers/physics_3d/body_3d_sw.cpp b/servers/physics_3d/body_3d_sw.cpp
index 0c4079332d..7f62e4eb85 100644
--- a/servers/physics_3d/body_3d_sw.cpp
+++ b/servers/physics_3d/body_3d_sw.cpp
@@ -29,7 +29,9 @@
/*************************************************************************/
#include "body_3d_sw.h"
+
#include "area_3d_sw.h"
+#include "body_direct_state_3d_sw.h"
#include "space_3d_sw.h"
void Body3DSW::_update_inertia() {
@@ -379,17 +381,10 @@ void Body3DSW::set_space(Space3DSW *p_space) {
first_integration = true;
}
-void Body3DSW::_compute_area_gravity_and_dampenings(const Area3DSW *p_area) {
- if (p_area->is_gravity_point()) {
- if (p_area->get_gravity_distance_scale() > 0) {
- Vector3 v = p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin();
- gravity += v.normalized() * (p_area->get_gravity() / Math::pow(v.length() * p_area->get_gravity_distance_scale() + 1, 2));
- } else {
- gravity += (p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin()).normalized() * p_area->get_gravity();
- }
- } else {
- gravity += p_area->get_gravity_vector() * p_area->get_gravity();
- }
+void Body3DSW::_compute_area_gravity_and_damping(const Area3DSW *p_area) {
+ Vector3 area_gravity;
+ p_area->compute_gravity(get_transform().get_origin(), area_gravity);
+ gravity += area_gravity;
area_linear_damp += p_area->get_linear_damp();
area_angular_damp += p_area->get_angular_damp();
@@ -431,7 +426,7 @@ void Body3DSW::integrate_forces(real_t p_step) {
switch (mode) {
case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE:
case PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: {
- _compute_area_gravity_and_dampenings(aa[i].area);
+ _compute_area_gravity_and_damping(aa[i].area);
stopped = mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE;
} break;
case PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE:
@@ -439,7 +434,7 @@ void Body3DSW::integrate_forces(real_t p_step) {
gravity = Vector3(0, 0, 0);
area_angular_damp = 0;
area_linear_damp = 0;
- _compute_area_gravity_and_dampenings(aa[i].area);
+ _compute_area_gravity_and_damping(aa[i].area);
stopped = mode == PhysicsServer3D::AREA_SPACE_OVERRIDE_REPLACE;
} break;
default: {
@@ -449,7 +444,7 @@ void Body3DSW::integrate_forces(real_t p_step) {
}
if (!stopped) {
- _compute_area_gravity_and_dampenings(def_area);
+ _compute_area_gravity_and_damping(def_area);
}
gravity *= gravity_scale;
@@ -543,7 +538,7 @@ void Body3DSW::integrate_velocities(real_t p_step) {
return;
}
- if (fi_callback) {
+ if (fi_callback_data || body_state_callback) {
get_space()->body_add_to_state_query_list(&direct_state_query_list);
}
@@ -600,11 +595,6 @@ void Body3DSW::integrate_velocities(real_t p_step) {
_set_inv_transform(get_transform().inverse());
_update_transform_dependant();
-
- /*
- if (fi_callback) {
- get_space()->body_add_to_state_query_list(&direct_state_query_list);
- */
}
/*
@@ -662,24 +652,23 @@ void Body3DSW::wakeup_neighbours() {
}
void Body3DSW::call_queries() {
- if (fi_callback) {
- PhysicsDirectBodyState3DSW *dbs = PhysicsDirectBodyState3DSW::singleton;
- dbs->body = this;
-
- Variant v = dbs;
-
- Object *obj = fi_callback->callable.get_object();
- if (!obj) {
+ if (fi_callback_data) {
+ if (!fi_callback_data->callable.get_object()) {
set_force_integration_callback(Callable());
} else {
- const Variant *vp[2] = { &v, &fi_callback->udata };
+ Variant direct_state_variant = get_direct_state();
+ const Variant *vp[2] = { &direct_state_variant, &fi_callback_data->udata };
Callable::CallError ce;
- int argc = (fi_callback->udata.get_type() == Variant::NIL) ? 1 : 2;
+ int argc = (fi_callback_data->udata.get_type() == Variant::NIL) ? 1 : 2;
Variant rv;
- fi_callback->callable.call(vp, argc, rv, ce);
+ fi_callback_data->callable.call(vp, argc, rv, ce);
}
}
+
+ if (body_state_callback_instance) {
+ (body_state_callback)(body_state_callback_instance, get_direct_state());
+ }
}
bool Body3DSW::sleep_test(real_t p_step) {
@@ -699,22 +688,34 @@ bool Body3DSW::sleep_test(real_t p_step) {
}
}
+void Body3DSW::set_state_sync_callback(void *p_instance, PhysicsServer3D::BodyStateCallback p_callback) {
+ body_state_callback_instance = p_instance;
+ body_state_callback = p_callback;
+}
+
void Body3DSW::set_force_integration_callback(const Callable &p_callable, const Variant &p_udata) {
- if (fi_callback) {
- memdelete(fi_callback);
- fi_callback = nullptr;
+ if (p_callable.get_object()) {
+ if (!fi_callback_data) {
+ fi_callback_data = memnew(ForceIntegrationCallbackData);
+ }
+ fi_callback_data->callable = p_callable;
+ fi_callback_data->udata = p_udata;
+ } else if (fi_callback_data) {
+ memdelete(fi_callback_data);
+ fi_callback_data = nullptr;
}
+}
- if (p_callable.get_object()) {
- fi_callback = memnew(ForceIntegrationCallback);
- fi_callback->callable = p_callable;
- fi_callback->udata = p_udata;
+PhysicsDirectBodyState3DSW *Body3DSW::get_direct_state() {
+ if (!direct_state) {
+ direct_state = memnew(PhysicsDirectBodyState3DSW);
+ direct_state->body = this;
}
+ return direct_state;
}
Body3DSW::Body3DSW() :
CollisionObject3DSW(TYPE_BODY),
-
active_list(this),
inertia_update_list(this),
direct_state_query_list(this) {
@@ -742,17 +743,13 @@ Body3DSW::Body3DSW() :
still_time = 0;
continuous_cd = false;
can_sleep = true;
- fi_callback = nullptr;
}
Body3DSW::~Body3DSW() {
- if (fi_callback) {
- memdelete(fi_callback);
+ if (fi_callback_data) {
+ memdelete(fi_callback_data);
+ }
+ if (direct_state) {
+ memdelete(direct_state);
}
-}
-
-PhysicsDirectBodyState3DSW *PhysicsDirectBodyState3DSW::singleton = nullptr;
-
-PhysicsDirectSpaceState3D *PhysicsDirectBodyState3DSW::get_space_state() {
- return body->get_space()->get_direct_state();
}
diff --git a/servers/physics_3d/body_3d_sw.h b/servers/physics_3d/body_3d_sw.h
index efb114a325..f58f40652b 100644
--- a/servers/physics_3d/body_3d_sw.h
+++ b/servers/physics_3d/body_3d_sw.h
@@ -28,14 +28,15 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef BODY_SW_H
-#define BODY_SW_H
+#ifndef BODY_3D_SW_H
+#define BODY_3D_SW_H
#include "area_3d_sw.h"
#include "collision_object_3d_sw.h"
#include "core/templates/vset.h"
class Constraint3DSW;
+class PhysicsDirectBodyState3DSW;
class Body3DSW : public CollisionObject3DSW {
PhysicsServer3D::BodyMode mode;
@@ -113,24 +114,32 @@ class Body3DSW : public CollisionObject3DSW {
Vector<Contact> contacts; //no contacts by default
int contact_count;
- struct ForceIntegrationCallback {
+ void *body_state_callback_instance = nullptr;
+ PhysicsServer3D::BodyStateCallback body_state_callback = nullptr;
+
+ struct ForceIntegrationCallbackData {
Callable callable;
Variant udata;
};
- ForceIntegrationCallback *fi_callback;
+ ForceIntegrationCallbackData *fi_callback_data = nullptr;
+
+ PhysicsDirectBodyState3DSW *direct_state = nullptr;
uint64_t island_step;
- _FORCE_INLINE_ void _compute_area_gravity_and_dampenings(const Area3DSW *p_area);
+ _FORCE_INLINE_ void _compute_area_gravity_and_damping(const Area3DSW *p_area);
_FORCE_INLINE_ void _update_transform_dependant();
friend class PhysicsDirectBodyState3DSW; // i give up, too many functions to expose
public:
+ void set_state_sync_callback(void *p_instance, PhysicsServer3D::BodyStateCallback p_callback);
void set_force_integration_callback(const Callable &p_callable, const Variant &p_udata = Variant());
+ PhysicsDirectBodyState3DSW *get_direct_state();
+
_FORCE_INLINE_ void add_area(Area3DSW *p_area) {
int index = areas.find(AreaCMP(p_area));
if (index > -1) {
@@ -349,96 +358,4 @@ void Body3DSW::add_contact(const Vector3 &p_local_pos, const Vector3 &p_local_no
c[idx].collider_velocity_at_pos = p_collider_velocity_at_pos;
}
-class PhysicsDirectBodyState3DSW : public PhysicsDirectBodyState3D {
- GDCLASS(PhysicsDirectBodyState3DSW, PhysicsDirectBodyState3D);
-
-public:
- static PhysicsDirectBodyState3DSW *singleton;
- Body3DSW *body;
- real_t step;
-
- virtual Vector3 get_total_gravity() const override { return body->gravity; } // get gravity vector working on this body space/area
- virtual real_t get_total_angular_damp() const override { return body->area_angular_damp; } // get density of this body space/area
- virtual real_t get_total_linear_damp() const override { return body->area_linear_damp; } // get density of this body space/area
-
- virtual Vector3 get_center_of_mass() const override { return body->get_center_of_mass(); }
- virtual Basis get_principal_inertia_axes() const override { return body->get_principal_inertia_axes(); }
-
- virtual real_t get_inverse_mass() const override { return body->get_inv_mass(); } // get the mass
- virtual Vector3 get_inverse_inertia() const override { return body->get_inv_inertia(); } // get density of this body space
- virtual Basis get_inverse_inertia_tensor() const override { return body->get_inv_inertia_tensor(); } // get density of this body space
-
- virtual void set_linear_velocity(const Vector3 &p_velocity) override { body->set_linear_velocity(p_velocity); }
- virtual Vector3 get_linear_velocity() const override { return body->get_linear_velocity(); }
-
- virtual void set_angular_velocity(const Vector3 &p_velocity) override { body->set_angular_velocity(p_velocity); }
- virtual Vector3 get_angular_velocity() const override { return body->get_angular_velocity(); }
-
- virtual void set_transform(const Transform3D &p_transform) override { body->set_state(PhysicsServer3D::BODY_STATE_TRANSFORM, p_transform); }
- virtual Transform3D get_transform() const override { return body->get_transform(); }
-
- virtual Vector3 get_velocity_at_local_position(const Vector3 &p_position) const override { return body->get_velocity_in_local_point(p_position); }
-
- virtual void add_central_force(const Vector3 &p_force) override { body->add_central_force(p_force); }
- virtual void add_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) override {
- body->add_force(p_force, p_position);
- }
- virtual void add_torque(const Vector3 &p_torque) override { body->add_torque(p_torque); }
- virtual void apply_central_impulse(const Vector3 &p_impulse) override { body->apply_central_impulse(p_impulse); }
- virtual void apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3()) override {
- body->apply_impulse(p_impulse, p_position);
- }
- virtual void apply_torque_impulse(const Vector3 &p_impulse) override { body->apply_torque_impulse(p_impulse); }
-
- virtual void set_sleep_state(bool p_sleep) override { body->set_active(!p_sleep); }
- virtual bool is_sleeping() const override { return !body->is_active(); }
-
- virtual int get_contact_count() const override { return body->contact_count; }
-
- virtual Vector3 get_contact_local_position(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
- return body->contacts[p_contact_idx].local_pos;
- }
- virtual Vector3 get_contact_local_normal(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
- return body->contacts[p_contact_idx].local_normal;
- }
- virtual real_t get_contact_impulse(int p_contact_idx) const override {
- return 0.0f; // Only implemented for bullet
- }
- virtual int get_contact_local_shape(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1);
- return body->contacts[p_contact_idx].local_shape;
- }
-
- virtual RID get_contact_collider(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID());
- return body->contacts[p_contact_idx].collider;
- }
- virtual Vector3 get_contact_collider_position(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
- return body->contacts[p_contact_idx].collider_pos;
- }
- virtual ObjectID get_contact_collider_id(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID());
- return body->contacts[p_contact_idx].collider_instance_id;
- }
- virtual int get_contact_collider_shape(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0);
- return body->contacts[p_contact_idx].collider_shape;
- }
- virtual Vector3 get_contact_collider_velocity_at_position(int p_contact_idx) const override {
- ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
- return body->contacts[p_contact_idx].collider_velocity_at_pos;
- }
-
- virtual PhysicsDirectSpaceState3D *get_space_state() override;
-
- virtual real_t get_step() const override { return step; }
- PhysicsDirectBodyState3DSW() {
- singleton = this;
- body = nullptr;
- }
-};
-
-#endif // BODY__SW_H
+#endif // BODY_3D_SW_H
diff --git a/servers/physics_3d/body_direct_state_3d_sw.cpp b/servers/physics_3d/body_direct_state_3d_sw.cpp
new file mode 100644
index 0000000000..d197dd288d
--- /dev/null
+++ b/servers/physics_3d/body_direct_state_3d_sw.cpp
@@ -0,0 +1,182 @@
+/*************************************************************************/
+/* body_direct_state_3d_sw.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "body_direct_state_3d_sw.h"
+
+#include "body_3d_sw.h"
+#include "space_3d_sw.h"
+
+Vector3 PhysicsDirectBodyState3DSW::get_total_gravity() const {
+ return body->gravity;
+}
+
+real_t PhysicsDirectBodyState3DSW::get_total_angular_damp() const {
+ return body->area_angular_damp;
+}
+
+real_t PhysicsDirectBodyState3DSW::get_total_linear_damp() const {
+ return body->area_linear_damp;
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_center_of_mass() const {
+ return body->get_center_of_mass();
+}
+
+Basis PhysicsDirectBodyState3DSW::get_principal_inertia_axes() const {
+ return body->get_principal_inertia_axes();
+}
+
+real_t PhysicsDirectBodyState3DSW::get_inverse_mass() const {
+ return body->get_inv_mass();
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_inverse_inertia() const {
+ return body->get_inv_inertia();
+}
+
+Basis PhysicsDirectBodyState3DSW::get_inverse_inertia_tensor() const {
+ return body->get_inv_inertia_tensor();
+}
+
+void PhysicsDirectBodyState3DSW::set_linear_velocity(const Vector3 &p_velocity) {
+ body->set_linear_velocity(p_velocity);
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_linear_velocity() const {
+ return body->get_linear_velocity();
+}
+
+void PhysicsDirectBodyState3DSW::set_angular_velocity(const Vector3 &p_velocity) {
+ body->set_angular_velocity(p_velocity);
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_angular_velocity() const {
+ return body->get_angular_velocity();
+}
+
+void PhysicsDirectBodyState3DSW::set_transform(const Transform3D &p_transform) {
+ body->set_state(PhysicsServer3D::BODY_STATE_TRANSFORM, p_transform);
+}
+
+Transform3D PhysicsDirectBodyState3DSW::get_transform() const {
+ return body->get_transform();
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_velocity_at_local_position(const Vector3 &p_position) const {
+ return body->get_velocity_in_local_point(p_position);
+}
+
+void PhysicsDirectBodyState3DSW::add_central_force(const Vector3 &p_force) {
+ body->add_central_force(p_force);
+}
+
+void PhysicsDirectBodyState3DSW::add_force(const Vector3 &p_force, const Vector3 &p_position) {
+ body->add_force(p_force, p_position);
+}
+
+void PhysicsDirectBodyState3DSW::add_torque(const Vector3 &p_torque) {
+ body->add_torque(p_torque);
+}
+
+void PhysicsDirectBodyState3DSW::apply_central_impulse(const Vector3 &p_impulse) {
+ body->apply_central_impulse(p_impulse);
+}
+
+void PhysicsDirectBodyState3DSW::apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position) {
+ body->apply_impulse(p_impulse, p_position);
+}
+
+void PhysicsDirectBodyState3DSW::apply_torque_impulse(const Vector3 &p_impulse) {
+ body->apply_torque_impulse(p_impulse);
+}
+
+void PhysicsDirectBodyState3DSW::set_sleep_state(bool p_sleep) {
+ body->set_active(!p_sleep);
+}
+
+bool PhysicsDirectBodyState3DSW::is_sleeping() const {
+ return !body->is_active();
+}
+
+int PhysicsDirectBodyState3DSW::get_contact_count() const {
+ return body->contact_count;
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_contact_local_position(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
+ return body->contacts[p_contact_idx].local_pos;
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_contact_local_normal(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
+ return body->contacts[p_contact_idx].local_normal;
+}
+
+real_t PhysicsDirectBodyState3DSW::get_contact_impulse(int p_contact_idx) const {
+ return 0.0f; // Only implemented for bullet
+}
+
+int PhysicsDirectBodyState3DSW::get_contact_local_shape(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1);
+ return body->contacts[p_contact_idx].local_shape;
+}
+
+RID PhysicsDirectBodyState3DSW::get_contact_collider(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, RID());
+ return body->contacts[p_contact_idx].collider;
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_contact_collider_position(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
+ return body->contacts[p_contact_idx].collider_pos;
+}
+
+ObjectID PhysicsDirectBodyState3DSW::get_contact_collider_id(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, ObjectID());
+ return body->contacts[p_contact_idx].collider_instance_id;
+}
+
+int PhysicsDirectBodyState3DSW::get_contact_collider_shape(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, 0);
+ return body->contacts[p_contact_idx].collider_shape;
+}
+
+Vector3 PhysicsDirectBodyState3DSW::get_contact_collider_velocity_at_position(int p_contact_idx) const {
+ ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
+ return body->contacts[p_contact_idx].collider_velocity_at_pos;
+}
+
+PhysicsDirectSpaceState3D *PhysicsDirectBodyState3DSW::get_space_state() {
+ return body->get_space()->get_direct_state();
+}
+
+real_t PhysicsDirectBodyState3DSW::get_step() const {
+ return body->get_space()->get_last_step();
+}
diff --git a/servers/physics_3d/body_direct_state_3d_sw.h b/servers/physics_3d/body_direct_state_3d_sw.h
new file mode 100644
index 0000000000..5132376715
--- /dev/null
+++ b/servers/physics_3d/body_direct_state_3d_sw.h
@@ -0,0 +1,94 @@
+/*************************************************************************/
+/* body_direct_state_3d_sw.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 BODY_DIRECT_STATE_3D_SW_H
+#define BODY_DIRECT_STATE_3D_SW_H
+
+#include "servers/physics_server_3d.h"
+
+class Body3DSW;
+
+class PhysicsDirectBodyState3DSW : public PhysicsDirectBodyState3D {
+ GDCLASS(PhysicsDirectBodyState3DSW, PhysicsDirectBodyState3D);
+
+public:
+ Body3DSW *body = nullptr;
+
+ virtual Vector3 get_total_gravity() const override;
+ virtual real_t get_total_angular_damp() const override;
+ virtual real_t get_total_linear_damp() const override;
+
+ virtual Vector3 get_center_of_mass() const override;
+ virtual Basis get_principal_inertia_axes() const override;
+
+ virtual real_t get_inverse_mass() const override;
+ virtual Vector3 get_inverse_inertia() const override;
+ virtual Basis get_inverse_inertia_tensor() const override;
+
+ virtual void set_linear_velocity(const Vector3 &p_velocity) override;
+ virtual Vector3 get_linear_velocity() const override;
+
+ virtual void set_angular_velocity(const Vector3 &p_velocity) override;
+ virtual Vector3 get_angular_velocity() const override;
+
+ virtual void set_transform(const Transform3D &p_transform) override;
+ virtual Transform3D get_transform() const override;
+
+ virtual Vector3 get_velocity_at_local_position(const Vector3 &p_position) const override;
+
+ virtual void add_central_force(const Vector3 &p_force) override;
+ virtual void add_force(const Vector3 &p_force, const Vector3 &p_position = Vector3()) override;
+ virtual void add_torque(const Vector3 &p_torque) override;
+ virtual void apply_central_impulse(const Vector3 &p_impulse) override;
+ virtual void apply_impulse(const Vector3 &p_impulse, const Vector3 &p_position = Vector3()) override;
+ virtual void apply_torque_impulse(const Vector3 &p_impulse) override;
+
+ virtual void set_sleep_state(bool p_sleep) override;
+ virtual bool is_sleeping() const override;
+
+ virtual int get_contact_count() const override;
+
+ virtual Vector3 get_contact_local_position(int p_contact_idx) const override;
+ virtual Vector3 get_contact_local_normal(int p_contact_idx) const override;
+ virtual real_t get_contact_impulse(int p_contact_idx) const override;
+ virtual int get_contact_local_shape(int p_contact_idx) const override;
+
+ virtual RID get_contact_collider(int p_contact_idx) const override;
+ virtual Vector3 get_contact_collider_position(int p_contact_idx) const override;
+ virtual ObjectID get_contact_collider_id(int p_contact_idx) const override;
+ virtual int get_contact_collider_shape(int p_contact_idx) const override;
+ virtual Vector3 get_contact_collider_velocity_at_position(int p_contact_idx) const override;
+
+ virtual PhysicsDirectSpaceState3D *get_space_state() override;
+
+ virtual real_t get_step() const override;
+};
+
+#endif // BODY_DIRECT_STATE_3D_SW_H
diff --git a/servers/physics_3d/collision_solver_3d_sw.cpp b/servers/physics_3d/collision_solver_3d_sw.cpp
index e7b79e8450..4a4a8164d3 100644
--- a/servers/physics_3d/collision_solver_3d_sw.cpp
+++ b/servers/physics_3d/collision_solver_3d_sw.cpp
@@ -178,17 +178,17 @@ bool CollisionSolver3DSW::soft_body_query_callback(uint32_t p_node_index, void *
transform_B.origin = query_cinfo.node_transform.xform(node_position);
query_cinfo.contact_info.node_index = p_node_index;
- solve_static(query_cinfo.shape_A, query_cinfo.transform_A, query_cinfo.shape_B, transform_B, soft_body_contact_callback, &query_cinfo.contact_info);
+ bool collided = solve_static(query_cinfo.shape_A, query_cinfo.transform_A, query_cinfo.shape_B, transform_B, soft_body_contact_callback, &query_cinfo.contact_info);
#ifdef DEBUG_ENABLED
++query_cinfo.node_query_count;
#endif
- // Continue with the query.
- return false;
+ // Stop at first collision if contacts are not needed.
+ return (collided && !query_cinfo.contact_info.result_callback);
}
-void CollisionSolver3DSW::soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex) {
+bool CollisionSolver3DSW::soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex) {
_SoftBodyQueryInfo &query_cinfo = *(_SoftBodyQueryInfo *)(p_userdata);
query_cinfo.shape_A = p_convex;
@@ -210,9 +210,14 @@ void CollisionSolver3DSW::soft_body_concave_callback(void *p_userdata, Shape3DSW
query_cinfo.soft_body->query_aabb(shape_aabb, soft_body_query_callback, &query_cinfo);
+ bool collided = (query_cinfo.contact_info.contact_count > 0);
+
#ifdef DEBUG_ENABLED
++query_cinfo.convex_query_count;
#endif
+
+ // Stop at first collision if contacts are not needed.
+ return (collided && !query_cinfo.contact_info.result_callback);
}
bool CollisionSolver3DSW::solve_soft_body(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result) {
@@ -286,17 +291,20 @@ struct _ConcaveCollisionInfo {
Vector3 close_A, close_B;
};
-void CollisionSolver3DSW::concave_callback(void *p_userdata, Shape3DSW *p_convex) {
+bool CollisionSolver3DSW::concave_callback(void *p_userdata, Shape3DSW *p_convex) {
_ConcaveCollisionInfo &cinfo = *(_ConcaveCollisionInfo *)(p_userdata);
cinfo.aabb_tests++;
bool collided = collision_solver(cinfo.shape_A, *cinfo.transform_A, p_convex, *cinfo.transform_B, cinfo.result_callback, cinfo.userdata, cinfo.swap_result, nullptr, cinfo.margin_A, cinfo.margin_B);
if (!collided) {
- return;
+ return false;
}
cinfo.collided = true;
cinfo.collisions++;
+
+ // Stop at first collision if contacts are not needed.
+ return !cinfo.result_callback;
}
bool CollisionSolver3DSW::solve_concave(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A, real_t p_margin_B) {
@@ -413,19 +421,18 @@ bool CollisionSolver3DSW::solve_static(const Shape3DSW *p_shape_A, const Transfo
}
}
-void CollisionSolver3DSW::concave_distance_callback(void *p_userdata, Shape3DSW *p_convex) {
+bool CollisionSolver3DSW::concave_distance_callback(void *p_userdata, Shape3DSW *p_convex) {
_ConcaveCollisionInfo &cinfo = *(_ConcaveCollisionInfo *)(p_userdata);
cinfo.aabb_tests++;
- if (cinfo.collided) {
- return;
- }
Vector3 close_A, close_B;
cinfo.collided = !gjk_epa_calculate_distance(cinfo.shape_A, *cinfo.transform_A, p_convex, *cinfo.transform_B, close_A, close_B);
if (cinfo.collided) {
- return;
+ // No need to process any more result.
+ return true;
}
+
if (!cinfo.tested || close_A.distance_squared_to(close_B) < cinfo.close_A.distance_squared_to(cinfo.close_B)) {
cinfo.close_A = close_A;
cinfo.close_B = close_B;
@@ -433,6 +440,7 @@ void CollisionSolver3DSW::concave_distance_callback(void *p_userdata, Shape3DSW
}
cinfo.collisions++;
+ return false;
}
bool CollisionSolver3DSW::solve_distance_plane(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B) {
diff --git a/servers/physics_3d/collision_solver_3d_sw.h b/servers/physics_3d/collision_solver_3d_sw.h
index c1a69e708f..c13614ab3e 100644
--- a/servers/physics_3d/collision_solver_3d_sw.h
+++ b/servers/physics_3d/collision_solver_3d_sw.h
@@ -40,13 +40,13 @@ public:
private:
static bool soft_body_query_callback(uint32_t p_node_index, void *p_userdata);
static void soft_body_contact_callback(const Vector3 &p_point_A, int p_index_A, const Vector3 &p_point_B, int p_index_B, void *p_userdata);
- static void soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex);
- static void concave_callback(void *p_userdata, Shape3DSW *p_convex);
+ static bool soft_body_concave_callback(void *p_userdata, Shape3DSW *p_convex);
+ static bool concave_callback(void *p_userdata, Shape3DSW *p_convex);
static bool solve_static_plane(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
static bool solve_separation_ray(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin = 0);
static bool solve_soft_body(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result);
static bool solve_concave(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, CallbackResult p_result_callback, void *p_userdata, bool p_swap_result, real_t p_margin_A = 0, real_t p_margin_B = 0);
- static void concave_distance_callback(void *p_userdata, Shape3DSW *p_convex);
+ static bool concave_distance_callback(void *p_userdata, Shape3DSW *p_convex);
static bool solve_distance_plane(const Shape3DSW *p_shape_A, const Transform3D &p_transform_A, const Shape3DSW *p_shape_B, const Transform3D &p_transform_B, Vector3 &r_point_A, Vector3 &r_point_B);
public:
diff --git a/servers/physics_3d/physics_server_3d_sw.cpp b/servers/physics_3d/physics_server_3d_sw.cpp
index fbc6f7eee8..a214e80c6c 100644
--- a/servers/physics_3d/physics_server_3d_sw.cpp
+++ b/servers/physics_3d/physics_server_3d_sw.cpp
@@ -30,6 +30,7 @@
#include "physics_server_3d_sw.h"
+#include "body_direct_state_3d_sw.h"
#include "broad_phase_3d_bvh.h"
#include "core/debugger/engine_debugger.h"
#include "core/os/os.h"
@@ -842,6 +843,12 @@ int PhysicsServer3DSW::body_get_max_contacts_reported(RID p_body) const {
return body->get_max_contacts_reported();
}
+void PhysicsServer3DSW::body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) {
+ Body3DSW *body = body_owner.getornull(p_body);
+ ERR_FAIL_COND(!body);
+ body->set_state_sync_callback(p_instance, p_callback);
+}
+
void PhysicsServer3DSW::body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata) {
Body3DSW *body = body_owner.getornull(p_body);
ERR_FAIL_COND(!body);
@@ -869,11 +876,12 @@ PhysicsDirectBodyState3D *PhysicsServer3DSW::body_get_direct_state(RID p_body) {
ERR_FAIL_COND_V_MSG((using_threads && !doing_sync), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification.");
Body3DSW *body = body_owner.getornull(p_body);
- ERR_FAIL_COND_V(!body, nullptr);
+ ERR_FAIL_NULL_V(body, nullptr);
+
+ ERR_FAIL_NULL_V(body->get_space(), nullptr);
ERR_FAIL_COND_V_MSG(body->get_space()->is_locked(), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification.");
- direct_state->body = body;
- return direct_state;
+ return body->get_direct_state();
}
/* SOFT BODY */
@@ -1578,10 +1586,8 @@ void PhysicsServer3DSW::set_collision_iterations(int p_iterations) {
};
void PhysicsServer3DSW::init() {
- last_step = 0.001;
iterations = 8; // 8?
stepper = memnew(Step3DSW);
- direct_state = memnew(PhysicsDirectBodyState3DSW);
};
void PhysicsServer3DSW::step(real_t p_step) {
@@ -1593,9 +1599,6 @@ void PhysicsServer3DSW::step(real_t p_step) {
_update_shapes();
- last_step = p_step;
- PhysicsDirectBodyState3DSW::singleton->step = p_step;
-
island_count = 0;
active_objects = 0;
collision_pairs = 0;
@@ -1671,7 +1674,6 @@ void PhysicsServer3DSW::end_sync() {
void PhysicsServer3DSW::finish() {
memdelete(stepper);
- memdelete(direct_state);
};
int PhysicsServer3DSW::get_process_info(ProcessInfo p_info) {
diff --git a/servers/physics_3d/physics_server_3d_sw.h b/servers/physics_3d/physics_server_3d_sw.h
index a18593c90c..f283f83112 100644
--- a/servers/physics_3d/physics_server_3d_sw.h
+++ b/servers/physics_3d/physics_server_3d_sw.h
@@ -44,7 +44,6 @@ class PhysicsServer3DSW : public PhysicsServer3D {
friend class PhysicsDirectSpaceState3DSW;
bool active;
int iterations;
- real_t last_step;
int island_count;
int active_objects;
@@ -57,8 +56,6 @@ class PhysicsServer3DSW : public PhysicsServer3D {
Step3DSW *stepper;
Set<const Space3DSW *> active_spaces;
- PhysicsDirectBodyState3DSW *direct_state;
-
mutable RID_PtrOwner<Shape3DSW, true> shape_owner;
mutable RID_PtrOwner<Space3DSW, true> space_owner;
mutable RID_PtrOwner<Area3DSW, true> area_owner;
@@ -238,6 +235,7 @@ public:
virtual void body_set_max_contacts_reported(RID p_body, int p_contacts) override;
virtual int body_get_max_contacts_reported(RID p_body) const override;
+ virtual void body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) override;
virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) override;
virtual void body_set_ray_pickable(RID p_body, bool p_enable) override;
diff --git a/servers/physics_3d/physics_server_3d_wrap_mt.h b/servers/physics_3d/physics_server_3d_wrap_mt.h
index 674c52d4a3..8865bd4bd2 100644
--- a/servers/physics_3d/physics_server_3d_wrap_mt.h
+++ b/servers/physics_3d/physics_server_3d_wrap_mt.h
@@ -246,6 +246,7 @@ public:
FUNC2(body_set_omit_force_integration, RID, bool);
FUNC1RC(bool, body_is_omitting_force_integration, RID);
+ FUNC3(body_set_state_sync_callback, RID, void *, BodyStateCallback);
FUNC3(body_set_force_integration_callback, RID, const Callable &, const Variant &);
FUNC2(body_set_ray_pickable, RID, bool);
diff --git a/servers/physics_3d/shape_3d_sw.cpp b/servers/physics_3d/shape_3d_sw.cpp
index 0794e7888e..b81d3272c3 100644
--- a/servers/physics_3d/shape_3d_sw.cpp
+++ b/servers/physics_3d/shape_3d_sw.cpp
@@ -1382,11 +1382,11 @@ Vector3 ConcavePolygonShape3DSW::get_closest_point_to(const Vector3 &p_point) co
return Vector3();
}
-void ConcavePolygonShape3DSW::_cull(int p_idx, _CullParams *p_params) const {
+bool ConcavePolygonShape3DSW::_cull(int p_idx, _CullParams *p_params) const {
const BVH *bvh = &p_params->bvh[p_idx];
if (!p_params->aabb.intersects(bvh->aabb)) {
- return;
+ return false;
}
if (bvh->face_index >= 0) {
@@ -1396,20 +1396,27 @@ void ConcavePolygonShape3DSW::_cull(int p_idx, _CullParams *p_params) const {
face->vertex[0] = p_params->vertices[f->indices[0]];
face->vertex[1] = p_params->vertices[f->indices[1]];
face->vertex[2] = p_params->vertices[f->indices[2]];
- p_params->callback(p_params->userdata, face);
-
+ if (p_params->callback(p_params->userdata, face)) {
+ return true;
+ }
} else {
if (bvh->left >= 0) {
- _cull(bvh->left, p_params);
+ if (_cull(bvh->left, p_params)) {
+ return true;
+ }
}
if (bvh->right >= 0) {
- _cull(bvh->right, p_params);
+ if (_cull(bvh->right, p_params)) {
+ return true;
+ }
}
}
+
+ return false;
}
-void ConcavePolygonShape3DSW::cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const {
+void ConcavePolygonShape3DSW::cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const {
// make matrix local to concave
if (faces.size() == 0) {
return;
@@ -1875,7 +1882,7 @@ void HeightMapShape3DSW::_get_cell(const Vector3 &p_point, int &r_x, int &r_y, i
r_z = (clamped_point.z < 0.0) ? (clamped_point.z - 0.5) : (clamped_point.z + 0.5);
}
-void HeightMapShape3DSW::cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const {
+void HeightMapShape3DSW::cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const {
if (heights.is_empty()) {
return;
}
@@ -1911,13 +1918,17 @@ void HeightMapShape3DSW::cull(const AABB &p_local_aabb, Callback p_callback, voi
_get_point(x + 1, z, face.vertex[1]);
_get_point(x, z + 1, face.vertex[2]);
face.normal = Plane(face.vertex[0], face.vertex[2], face.vertex[1]).normal;
- p_callback(p_userdata, &face);
+ if (p_callback(p_userdata, &face)) {
+ return;
+ }
// Second triangle.
face.vertex[0] = face.vertex[1];
_get_point(x + 1, z + 1, face.vertex[1]);
face.normal = Plane(face.vertex[0], face.vertex[2], face.vertex[1]).normal;
- p_callback(p_userdata, &face);
+ if (p_callback(p_userdata, &face)) {
+ return;
+ }
}
}
}
diff --git a/servers/physics_3d/shape_3d_sw.h b/servers/physics_3d/shape_3d_sw.h
index dc35c3b172..b05f65f268 100644
--- a/servers/physics_3d/shape_3d_sw.h
+++ b/servers/physics_3d/shape_3d_sw.h
@@ -101,10 +101,12 @@ public:
class ConcaveShape3DSW : public Shape3DSW {
public:
virtual bool is_concave() const override { return true; }
- typedef void (*Callback)(void *p_userdata, Shape3DSW *p_convex);
virtual void get_supports(const Vector3 &p_normal, int p_max, Vector3 *r_supports, int &r_amount, FeatureType &r_type) const override { r_amount = 0; }
- virtual void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const = 0;
+ // Returns true to stop the query.
+ typedef bool (*QueryCallback)(void *p_userdata, Shape3DSW *p_convex);
+
+ virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const = 0;
ConcaveShape3DSW() {}
};
@@ -323,7 +325,7 @@ struct ConcavePolygonShape3DSW : public ConcaveShape3DSW {
struct _CullParams {
AABB aabb;
- Callback callback = nullptr;
+ QueryCallback callback = nullptr;
void *userdata = nullptr;
const Face *faces = nullptr;
const Vector3 *vertices = nullptr;
@@ -349,7 +351,7 @@ struct ConcavePolygonShape3DSW : public ConcaveShape3DSW {
bool backface_collision = false;
void _cull_segment(int p_idx, _SegmentCullParams *p_params) const;
- void _cull(int p_idx, _CullParams *p_params) const;
+ bool _cull(int p_idx, _CullParams *p_params) const;
void _fill_bvh(_VolumeSW_BVH *p_bvh_tree, BVH *p_bvh_array, int &p_idx);
@@ -367,7 +369,7 @@ public:
virtual bool intersect_point(const Vector3 &p_point) const override;
virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override;
- virtual void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const override;
+ virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const override;
virtual Vector3 get_moment_of_inertia(real_t p_mass) const override;
@@ -410,7 +412,7 @@ public:
virtual bool intersect_point(const Vector3 &p_point) const override;
virtual Vector3 get_closest_point_to(const Vector3 &p_point) const override;
- virtual void cull(const AABB &p_local_aabb, Callback p_callback, void *p_userdata) const override;
+ virtual void cull(const AABB &p_local_aabb, QueryCallback p_callback, void *p_userdata) const override;
virtual Vector3 get_moment_of_inertia(real_t p_mass) const override;
diff --git a/servers/physics_3d/soft_body_3d_sw.cpp b/servers/physics_3d/soft_body_3d_sw.cpp
index ead6497f1f..d7e13867bf 100644
--- a/servers/physics_3d/soft_body_3d_sw.cpp
+++ b/servers/physics_3d/soft_body_3d_sw.cpp
@@ -964,16 +964,9 @@ void SoftBody3DSW::apply_forces(bool p_has_wind_forces) {
}
void SoftBody3DSW::_compute_area_gravity(const Area3DSW *p_area) {
- if (p_area->is_gravity_point()) {
- if (p_area->get_gravity_distance_scale() > 0) {
- Vector3 v = p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin();
- gravity += v.normalized() * (p_area->get_gravity() / Math::pow(v.length() * p_area->get_gravity_distance_scale() + 1, 2));
- } else {
- gravity += (p_area->get_transform().xform(p_area->get_gravity_vector()) - get_transform().get_origin()).normalized() * p_area->get_gravity();
- }
- } else {
- gravity += p_area->get_gravity_vector() * p_area->get_gravity();
- }
+ Vector3 area_gravity;
+ p_area->compute_gravity(get_transform().get_origin(), area_gravity);
+ gravity += area_gravity;
}
Vector3 SoftBody3DSW::_compute_area_windforce(const Area3DSW *p_area, const Face *p_face) {
diff --git a/servers/physics_3d/space_3d_sw.h b/servers/physics_3d/space_3d_sw.h
index 1c3d1cf9f6..eff494a7bc 100644
--- a/servers/physics_3d/space_3d_sw.h
+++ b/servers/physics_3d/space_3d_sw.h
@@ -112,6 +112,8 @@ private:
bool locked;
+ real_t last_step = 0.001;
+
int island_count;
int active_objects;
int collision_pairs;
@@ -174,6 +176,9 @@ public:
void lock();
void unlock();
+ real_t get_last_step() const { return last_step; }
+ void set_last_step(real_t p_step) { last_step = p_step; }
+
void set_param(PhysicsServer3D::SpaceParameter p_param, real_t p_value);
real_t get_param(PhysicsServer3D::SpaceParameter p_param) const;
diff --git a/servers/physics_3d/step_3d_sw.cpp b/servers/physics_3d/step_3d_sw.cpp
index ba18bac611..d0604b0aa0 100644
--- a/servers/physics_3d/step_3d_sw.cpp
+++ b/servers/physics_3d/step_3d_sw.cpp
@@ -185,6 +185,8 @@ void Step3DSW::step(Space3DSW *p_space, real_t p_delta, int p_iterations) {
p_space->setup(); //update inertias, etc
+ p_space->set_last_step(p_delta);
+
iterations = p_iterations;
delta = p_delta;
diff --git a/servers/physics_server_2d.h b/servers/physics_server_2d.h
index 9afac0cbd1..021d7be7c0 100644
--- a/servers/physics_server_2d.h
+++ b/servers/physics_server_2d.h
@@ -457,6 +457,10 @@ public:
virtual void body_set_omit_force_integration(RID p_body, bool p_omit) = 0;
virtual bool body_is_omitting_force_integration(RID p_body) const = 0;
+ // Callback for C++ use only.
+ typedef void (*BodyStateCallback)(void *p_instance, PhysicsDirectBodyState2D *p_state);
+ virtual void body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) = 0;
+
virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) = 0;
virtual bool body_collide_shape(RID p_body, int p_body_shape, RID p_shape, const Transform2D &p_shape_xform, const Vector2 &p_motion, Vector2 *r_results, int p_result_max, int &r_result_count) = 0;
diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h
index 8f83e360f2..a365802220 100644
--- a/servers/physics_server_3d.h
+++ b/servers/physics_server_3d.h
@@ -471,6 +471,10 @@ public:
virtual void body_set_omit_force_integration(RID p_body, bool p_omit) = 0;
virtual bool body_is_omitting_force_integration(RID p_body) const = 0;
+ // Callback for C++ use only.
+ typedef void (*BodyStateCallback)(void *p_instance, PhysicsDirectBodyState3D *p_state);
+ virtual void body_set_state_sync_callback(RID p_body, void *p_instance, BodyStateCallback p_callback) = 0;
+
virtual void body_set_force_integration_callback(RID p_body, const Callable &p_callable, const Variant &p_udata = Variant()) = 0;
virtual void body_set_ray_pickable(RID p_body, bool p_enable) = 0;
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index 611f7c6494..1b730567d9 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -484,7 +484,7 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p
if (material_uniform_set != prev_material_uniform_set) {
// Update uniform set.
- if (RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set.
+ if (material_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set.
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET);
}
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 1b4052b622..276a44bc27 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -1982,7 +1982,7 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
if (material_uniform_set != prev_material_uniform_set) {
// Update uniform set.
- if (RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set.
+ if (material_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set.
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET);
}
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 6267f684e3..647c348d9f 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -1102,7 +1102,7 @@ void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_co
if (material_data->shader_data->version.is_valid() && material_data->shader_data->valid) {
pipeline_variants = &material_data->shader_data->pipeline_variants;
// Update uniform set.
- if (RD::get_singleton()->uniform_set_is_valid(material_data->uniform_set)) { // Material may not have a uniform set.
+ if (material_data->uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_data->uniform_set)) { // Material may not have a uniform set.
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_data->uniform_set, MATERIAL_UNIFORM_SET);
}
} else {
diff --git a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp
index da84f615b2..c388da755c 100644
--- a/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_sky_rd.cpp
@@ -288,7 +288,7 @@ void RendererSceneSkyRD::_render_sky(RD::DrawListID p_list, float p_time, RID p_
// Update uniform sets.
{
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, sky_scene_state.uniform_set, 0);
- if (RD::get_singleton()->uniform_set_is_valid(p_uniform_set)) { // Material may not have a uniform set.
+ if (p_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(p_uniform_set)) { // Material may not have a uniform set.
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_uniform_set, 1);
}
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_texture_set, 2);
diff --git a/servers/rendering/renderer_rd/renderer_storage_rd.cpp b/servers/rendering/renderer_rd/renderer_storage_rd.cpp
index 14a5a01054..ec0d25376f 100644
--- a/servers/rendering/renderer_rd/renderer_storage_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_storage_rd.cpp
@@ -4848,7 +4848,7 @@ void RendererStorageRD::_particles_process(Particles *p_particles, double p_delt
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, p_particles->particles_material_uniform_set, 1);
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, p_particles->collision_textures_uniform_set, 2);
- if (m->uniform_set.is_valid()) {
+ if (m->uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(m->uniform_set)) {
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, m->uniform_set, 3);
}
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index c05ff78718..2f343e8f80 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -291,7 +291,7 @@ void TextServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("font_set_glyph_offset", "font_rid", "size", "glyph", "offset"), &TextServer::font_set_glyph_offset);
ClassDB::bind_method(D_METHOD("font_get_glyph_size", "font_rid", "size", "glyph"), &TextServer::font_get_glyph_size);
- ClassDB::bind_method(D_METHOD("font_set_glyph_size", "font_rid", "size", "glyph", "size"), &TextServer::font_set_glyph_size);
+ ClassDB::bind_method(D_METHOD("font_set_glyph_size", "font_rid", "size", "glyph", "gl_size"), &TextServer::font_set_glyph_size);
ClassDB::bind_method(D_METHOD("font_get_glyph_uv_rect", "font_rid", "size", "glyph"), &TextServer::font_get_glyph_uv_rect);
ClassDB::bind_method(D_METHOD("font_set_glyph_uv_rect", "font_rid", "size", "glyph", "uv_rect"), &TextServer::font_set_glyph_uv_rect);
diff --git a/tests/test_config_file.h b/tests/test_config_file.h
index 33fd30ffa1..e3f40e16fd 100644
--- a/tests/test_config_file.h
+++ b/tests/test_config_file.h
@@ -122,6 +122,8 @@ TEST_CASE("[ConfigFile] Saving file") {
config_file.set_value("player", "position", Vector2(3, 4));
config_file.set_value("graphics", "antialiasing", true);
config_file.set_value("graphics", "antiAliasing", false);
+ config_file.set_value("quoted", String::utf8("静音"), 42);
+ config_file.set_value("quoted", "a=b", 7);
#ifdef WINDOWS_ENABLED
const String config_path = OS::get_singleton()->get_environment("TEMP").plus_file("config.ini");
@@ -132,7 +134,7 @@ TEST_CASE("[ConfigFile] Saving file") {
config_file.save(config_path);
// Expected contents of the saved ConfigFile.
- const String contents = R"([player]
+ const String contents = String::utf8(R"([player]
name="Unnamed Player"
tagline="Waiting
@@ -145,7 +147,12 @@ position=Vector2(3, 4)
antialiasing=true
antiAliasing=false
-)";
+
+[quoted]
+
+"静音"=42
+"a=b"=7
+)");
FileAccessRef file = FileAccess::open(config_path, FileAccess::READ);
CHECK_MESSAGE(file->get_as_utf8_string() == contents,
diff --git a/tests/test_string.h b/tests/test_string.h
index 79fdb7bb56..82b23d8a00 100644
--- a/tests/test_string.h
+++ b/tests/test_string.h
@@ -1155,7 +1155,7 @@ TEST_CASE("[String] Path functions") {
CHECK(String(path[i]).get_extension() == ext[i]);
CHECK(String(path[i]).get_file() == file[i]);
CHECK(String(path[i]).is_absolute_path() == abs[i]);
- CHECK(String(path[i]).is_rel_path() != abs[i]);
+ CHECK(String(path[i]).is_relative_path() != abs[i]);
CHECK(String(path[i]).simplify_path().get_base_dir().plus_file(file[i]) == String(path[i]).simplify_path());
}