summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/input/input.cpp59
-rw-r--r--core/input/input.h7
-rw-r--r--core/input/input_event.cpp57
-rw-r--r--core/input/input_event.h13
-rw-r--r--core/input/input_map.cpp22
-rw-r--r--core/input/input_map.h5
-rw-r--r--core/io/http_client.cpp59
-rw-r--r--core/io/resource_loader.cpp2
-rw-r--r--core/io/resource_saver.cpp2
-rw-r--r--core/math/expression.cpp10
-rw-r--r--core/math/transform_2d.cpp2
-rw-r--r--core/object/object.cpp3
-rw-r--r--core/variant/binder_common.h33
-rw-r--r--core/variant/variant.cpp7
-rw-r--r--core/variant/variant.h115
-rw-r--r--core/variant/variant_call.cpp1155
-rw-r--r--core/variant/variant_utility.cpp (renamed from core/variant/variant_builtin_funcs.cpp)358
-rw-r--r--doc/classes/Array.xml3
-rw-r--r--doc/classes/Color.xml34
-rw-r--r--doc/classes/Control.xml20
-rw-r--r--doc/classes/Dictionary.xml78
-rw-r--r--doc/classes/EditorNode3DGizmoPlugin.xml3
-rw-r--r--doc/classes/Input.xml40
-rw-r--r--doc/classes/InstancePlaceholder.xml3
-rw-r--r--doc/classes/LineEdit.xml7
-rw-r--r--doc/classes/MarginContainer.xml13
-rw-r--r--doc/classes/MeshDataTool.xml34
-rw-r--r--doc/classes/NavigationPolygon.xml29
-rw-r--r--doc/classes/Node.xml38
-rw-r--r--doc/classes/NodePath.xml54
-rw-r--r--doc/classes/OS.xml47
-rw-r--r--doc/classes/PCKPacker.xml12
-rw-r--r--doc/classes/PackedByteArray.xml10
-rw-r--r--doc/classes/PackedScene.xml49
-rw-r--r--doc/classes/PacketPeerUDP.xml39
-rw-r--r--doc/classes/Performance.xml66
-rw-r--r--doc/classes/PhysicsShapeQueryParameters2D.xml35
-rw-r--r--doc/classes/PhysicsShapeQueryParameters3D.xml35
-rw-r--r--doc/classes/PrimitiveMesh.xml15
-rw-r--r--doc/classes/ProjectSettings.xml87
-rw-r--r--doc/classes/TextEdit.xml2
-rw-r--r--doc/classes/Theme.xml126
-rw-r--r--doc/classes/Tree.xml11
-rw-r--r--doc/classes/World2D.xml2
-rw-r--r--doc/classes/World3D.xml2
-rw-r--r--drivers/dummy/rasterizer_dummy.h24
-rw-r--r--drivers/vulkan/vulkan_context.cpp3
-rw-r--r--editor/code_editor.cpp6
-rw-r--r--editor/debugger/script_editor_debugger.cpp19
-rw-r--r--editor/dependency_editor.cpp4
-rw-r--r--editor/doc_data.cpp20
-rw-r--r--editor/editor_export.cpp3
-rw-r--r--editor/editor_node.cpp71
-rw-r--r--editor/editor_themes.cpp3
-rw-r--r--editor/filesystem_dock.cpp2
-rw-r--r--editor/icons/CanvasGroup.svg1
-rw-r--r--editor/icons/CodeEdit.svg1
-rw-r--r--editor/icons/EditorCurveHandle.svg2
-rw-r--r--editor/icons/EditorPathSharpHandle.svg2
-rw-r--r--editor/icons/EditorPathSmoothHandle.svg2
-rw-r--r--editor/import/resource_importer_texture.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp6
-rw-r--r--editor/plugins/node_3d_editor_plugin.h2
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp6
-rw-r--r--editor/project_export.cpp53
-rw-r--r--editor/project_export.h2
-rw-r--r--editor/project_manager.cpp56
-rw-r--r--editor/project_manager.h1
-rw-r--r--editor/scene_tree_dock.cpp4
-rw-r--r--main/main.cpp1
-rw-r--r--misc/dist/windows/.gitignore2
-rw-r--r--misc/dist/windows/README.md17
-rw-r--r--misc/dist/windows/godot.iss63
-rw-r--r--misc/dist/windows/modpath.pas219
-rw-r--r--modules/bullet/bullet_physics_server.cpp3
-rw-r--r--modules/bullet/bullet_physics_server.h1
-rw-r--r--modules/gdnative/gdnative/variant.cpp4
-rw-r--r--modules/gdnative/net/webrtc_gdnative.cpp2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp2
-rw-r--r--modules/gdscript/gdscript_function.cpp5
-rw-r--r--modules/gdscript/gdscript_functions.cpp16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs61
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs8
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs110
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs3
-rw-r--r--modules/visual_script/visual_script_expression.cpp2
-rw-r--r--modules/visual_script/visual_script_func_nodes.cpp34
-rw-r--r--modules/webrtc/SCsub4
-rw-r--r--modules/webrtc/library_godot_webrtc.js407
-rw-r--r--modules/webrtc/webrtc_data_channel_js.cpp245
-rw-r--r--modules/webrtc/webrtc_data_channel_js.h10
-rw-r--r--modules/webrtc/webrtc_peer_connection_js.cpp244
-rw-r--r--modules/webrtc/webrtc_peer_connection_js.h23
-rw-r--r--modules/websocket/SCsub8
-rw-r--r--modules/websocket/emws_client.cpp144
-rw-r--r--modules/websocket/emws_client.h10
-rw-r--r--modules/websocket/emws_peer.cpp38
-rw-r--r--modules/websocket/emws_peer.h14
-rw-r--r--modules/websocket/library_godot_websocket.js187
-rw-r--r--platform/android/export/export.cpp132
-rw-r--r--platform/android/export/gradle_export_util.h63
-rw-r--r--platform/android/java/build.gradle2
-rw-r--r--platform/android/java/lib/build.gradle34
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java6
-rw-r--r--platform/iphone/SCsub2
-rw-r--r--platform/iphone/display_server_iphone.mm11
-rw-r--r--platform/iphone/godot_app_delegate.h (renamed from platform/javascript/native/id_handler.js)36
-rw-r--r--platform/iphone/godot_app_delegate.m497
-rw-r--r--platform/iphone/godot_view.h4
-rw-r--r--platform/iphone/godot_view.mm35
-rw-r--r--platform/iphone/keyboard_input_view.h37
-rw-r--r--platform/iphone/keyboard_input_view.mm195
-rw-r--r--platform/iphone/main.m5
-rw-r--r--platform/iphone/view_controller.h2
-rw-r--r--platform/iphone/view_controller.mm9
-rw-r--r--platform/javascript/SCsub32
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.cpp25
-rw-r--r--platform/javascript/audio_driver_javascript.cpp256
-rw-r--r--platform/javascript/audio_driver_javascript.h97
-rw-r--r--platform/javascript/detect.py10
-rw-r--r--platform/javascript/display_server_javascript.cpp318
-rw-r--r--platform/javascript/display_server_javascript.h35
-rw-r--r--platform/javascript/emscripten_helpers.py6
-rw-r--r--platform/javascript/engine/engine.js36
-rw-r--r--platform/javascript/engine/utils.js2
-rw-r--r--platform/javascript/export/export.cpp7
-rw-r--r--platform/javascript/godot_audio.h21
-rw-r--r--platform/javascript/godot_js.h87
-rw-r--r--platform/javascript/javascript_eval.cpp105
-rw-r--r--platform/javascript/javascript_main.cpp83
-rw-r--r--platform/javascript/native/audio.worklet.js186
-rw-r--r--platform/javascript/native/library_godot_audio.js374
-rw-r--r--platform/javascript/native/library_godot_display.js478
-rw-r--r--platform/javascript/native/library_godot_editor_tools.js57
-rw-r--r--platform/javascript/native/library_godot_eval.js87
-rw-r--r--platform/javascript/native/library_godot_os.js313
-rw-r--r--platform/javascript/native/utils.js292
-rw-r--r--platform/javascript/os_javascript.cpp52
-rw-r--r--platform/javascript/os_javascript.h8
-rw-r--r--platform/linuxbsd/display_server_x11.cpp88
-rw-r--r--platform/linuxbsd/joypad_linux.cpp8
-rw-r--r--platform/windows/os_windows.cpp4
-rw-r--r--scene/3d/camera_3d.cpp4
-rw-r--r--scene/gui/control.cpp190
-rw-r--r--scene/gui/control.h52
-rw-r--r--scene/gui/line_edit.cpp65
-rw-r--r--scene/gui/line_edit.h7
-rw-r--r--scene/gui/popup.cpp14
-rw-r--r--scene/gui/popup.h5
-rw-r--r--scene/gui/popup_menu.cpp50
-rw-r--r--scene/gui/popup_menu.h8
-rw-r--r--scene/gui/text_edit.cpp14
-rw-r--r--scene/main/node.cpp1
-rw-r--r--scene/resources/audio_stream_sample.cpp3
-rw-r--r--scene/resources/capsule_shape_3d.cpp4
-rw-r--r--scene/resources/cylinder_shape_3d.cpp4
-rw-r--r--scene/resources/ray_shape_3d.cpp2
-rw-r--r--scene/resources/sphere_shape_3d.cpp2
-rw-r--r--scene/resources/texture.cpp6
-rw-r--r--scene/resources/theme.cpp286
-rw-r--r--scene/resources/theme.h80
-rw-r--r--servers/physics_2d/physics_server_2d_sw.cpp2
-rw-r--r--servers/physics_3d/physics_server_3d_sw.cpp9
-rw-r--r--servers/physics_3d/physics_server_3d_sw.h2
-rw-r--r--servers/physics_server_3d.h1
-rw-r--r--servers/rendering/rendering_server_canvas.cpp2
-rw-r--r--tests/test_config_file.h157
-rw-r--r--tests/test_curve.h222
-rw-r--r--tests/test_main.cpp4
-rw-r--r--tests/test_node_path.h173
-rw-r--r--tests/test_pck_packer.h115
171 files changed, 7167 insertions, 3439 deletions
diff --git a/core/input/input.cpp b/core/input/input.cpp
index ee66bf94cb..aa64ac09a7 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -149,6 +149,9 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_action_just_pressed", "action"), &Input::is_action_just_pressed);
ClassDB::bind_method(D_METHOD("is_action_just_released", "action"), &Input::is_action_just_released);
ClassDB::bind_method(D_METHOD("get_action_strength", "action"), &Input::get_action_strength);
+ ClassDB::bind_method(D_METHOD("get_action_raw_strength", "action"), &Input::get_action_strength);
+ ClassDB::bind_method(D_METHOD("get_axis", "negative_action", "positive_action"), &Input::get_axis);
+ ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone"), &Input::get_vector, DEFVAL(-1.0f));
ClassDB::bind_method(D_METHOD("add_joy_mapping", "mapping", "update_existing"), &Input::add_joy_mapping, DEFVAL(false));
ClassDB::bind_method(D_METHOD("remove_joy_mapping", "guid"), &Input::remove_joy_mapping);
ClassDB::bind_method(D_METHOD("joy_connection_changed", "device", "connected", "name", "guid"), &Input::joy_connection_changed);
@@ -215,7 +218,9 @@ void Input::get_argument_options(const StringName &p_function, int p_idx, List<S
const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\"";
String pf = p_function;
- if (p_idx == 0 && (pf == "is_action_pressed" || pf == "action_press" || pf == "action_release" || pf == "is_action_just_pressed" || pf == "is_action_just_released" || pf == "get_action_strength")) {
+ if (p_idx == 0 && (pf == "is_action_pressed" || pf == "action_press" || pf == "action_release" ||
+ pf == "is_action_just_pressed" || pf == "is_action_just_released" ||
+ pf == "get_action_strength" || pf == "get_axis" || pf == "get_vector")) {
List<PropertyInfo> pinfo;
ProjectSettings::get_singleton()->get_property_list(&pinfo);
@@ -326,6 +331,46 @@ float Input::get_action_strength(const StringName &p_action) const {
return E->get().strength;
}
+float Input::get_action_raw_strength(const StringName &p_action) const {
+ const Map<StringName, Action>::Element *E = action_state.find(p_action);
+ if (!E) {
+ return 0.0f;
+ }
+
+ return E->get().raw_strength;
+}
+
+float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const {
+ return get_action_strength(p_positive_action) - get_action_strength(p_negative_action);
+}
+
+Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone) const {
+ Vector2 vector = Vector2(
+ get_action_raw_strength(p_positive_x) - get_action_raw_strength(p_negative_x),
+ get_action_raw_strength(p_positive_y) - get_action_raw_strength(p_negative_y));
+
+ if (p_deadzone < 0.0f) {
+ // If the deadzone isn't specified, get it from the average of the actions.
+ p_deadzone = (InputMap::get_singleton()->action_get_deadzone(p_positive_x) +
+ InputMap::get_singleton()->action_get_deadzone(p_negative_x) +
+ InputMap::get_singleton()->action_get_deadzone(p_positive_y) +
+ InputMap::get_singleton()->action_get_deadzone(p_negative_y)) /
+ 4;
+ }
+
+ // Circular length limiting and deadzone.
+ float length = vector.length();
+ if (length <= p_deadzone) {
+ return Vector2();
+ } else if (length > 1.0f) {
+ return vector / length;
+ } else {
+ // Inverse lerp length to map (p_deadzone, 1) to (0, 1).
+ return vector * (Math::inverse_lerp(p_deadzone, 1.0f, length) / length);
+ }
+ return vector;
+}
+
float Input::get_joy_axis(int p_device, int p_axis) const {
_THREAD_SAFE_METHOD_
int c = _combine_device(p_axis, p_device);
@@ -603,10 +648,12 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
action.physics_frame = Engine::get_singleton()->get_physics_frames();
action.idle_frame = Engine::get_singleton()->get_idle_frames();
action.pressed = p_event->is_action_pressed(E->key());
- action.strength = 0.f;
+ action.strength = 0.0f;
+ action.raw_strength = 0.0f;
action_state[E->key()] = action;
}
action_state[E->key()].strength = p_event->get_action_strength(E->key());
+ action_state[E->key()].raw_strength = p_event->get_action_raw_strength(E->key());
}
}
@@ -890,14 +937,6 @@ void Input::joy_axis(int p_device, int p_axis, const JoyAxis &p_value) {
return;
}
- if (p_value.value > joy.last_axis[p_axis]) {
- if (p_value.value < joy.last_axis[p_axis] + joy.filter) {
- return;
- }
- } else if (p_value.value > joy.last_axis[p_axis] - joy.filter) {
- return;
- }
-
//when changing direction quickly, insert fake event to release pending inputmap actions
float last = joy.last_axis[p_axis];
if (p_value.min == 0 && (last < 0.25 || last > 0.75) && (last - 0.5) * (p_value.value - 0.5) < 0) {
diff --git a/core/input/input.h b/core/input/input.h
index 98bbff6441..46e9d24bf2 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -117,6 +117,7 @@ private:
uint64_t idle_frame;
bool pressed;
float strength;
+ float raw_strength;
};
Map<StringName, Action> action_state;
@@ -146,7 +147,6 @@ private:
bool connected = false;
bool last_buttons[JOY_BUTTON_MAX] = { false };
float last_axis[JOY_AXIS_MAX] = { 0.0f };
- float filter = 0.01f;
int last_hat = HAT_MASK_CENTER;
int mapping = -1;
int hat_current = 0;
@@ -223,7 +223,6 @@ private:
JoyAxisList _get_output_axis(String output);
void _button_event(int p_device, int p_index, bool p_pressed);
void _axis_event(int p_device, int p_axis, float p_value);
- float _handle_deadzone(int p_device, int p_axis, float p_value);
void _parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_emulated);
@@ -266,6 +265,10 @@ public:
bool is_action_just_pressed(const StringName &p_action) const;
bool is_action_just_released(const StringName &p_action) const;
float get_action_strength(const StringName &p_action) const;
+ float get_action_raw_strength(const StringName &p_action) const;
+
+ float get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const;
+ Vector2 get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone = -1.0f) const;
float get_joy_axis(int p_device, int p_axis) const;
String get_joy_name(int p_idx);
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index 6ba082f86f..31ce1bb892 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -61,12 +61,17 @@ bool InputEvent::is_action_released(const StringName &p_action) const {
}
float InputEvent::get_action_strength(const StringName &p_action) const {
- bool pressed;
float strength;
- bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, &pressed, &strength);
+ bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, nullptr, &strength);
return valid ? strength : 0.0f;
}
+float InputEvent::get_action_raw_strength(const StringName &p_action) const {
+ float raw_strength;
+ bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, nullptr, nullptr, &raw_strength);
+ return valid ? raw_strength : 0.0f;
+}
+
bool InputEvent::is_pressed() const {
return false;
}
@@ -83,7 +88,7 @@ String InputEvent::as_text() const {
return String();
}
-bool InputEvent::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
+bool InputEvent::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
return false;
}
@@ -307,7 +312,7 @@ String InputEventKey::as_text() const {
return kc;
}
-bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
+bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventKey> key = p_event;
if (key.is_null()) {
return false;
@@ -329,8 +334,12 @@ bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed
if (p_pressed != nullptr) {
*p_pressed = key->is_pressed();
}
+ float strength = (p_pressed != nullptr && *p_pressed) ? 1.0f : 0.0f;
if (p_strength != nullptr) {
- *p_strength = (p_pressed != nullptr && *p_pressed) ? 1.0f : 0.0f;
+ *p_strength = strength;
+ }
+ if (p_raw_strength != nullptr) {
+ *p_raw_strength = strength;
}
}
return match;
@@ -470,7 +479,7 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co
return mb;
}
-bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
+bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_null()) {
return false;
@@ -481,8 +490,12 @@ bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p
if (p_pressed != nullptr) {
*p_pressed = mb->is_pressed();
}
+ float strength = (p_pressed != nullptr && *p_pressed) ? 1.0f : 0.0f;
if (p_strength != nullptr) {
- *p_strength = (p_pressed != nullptr && *p_pressed) ? 1.0f : 0.0f;
+ *p_strength = strength;
+ }
+ if (p_raw_strength != nullptr) {
+ *p_raw_strength = strength;
}
}
@@ -713,7 +726,7 @@ bool InputEventJoypadMotion::is_pressed() const {
return Math::abs(axis_value) >= 0.5f;
}
-bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
+bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventJoypadMotion> jm = p_event;
if (jm.is_null()) {
return false;
@@ -721,8 +734,9 @@ bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool *
bool match = (axis == jm->axis); // Matches even if not in the same direction, but returns a "not pressed" event.
if (match) {
+ float jm_abs_axis_value = Math::abs(jm->get_axis_value());
bool same_direction = (((axis_value < 0) == (jm->axis_value < 0)) || jm->axis_value == 0);
- bool pressed = same_direction ? Math::abs(jm->get_axis_value()) >= p_deadzone : false;
+ bool pressed = same_direction && jm_abs_axis_value >= p_deadzone;
if (p_pressed != nullptr) {
*p_pressed = pressed;
}
@@ -731,12 +745,19 @@ bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool *
if (p_deadzone == 1.0f) {
*p_strength = 1.0f;
} else {
- *p_strength = CLAMP(Math::inverse_lerp(p_deadzone, 1.0f, Math::abs(jm->get_axis_value())), 0.0f, 1.0f);
+ *p_strength = CLAMP(Math::inverse_lerp(p_deadzone, 1.0f, jm_abs_axis_value), 0.0f, 1.0f);
}
} else {
*p_strength = 0.0f;
}
}
+ if (p_raw_strength != nullptr) {
+ if (same_direction) { // NOT pressed, because we want to ignore the deadzone.
+ *p_raw_strength = jm_abs_axis_value;
+ } else {
+ *p_raw_strength = 0.0f;
+ }
+ }
}
return match;
}
@@ -782,7 +803,7 @@ float InputEventJoypadButton::get_pressure() const {
return pressure;
}
-bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
+bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventJoypadButton> jb = p_event;
if (jb.is_null()) {
return false;
@@ -793,8 +814,12 @@ bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event, bool *
if (p_pressed != nullptr) {
*p_pressed = jb->is_pressed();
}
+ float strength = (p_pressed != nullptr && *p_pressed) ? 1.0f : 0.0f;
if (p_strength != nullptr) {
- *p_strength = (p_pressed != nullptr && *p_pressed) ? 1.0f : 0.0f;
+ *p_strength = strength;
+ }
+ if (p_raw_strength != nullptr) {
+ *p_raw_strength = strength;
}
}
@@ -997,7 +1022,7 @@ bool InputEventAction::is_action(const StringName &p_action) const {
return action == p_action;
}
-bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const {
+bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const {
Ref<InputEventAction> act = p_event;
if (act.is_null()) {
return false;
@@ -1008,8 +1033,12 @@ bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool *p_pres
if (p_pressed != nullptr) {
*p_pressed = act->pressed;
}
+ float strength = (p_pressed != nullptr && *p_pressed) ? 1.0f : 0.0f;
if (p_strength != nullptr) {
- *p_strength = (p_pressed != nullptr && *p_pressed) ? 1.0f : 0.0f;
+ *p_strength = strength;
+ }
+ if (p_raw_strength != nullptr) {
+ *p_raw_strength = strength;
}
}
return match;
diff --git a/core/input/input_event.h b/core/input/input_event.h
index 8b58cf08c2..50d56291d1 100644
--- a/core/input/input_event.h
+++ b/core/input/input_event.h
@@ -173,6 +173,7 @@ public:
bool is_action_pressed(const StringName &p_action, bool p_allow_echo = false) const;
bool is_action_released(const StringName &p_action) const;
float get_action_strength(const StringName &p_action) const;
+ float get_action_raw_strength(const StringName &p_action) const;
// To be removed someday, since they do not make sense for all events
virtual bool is_pressed() const;
@@ -182,7 +183,7 @@ public:
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const;
- virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const;
+ virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const;
virtual bool shortcut_match(const Ref<InputEvent> &p_event) const;
virtual bool is_action_type() const;
@@ -283,7 +284,7 @@ public:
uint32_t get_keycode_with_modifiers() const;
uint32_t get_physical_keycode_with_modifiers() const;
- virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const override;
+ virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override;
virtual bool shortcut_match(const Ref<InputEvent> &p_event) const override;
virtual bool is_action_type() const override { return true; }
@@ -342,7 +343,7 @@ public:
bool is_doubleclick() const;
virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override;
- virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const override;
+ virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override;
virtual bool is_action_type() const override { return true; }
virtual String as_text() const override;
@@ -399,7 +400,7 @@ public:
virtual bool is_pressed() const override;
- virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const override;
+ virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override;
virtual bool is_action_type() const override { return true; }
virtual String as_text() const override;
@@ -426,7 +427,7 @@ public:
void set_pressure(float p_pressure);
float get_pressure() const;
- virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const override;
+ virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override;
virtual bool shortcut_match(const Ref<InputEvent> &p_event) const override;
virtual bool is_action_type() const override { return true; }
@@ -511,7 +512,7 @@ public:
virtual bool is_action(const StringName &p_action) const;
- virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const override;
+ virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const override;
virtual bool shortcut_match(const Ref<InputEvent> &p_event) const override;
virtual bool is_action_type() const override { return true; }
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index ba1de3c58d..9fd5476f51 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -31,6 +31,7 @@
#include "input_map.h"
#include "core/config/project_settings.h"
+#include "core/input/input.h"
#include "core/os/keyboard.h"
InputMap *InputMap::singleton = nullptr;
@@ -94,7 +95,7 @@ List<StringName> InputMap::get_actions() const {
return actions;
}
-List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength) const {
+List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength) const {
ERR_FAIL_COND_V(!p_event.is_valid(), nullptr);
for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) {
@@ -105,7 +106,7 @@ List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Re
int device = e->get_device();
if (device == ALL_DEVICES || device == p_event->get_device()) {
- if (e->action_match(p_event, p_pressed, p_strength, p_action.deadzone)) {
+ if (e->action_match(p_event, p_pressed, p_strength, p_raw_strength, p_action.deadzone)) {
return E;
}
}
@@ -118,6 +119,12 @@ bool InputMap::has_action(const StringName &p_action) const {
return input_map.has(p_action);
}
+float InputMap::action_get_deadzone(const StringName &p_action) {
+ ERR_FAIL_COND_V_MSG(!input_map.has(p_action), 0.0f, "Request for nonexistent InputMap action '" + String(p_action) + "'.");
+
+ return input_map[p_action].deadzone;
+}
+
void InputMap::action_set_deadzone(const StringName &p_action, float p_deadzone) {
ERR_FAIL_COND_MSG(!input_map.has(p_action), "Request for nonexistent InputMap action '" + String(p_action) + "'.");
@@ -145,6 +152,9 @@ void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEve
List<Ref<InputEvent>>::Element *E = _find_event(input_map[p_action], p_event);
if (E) {
input_map[p_action].inputs.erase(E);
+ if (Input::get_singleton()->is_action_pressed(p_action)) {
+ Input::get_singleton()->action_release(p_action);
+ }
}
}
@@ -179,7 +189,7 @@ bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName
return event_get_action_status(p_event, p_action);
}
-bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed, float *p_strength) const {
+bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed, float *p_strength, float *p_raw_strength) const {
Map<StringName, Action>::Element *E = input_map.find(p_action);
ERR_FAIL_COND_V_MSG(!E, false, "Request for nonexistent InputMap action '" + String(p_action) + "'.");
@@ -196,7 +206,8 @@ bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const Str
bool pressed;
float strength;
- List<Ref<InputEvent>>::Element *event = _find_event(E->get(), p_event, &pressed, &strength);
+ float raw_strength;
+ List<Ref<InputEvent>>::Element *event = _find_event(E->get(), p_event, &pressed, &strength, &raw_strength);
if (event != nullptr) {
if (p_pressed != nullptr) {
*p_pressed = pressed;
@@ -204,6 +215,9 @@ bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const Str
if (p_strength != nullptr) {
*p_strength = strength;
}
+ if (p_raw_strength != nullptr) {
+ *p_raw_strength = raw_strength;
+ }
return true;
} else {
return false;
diff --git a/core/input/input_map.h b/core/input/input_map.h
index 35c65d0881..019a1056fb 100644
--- a/core/input/input_map.h
+++ b/core/input/input_map.h
@@ -54,7 +54,7 @@ private:
mutable Map<StringName, Action> input_map;
- List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed = nullptr, float *p_strength = nullptr) const;
+ List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed = nullptr, float *p_strength = nullptr, float *p_raw_strength = nullptr) const;
Array _action_get_events(const StringName &p_action);
Array _get_actions();
@@ -70,6 +70,7 @@ public:
void add_action(const StringName &p_action, float p_deadzone = 0.5);
void erase_action(const StringName &p_action);
+ float action_get_deadzone(const StringName &p_action);
void action_set_deadzone(const StringName &p_action, float p_deadzone);
void action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event);
bool action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event);
@@ -78,7 +79,7 @@ public:
const List<Ref<InputEvent>> *action_get_events(const StringName &p_action);
bool event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action) const;
- bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed = nullptr, float *p_strength = nullptr) const;
+ bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool *p_pressed = nullptr, float *p_strength = nullptr, float *p_raw_strength = nullptr) const;
const Map<StringName, Action> &get_action_map() const;
void load_from_globals();
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index a25413b21b..768fcdbb14 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -109,24 +109,41 @@ Ref<StreamPeer> HTTPClient::get_connection() const {
return connection;
}
+static bool _check_request_url(HTTPClient::Method p_method, const String &p_url) {
+ switch (p_method) {
+ case HTTPClient::METHOD_CONNECT: {
+ // Authority in host:port format, as in RFC7231
+ int pos = p_url.find_char(':');
+ return 0 < pos && pos < p_url.length() - 1;
+ }
+ case HTTPClient::METHOD_OPTIONS: {
+ if (p_url == "*") {
+ return true;
+ }
+ [[fallthrough]];
+ }
+ default:
+ // Absolute path or absolute URL
+ return p_url.begins_with("/") || p_url.begins_with("http://") || p_url.begins_with("https://");
+ }
+}
+
Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n";
- if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
- // Don't append the standard ports
- request += "Host: " + conn_host + "\r\n";
- } else {
- request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
- }
+ bool add_host = true;
bool add_clen = p_body.size() > 0;
bool add_uagent = true;
bool add_accept = true;
for (int i = 0; i < p_headers.size(); i++) {
request += p_headers[i] + "\r\n";
+ if (add_host && p_headers[i].findn("Host:") == 0) {
+ add_host = false;
+ }
if (add_clen && p_headers[i].findn("Content-Length:") == 0) {
add_clen = false;
}
@@ -137,6 +154,14 @@ Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector
add_accept = false;
}
}
+ if (add_host) {
+ if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
+ // Don't append the standard ports
+ request += "Host: " + conn_host + "\r\n";
+ } else {
+ request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
+ }
+ }
if (add_clen) {
request += "Content-Length: " + itos(p_body.size()) + "\r\n";
// Should it add utf8 encoding?
@@ -178,22 +203,20 @@ Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector
Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n";
- if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
- // Don't append the standard ports
- request += "Host: " + conn_host + "\r\n";
- } else {
- request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
- }
+ bool add_host = true;
bool add_uagent = true;
bool add_accept = true;
bool add_clen = p_body.length() > 0;
for (int i = 0; i < p_headers.size(); i++) {
request += p_headers[i] + "\r\n";
+ if (add_host && p_headers[i].findn("Host:") == 0) {
+ add_host = false;
+ }
if (add_clen && p_headers[i].findn("Content-Length:") == 0) {
add_clen = false;
}
@@ -204,6 +227,14 @@ Error HTTPClient::request(Method p_method, const String &p_url, const Vector<Str
add_accept = false;
}
}
+ if (add_host) {
+ if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) {
+ // Don't append the standard ports
+ request += "Host: " + conn_host + "\r\n";
+ } else {
+ request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n";
+ }
+ }
if (add_clen) {
request += "Content-Length: " + itos(p_body.utf8().length()) + "\r\n";
// Should it add utf8 encoding?
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 9991ee405e..a8ca6a817e 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -1057,7 +1057,7 @@ bool ResourceLoader::add_custom_resource_format_loader(String script_path) {
ERR_FAIL_COND_V_MSG(obj == nullptr, false, "Cannot instance script as custom resource loader, expected 'ResourceFormatLoader' inheritance, got: " + String(ibt) + ".");
- ResourceFormatLoader *crl = Object::cast_to<ResourceFormatLoader>(obj);
+ Ref<ResourceFormatLoader> crl = Object::cast_to<ResourceFormatLoader>(obj);
crl->set_script(s);
ResourceLoader::add_resource_format_loader(crl);
diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp
index 2eac2a6b4d..6ded27d82f 100644
--- a/core/io/resource_saver.cpp
+++ b/core/io/resource_saver.cpp
@@ -214,7 +214,7 @@ bool ResourceSaver::add_custom_resource_format_saver(String script_path) {
ERR_FAIL_COND_V_MSG(obj == nullptr, false, "Cannot instance script as custom resource saver, expected 'ResourceFormatSaver' inheritance, got: " + String(ibt) + ".");
- ResourceFormatSaver *crl = Object::cast_to<ResourceFormatSaver>(obj);
+ Ref<ResourceFormatSaver> crl = Object::cast_to<ResourceFormatSaver>(obj);
crl->set_script(s);
ResourceSaver::add_resource_format_saver(crl);
diff --git a/core/math/expression.cpp b/core/math/expression.cpp
index 13c8904f7e..d1f15caa5e 100644
--- a/core/math/expression.cpp
+++ b/core/math/expression.cpp
@@ -410,7 +410,7 @@ Error Expression::_get_token(Token &r_token) {
} else if (id == "self") {
r_token.type = TK_SELF;
} else {
- if (Variant::has_builtin_func(id)) {
+ if (Variant::has_utility_function(id)) {
r_token.type = TK_BUILTIN_FUNC;
r_token.value = id;
return OK;
@@ -747,8 +747,8 @@ Expression::ENode *Expression::_parse_expression() {
}
}
- if (!Variant::is_builtin_func_vararg(bifunc->func)) {
- int expected_args = Variant::get_builtin_func_argument_count(bifunc->func);
+ if (!Variant::is_utility_function_vararg(bifunc->func)) {
+ int expected_args = Variant::get_utility_function_argument_count(bifunc->func);
if (expected_args != bifunc->arguments.size()) {
_set_error("Builtin func '" + String(bifunc->func) + "' expects " + itos(expected_args) + " arguments.");
}
@@ -1362,7 +1362,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression:
r_ret = Variant(); //may not return anything
Callable::CallError ce;
- Variant::call_builtin_func(bifunc->func, &r_ret, (const Variant **)argp.ptr(), argp.size(), ce);
+ Variant::call_utility_function(bifunc->func, &r_ret, (const Variant **)argp.ptr(), argp.size(), ce);
if (ce.error != Callable::CallError::CALL_OK) {
r_error_str = "Builtin Call Failed. " + Variant::get_call_error_text(bifunc->func, (const Variant **)argp.ptr(), argp.size(), ce);
return true;
@@ -1396,7 +1396,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression:
}
Callable::CallError ce;
- r_ret = base.call(call->method, (const Variant **)argp.ptr(), argp.size(), ce);
+ base.call(call->method, (const Variant **)argp.ptr(), argp.size(), r_ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
r_error_str = vformat(RTR("On call to '%s':"), String(call->method));
diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp
index 180aeaa0af..00e561f973 100644
--- a/core/math/transform_2d.cpp
+++ b/core/math/transform_2d.cpp
@@ -251,7 +251,7 @@ Transform2D Transform2D::interpolate_with(const Transform2D &p_transform, real_t
real_t dot = v1.dot(v2);
- dot = (dot < -1.0) ? -1.0 : ((dot > 1.0) ? 1.0 : dot); //clamp dot to [-1,1]
+ dot = CLAMP(dot, -1.0, 1.0);
Vector2 v;
diff --git a/core/object/object.cpp b/core/object/object.cpp
index c3f49856ca..96a41d6852 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -597,9 +597,6 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons
_get_property_listv(p_list, p_reversed);
if (!is_class("Script")) { // can still be set, but this is for userfriendlyness
-#ifdef TOOLS_ENABLED
- p_list->push_back(PropertyInfo(Variant::NIL, "Script", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
-#endif
p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT));
}
if (!metadata.empty()) {
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index e72f76c5e1..2e38ce5b06 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -650,6 +650,39 @@ void call_with_variant_args_retc_static_helper(T *p_instance, R (*p_method)(T *,
(void)p_args;
}
+template <class T, class R, class... P>
+void call_with_variant_args_retc_static_helper_dv(T *p_instance, R (*p_method)(T *, P...), const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &default_values, Callable::CallError &r_error) {
+#ifdef DEBUG_ENABLED
+ if ((size_t)p_argcount > sizeof...(P)) {
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
+ r_error.argument = sizeof...(P);
+ return;
+ }
+#endif
+
+ int32_t missing = (int32_t)sizeof...(P) - (int32_t)p_argcount;
+
+ int32_t dvs = default_values.size();
+#ifdef DEBUG_ENABLED
+ if (missing > dvs) {
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
+ r_error.argument = sizeof...(P);
+ return;
+ }
+#endif
+
+ const Variant *args[sizeof...(P) == 0 ? 1 : sizeof...(P)]; //avoid zero sized array
+ for (int32_t i = 0; i < (int32_t)sizeof...(P); i++) {
+ if (i < p_argcount) {
+ args[i] = p_args[i];
+ } else {
+ args[i] = &default_values[i - p_argcount + (dvs - missing)];
+ }
+ }
+
+ call_with_variant_args_retc_static_helper(p_instance, p_method, args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
+}
+
#if defined(DEBUG_METHODS_ENABLED) && defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 055f61cf92..741d05c139 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -3401,7 +3401,8 @@ Variant Variant::call(const StringName &p_method, VARIANT_ARG_DECLARE) {
Callable::CallError error;
- Variant ret = call(p_method, argptr, argc, error);
+ Variant ret;
+ call(p_method, argptr, argc, ret, error);
switch (error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
@@ -3549,12 +3550,12 @@ void Variant::register_types() {
_register_variant_methods();
_register_variant_setters_getters();
_register_variant_constructors();
- _register_variant_builtin_funcs();
+ _register_variant_utility_functions();
}
void Variant::unregister_types() {
_unregister_variant_operators();
_unregister_variant_methods();
_unregister_variant_setters_getters();
_unregister_variant_constructors();
- _unregister_variant_builtin_funcs();
+ _unregister_variant_utility_functions();
}
diff --git a/core/variant/variant.h b/core/variant/variant.h
index d3e2f84357..1a684eeea0 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -267,8 +267,8 @@ private:
static void _unregister_variant_setters_getters();
static void _register_variant_constructors();
static void _unregister_variant_constructors();
- static void _register_variant_builtin_funcs();
- static void _unregister_variant_builtin_funcs();
+ static void _register_variant_utility_functions();
+ static void _unregister_variant_utility_functions();
public:
_FORCE_INLINE_ Type get_type() const {
@@ -482,54 +482,39 @@ public:
static void blend(const Variant &a, const Variant &b, float c, Variant &r_dst);
static void interpolate(const Variant &a, const Variant &b, float c, Variant &r_dst);
- class InternalMethod {
-#ifdef DEBUG_ENABLED
- protected:
- StringName method_name;
- Variant::Type base_type;
-#endif
- public:
- enum Flags {
- FLAG_IS_CONST = 1,
- FLAG_RETURNS_VARIANT = 2,
- FLAG_NO_PTRCALL = 4,
- FLAG_VARARGS = 8
- };
+ /* Built-In Methods */
- virtual int get_argument_count() const = 0;
- virtual Type get_argument_type(int p_arg) const = 0;
- virtual Type get_return_type() const = 0;
- virtual uint32_t get_flags() const = 0;
+ typedef void (*ValidatedBuiltInMethod)(Variant *base, const Variant **p_args, int p_argcount, Variant *r_ret);
+ typedef void (*PTRBuiltInMethod)(void *p_base, const void **p_args, void *r_ret, int p_argcount);
-#ifdef DEBUG_ENABLED
- virtual String get_argument_name(int p_arg) const = 0;
- StringName get_name() const {
- return method_name;
- }
- Variant::Type get_base_type() const {
- return base_type;
- }
-#endif
- virtual Vector<Variant> get_default_arguments() const = 0;
- virtual void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) = 0;
- virtual void validated_call(Variant *base, const Variant **p_args, Variant *r_ret) = 0;
-#ifdef PTRCALL_ENABLED
- virtual void ptrcall(void *p_base, const void **p_args, void *r_ret) = 0;
-#endif
- virtual ~InternalMethod() {}
- };
+ static bool has_builtin_method(Variant::Type p_type, const StringName &p_method);
+
+ static ValidatedBuiltInMethod get_validated_builtin_method(Variant::Type p_type, const StringName &p_method);
+ static PTRBuiltInMethod get_ptr_builtin_method(Variant::Type p_type, const StringName &p_method);
- static InternalMethod *get_internal_method(Type p_type, const StringName &p_method_name);
+ static int get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method);
+ static Variant::Type get_builtin_method_argument_type(Variant::Type p_type, const StringName &p_method, int p_argument);
+ static String get_builtin_method_argument_name(Variant::Type p_type, const StringName &p_method, int p_argument);
+ static Vector<Variant> get_builtin_method_default_arguments(Variant::Type p_type, const StringName &p_method);
+ static bool has_builtin_method_return_value(Variant::Type p_type, const StringName &p_method);
+ static Variant::Type get_builtin_method_return_type(Variant::Type p_type, const StringName &p_method);
+ static bool is_builtin_method_const(Variant::Type p_type, const StringName &p_method);
+ static bool is_builtin_method_vararg(Variant::Type p_type, const StringName &p_method);
+ static void get_builtin_method_list(Variant::Type p_type, List<StringName> *p_list);
- void call_ptr(const StringName &p_method, const Variant **p_args, int p_argcount, Variant *r_ret, Callable::CallError &r_error);
- Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
+ void call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error);
Variant call(const StringName &p_method, const Variant &p_arg1 = Variant(), const Variant &p_arg2 = Variant(), const Variant &p_arg3 = Variant(), const Variant &p_arg4 = Variant(), const Variant &p_arg5 = Variant());
static String get_call_error_text(const StringName &p_method, const Variant **p_argptrs, int p_argcount, const Callable::CallError &ce);
static String get_call_error_text(Object *p_base, const StringName &p_method, const Variant **p_argptrs, int p_argcount, const Callable::CallError &ce);
static String get_callable_error_text(const Callable &p_callable, const Variant **p_argptrs, int p_argcount, const Callable::CallError &ce);
- // constructor
+ //dynamic (includes Object)
+ void get_method_list(List<MethodInfo> *p_list) const;
+ bool has_method(const StringName &p_method) const;
+
+ /* Constructors */
+
typedef void (*ValidatedConstructor)(Variant &r_base, const Variant **p_args);
typedef void (*PTRConstructor)(void *base, const void **p_args);
@@ -543,13 +528,7 @@ public:
static void get_constructor_list(Type p_type, List<MethodInfo> *r_list); //convenience
- void get_method_list(List<MethodInfo> *p_list) const;
- bool has_method(const StringName &p_method) const;
- static Vector<Variant::Type> get_method_argument_types(Variant::Type p_type, const StringName &p_method);
- static Vector<Variant> get_method_default_arguments(Variant::Type p_type, const StringName &p_method);
- static Variant::Type get_method_return_type(Variant::Type p_type, const StringName &p_method, bool *r_has_return = nullptr);
- static Vector<StringName> get_method_argument_names(Variant::Type p_type, const StringName &p_method);
- static bool is_method_const(Variant::Type p_type, const StringName &p_method);
+ /* Properties */
void set_named(const StringName &p_member, const Variant &p_value, bool &r_valid);
Variant get_named(const StringName &p_member, bool &r_valid) const;
@@ -570,6 +549,8 @@ public:
static PTRSetter get_member_ptr_setter(Variant::Type p_type, const StringName &p_member);
static PTRGetter get_member_ptr_getter(Variant::Type p_type, const StringName &p_member);
+ /* Indexing */
+
static bool has_indexing(Variant::Type p_type);
static Variant::Type get_indexed_element_type(Variant::Type p_type);
@@ -590,6 +571,8 @@ public:
uint64_t get_indexed_size() const;
+ /* Keying */
+
static bool is_keyed(Variant::Type p_type);
typedef void (*ValidatedKeyedSetter)(Variant *base, const Variant *key, const Variant *value, bool &valid);
@@ -612,6 +595,8 @@ public:
Variant get_keyed(const Variant &p_key, bool &r_valid) const;
bool has_key(const Variant &p_key, bool &r_valid) const;
+ /* Generic */
+
void set(const Variant &p_index, const Variant &p_value, bool *r_valid = nullptr);
Variant get(const Variant &p_index, bool *r_valid = nullptr) const;
bool in(const Variant &p_index, bool *r_valid = nullptr) const;
@@ -622,31 +607,31 @@ public:
void get_property_list(List<PropertyInfo> *p_list) const;
- static void call_builtin_func(const StringName &p_name, Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
- static bool has_builtin_func(const StringName &p_name);
+ static void call_utility_function(const StringName &p_name, Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
+ static bool has_utility_function(const StringName &p_name);
- typedef void (*BuiltinFunctionValidatedCall)(Variant *r_ret, const Variant **p_args, int p_argcount);
- typedef void (*BuiltinFunctionPTRCall)(void *r_ret, const void **p_args, int p_argcount);
+ typedef void (*ValidatedUtilityFunction)(Variant *r_ret, const Variant **p_args, int p_argcount);
+ typedef void (*PTRUtilityFunction)(void *r_ret, const void **p_args, int p_argcount);
- static BuiltinFunctionValidatedCall get_builtin_validated_caller(const StringName &p_name);
- static BuiltinFunctionPTRCall get_builtin_ptr_caller(const StringName &p_name);
+ static ValidatedUtilityFunction get_validated_utility_function(const StringName &p_name);
+ static PTRUtilityFunction get_ptr_utility_function(const StringName &p_name);
- enum BuiltInFunctionType {
- BUILTIN_FUNC_TYPE_MATH,
- BUILTIN_FUNC_TYPE_RANDOM,
- BUILTIN_FUNC_TYPE_UTILITY,
+ enum UtilityFunctionType {
+ UTILITY_FUNC_TYPE_MATH,
+ UTILITY_FUNC_TYPE_RANDOM,
+ UTILITY_FUNC_TYPE_GENERAL,
};
- static BuiltInFunctionType get_builtin_func_type(const StringName &p_name);
+ static UtilityFunctionType get_utility_function_type(const StringName &p_name);
- static int get_builtin_func_argument_count(const StringName &p_name);
- static Variant::Type get_builtin_func_argument_type(const StringName &p_name, int p_arg);
- static String get_builtin_func_argument_name(const StringName &p_name, int p_arg);
- static bool has_builtin_func_return_value(const StringName &p_name);
- static Variant::Type get_builtin_func_return_type(const StringName &p_name);
- static bool is_builtin_func_vararg(const StringName &p_name);
+ static int get_utility_function_argument_count(const StringName &p_name);
+ static Variant::Type get_utility_function_argument_type(const StringName &p_name, int p_arg);
+ static String get_utility_function_argument_name(const StringName &p_name, int p_arg);
+ static bool has_utility_function_return_value(const StringName &p_name);
+ static Variant::Type get_utility_function_return_type(const StringName &p_name);
+ static bool is_utility_function_vararg(const StringName &p_name);
- static void get_builtin_function_list(List<StringName> *r_functions);
+ static void get_utility_function_list(List<StringName> *r_functions);
//argsVariant call()
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 39290f2b04..4cb8457ccd 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -36,6 +36,7 @@
#include "core/io/compression.h"
#include "core/object/class_db.h"
#include "core/os/os.h"
+#include "core/templates/local_vector.h"
#include "core/templates/oa_hash_map.h"
typedef void (*VariantFunc)(Variant &r_ret, Variant &p_self, const Variant **p_args);
@@ -62,499 +63,330 @@ struct TypeAdjust<Object *> {
}
};
-struct _VariantCall {
- template <class T, class... P>
- class InternalMethod : public Variant::InternalMethod {
- public:
- void (T::*method)(P...);
- Vector<Variant> default_values;
-#ifdef DEBUG_ENABLED
- Vector<String> argument_names;
-#endif
-
- virtual int get_argument_count() const {
- return sizeof...(P);
- }
- virtual Variant::Type get_argument_type(int p_arg) const {
- return call_get_argument_type<P...>(p_arg);
- }
-#ifdef DEBUG_ENABLED
- virtual String get_argument_name(int p_arg) const {
- ERR_FAIL_INDEX_V(p_arg, argument_names.size(), String());
- return argument_names[p_arg];
- }
-#endif
- virtual Vector<Variant> get_default_arguments() const {
- return default_values;
- }
-
- virtual Variant::Type get_return_type() const {
- return Variant::NIL;
- }
- virtual uint32_t get_flags() const {
- return 0;
- }
-
- virtual void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
- call_with_variant_args_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_error, default_values);
- }
-
- virtual void validated_call(Variant *base, const Variant **p_args, Variant *r_ret) {
- call_with_validated_variant_args(base, method, p_args);
- }
-
-#ifdef PTRCALL_ENABLED
- virtual void ptrcall(void *p_base, const void **p_args, void *r_ret) {
- call_with_ptr_args<T, P...>(reinterpret_cast<T *>(p_base), method, p_args);
- }
-#endif
- InternalMethod(void (T::*p_method)(P...), const Vector<Variant> &p_default_args
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_arg_names, const StringName &p_method_name, Variant::Type p_base_type
-#endif
- ) {
- method = p_method;
- default_values = p_default_args;
-#ifdef DEBUG_ENABLED
- argument_names = p_arg_names;
- method_name = p_method_name;
- base_type = p_base_type;
-#endif
- }
- };
-
- template <class T, class R, class... P>
- class InternalMethodR : public Variant::InternalMethod {
- public:
- R(T::*method)
- (P...);
- Vector<Variant> default_values;
-#ifdef DEBUG_ENABLED
- Vector<String> argument_names;
-#endif
-
- virtual int get_argument_count() const {
- return sizeof...(P);
- }
- virtual Variant::Type get_argument_type(int p_arg) const {
- return call_get_argument_type<P...>(p_arg);
- return Variant::NIL;
- }
-#ifdef DEBUG_ENABLED
- virtual String get_argument_name(int p_arg) const {
- ERR_FAIL_INDEX_V(p_arg, argument_names.size(), String());
- return argument_names[p_arg];
- }
-#endif
- virtual Vector<Variant> get_default_arguments() const {
- return default_values;
- }
+template <class R, class T, class... P>
+static _FORCE_INLINE_ void vc_method_call(R (T::*method)(P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
+ call_with_variant_args_ret_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_ret, r_error, p_defvals);
+}
- virtual Variant::Type get_return_type() const {
- return GetTypeInfo<R>::VARIANT_TYPE;
- }
- virtual uint32_t get_flags() const {
- uint32_t f = 0;
- if (get_return_type() == Variant::NIL) {
- f |= FLAG_RETURNS_VARIANT;
- }
- return f;
- }
+template <class R, class T, class... P>
+static _FORCE_INLINE_ void vc_method_call(R (T::*method)(P...) const, Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
+ call_with_variant_args_retc_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_ret, r_error, p_defvals);
+}
- virtual void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
- call_with_variant_args_ret_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_ret, r_error, default_values);
- }
+template <class T, class... P>
+static _FORCE_INLINE_ void vc_method_call(void (T::*method)(P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
+ call_with_variant_args_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_error, p_defvals);
+}
- virtual void validated_call(Variant *base, const Variant **p_args, Variant *r_ret) {
- TypeAdjust<R>::adjust(r_ret);
- call_with_validated_variant_args_ret(base, method, p_args, r_ret);
- }
-#ifdef PTRCALL_ENABLED
- virtual void ptrcall(void *p_base, const void **p_args, void *r_ret) {
- call_with_ptr_args_ret<T, R, P...>(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
- }
-#endif
- InternalMethodR(R (T::*p_method)(P...), const Vector<Variant> &p_default_args
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_arg_names, const StringName &p_method_name, Variant::Type p_base_type
-#endif
- ) {
- method = p_method;
- default_values = p_default_args;
-#ifdef DEBUG_ENABLED
- argument_names = p_arg_names;
- method_name = p_method_name;
- base_type = p_base_type;
-#endif
- }
- };
+template <class T, class... P>
+static _FORCE_INLINE_ void vc_method_call(void (T::*method)(P...) const, Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) {
+ call_with_variant_argsc_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_error, p_defvals);
+}
- template <class T, class R, class... P>
- class InternalMethodRC : public Variant::InternalMethod {
- public:
- R(T::*method)
- (P...) const;
- Vector<Variant> default_values;
-#ifdef DEBUG_ENABLED
- Vector<String> argument_names;
-#endif
+template <class R, class T, class... P>
+static _FORCE_INLINE_ void vc_validated_call(R (T::*method)(P...), Variant *base, const Variant **p_args, Variant *r_ret) {
+ call_with_validated_variant_args_ret(base, method, p_args, r_ret);
+}
- virtual int get_argument_count() const {
- return sizeof...(P);
- }
- virtual Variant::Type get_argument_type(int p_arg) const {
- return call_get_argument_type<P...>(p_arg);
- }
-#ifdef DEBUG_ENABLED
- virtual String get_argument_name(int p_arg) const {
- ERR_FAIL_INDEX_V(p_arg, argument_names.size(), String());
- return argument_names[p_arg];
- }
-#endif
- virtual Vector<Variant> get_default_arguments() const {
- return default_values;
- }
+template <class R, class T, class... P>
+static _FORCE_INLINE_ void vc_validated_call(R (T::*method)(P...) const, Variant *base, const Variant **p_args, Variant *r_ret) {
+ call_with_validated_variant_args_retc(base, method, p_args, r_ret);
+}
+template <class T, class... P>
+static _FORCE_INLINE_ void vc_validated_call(void (T::*method)(P...), Variant *base, const Variant **p_args, Variant *r_ret) {
+ call_with_validated_variant_args(base, method, p_args);
+}
- virtual Variant::Type get_return_type() const {
- return GetTypeInfo<R>::VARIANT_TYPE;
- }
- virtual uint32_t get_flags() const {
- uint32_t f = FLAG_IS_CONST;
- if (get_return_type() == Variant::NIL) {
- f |= FLAG_RETURNS_VARIANT;
- }
- return f;
- }
+template <class T, class... P>
+static _FORCE_INLINE_ void vc_validated_call(void (T::*method)(P...) const, Variant *base, const Variant **p_args, Variant *r_ret) {
+ call_with_validated_variant_argsc(base, method, p_args);
+}
- virtual void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
- call_with_variant_args_retc_dv(VariantGetInternalPtr<T>::get_ptr(base), method, p_args, p_argcount, r_ret, r_error, default_values);
- }
+template <class R, class T, class... P>
+static _FORCE_INLINE_ void vc_ptrcall(R (T::*method)(P...), void *p_base, const void **p_args, void *r_ret) {
+ call_with_ptr_args_ret(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
+}
- virtual void validated_call(Variant *base, const Variant **p_args, Variant *r_ret) {
- TypeAdjust<R>::adjust(r_ret);
- call_with_validated_variant_args_retc(base, method, p_args, r_ret);
- }
-#ifdef PTRCALL_ENABLED
- virtual void ptrcall(void *p_base, const void **p_args, void *r_ret) {
- call_with_ptr_args_retc<T, R, P...>(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
- }
-#endif
- InternalMethodRC(R (T::*p_method)(P...) const, const Vector<Variant> &p_default_args
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_arg_names, const StringName &p_method_name, Variant::Type p_base_type
-#endif
- ) {
- method = p_method;
- default_values = p_default_args;
-#ifdef DEBUG_ENABLED
- argument_names = p_arg_names;
- method_name = p_method_name;
- base_type = p_base_type;
-#endif
- }
- };
+template <class R, class T, class... P>
+static _FORCE_INLINE_ void vc_ptrcall(R (T::*method)(P...) const, void *p_base, const void **p_args, void *r_ret) {
+ call_with_ptr_args_retc(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
+}
- template <class T, class R, class... P>
- class InternalMethodRS : public Variant::InternalMethod {
- public:
- R(*method)
- (T *, P...);
- Vector<Variant> default_values;
-#ifdef DEBUG_ENABLED
- Vector<String> argument_names;
-#endif
+template <class T, class... P>
+static _FORCE_INLINE_ void vc_ptrcall(void (T::*method)(P...), void *p_base, const void **p_args, void *r_ret) {
+ call_with_ptr_args(reinterpret_cast<T *>(p_base), method, p_args);
+}
- virtual int get_argument_count() const {
- return sizeof...(P);
- }
- virtual Variant::Type get_argument_type(int p_arg) const {
- return call_get_argument_type<P...>(p_arg);
- }
-#ifdef DEBUG_ENABLED
- virtual String get_argument_name(int p_arg) const {
- ERR_FAIL_INDEX_V(p_arg, argument_names.size(), String());
- return argument_names[p_arg];
- }
-#endif
- virtual Vector<Variant> get_default_arguments() const {
- return default_values;
- }
+template <class T, class... P>
+static _FORCE_INLINE_ void vc_ptrcall(void (T::*method)(P...) const, void *p_base, const void **p_args, void *r_ret) {
+ call_with_ptr_argsc(reinterpret_cast<T *>(p_base), method, p_args);
+}
- virtual Variant::Type get_return_type() const {
- return GetTypeInfo<R>::VARIANT_TYPE;
- }
- virtual uint32_t get_flags() const {
- uint32_t f = 0;
- if (get_return_type() == Variant::NIL) {
- f |= FLAG_RETURNS_VARIANT;
- }
- return f;
- }
+template <class R, class T, class... P>
+static _FORCE_INLINE_ int vc_get_argument_count(R (T::*method)(P...)) {
+ return sizeof...(P);
+}
+template <class R, class T, class... P>
+static _FORCE_INLINE_ int vc_get_argument_count(R (T::*method)(P...) const) {
+ return sizeof...(P);
+}
- virtual void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
- const Variant **args = p_args;
-#ifdef DEBUG_ENABLED
- if ((size_t)p_argcount > sizeof...(P)) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.argument = sizeof...(P);
- return;
- }
-#endif
- if ((size_t)p_argcount < sizeof...(P)) {
- size_t missing = sizeof...(P) - (size_t)p_argcount;
- if (missing <= (size_t)default_values.size()) {
- args = (const Variant **)alloca(sizeof...(P) * sizeof(const Variant *));
- // GCC fails to see that `sizeof...(P)` cannot be 0 here given the previous
- // conditions, so it raises a warning on the potential use of `i < 0` as the
- // execution condition.
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wtype-limits"
-#endif
- for (size_t i = 0; i < sizeof...(P); i++) {
- if (i < (size_t)p_argcount) {
- args[i] = p_args[i];
- } else {
- args[i] = &default_values[i - p_argcount + (default_values.size() - missing)];
- }
- }
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
- } else {
-#ifdef DEBUG_ENABLED
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = sizeof...(P);
-#endif
- return;
- }
- }
- call_with_variant_args_retc_static_helper(VariantGetInternalPtr<T>::get_ptr(base), method, args, r_ret, r_error, BuildIndexSequence<sizeof...(P)>{});
- }
+template <class T, class... P>
+static _FORCE_INLINE_ int vc_get_argument_count(void (T::*method)(P...)) {
+ return sizeof...(P);
+}
- virtual void validated_call(Variant *base, const Variant **p_args, Variant *r_ret) {
- TypeAdjust<R>::adjust(r_ret);
- call_with_validated_variant_args_static_retc(base, method, p_args, r_ret);
- }
-#ifdef PTRCALL_ENABLED
- virtual void ptrcall(void *p_base, const void **p_args, void *r_ret) {
- call_with_ptr_args_static_retc<T, R, P...>(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
- }
-#endif
- InternalMethodRS(R (*p_method)(T *, P...), const Vector<Variant> &p_default_args
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_arg_names, const StringName &p_method_name, Variant::Type p_base_type
-#endif
- ) {
- method = p_method;
- default_values = p_default_args;
-#ifdef DEBUG_ENABLED
- argument_names = p_arg_names;
- method_name = p_method_name;
- base_type = p_base_type;
-#endif
- }
- };
+template <class T, class... P>
+static _FORCE_INLINE_ int vc_get_argument_count(void (T::*method)(P...) const) {
+ return sizeof...(P);
+}
- class InternalMethodVC : public Variant::InternalMethod {
- public:
- typedef void (*MethodVC)(Variant *, const Variant **, int, Variant &r_ret, Callable::CallError &);
- MethodVC methodvc = nullptr;
- uint32_t base_flags = 0;
- Vector<String> argument_names;
- Vector<Variant::Type> argument_types;
- Variant::Type return_type = Variant::NIL;
-
- virtual int get_argument_count() const {
- return argument_names.size();
- }
- virtual Variant::Type get_argument_type(int p_arg) const {
- ERR_FAIL_INDEX_V(p_arg, argument_types.size(), Variant::NIL);
- return argument_types[p_arg];
- }
-#ifdef DEBUG_ENABLED
- virtual String get_argument_name(int p_arg) const {
- ERR_FAIL_INDEX_V(p_arg, argument_names.size(), String());
- return argument_names[p_arg];
- }
-#endif
- virtual Vector<Variant> get_default_arguments() const {
- return Vector<Variant>();
- }
+template <class R, class T, class... P>
+static _FORCE_INLINE_ int vc_get_argument_count(R (*method)(T *, P...)) {
+ return sizeof...(P);
+}
- virtual Variant::Type get_return_type() const {
- return return_type;
- }
- virtual uint32_t get_flags() const {
- return base_flags | FLAG_NO_PTRCALL;
- }
+template <class R, class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_argument_type(R (T::*method)(P...), int p_arg) {
+ return call_get_argument_type<P...>(p_arg);
+}
+template <class R, class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_argument_type(R (T::*method)(P...) const, int p_arg) {
+ return call_get_argument_type<P...>(p_arg);
+}
- virtual void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
- methodvc(base, p_args, p_argcount, r_ret, r_error);
- }
+template <class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_argument_type(void (T::*method)(P...), int p_arg) {
+ return call_get_argument_type<P...>(p_arg);
+}
- virtual void validated_call(Variant *base, const Variant **p_args, Variant *r_ret) {
- ERR_FAIL_MSG("No support for validated call");
- }
-#ifdef PTRCALL_ENABLED
- virtual void ptrcall(void *p_base, const void **p_args, void *r_ret) {
- ERR_FAIL_MSG("No support for ptrcall call");
- }
-#endif
- InternalMethodVC(MethodVC p_method, uint32_t p_flags, const Vector<Variant::Type> &p_argument_types, const Variant::Type &p_return_type
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_arg_names, const StringName &p_method_name, Variant::Type p_base_type
-#endif
- ) {
- methodvc = p_method;
- argument_types = p_argument_types;
- return_type = p_return_type;
- base_flags = p_flags;
-#ifdef DEBUG_ENABLED
- argument_names = p_arg_names;
- method_name = p_method_name;
- base_type = p_base_type;
-#endif
- }
- };
+template <class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_argument_type(void (T::*method)(P...) const, int p_arg) {
+ return call_get_argument_type<P...>(p_arg);
+}
- typedef OAHashMap<StringName, Variant::InternalMethod *> MethodMap;
- static MethodMap *type_internal_methods;
- static List<StringName> *type_internal_method_names;
+template <class R, class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_argument_type(R (*method)(T *, P...), int p_arg) {
+ return call_get_argument_type<P...>(p_arg);
+}
- template <class T, class... P>
- static void _bind_method(const StringName &p_name, void (T::*p_method)(P...), const Vector<Variant> &p_default_args = Vector<Variant>()
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_argument_names = Vector<String>()
-#endif
- ) {
+template <class R, class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_return_type(R (T::*method)(P...)) {
+ return GetTypeInfo<R>::VARIANT_TYPE;
+}
-#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_MSG(p_argument_names.size() != sizeof...(P), "Wrong argument name count supplied for method: " + Variant::get_type_name(GetTypeInfo<T>::VARIANT_TYPE) + "::" + String(p_name));
- ERR_FAIL_COND(type_internal_methods[GetTypeInfo<T>::VARIANT_TYPE].has(p_name));
-#endif
-#ifdef DEBUG_ENABLED
- Variant::InternalMethod *m = memnew((InternalMethod<T, P...>)(p_method, p_default_args, p_argument_names, p_name, GetTypeInfo<T>::VARIANT_TYPE));
-#else
- Variant::InternalMethod *m = memnew((InternalMethod<T, P...>)(p_method, p_default_args));
-#endif
+template <class R, class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_return_type(R (T::*method)(P...) const) {
+ return GetTypeInfo<R>::VARIANT_TYPE;
+}
- type_internal_methods[GetTypeInfo<T>::VARIANT_TYPE].insert(p_name, m);
- type_internal_method_names[GetTypeInfo<T>::VARIANT_TYPE].push_back(p_name);
- }
+template <class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_return_type(void (T::*method)(P...)) {
+ return Variant::NIL;
+}
- template <class T, class R, class... P>
- static void _bind_method(const StringName &p_name, R (T::*p_method)(P...) const, const Vector<Variant> &p_default_args = Vector<Variant>()
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_argument_names = Vector<String>()
-#endif
- ) {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_MSG(p_argument_names.size() != sizeof...(P), "Wrong argument name count supplied for method: " + Variant::get_type_name(GetTypeInfo<T>::VARIANT_TYPE) + "::" + String(p_name));
- ERR_FAIL_COND_MSG(type_internal_methods[GetTypeInfo<T>::VARIANT_TYPE].has(p_name), " Method already registered: " + Variant::get_type_name(GetTypeInfo<T>::VARIANT_TYPE) + "::" + String(p_name));
+template <class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_return_type(void (T::*method)(P...) const) {
+ return Variant::NIL;
+}
-#endif
-#ifdef DEBUG_ENABLED
- Variant::InternalMethod *m = memnew((InternalMethodRC<T, R, P...>)(p_method, p_default_args, p_argument_names, p_name, GetTypeInfo<T>::VARIANT_TYPE));
-#else
- Variant::InternalMethod *m = memnew((InternalMethodRC<T, R, P...>)(p_method, p_default_args));
-#endif
+template <class R, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_return_type(R (*method)(P...)) {
+ return GetTypeInfo<R>::VARIANT_TYPE;
+}
- type_internal_methods[GetTypeInfo<T>::VARIANT_TYPE].insert(p_name, m);
- type_internal_method_names[GetTypeInfo<T>::VARIANT_TYPE].push_back(p_name);
- }
+template <class R, class T, class... P>
+static _FORCE_INLINE_ bool vc_has_return_type(R (T::*method)(P...)) {
+ return true;
+}
+template <class R, class T, class... P>
+static _FORCE_INLINE_ bool vc_has_return_type(R (T::*method)(P...) const) {
+ return true;
+}
- template <class T, class R, class... P>
- static void _bind_method(const StringName &p_name, R (T::*p_method)(P...), const Vector<Variant> &p_default_args = Vector<Variant>()
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_argument_names = Vector<String>()
-#endif
- ) {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_MSG(p_argument_names.size() != sizeof...(P), "Wrong argument name count supplied for method: " + Variant::get_type_name(GetTypeInfo<T>::VARIANT_TYPE) + "::" + String(p_name));
- ERR_FAIL_COND_MSG(type_internal_methods[GetTypeInfo<T>::VARIANT_TYPE].has(p_name), " Method already registered: " + Variant::get_type_name(GetTypeInfo<T>::VARIANT_TYPE) + "::" + String(p_name));
-#endif
+template <class T, class... P>
+static _FORCE_INLINE_ bool vc_has_return_type(void (T::*method)(P...)) {
+ return false;
+}
-#ifdef DEBUG_ENABLED
- Variant::InternalMethod *m = memnew((InternalMethodR<T, R, P...>)(p_method, p_default_args, p_argument_names, p_name, GetTypeInfo<T>::VARIANT_TYPE));
-#else
- Variant::InternalMethod *m = memnew((InternalMethodR<T, R, P...>)(p_method, p_default_args));
-#endif
- type_internal_methods[GetTypeInfo<T>::VARIANT_TYPE].insert(p_name, m);
- type_internal_method_names[GetTypeInfo<T>::VARIANT_TYPE].push_back(p_name);
- }
+template <class T, class... P>
+static _FORCE_INLINE_ bool vc_has_return_type(void (T::*method)(P...) const) {
+ return false;
+}
-#ifdef DEBUG_ENABLED
-#define bind_method(m_type, m_method, m_arg_names, m_default_args) _VariantCall::_bind_method(#m_method, &m_type ::m_method, m_default_args, m_arg_names)
-#else
-#define bind_method(m_type, m_method, m_arg_names, m_default_args) _VariantCall::_bind_method(#m_method, &m_type ::m_method, m_default_args)
-#endif
+template <class R, class T, class... P>
+static _FORCE_INLINE_ bool vc_is_const(R (T::*method)(P...)) {
+ return false;
+}
+template <class R, class T, class... P>
+static _FORCE_INLINE_ bool vc_is_const(R (T::*method)(P...) const) {
+ return true;
+}
-#ifdef DEBUG_ENABLED
-#define bind_methodv(m_name, m_method, m_arg_names, m_default_args) _VariantCall::_bind_method(#m_name, m_method, m_default_args, m_arg_names)
-#else
-#define bind_methodv(m_name, m_method, m_arg_names, m_default_args) _VariantCall::_bind_method(#m_name, m_method, m_default_args)
-#endif
+template <class T, class... P>
+static _FORCE_INLINE_ bool vc_is_const(void (T::*method)(P...)) {
+ return false;
+}
- template <class T, class R, class... P>
- static void _bind_function(const StringName &p_name, R (*p_method)(T *, P...), const Vector<Variant> &p_default_args = Vector<Variant>()
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_argument_names = Vector<String>()
-#endif
- ) {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_MSG(p_argument_names.size() != sizeof...(P), "Wrong argument name count supplied for method: " + Variant::get_type_name(GetTypeInfo<T>::VARIANT_TYPE) + "::" + String(p_name));
- ERR_FAIL_COND_MSG(type_internal_methods[GetTypeInfo<T>::VARIANT_TYPE].has(p_name), " Method already registered: " + Variant::get_type_name(GetTypeInfo<T>::VARIANT_TYPE) + "::" + String(p_name));
-#endif
+template <class T, class... P>
+static _FORCE_INLINE_ bool vc_is_const(void (T::*method)(P...) const) {
+ return true;
+}
-#ifdef DEBUG_ENABLED
- Variant::InternalMethod *m = memnew((InternalMethodRS<T, R, P...>)(p_method, p_default_args, p_argument_names, p_name, GetTypeInfo<T>::VARIANT_TYPE));
-#else
- Variant::InternalMethod *m = memnew((InternalMethodRS<T, R, P...>)(p_method, p_default_args));
-#endif
+template <class R, class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_base_type(R (T::*method)(P...)) {
+ return GetTypeInfo<T>::VARIANT_TYPE;
+}
+template <class R, class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_base_type(R (T::*method)(P...) const) {
+ return GetTypeInfo<T>::VARIANT_TYPE;
+}
- type_internal_methods[GetTypeInfo<T>::VARIANT_TYPE].insert(p_name, m);
- type_internal_method_names[GetTypeInfo<T>::VARIANT_TYPE].push_back(p_name);
- }
+template <class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_base_type(void (T::*method)(P...)) {
+ return GetTypeInfo<T>::VARIANT_TYPE;
+}
-#ifdef DEBUG_ENABLED
-#define bind_function(m_name, m_method, m_arg_names, m_default_args) _VariantCall::_bind_function(m_name, m_method, m_default_args, m_arg_names)
-#else
-#define bind_function(m_name, m_method, m_arg_names, m_default_args) _VariantCall::_bind_function(m_name, m_method, m_default_args)
-#endif
+template <class T, class... P>
+static _FORCE_INLINE_ Variant::Type vc_get_base_type(void (T::*method)(P...) const) {
+ return GetTypeInfo<T>::VARIANT_TYPE;
+}
- static void _bind_custom(Variant::Type p_type, const StringName &p_name, InternalMethodVC::MethodVC p_method, uint32_t p_flags, const Vector<Variant::Type> &p_argument_types, const Variant::Type &p_return_type
-#ifdef DEBUG_ENABLED
- ,
- const Vector<String> &p_argument_names = Vector<String>()
-#endif
- ) {
+#define METHOD_CLASS(m_class, m_method_name, m_method_ptr) \
+ struct Method_##m_class##_##m_method_name { \
+ static void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) { \
+ vc_method_call(m_method_ptr, base, p_args, p_argcount, r_ret, p_defvals, r_error); \
+ } \
+ static void validated_call(Variant *base, const Variant **p_args, int p_argcount, Variant *r_ret) { \
+ TypeAdjust<m_class>::adjust(r_ret); \
+ vc_validated_call(m_method_ptr, base, p_args, r_ret); \
+ } \
+ static void ptrcall(void *p_base, const void **p_args, void *r_ret, int p_argcount) { \
+ vc_ptrcall(m_method_ptr, p_base, p_args, r_ret); \
+ } \
+ static int get_argument_count() { \
+ return vc_get_argument_count(m_method_ptr); \
+ } \
+ static Variant::Type get_argument_type(int p_arg) { \
+ return vc_get_argument_type(m_method_ptr, p_arg); \
+ } \
+ static Variant::Type get_return_type() { \
+ return vc_get_return_type(m_method_ptr); \
+ } \
+ static bool has_return_type() { \
+ return vc_has_return_type(m_method_ptr); \
+ } \
+ static bool is_const() { \
+ return vc_is_const(m_method_ptr); \
+ } \
+ static bool is_vararg() { \
+ return false; \
+ } \
+ static Variant::Type get_base_type() { \
+ return vc_get_base_type(m_method_ptr); \
+ } \
+ static StringName get_name() { \
+ return #m_method_name; \
+ } \
+ };
-#ifdef DEBUG_ENABLED
- Variant::InternalMethod *m = memnew(InternalMethodVC(p_method, p_flags, p_argument_types, p_return_type, p_argument_names, p_name, p_type));
-#else
- Variant::InternalMethod *m = memnew(InternalMethodVC(p_method, p_flags, p_argument_types, p_return_type));
-#endif
+template <class R, class T, class... P>
+static _FORCE_INLINE_ void vc_ptrcall(R (*method)(T *, P...), void *p_base, const void **p_args, void *r_ret) {
+ call_with_ptr_args_static_retc<T, R, P...>(reinterpret_cast<T *>(p_base), method, p_args, r_ret);
+}
- type_internal_methods[p_type].insert(p_name, m);
- type_internal_method_names[p_type].push_back(p_name);
- }
+#define FUNCTION_CLASS(m_class, m_method_name, m_method_ptr) \
+ struct Method_##m_class##_##m_method_name { \
+ static void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) { \
+ call_with_variant_args_retc_static_helper_dv(VariantGetInternalPtr<m_class>::get_ptr(base), m_method_ptr, p_args, p_argcount, r_ret, p_defvals, r_error); \
+ } \
+ static void validated_call(Variant *base, const Variant **p_args, int p_argcount, Variant *r_ret) { \
+ TypeAdjust<m_class>::adjust(r_ret); \
+ call_with_validated_variant_args_static_retc(base, m_method_ptr, p_args, r_ret); \
+ } \
+ static void ptrcall(void *p_base, const void **p_args, void *r_ret, int p_argcount) { \
+ vc_ptrcall(m_method_ptr, p_base, p_args, r_ret); \
+ } \
+ static int get_argument_count() { \
+ return vc_get_argument_count(m_method_ptr); \
+ } \
+ static Variant::Type get_argument_type(int p_arg) { \
+ return vc_get_argument_type(m_method_ptr, p_arg); \
+ } \
+ static Variant::Type get_return_type() { \
+ return vc_get_return_type(m_method_ptr); \
+ } \
+ static bool has_return_type() { \
+ return true; \
+ } \
+ static bool is_const() { \
+ return true; \
+ } \
+ static bool is_vararg() { \
+ return false; \
+ } \
+ static Variant::Type get_base_type() { \
+ return GetTypeInfo<m_class>::VARIANT_TYPE; \
+ } \
+ static StringName get_name() { \
+ return #m_method_name; \
+ } \
+ };
-#ifdef DEBUG_ENABLED
-#define bind_custom(m_type, m_name, m_method, m_flags, m_arg_types, m_ret_type, m_arg_names) _VariantCall::_bind_custom(m_type, m_name, m_method, m_flags, m_arg_types, m_ret_type, m_arg_names)
-#else
-#define bind_custom(m_type, m_name, m_method, m_flags, m_arg_types, m_ret_type, m_arg_names) _VariantCall::_bind_custom(m_type, m_name, m_method, m_flags, m_arg_types, m_ret_type)
-#endif
+#define VARARG_CLASS(m_class, m_method_name, m_method_ptr, m_has_return, m_return_type) \
+ struct Method_##m_class##_##m_method_name { \
+ static void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error) { \
+ m_method_ptr(base, p_args, p_argcount, r_ret, r_error); \
+ } \
+ static void validated_call(Variant *base, const Variant **p_args, int p_argcount, Variant *r_ret) { \
+ Callable::CallError ce; \
+ m_method_ptr(base, p_args, p_argcount, *r_ret, ce); \
+ } \
+ static void ptrcall(void *p_base, const void **p_args, void *r_ret, int p_argcount) { \
+ LocalVector<Variant> vars; \
+ vars.resize(p_argcount); \
+ LocalVector<const Variant *> vars_ptrs; \
+ vars_ptrs.resize(p_argcount); \
+ for (int i = 0; i < p_argcount; i++) { \
+ vars[i] = PtrToArg<Variant>::convert(p_args[i]); \
+ vars_ptrs[i] = &vars[i]; \
+ } \
+ Variant base = PtrToArg<m_class>::convert(p_base); \
+ Variant ret; \
+ Callable::CallError ce; \
+ m_method_ptr(&base, (const Variant **)&vars_ptrs[0], p_argcount, ret, ce); \
+ if (m_has_return) { \
+ m_return_type r = ret; \
+ PtrToArg<m_return_type>::encode(ret, r_ret); \
+ } \
+ } \
+ static int get_argument_count() { \
+ return 0; \
+ } \
+ static Variant::Type get_argument_type(int p_arg) { \
+ return Variant::NIL; \
+ } \
+ static Variant::Type get_return_type() { \
+ return GetTypeInfo<m_return_type>::VARIANT_TYPE; \
+ } \
+ static bool has_return_type() { \
+ return m_has_return; \
+ } \
+ static bool is_const() { \
+ return true; \
+ } \
+ static bool is_vararg() { \
+ return true; \
+ } \
+ static Variant::Type get_base_type() { \
+ return GetTypeInfo<m_class>::VARIANT_TYPE; \
+ } \
+ static StringName get_name() { \
+ return #m_method_name; \
+ } \
+ };
+struct _VariantCall {
static String func_PackedByteArray_get_string_from_ascii(PackedByteArray *p_instance) {
String s;
if (p_instance->size() > 0) {
@@ -703,18 +535,61 @@ struct _VariantCall {
};
_VariantCall::ConstantData *_VariantCall::constant_data = nullptr;
-_VariantCall::MethodMap *_VariantCall::type_internal_methods = nullptr;
-List<StringName> *_VariantCall::type_internal_method_names = nullptr;
-Variant Variant::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
- Variant ret;
- call_ptr(p_method, p_args, p_argcount, &ret, r_error);
- return ret;
-}
+struct VariantBuiltInMethodInfo {
+ void (*call)(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector<Variant> &p_defvals, Callable::CallError &r_error);
+ Variant::ValidatedBuiltInMethod validated_call;
+ Variant::PTRBuiltInMethod ptrcall;
-void Variant::call_ptr(const StringName &p_method, const Variant **p_args, int p_argcount, Variant *r_ret, Callable::CallError &r_error) {
- Variant ret;
+ Vector<Variant> default_arguments;
+ Vector<String> argument_names;
+ bool is_const;
+ bool has_return_type;
+ bool is_vararg;
+ Variant::Type return_type;
+ int argument_count;
+ Variant::Type (*get_argument_type)(int p_arg);
+};
+
+typedef OAHashMap<StringName, VariantBuiltInMethodInfo> BuiltinMethodMap;
+static BuiltinMethodMap *builtin_method_info;
+static List<StringName> *builtin_method_names;
+
+template <class T>
+static void register_builtin_method(const Vector<String> &p_argnames, const Vector<Variant> &p_def_args) {
+ StringName name = T::get_name();
+
+ ERR_FAIL_COND(builtin_method_info[T::get_base_type()].has(name));
+
+ VariantBuiltInMethodInfo imi;
+
+ imi.call = T::call;
+ imi.validated_call = T::validated_call;
+ if (T::is_vararg()) {
+ imi.ptrcall = nullptr;
+ } else {
+ imi.ptrcall = T::ptrcall;
+ }
+
+ imi.default_arguments = p_def_args;
+ imi.argument_names = p_argnames;
+
+ imi.is_const = T::is_const();
+ imi.is_vararg = T::is_vararg();
+ imi.has_return_type = T::has_return_type();
+ imi.return_type = T::get_return_type();
+ imi.argument_count = T::get_argument_count();
+ imi.get_argument_type = T::get_argument_type;
+#ifdef DEBUG_METHODS_ENABLED
+ ERR_FAIL_COND(!imi.is_vararg && imi.argument_count != imi.argument_names.size());
+#endif
+
+ builtin_method_info[T::get_base_type()].insert(name, imi);
+ builtin_method_names[T::get_base_type()].push_back(name);
+}
+
+void Variant::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) {
if (type == Variant::OBJECT) {
//call object
Object *obj = _get_obj().obj;
@@ -729,31 +604,24 @@ void Variant::call_ptr(const StringName &p_method, const Variant **p_args, int p
}
#endif
- ret = _get_obj().obj->call(p_method, p_args, p_argcount, r_error);
+ r_ret = _get_obj().obj->call(p_method, p_args, p_argcount, r_error);
//else if (type==Variant::METHOD) {
} else {
r_error.error = Callable::CallError::CALL_OK;
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[type].lookup_ptr(p_method);
+ const VariantBuiltInMethodInfo *imf = builtin_method_info[type].lookup_ptr(p_method);
- if (m) {
- (*m)->call((Variant *)this, p_args, p_argcount, ret, r_error);
- } else {
- //ok fail because not found
+ if (!imf) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return;
}
- }
- if (r_error.error == Callable::CallError::CALL_OK && r_ret) {
- *r_ret = ret;
+ imf->call(this, p_args, p_argcount, r_ret, imf->default_arguments, r_error);
}
}
-#define VCALL(m_type, m_method) _VariantCall::_call_##m_type##_##m_method
-
bool Variant::has_method(const StringName &p_method) const {
if (type == OBJECT) {
Object *obj = get_validated_object();
@@ -764,97 +632,143 @@ bool Variant::has_method(const StringName &p_method) const {
return obj->has_method(p_method);
}
- return _VariantCall::type_internal_methods[type].has(p_method);
+ return builtin_method_info[type].has(p_method);
}
-Vector<Variant::Type> Variant::get_method_argument_types(Variant::Type p_type, const StringName &p_method) {
- Vector<Variant::Type> types;
+bool Variant::has_builtin_method(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, false);
+ return builtin_method_info[p_type].has(p_method);
+}
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[p_type].lookup_ptr(p_method);
- if (*m) {
- types.resize((*m)->get_argument_count());
- for (int i = 0; i < (*m)->get_argument_count(); i++) {
- types.write[i] = (*m)->get_argument_type(i);
- }
- }
+Variant::ValidatedBuiltInMethod Variant::get_validated_builtin_method(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, nullptr);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, nullptr);
+ return method->validated_call;
+}
- return types;
+Variant::PTRBuiltInMethod Variant::get_ptr_builtin_method(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, nullptr);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, nullptr);
+ return method->ptrcall;
}
-bool Variant::is_method_const(Variant::Type p_type, const StringName &p_method) {
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[p_type].lookup_ptr(p_method);
- if (*m) {
- return (*m)->get_flags() & Variant::InternalMethod::FLAG_IS_CONST;
- }
- return false;
+int Variant::get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, 0);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, 0);
+ return method->argument_count;
}
-Vector<StringName> Variant::get_method_argument_names(Variant::Type p_type, const StringName &p_method) {
- Vector<StringName> argnames;
+Variant::Type Variant::get_builtin_method_argument_type(Variant::Type p_type, const StringName &p_method, int p_argument) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, Variant::NIL);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, Variant::NIL);
+ ERR_FAIL_INDEX_V(p_argument, method->argument_count, Variant::NIL);
+ return method->get_argument_type(p_argument);
+}
-#ifdef DEBUG_ENABLED
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[p_type].lookup_ptr(p_method);
- if (*m) {
- argnames.resize((*m)->get_argument_count());
- for (int i = 0; i < (*m)->get_argument_count(); i++) {
- argnames.write[i] = (*m)->get_argument_name(i);
- }
- }
+String Variant::get_builtin_method_argument_name(Variant::Type p_type, const StringName &p_method, int p_argument) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, String());
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, String());
+#ifdef DEBUG_METHODS_ENABLED
+ ERR_FAIL_INDEX_V(p_argument, method->argument_count, String());
+ return method->argument_names[p_argument];
+#else
+ return "arg" + itos(p_argument + 1);
#endif
- return argnames;
}
-Variant::Type Variant::get_method_return_type(Variant::Type p_type, const StringName &p_method, bool *r_has_return) {
- Variant::Type rt = Variant::NIL;
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[p_type].lookup_ptr(p_method);
- if (*m) {
- rt = (*m)->get_return_type();
- if (r_has_return) {
- *r_has_return = ((*m)->get_flags() & Variant::InternalMethod::FLAG_RETURNS_VARIANT) || rt != Variant::NIL;
- }
- }
- return rt;
+Vector<Variant> Variant::get_builtin_method_default_arguments(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, Vector<Variant>());
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, Vector<Variant>());
+ return method->default_arguments;
}
-Vector<Variant> Variant::get_method_default_arguments(Variant::Type p_type, const StringName &p_method) {
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[p_type].lookup_ptr(p_method);
- if (*m) {
- return (*m)->get_default_arguments();
+bool Variant::has_builtin_method_return_value(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, false);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, false);
+ return method->has_return_type;
+}
+
+void Variant::get_builtin_method_list(Variant::Type p_type, List<StringName> *p_list) {
+ ERR_FAIL_INDEX(p_type, Variant::VARIANT_MAX);
+ for (List<StringName>::Element *E = builtin_method_names[p_type].front(); E; E = E->next()) {
+ p_list->push_back(E->get());
}
- return Vector<Variant>();
+}
+
+Variant::Type Variant::get_builtin_method_return_type(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, Variant::NIL);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, Variant::NIL);
+ return method->return_type;
+}
+
+bool Variant::is_builtin_method_const(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, false);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, false);
+ return method->is_const;
+}
+
+bool Variant::is_builtin_method_vararg(Variant::Type p_type, const StringName &p_method) {
+ ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, false);
+ const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method);
+ ERR_FAIL_COND_V(!method, false);
+ return method->is_vararg;
}
void Variant::get_method_list(List<MethodInfo> *p_list) const {
- for (List<StringName>::Element *E = _VariantCall::type_internal_method_names[type].front(); E; E = E->next()) {
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[type].lookup_ptr(E->get());
- ERR_CONTINUE(!*m);
-
- MethodInfo mi;
- mi.name = E->get();
- mi.return_val.type = (*m)->get_return_type();
- if ((*m)->get_flags() & Variant::InternalMethod::FLAG_RETURNS_VARIANT) {
- mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- }
- if ((*m)->get_flags() & Variant::InternalMethod::FLAG_IS_CONST) {
- mi.flags |= METHOD_FLAG_CONST;
- }
- if ((*m)->get_flags() & Variant::InternalMethod::FLAG_VARARGS) {
- mi.flags |= METHOD_FLAG_VARARG;
+ if (type == OBJECT) {
+ Object *obj = get_validated_object();
+ if (obj) {
+ obj->get_method_list(p_list);
}
+ } else {
+ for (List<StringName>::Element *E = builtin_method_names[type].front(); E; E = E->next()) {
+ const VariantBuiltInMethodInfo *method = builtin_method_info[type].lookup_ptr(E->get());
+ ERR_CONTINUE(!method);
+
+ MethodInfo mi;
+ mi.name = E->get();
+
+ //return type
+ if (method->has_return_type) {
+ mi.return_val.type = method->return_type;
+ if (mi.return_val.type == Variant::NIL) {
+ mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
+ }
- for (int i = 0; i < (*m)->get_argument_count(); i++) {
- PropertyInfo arg;
-#ifdef DEBUG_ENABLED
- arg.name = (*m)->get_argument_name(i);
+ if (method->is_const) {
+ mi.flags |= METHOD_FLAG_CONST;
+ }
+ if (method->is_vararg) {
+ mi.flags |= METHOD_FLAG_VARARG;
+ }
+
+ for (int i = 0; i < method->argument_count; i++) {
+ PropertyInfo pi;
+#ifdef DEBUG_METHODS_ENABLED
+ pi.name = method->argument_names[i];
#else
- arg.name = "arg" + itos(i + 1);
+ pi.name = "arg" + itos(i + 1);
#endif
- arg.type = (*m)->get_argument_type(i);
- mi.arguments.push_back(arg);
- }
+ pi.type = method->get_argument_type(i);
+ if (pi.type == Variant::NIL) {
+ pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
+ mi.arguments.push_back(pi);
+ }
- mi.default_arguments = (*m)->get_default_arguments();
- p_list->push_back(mi);
+ mi.default_arguments = method->default_arguments;
+ p_list->push_back(mi);
+ }
}
}
@@ -915,20 +829,44 @@ Variant Variant::get_constant_value(Variant::Type p_type, const StringName &p_va
return E->get();
}
-Variant::InternalMethod *Variant::get_internal_method(Type p_type, const StringName &p_method_name) {
- ERR_FAIL_INDEX_V(p_type, VARIANT_MAX, nullptr);
+#ifdef DEBUG_METHODS_ENABLED
+#define bind_method(m_type, m_method, m_arg_names, m_default_args) \
+ METHOD_CLASS(m_type, m_method, &m_type::m_method); \
+ register_builtin_method<Method_##m_type##_##m_method>(m_arg_names, m_default_args);
+#else
+#define bind_method(m_type, m_method, m_arg_names, m_default_args) \
+ METHOD_CLASS(m_type, m_method, &m_type ::m_method); \
+ register_builtin_method<Method_##m_type##_##m_method>(sarray(), m_default_args);
+#endif
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[p_type].lookup_ptr(p_method_name);
- if (*m) {
- return *m;
- }
- return nullptr;
-}
+#ifdef DEBUG_METHODS_ENABLED
+#define bind_methodv(m_type, m_name, m_method, m_arg_names, m_default_args) \
+ METHOD_CLASS(m_type, m_name, m_method); \
+ register_builtin_method<Method_##m_type##_##m_name>(m_arg_names, m_default_args);
+#else
+#define bind_methodv(m_type, m_name, m_method, m_arg_names, m_default_args) \
+ METHOD_CLASS(m_type, m_name, m_method); \
+ register_builtin_method<Method_##m_type##_##m_name>(sarray(), m_default_args);
+#endif
-void Variant::_register_variant_methods() {
- _VariantCall::type_internal_methods = memnew_arr(_VariantCall::MethodMap, Variant::VARIANT_MAX);
- _VariantCall::type_internal_method_names = memnew_arr(List<StringName>, Variant::VARIANT_MAX);
+#ifdef DEBUG_METHODS_ENABLED
+#define bind_function(m_type, m_name, m_method, m_arg_names, m_default_args) \
+ FUNCTION_CLASS(m_type, m_name, m_method); \
+ register_builtin_method<Method_##m_type##_##m_name>(m_arg_names, m_default_args);
+#else
+#define bind_function(m_type, m_name, m_method, m_arg_names, m_default_args) \
+ FUNCTION_CLASS(m_type, m_name, m_method); \
+ register_builtin_method<Method_##m_type##_##m_name>(sarray(), m_default_args);
+#endif
+
+#define bind_custom(m_type, m_name, m_method, m_has_return, m_ret_type) \
+ VARARG_CLASS(m_type, m_name, m_method, m_has_return, m_ret_type) \
+ register_builtin_method<Method_##m_type##_##m_name>(sarray(), Vector<Variant>());
+
+static void _register_variant_builtin_methods() {
_VariantCall::constant_data = memnew_arr(_VariantCall::ConstantData, Variant::VARIANT_MAX);
+ builtin_method_info = memnew_arr(BuiltinMethodMap, Variant::VARIANT_MAX);
+ builtin_method_names = memnew_arr(List<StringName>, Variant::VARIANT_MAX);
/* String */
@@ -937,7 +875,7 @@ void Variant::_register_variant_methods() {
bind_method(String, naturalnocasecmp_to, sarray("to"), varray());
bind_method(String, length, sarray(), varray());
bind_method(String, substr, sarray("from", "len"), varray(-1));
- bind_methodv(find, static_cast<int (String::*)(const String &, int) const>(&String::find), sarray("what", "from"), varray(0));
+ bind_methodv(String, find, static_cast<int (String::*)(const String &, int) const>(&String::find), sarray("what", "from"), varray(0));
bind_method(String, count, sarray("what", "from", "to"), varray(0, 0));
bind_method(String, countn, sarray("what", "from", "to"), varray(0, 0));
bind_method(String, findn, sarray("what", "from"), varray(0));
@@ -945,7 +883,7 @@ void Variant::_register_variant_methods() {
bind_method(String, rfindn, sarray("what", "from"), varray(-1));
bind_method(String, match, sarray("expr"), varray());
bind_method(String, matchn, sarray("expr"), varray());
- bind_methodv(begins_with, static_cast<bool (String::*)(const String &) const>(&String::begins_with), sarray("text"), varray());
+ bind_methodv(String, begins_with, static_cast<bool (String::*)(const String &) const>(&String::begins_with), sarray("text"), varray());
bind_method(String, ends_with, sarray("text"), varray());
bind_method(String, is_subsequence_of, sarray("text"), varray());
bind_method(String, is_subsequence_ofi, sarray("text"), varray());
@@ -953,7 +891,7 @@ void Variant::_register_variant_methods() {
bind_method(String, similarity, sarray("text"), varray());
bind_method(String, format, sarray("values", "placeholder"), varray("{_}"));
- bind_methodv(replace, static_cast<String (String::*)(const String &, const String &) const>(&String::replace), sarray("what", "forwhat"), varray());
+ bind_methodv(String, replace, static_cast<String (String::*)(const String &, const String &) const>(&String::replace), sarray("what", "forwhat"), varray());
bind_method(String, replacen, sarray("what", "forwhat"), varray());
bind_method(String, repeat, sarray("count"), varray());
bind_method(String, insert, sarray("position", "what"), varray());
@@ -1084,7 +1022,7 @@ void Variant::_register_variant_methods() {
bind_method(Rect2, merge, sarray("b"), varray());
bind_method(Rect2, expand, sarray("to"), varray());
bind_method(Rect2, grow, sarray("by"), varray());
- bind_methodv(grow_margin, &Rect2::grow_margin_bind, sarray("margin", "by"), varray());
+ bind_methodv(Rect2, grow_margin, &Rect2::grow_margin_bind, sarray("margin", "by"), varray());
bind_method(Rect2, grow_individual, sarray("left", "top", "right", "bottom"), varray());
bind_method(Rect2, abs, sarray(), varray());
@@ -1099,7 +1037,7 @@ void Variant::_register_variant_methods() {
bind_method(Rect2i, merge, sarray("b"), varray());
bind_method(Rect2i, expand, sarray("to"), varray());
bind_method(Rect2i, grow, sarray("by"), varray());
- bind_methodv(grow_margin, &Rect2i::grow_margin_bind, sarray("margin", "by"), varray());
+ bind_methodv(Rect2i, grow_margin, &Rect2i::grow_margin_bind, sarray("margin", "by"), varray());
bind_method(Rect2i, grow_individual, sarray("left", "top", "right", "bottom"), varray());
bind_method(Rect2i, abs, sarray(), varray());
@@ -1155,9 +1093,9 @@ void Variant::_register_variant_methods() {
bind_method(Plane, distance_to, sarray("point"), varray());
bind_method(Plane, has_point, sarray("point", "epsilon"), varray(CMP_EPSILON));
bind_method(Plane, project, sarray("point"), varray());
- bind_methodv(intersect_3, &Plane::intersect_3_bind, sarray("b", "c"), varray());
- bind_methodv(intersects_ray, &Plane::intersects_ray_bind, sarray("from", "dir"), varray());
- bind_methodv(intersects_segment, &Plane::intersects_segment_bind, sarray("from", "to"), varray());
+ bind_methodv(Plane, intersect_3, &Plane::intersect_3_bind, sarray("b", "c"), varray());
+ bind_methodv(Plane, intersects_ray, &Plane::intersects_ray_bind, sarray("from", "dir"), varray());
+ bind_methodv(Plane, intersects_segment, &Plane::intersects_segment_bind, sarray("from", "to"), varray());
/* Quat */
@@ -1199,7 +1137,7 @@ void Variant::_register_variant_methods() {
/* RID */
- bind_method(::RID, get_id, sarray(), varray());
+ bind_method(RID, get_id, sarray(), varray());
/* NodePath */
@@ -1223,9 +1161,9 @@ void Variant::_register_variant_methods() {
bind_method(Callable, hash, sarray(), varray());
bind_method(Callable, unbind, sarray("argcount"), varray());
- bind_custom(Variant::CALLABLE, "call", _VariantCall::func_Callable_call, Variant::InternalMethod::FLAG_VARARGS | Variant::InternalMethod::FLAG_RETURNS_VARIANT, Vector<Variant::Type>(), Variant::NIL, sarray());
- bind_custom(Variant::CALLABLE, "call_deferred", _VariantCall::func_Callable_call_deferred, Variant::InternalMethod::FLAG_VARARGS, Vector<Variant::Type>(), Variant::NIL, sarray());
- bind_custom(Variant::CALLABLE, "bind", _VariantCall::func_Callable_bind, Variant::InternalMethod::FLAG_VARARGS, Vector<Variant::Type>(), Variant::CALLABLE, sarray());
+ bind_custom(Callable, call, _VariantCall::func_Callable_call, true, Variant);
+ bind_custom(Callable, call_deferred, _VariantCall::func_Callable_call_deferred, false, Variant);
+ bind_custom(Callable, bind, _VariantCall::func_Callable_bind, true, Callable);
/* Signal */
@@ -1239,7 +1177,7 @@ void Variant::_register_variant_methods() {
bind_method(Signal, is_connected, sarray("callable"), varray());
bind_method(Signal, get_connections, sarray(), varray());
- bind_custom(Variant::SIGNAL, "emit", _VariantCall::func_Signal_emit, Variant::InternalMethod::FLAG_VARARGS, Vector<Variant::Type>(), Variant::NIL, sarray());
+ bind_custom(Signal, emit, _VariantCall::func_Signal_emit, false, Variant);
/* Transform2D */
@@ -1263,7 +1201,7 @@ void Variant::_register_variant_methods() {
bind_method(Basis, transposed, sarray(), varray());
bind_method(Basis, orthonormalized, sarray(), varray());
bind_method(Basis, determinant, sarray(), varray());
- bind_methodv(rotated, static_cast<Basis (Basis::*)(const Vector3 &, float) const>(&Basis::rotated), sarray("axis", "phi"), varray());
+ bind_methodv(Basis, rotated, static_cast<Basis (Basis::*)(const Vector3 &, float) const>(&Basis::rotated), sarray("axis", "phi"), varray());
bind_method(Basis, scaled, sarray("scale"), varray());
bind_method(Basis, get_scale, sarray(), varray());
bind_method(Basis, get_euler, sarray(), varray());
@@ -1277,29 +1215,29 @@ void Variant::_register_variant_methods() {
/* AABB */
- bind_method(::AABB, abs, sarray(), varray());
- bind_method(::AABB, get_area, sarray(), varray());
- bind_method(::AABB, has_no_area, sarray(), varray());
- bind_method(::AABB, has_no_surface, sarray(), varray());
- bind_method(::AABB, has_point, sarray("point"), varray());
- bind_method(::AABB, is_equal_approx, sarray("aabb"), varray());
- bind_method(::AABB, intersects, sarray("with"), varray());
- bind_method(::AABB, encloses, sarray("with"), varray());
- bind_method(::AABB, intersects_plane, sarray("plane"), varray());
- bind_method(::AABB, intersection, sarray("with"), varray());
- bind_method(::AABB, merge, sarray("with"), varray());
- bind_method(::AABB, expand, sarray("to_point"), varray());
- bind_method(::AABB, grow, sarray("by"), varray());
- bind_method(::AABB, get_support, sarray("dir"), varray());
- bind_method(::AABB, get_longest_axis, sarray(), varray());
- bind_method(::AABB, get_longest_axis_index, sarray(), varray());
- bind_method(::AABB, get_longest_axis_size, sarray(), varray());
- bind_method(::AABB, get_shortest_axis, sarray(), varray());
- bind_method(::AABB, get_shortest_axis_index, sarray(), varray());
- bind_method(::AABB, get_shortest_axis_size, sarray(), varray());
- bind_method(::AABB, get_endpoint, sarray("idx"), varray());
- bind_methodv(intersects_segment, &AABB::intersects_segment_bind, sarray("from", "to"), varray());
- bind_methodv(intersects_ray, &AABB::intersects_ray_bind, sarray("from", "dir"), varray());
+ bind_method(AABB, abs, sarray(), varray());
+ bind_method(AABB, get_area, sarray(), varray());
+ bind_method(AABB, has_no_area, sarray(), varray());
+ bind_method(AABB, has_no_surface, sarray(), varray());
+ bind_method(AABB, has_point, sarray("point"), varray());
+ bind_method(AABB, is_equal_approx, sarray("aabb"), varray());
+ bind_method(AABB, intersects, sarray("with"), varray());
+ bind_method(AABB, encloses, sarray("with"), varray());
+ bind_method(AABB, intersects_plane, sarray("plane"), varray());
+ bind_method(AABB, intersection, sarray("with"), varray());
+ bind_method(AABB, merge, sarray("with"), varray());
+ bind_method(AABB, expand, sarray("to_point"), varray());
+ bind_method(AABB, grow, sarray("by"), varray());
+ bind_method(AABB, get_support, sarray("dir"), varray());
+ bind_method(AABB, get_longest_axis, sarray(), varray());
+ bind_method(AABB, get_longest_axis_index, sarray(), varray());
+ bind_method(AABB, get_longest_axis_size, sarray(), varray());
+ bind_method(AABB, get_shortest_axis, sarray(), varray());
+ bind_method(AABB, get_shortest_axis_index, sarray(), varray());
+ bind_method(AABB, get_shortest_axis_size, sarray(), varray());
+ bind_method(AABB, get_endpoint, sarray("idx"), varray());
+ bind_methodv(AABB, intersects_segment, &AABB::intersects_segment_bind, sarray("from", "to"), varray());
+ bind_methodv(AABB, intersects_ray, &AABB::intersects_ray_bind, sarray("from", "dir"), varray());
/* Transform */
@@ -1376,14 +1314,14 @@ void Variant::_register_variant_methods() {
bind_method(PackedByteArray, subarray, sarray("from", "to"), varray());
bind_method(PackedByteArray, sort, sarray(), varray());
- bind_function("get_string_from_ascii", _VariantCall::func_PackedByteArray_get_string_from_ascii, sarray(), varray());
- bind_function("get_string_from_utf8", _VariantCall::func_PackedByteArray_get_string_from_utf8, sarray(), varray());
- bind_function("get_string_from_utf16", _VariantCall::func_PackedByteArray_get_string_from_utf16, sarray(), varray());
- bind_function("get_string_from_utf32", _VariantCall::func_PackedByteArray_get_string_from_utf32, sarray(), varray());
- bind_function("hex_encode", _VariantCall::func_PackedByteArray_hex_encode, sarray(), varray());
- bind_function("compress", _VariantCall::func_PackedByteArray_compress, sarray("compression_mode"), varray(0));
- bind_function("decompress", _VariantCall::func_PackedByteArray_decompress, sarray("buffer_size", "compression_mode"), varray(0));
- bind_function("decompress_dynamic", _VariantCall::func_PackedByteArray_decompress_dynamic, sarray("max_output_size", "compression_mode"), varray(0));
+ bind_function(PackedByteArray, get_string_from_ascii, _VariantCall::func_PackedByteArray_get_string_from_ascii, sarray(), varray());
+ bind_function(PackedByteArray, get_string_from_utf8, _VariantCall::func_PackedByteArray_get_string_from_utf8, sarray(), varray());
+ bind_function(PackedByteArray, get_string_from_utf16, _VariantCall::func_PackedByteArray_get_string_from_utf16, sarray(), varray());
+ bind_function(PackedByteArray, get_string_from_utf32, _VariantCall::func_PackedByteArray_get_string_from_utf32, sarray(), varray());
+ bind_function(PackedByteArray, hex_encode, _VariantCall::func_PackedByteArray_hex_encode, sarray(), varray());
+ bind_function(PackedByteArray, compress, _VariantCall::func_PackedByteArray_compress, sarray("compression_mode"), varray(0));
+ bind_function(PackedByteArray, decompress, _VariantCall::func_PackedByteArray_decompress, sarray("buffer_size", "compression_mode"), varray(0));
+ bind_function(PackedByteArray, decompress_dynamic, _VariantCall::func_PackedByteArray_decompress_dynamic, sarray("max_output_size", "compression_mode"), varray(0));
/* Int32 Array */
@@ -1605,18 +1543,13 @@ void Variant::_register_variant_methods() {
_VariantCall::add_variant_constant(Variant::QUAT, "IDENTITY", Quat(0, 0, 0, 1));
}
+void Variant::_register_variant_methods() {
+ _register_variant_builtin_methods(); //needs to be out due to namespace
+}
+
void Variant::_unregister_variant_methods() {
//clear methods
- for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- for (List<StringName>::Element *E = _VariantCall::type_internal_method_names[i].front(); E; E = E->next()) {
- Variant::InternalMethod **m = _VariantCall::type_internal_methods[i].lookup_ptr(E->get());
- if (*m) {
- memdelete(*m);
- }
- }
- }
-
- memdelete_arr(_VariantCall::type_internal_methods);
- memdelete_arr(_VariantCall::type_internal_method_names);
+ memdelete_arr(builtin_method_names);
+ memdelete_arr(builtin_method_info);
memdelete_arr(_VariantCall::constant_data);
}
diff --git a/core/variant/variant_builtin_funcs.cpp b/core/variant/variant_utility.cpp
index e89b83e6dd..91a1b0262c 100644
--- a/core/variant/variant_builtin_funcs.cpp
+++ b/core/variant/variant_utility.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* variant_builtin_funcs.cpp */
+/* variant_utility.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -38,7 +38,7 @@
#include "core/variant/binder_common.h"
#include "core/variant/variant_parser.h"
-struct VariantBuiltinFunctions {
+struct VariantUtilityFunctions {
// Math
static inline double sin(double arg) {
return Math::sin(arg);
@@ -725,7 +725,7 @@ template <class R, class... P, size_t... Is>
static _FORCE_INLINE_ void call_helperpr(R (*p_func)(P...), Variant *ret, const Variant **p_args, Callable::CallError &r_error, IndexSequence<Is...>) {
r_error.error = Callable::CallError::CALL_OK;
VCALLR;
- (void)p_args;
+ (void)p_args; // avoid gcc warning
(void)r_error;
}
@@ -827,50 +827,50 @@ static _FORCE_INLINE_ Variant::Type get_ret_type_helper(void (*p_func)(P...)) {
class Func_##m_func { \
public: \
static void call(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { \
- call_helperr(VariantBuiltinFunctions::m_func, r_ret, p_args, r_error); \
+ call_helperr(VariantUtilityFunctions::m_func, r_ret, p_args, r_error); \
} \
\
static void validated_call(Variant *r_ret, const Variant **p_args, int p_argcount) { \
- validated_call_helperr(VariantBuiltinFunctions::m_func, r_ret, p_args); \
+ validated_call_helperr(VariantUtilityFunctions::m_func, r_ret, p_args); \
} \
static void ptrcall(void *ret, const void **p_args, int p_argcount) { \
- ptr_call_helperr(VariantBuiltinFunctions::m_func, ret, p_args); \
+ ptr_call_helperr(VariantUtilityFunctions::m_func, ret, p_args); \
} \
\
static int get_argument_count() { \
- return get_arg_count_helperr(VariantBuiltinFunctions::m_func); \
+ return get_arg_count_helperr(VariantUtilityFunctions::m_func); \
} \
\
static Variant::Type get_argument_type(int p_arg) { \
- return get_arg_type_helperr(VariantBuiltinFunctions::m_func, p_arg); \
+ return get_arg_type_helperr(VariantUtilityFunctions::m_func, p_arg); \
} \
\
static Variant::Type get_return_type() { \
- return get_ret_type_helperr(VariantBuiltinFunctions::m_func); \
+ return get_ret_type_helperr(VariantUtilityFunctions::m_func); \
} \
static bool has_return_type() { \
return true; \
} \
static bool is_vararg() { return false; } \
- static Variant::BuiltInFunctionType get_type() { return m_category; } \
+ static Variant::UtilityFunctionType get_type() { return m_category; } \
}; \
- register_builtin_function<Func_##m_func>(#m_func, m_args)
+ register_utility_function<Func_##m_func>(#m_func, m_args)
#define FUNCBINDVR(m_func, m_args, m_category) \
class Func_##m_func { \
public: \
static void call(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { \
r_error.error = Callable::CallError::CALL_OK; \
- *r_ret = VariantBuiltinFunctions::m_func(*p_args[0], r_error); \
+ *r_ret = VariantUtilityFunctions::m_func(*p_args[0], r_error); \
} \
\
static void validated_call(Variant *r_ret, const Variant **p_args, int p_argcount) { \
Callable::CallError ce; \
- *r_ret = VariantBuiltinFunctions::m_func(*p_args[0], ce); \
+ *r_ret = VariantUtilityFunctions::m_func(*p_args[0], ce); \
} \
static void ptrcall(void *ret, const void **p_args, int p_argcount) { \
Callable::CallError ce; \
- PtrToArg<Variant>::encode(VariantBuiltinFunctions::m_func(PtrToArg<Variant>::convert(p_args[0]), ce), ret); \
+ PtrToArg<Variant>::encode(VariantUtilityFunctions::m_func(PtrToArg<Variant>::convert(p_args[0]), ce), ret); \
} \
\
static int get_argument_count() { \
@@ -888,26 +888,26 @@ static _FORCE_INLINE_ Variant::Type get_ret_type_helper(void (*p_func)(P...)) {
return true; \
} \
static bool is_vararg() { return false; } \
- static Variant::BuiltInFunctionType get_type() { return m_category; } \
+ static Variant::UtilityFunctionType get_type() { return m_category; } \
}; \
- register_builtin_function<Func_##m_func>(#m_func, m_args)
+ register_utility_function<Func_##m_func>(#m_func, m_args)
#define FUNCBINDVR3(m_func, m_args, m_category) \
class Func_##m_func { \
public: \
static void call(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { \
r_error.error = Callable::CallError::CALL_OK; \
- *r_ret = VariantBuiltinFunctions::m_func(*p_args[0], *p_args[1], *p_args[2], r_error); \
+ *r_ret = VariantUtilityFunctions::m_func(*p_args[0], *p_args[1], *p_args[2], r_error); \
} \
\
static void validated_call(Variant *r_ret, const Variant **p_args, int p_argcount) { \
Callable::CallError ce; \
- *r_ret = VariantBuiltinFunctions::m_func(*p_args[0], *p_args[1], *p_args[2], ce); \
+ *r_ret = VariantUtilityFunctions::m_func(*p_args[0], *p_args[1], *p_args[2], ce); \
} \
static void ptrcall(void *ret, const void **p_args, int p_argcount) { \
Callable::CallError ce; \
Variant r; \
- r = VariantBuiltinFunctions::m_func(PtrToArg<Variant>::convert(p_args[0]), PtrToArg<Variant>::convert(p_args[1]), PtrToArg<Variant>::convert(p_args[2]), ce); \
+ r = VariantUtilityFunctions::m_func(PtrToArg<Variant>::convert(p_args[0]), PtrToArg<Variant>::convert(p_args[1]), PtrToArg<Variant>::convert(p_args[2]), ce); \
PtrToArg<Variant>::encode(r, ret); \
} \
\
@@ -926,21 +926,21 @@ static _FORCE_INLINE_ Variant::Type get_ret_type_helper(void (*p_func)(P...)) {
return true; \
} \
static bool is_vararg() { return false; } \
- static Variant::BuiltInFunctionType get_type() { return m_category; } \
+ static Variant::UtilityFunctionType get_type() { return m_category; } \
}; \
- register_builtin_function<Func_##m_func>(#m_func, m_args)
+ register_utility_function<Func_##m_func>(#m_func, m_args)
#define FUNCBINDVARARG(m_func, m_args, m_category) \
class Func_##m_func { \
public: \
static void call(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { \
r_error.error = Callable::CallError::CALL_OK; \
- *r_ret = VariantBuiltinFunctions::m_func(p_args, p_argcount, r_error); \
+ *r_ret = VariantUtilityFunctions::m_func(p_args, p_argcount, r_error); \
} \
\
static void validated_call(Variant *r_ret, const Variant **p_args, int p_argcount) { \
Callable::CallError c; \
- *r_ret = VariantBuiltinFunctions::m_func(p_args, p_argcount, c); \
+ *r_ret = VariantUtilityFunctions::m_func(p_args, p_argcount, c); \
} \
static void ptrcall(void *ret, const void **p_args, int p_argcount) { \
Vector<Variant> args; \
@@ -973,23 +973,23 @@ static _FORCE_INLINE_ Variant::Type get_ret_type_helper(void (*p_func)(P...)) {
static bool is_vararg() { \
return true; \
} \
- static Variant::BuiltInFunctionType get_type() { \
+ static Variant::UtilityFunctionType get_type() { \
return m_category; \
} \
}; \
- register_builtin_function<Func_##m_func>(#m_func, m_args)
+ register_utility_function<Func_##m_func>(#m_func, m_args)
#define FUNCBINDVARARGS(m_func, m_args, m_category) \
class Func_##m_func { \
public: \
static void call(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { \
r_error.error = Callable::CallError::CALL_OK; \
- *r_ret = VariantBuiltinFunctions::m_func(p_args, p_argcount, r_error); \
+ *r_ret = VariantUtilityFunctions::m_func(p_args, p_argcount, r_error); \
} \
\
static void validated_call(Variant *r_ret, const Variant **p_args, int p_argcount) { \
Callable::CallError c; \
- *r_ret = VariantBuiltinFunctions::m_func(p_args, p_argcount, c); \
+ *r_ret = VariantUtilityFunctions::m_func(p_args, p_argcount, c); \
} \
static void ptrcall(void *ret, const void **p_args, int p_argcount) { \
Vector<Variant> args; \
@@ -1022,23 +1022,23 @@ static _FORCE_INLINE_ Variant::Type get_ret_type_helper(void (*p_func)(P...)) {
static bool is_vararg() { \
return true; \
} \
- static Variant::BuiltInFunctionType get_type() { \
+ static Variant::UtilityFunctionType get_type() { \
return m_category; \
} \
}; \
- register_builtin_function<Func_##m_func>(#m_func, m_args)
+ register_utility_function<Func_##m_func>(#m_func, m_args)
#define FUNCBINDVARARGV(m_func, m_args, m_category) \
class Func_##m_func { \
public: \
static void call(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { \
r_error.error = Callable::CallError::CALL_OK; \
- VariantBuiltinFunctions::m_func(p_args, p_argcount, r_error); \
+ VariantUtilityFunctions::m_func(p_args, p_argcount, r_error); \
} \
\
static void validated_call(Variant *r_ret, const Variant **p_args, int p_argcount) { \
Callable::CallError c; \
- VariantBuiltinFunctions::m_func(p_args, p_argcount, c); \
+ VariantUtilityFunctions::m_func(p_args, p_argcount, c); \
} \
static void ptrcall(void *ret, const void **p_args, int p_argcount) { \
Vector<Variant> args; \
@@ -1070,218 +1070,218 @@ static _FORCE_INLINE_ Variant::Type get_ret_type_helper(void (*p_func)(P...)) {
static bool is_vararg() { \
return true; \
} \
- static Variant::BuiltInFunctionType get_type() { \
+ static Variant::UtilityFunctionType get_type() { \
return m_category; \
} \
}; \
- register_builtin_function<Func_##m_func>(#m_func, m_args)
+ register_utility_function<Func_##m_func>(#m_func, m_args)
#define FUNCBIND(m_func, m_args, m_category) \
class Func_##m_func { \
public: \
static void call(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { \
- call_helper(VariantBuiltinFunctions::m_func, p_args, r_error); \
+ call_helper(VariantUtilityFunctions::m_func, p_args, r_error); \
} \
\
static void validated_call(Variant *r_ret, const Variant **p_args, int p_argcount) { \
- validated_call_helper(VariantBuiltinFunctions::m_func, p_args); \
+ validated_call_helper(VariantUtilityFunctions::m_func, p_args); \
} \
static void ptrcall(void *ret, const void **p_args, int p_argcount) { \
- ptr_call_helper(VariantBuiltinFunctions::m_func, p_args); \
+ ptr_call_helper(VariantUtilityFunctions::m_func, p_args); \
} \
\
static int get_argument_count() { \
- return get_arg_count_helper(VariantBuiltinFunctions::m_func); \
+ return get_arg_count_helper(VariantUtilityFunctions::m_func); \
} \
\
static Variant::Type get_argument_type(int p_arg) { \
- return get_arg_type_helper(VariantBuiltinFunctions::m_func, p_arg); \
+ return get_arg_type_helper(VariantUtilityFunctions::m_func, p_arg); \
} \
\
static Variant::Type get_return_type() { \
- return get_ret_type_helper(VariantBuiltinFunctions::m_func); \
+ return get_ret_type_helper(VariantUtilityFunctions::m_func); \
} \
static bool has_return_type() { \
return false; \
} \
static bool is_vararg() { return false; } \
- static Variant::BuiltInFunctionType get_type() { return m_category; } \
+ static Variant::UtilityFunctionType get_type() { return m_category; } \
}; \
- register_builtin_function<Func_##m_func>(#m_func, m_args)
+ register_utility_function<Func_##m_func>(#m_func, m_args)
-struct VariantBuiltinFunctionInfo {
- void (*call_builtin)(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
- Variant::BuiltinFunctionValidatedCall validated_call_builtin;
- Variant::BuiltinFunctionPTRCall ptr_call_builtin;
+struct VariantUtilityFunctionInfo {
+ void (*call_utility)(Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
+ Variant::ValidatedUtilityFunction validated_call_utility;
+ Variant::PTRUtilityFunction ptr_call_utility;
Vector<String> argnames;
bool is_vararg;
bool returns_value;
int argcount;
Variant::Type (*get_arg_type)(int);
Variant::Type return_type;
- Variant::BuiltInFunctionType type;
+ Variant::UtilityFunctionType type;
};
-static OAHashMap<StringName, VariantBuiltinFunctionInfo> builtin_function_table;
-static List<StringName> builtin_function_name_table;
+static OAHashMap<StringName, VariantUtilityFunctionInfo> utility_function_table;
+static List<StringName> utility_function_name_table;
template <class T>
-static void register_builtin_function(const String &p_name, const Vector<String> &argnames) {
+static void register_utility_function(const String &p_name, const Vector<String> &argnames) {
String name = p_name;
if (name.begins_with("_")) {
name = name.substr(1, name.length() - 1);
}
StringName sname = name;
- ERR_FAIL_COND(builtin_function_table.has(sname));
+ ERR_FAIL_COND(utility_function_table.has(sname));
- VariantBuiltinFunctionInfo bfi;
- bfi.call_builtin = T::call;
- bfi.validated_call_builtin = T::validated_call;
- bfi.ptr_call_builtin = T::ptrcall;
+ VariantUtilityFunctionInfo bfi;
+ bfi.call_utility = T::call;
+ bfi.validated_call_utility = T::validated_call;
+ bfi.ptr_call_utility = T::ptrcall;
bfi.is_vararg = T::is_vararg();
bfi.argnames = argnames;
bfi.argcount = T::get_argument_count();
if (!bfi.is_vararg) {
- ERR_FAIL_COND_MSG(argnames.size() != bfi.argcount, "wrong number of arguments binding builtin function: " + name);
+ ERR_FAIL_COND_MSG(argnames.size() != bfi.argcount, "wrong number of arguments binding utility function: " + name);
}
bfi.get_arg_type = T::get_argument_type;
bfi.return_type = T::get_return_type();
bfi.type = T::get_type();
bfi.returns_value = T::has_return_type();
- builtin_function_table.insert(sname, bfi);
- builtin_function_name_table.push_back(sname);
+ utility_function_table.insert(sname, bfi);
+ utility_function_name_table.push_back(sname);
}
-void Variant::_register_variant_builtin_funcs() {
+void Variant::_register_variant_utility_functions() {
// Math
- FUNCBINDR(sin, sarray("angle_rad"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(cos, sarray("angle_rad"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(tan, sarray("angle_rad"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(sin, sarray("angle_rad"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(cos, sarray("angle_rad"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(tan, sarray("angle_rad"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(sinh, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(cosh, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(tanh, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(sinh, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(cosh, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(tanh, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(asin, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(acos, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(atan, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(asin, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(acos, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(atan, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(atan2, sarray("y", "x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(atan2, sarray("y", "x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(sqrt, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(fmod, sarray("x", "y"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(fposmod, sarray("x", "y"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(floor, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(ceil, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(round, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(sqrt, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(fmod, sarray("x", "y"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(fposmod, sarray("x", "y"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(floor, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(ceil, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(round, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDVR(abs, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDVR(abs, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(absf, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(absi, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(absf, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(absi, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDVR(sign, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDVR(sign, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(signf, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(signi, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(signf, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(signi, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(pow, sarray("x", "y"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(log, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(exp, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(pow, sarray("x", "y"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(log, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(exp, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(is_nan, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(is_inf, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(is_nan, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(is_inf, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(is_equal_approx, sarray("a", "b"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(is_zero_approx, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(is_equal_approx, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(is_zero_approx, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(ease, sarray("x", "c"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(step_decimals, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(range_step_decimals, sarray("x"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(stepify, sarray("x", "y"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(ease, sarray("x", "c"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(step_decimals, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(range_step_decimals, sarray("x"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(stepify, sarray("x", "y"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(lerp, sarray("from", "to", "c"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(lerp_angle, sarray("from", "to", "c"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(inverse_lerp, sarray("from", "to", "c"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(range_lerp, sarray("value", "istart", "istop", "ostart", "ostop"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(lerp, sarray("from", "to", "c"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(lerp_angle, sarray("from", "to", "c"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(inverse_lerp, sarray("from", "to", "c"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(range_lerp, sarray("value", "istart", "istop", "ostart", "ostop"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(smoothstep, sarray("from", "to", "c"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(move_toward, sarray("from", "to", "delta"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(dectime, sarray("value", "amount", "step"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(smoothstep, sarray("from", "to", "c"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(move_toward, sarray("from", "to", "delta"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(dectime, sarray("value", "amount", "step"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(deg2rad, sarray("deg"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(rad2deg, sarray("rad"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(linear2db, sarray("lin"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(db2linear, sarray("db"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(deg2rad, sarray("deg"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(rad2deg, sarray("rad"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(linear2db, sarray("lin"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(db2linear, sarray("db"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(polar2cartesian, sarray("r", "th"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(cartesian2polar, sarray("x", "y"), Variant::BUILTIN_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::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(wrapf, sarray("value", "min", "max"), Variant::BUILTIN_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);
- FUNCBINDVARARG(max, sarray(), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDVARARG(max, sarray(), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(maxi, sarray("a", "b"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(maxf, sarray("a", "b"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(maxi, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(maxf, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDVARARG(min, sarray(), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDVARARG(min, sarray(), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(mini, sarray("a", "b"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(minf, sarray("a", "b"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(mini, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(minf, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDVR3(clamp, sarray("value", "min", "max"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(clampi, sarray("value", "min", "max"), Variant::BUILTIN_FUNC_TYPE_MATH);
- FUNCBINDR(clampf, sarray("value", "min", "max"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDVR3(clamp, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(clampi, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
+ FUNCBINDR(clampf, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(nearest_po2, sarray("value"), Variant::BUILTIN_FUNC_TYPE_MATH);
+ FUNCBINDR(nearest_po2, sarray("value"), Variant::UTILITY_FUNC_TYPE_MATH);
//Random
- FUNCBIND(randomize, sarray(), Variant::BUILTIN_FUNC_TYPE_RANDOM);
- FUNCBINDR(randi, sarray(), Variant::BUILTIN_FUNC_TYPE_RANDOM);
- FUNCBINDR(randf, sarray(), Variant::BUILTIN_FUNC_TYPE_RANDOM);
- FUNCBINDR(randi_range, sarray("from", "to"), Variant::BUILTIN_FUNC_TYPE_RANDOM);
- FUNCBINDR(randf_range, sarray("from", "to"), Variant::BUILTIN_FUNC_TYPE_RANDOM);
- FUNCBIND(seed, sarray("base"), Variant::BUILTIN_FUNC_TYPE_RANDOM);
- FUNCBINDR(rand_from_seed, sarray("seed"), Variant::BUILTIN_FUNC_TYPE_RANDOM);
+ FUNCBIND(randomize, sarray(), Variant::UTILITY_FUNC_TYPE_RANDOM);
+ FUNCBINDR(randi, sarray(), Variant::UTILITY_FUNC_TYPE_RANDOM);
+ FUNCBINDR(randf, sarray(), Variant::UTILITY_FUNC_TYPE_RANDOM);
+ FUNCBINDR(randi_range, sarray("from", "to"), Variant::UTILITY_FUNC_TYPE_RANDOM);
+ FUNCBINDR(randf_range, sarray("from", "to"), Variant::UTILITY_FUNC_TYPE_RANDOM);
+ FUNCBIND(seed, sarray("base"), Variant::UTILITY_FUNC_TYPE_RANDOM);
+ FUNCBINDR(rand_from_seed, sarray("seed"), Variant::UTILITY_FUNC_TYPE_RANDOM);
// Utility
- FUNCBINDVR(weakref, sarray("from"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDR(_typeof, sarray("variable"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDVARARGS(str, sarray(), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDVARARGV(print, sarray(), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDVARARGV(printerr, sarray(), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDVARARGV(printt, sarray(), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDVARARGV(prints, sarray(), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDVARARGV(printraw, sarray(), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDVARARGV(push_error, sarray(), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDVARARGV(push_warning, sarray(), Variant::BUILTIN_FUNC_TYPE_UTILITY);
-
- FUNCBINDR(var2str, sarray("variable"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDR(str2var, sarray("string"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
-
- FUNCBINDR(var2bytes, sarray("variable"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDR(bytes2var, sarray("bytes"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
-
- FUNCBINDR(var2bytes_with_objects, sarray("variable"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDR(bytes2var_with_objects, sarray("bytes"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
-
- FUNCBINDR(hash, sarray("variable"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
-
- FUNCBINDR(instance_from_id, sarray("id"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDR(is_instance_id_valid, sarray("id"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
- FUNCBINDR(is_instance_valid, sarray("instance"), Variant::BUILTIN_FUNC_TYPE_UTILITY);
+ FUNCBINDVR(weakref, sarray("from"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDR(_typeof, sarray("variable"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDVARARGS(str, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDVARARGV(print, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDVARARGV(printerr, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDVARARGV(printt, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDVARARGV(prints, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDVARARGV(printraw, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDVARARGV(push_error, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDVARARGV(push_warning, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
+
+ FUNCBINDR(var2str, sarray("variable"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDR(str2var, sarray("string"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+
+ FUNCBINDR(var2bytes, sarray("variable"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDR(bytes2var, sarray("bytes"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+
+ FUNCBINDR(var2bytes_with_objects, sarray("variable"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDR(bytes2var_with_objects, sarray("bytes"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+
+ FUNCBINDR(hash, sarray("variable"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+
+ FUNCBINDR(instance_from_id, sarray("id"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDR(is_instance_id_valid, sarray("id"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDR(is_instance_valid, sarray("instance"), Variant::UTILITY_FUNC_TYPE_GENERAL);
}
-void Variant::_unregister_variant_builtin_funcs() {
- builtin_function_table.clear();
- builtin_function_name_table.clear();
+void Variant::_unregister_variant_utility_functions() {
+ utility_function_table.clear();
+ utility_function_name_table.clear();
}
-void Variant::call_builtin_func(const StringName &p_name, Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+void Variant::call_utility_function(const StringName &p_name, Variant *r_ret, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
r_error.argument = 0;
@@ -1303,57 +1303,57 @@ void Variant::call_builtin_func(const StringName &p_name, Variant *r_ret, const
return;
}
- bfi->call_builtin(r_ret, p_args, p_argcount, r_error);
+ bfi->call_utility(r_ret, p_args, p_argcount, r_error);
}
-bool Variant::has_builtin_func(const StringName &p_name) {
- return builtin_function_table.has(p_name);
+bool Variant::has_utility_function(const StringName &p_name) {
+ return utility_function_table.has(p_name);
}
-Variant::BuiltinFunctionValidatedCall Variant::get_builtin_validated_caller(const StringName &p_name) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+Variant::ValidatedUtilityFunction Variant::get_validated_utility_function(const StringName &p_name) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
return nullptr;
}
- return bfi->validated_call_builtin;
+ return bfi->validated_call_utility;
}
-Variant::BuiltinFunctionPTRCall Variant::get_builtin_ptr_caller(const StringName &p_name) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+Variant::PTRUtilityFunction Variant::get_ptr_utility_function(const StringName &p_name) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
return nullptr;
}
- return bfi->ptr_call_builtin;
+ return bfi->ptr_call_utility;
}
-Variant::BuiltInFunctionType Variant::get_builtin_func_type(const StringName &p_name) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+Variant::UtilityFunctionType Variant::get_utility_function_type(const StringName &p_name) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
- return Variant::BUILTIN_FUNC_TYPE_MATH;
+ return Variant::UTILITY_FUNC_TYPE_MATH;
}
return bfi->type;
}
-int Variant::get_builtin_func_argument_count(const StringName &p_name) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+int Variant::get_utility_function_argument_count(const StringName &p_name) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
return 0;
}
return bfi->argcount;
}
-Variant::Type Variant::get_builtin_func_argument_type(const StringName &p_name, int p_arg) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+Variant::Type Variant::get_utility_function_argument_type(const StringName &p_name, int p_arg) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
return Variant::NIL;
}
return bfi->get_arg_type(p_arg);
}
-String Variant::get_builtin_func_argument_name(const StringName &p_name, int p_arg) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+String Variant::get_utility_function_argument_name(const StringName &p_name, int p_arg) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
return String();
}
@@ -1361,23 +1361,23 @@ String Variant::get_builtin_func_argument_name(const StringName &p_name, int p_a
ERR_FAIL_INDEX_V(p_arg, bfi->argnames.size(), String());
return bfi->argnames[p_arg];
}
-bool Variant::has_builtin_func_return_value(const StringName &p_name) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+bool Variant::has_utility_function_return_value(const StringName &p_name) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
return false;
}
return bfi->returns_value;
}
-Variant::Type Variant::get_builtin_func_return_type(const StringName &p_name) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+Variant::Type Variant::get_utility_function_return_type(const StringName &p_name) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
return Variant::NIL;
}
return bfi->return_type;
}
-bool Variant::is_builtin_func_vararg(const StringName &p_name) {
- const VariantBuiltinFunctionInfo *bfi = builtin_function_table.lookup_ptr(p_name);
+bool Variant::is_utility_function_vararg(const StringName &p_name) {
+ const VariantUtilityFunctionInfo *bfi = utility_function_table.lookup_ptr(p_name);
if (!bfi) {
return false;
}
@@ -1385,8 +1385,8 @@ bool Variant::is_builtin_func_vararg(const StringName &p_name) {
return bfi->is_vararg;
}
-void Variant::get_builtin_function_list(List<StringName> *r_functions) {
- for (List<StringName>::Element *E = builtin_function_name_table.front(); E; E = E->next()) {
+void Variant::get_utility_function_list(List<StringName> *r_functions) {
+ for (List<StringName>::Element *E = utility_function_name_table.front(); E; E = E->next()) {
r_functions->push_back(E->get());
}
}
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index 0ad5960d4a..6a9eb89602 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -38,8 +38,9 @@
GD.Print(array1 + array2); // Prints [One, 2, 3, Four]
[/csharp]
[/codeblocks]
- Note that concatenating with [code]+=[/code] operator will create a new array. If you want to append another array to an existing array, [method append_array] is more efficient.
+ [b]Note:[/b] Concatenating with the [code]+=[/code] operator will create a new array, which has a cost. If you want to append another array to an existing array, [method append_array] is more efficient.
[b]Note:[/b] Arrays are always passed by reference. To get a copy of an array which can be modified independently of the original array, use [method duplicate].
+ [b]Note:[/b] When declaring an array with [code]const[/code], the array itself can still be mutated by defining the values at individual indices or pushing/removing elements. Using [code]const[/code] will only prevent assigning the constant with another value after it was initialized.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml
index ae8703c837..9705a196ed 100644
--- a/doc/classes/Color.xml
+++ b/doc/classes/Color.xml
@@ -63,13 +63,13 @@
<argument index="3" name="a" type="float">
</argument>
<description>
- Constructs a [Color] from an RGBA profile using values between 0 and 1.
+ Constructs a [Color] from RGBA values, typically between 0 and 1.
[codeblocks]
[gdscript]
- var color = Color(0.2, 1.0, 0.7, 0.8) # Equivalent to RGBA(51, 255, 178, 204)
+ var color = Color(0.2, 1.0, 0.7, 0.8) # Similar to `Color8(51, 255, 178, 204)`
[/gdscript]
[csharp]
- var color = new Color(0.2f, 1.0f, 0.7f, 0.8f); // Equivalent to RGBA(51, 255, 178, 255, 204)
+ var color = new Color(0.2f, 1.0f, 0.7f, 0.8f); // Similar to `Color.Color8(51, 255, 178, 255, 204)`
[/csharp]
[/codeblocks]
</description>
@@ -84,13 +84,13 @@
<argument index="2" name="b" type="float">
</argument>
<description>
- Constructs a color from an RGB profile using values between 0 and 1. Alpha will always be 1.
+ Constructs a [Color] from RGB values, typically between 0 and 1. Alpha will be 1.
[codeblocks]
[gdscript]
- var color = Color(0.2, 1.0, 0.7) # Equivalent to RGBA(51, 255, 178, 255)
+ var color = Color(0.2, 1.0, 0.7) # Similar to `Color8(51, 255, 178, 255)`
[/gdscript]
[csharp]
- var color = new Color(0.2f, 1.0f, 0.7f); // Equivalent to RGBA(51, 255, 178, 255)
+ var color = new Color(0.2f, 1.0f, 0.7f); // Similar to `Color.Color8(51, 255, 178, 255)`
[/csharp]
[/codeblocks]
</description>
@@ -143,11 +143,11 @@
[codeblocks]
[gdscript]
var color = Color(0.3, 0.4, 0.9)
- var inverted_color = color.inverted() # A color of an RGBA(178, 153, 26, 255)
+ var inverted_color = color.inverted() # Equivalent to `Color(0.7, 0.6, 0.1)`
[/gdscript]
[csharp]
var color = new Color(0.3f, 0.4f, 0.9f);
- Color invertedColor = color.Inverted(); // A color of an RGBA(178, 153, 26, 255)
+ Color invertedColor = color.Inverted(); // Equivalent to `new Color(0.7f, 0.6f, 0.1f)`
[/csharp]
[/codeblocks]
</description>
@@ -174,12 +174,12 @@
[gdscript]
var c1 = Color(1.0, 0.0, 0.0)
var c2 = Color(0.0, 1.0, 0.0)
- var lerp_color = c1.lerp(c2, 0.5) # A color of an RGBA(128, 128, 0, 255)
+ var lerp_color = c1.lerp(c2, 0.5) # Equivalent to `Color(0.5, 0.5, 0.0)`
[/gdscript]
[csharp]
var c1 = new Color(1.0f, 0.0f, 0.0f);
var c2 = new Color(0.0f, 1.0f, 0.0f);
- Color lerpColor = c1.Lerp(c2, 0.5f); // A color of an RGBA(128, 128, 0, 255)
+ Color lerpColor = c1.Lerp(c2, 0.5f); // Equivalent to `new Color(0.5f, 0.5f, 0.0f)`
[/csharp]
[/codeblocks]
</description>
@@ -299,7 +299,7 @@
<return type="int">
</return>
<description>
- Returns the color's 32-bit integer in ABGR format (each byte represents a component of the ABGR profile). ABGR is the reversed version of the default format.
+ Returns the color converted to a 32-bit integer in ABGR format (each byte represents a color channel). ABGR is the reversed version of the default format.
[codeblocks]
[gdscript]
var color = Color(1, 0.5, 0.2)
@@ -316,7 +316,7 @@
<return type="int">
</return>
<description>
- Returns the color's 64-bit integer in ABGR format (each word represents a component of the ABGR profile). ABGR is the reversed version of the default format.
+ Returns the color converted to a 64-bit integer in ABGR format (each word represents a color channel). ABGR is the reversed version of the default format.
[codeblocks]
[gdscript]
var color = Color(1, 0.5, 0.2)
@@ -333,7 +333,7 @@
<return type="int">
</return>
<description>
- Returns the color's 32-bit integer in ARGB format (each byte represents a component of the ARGB profile). ARGB is more compatible with DirectX.
+ Returns the color converted to a 32-bit integer in ARGB format (each byte represents a color channel). ARGB is more compatible with DirectX.
[codeblocks]
[gdscript]
var color = Color(1, 0.5, 0.2)
@@ -350,7 +350,7 @@
<return type="int">
</return>
<description>
- Returns the color's 64-bit integer in ARGB format (each word represents a component of the ARGB profile). ARGB is more compatible with DirectX.
+ Returns the color converted to a 64-bit integer in ARGB format (each word represents a color channel). ARGB is more compatible with DirectX.
[codeblocks]
[gdscript]
var color = Color(1, 0.5, 0.2)
@@ -369,7 +369,7 @@
<argument index="0" name="with_alpha" type="bool" default="true">
</argument>
<description>
- Returns the color's HTML hexadecimal color string in RGBA format (ex: [code]ff34f822[/code]).
+ Returns the color converted to an HTML hexadecimal color string in RGBA format (ex: [code]ff34f822[/code]).
Setting [code]with_alpha[/code] to [code]false[/code] excludes alpha from the hexadecimal string (and uses RGB instead of RGBA format).
[codeblocks]
[gdscript]
@@ -389,7 +389,7 @@
<return type="int">
</return>
<description>
- Returns the color's 32-bit integer in RGBA format (each byte represents a component of the RGBA profile). RGBA is Godot's default format.
+ Returns the color converted to a 32-bit integer in RGBA format (each byte represents a color channel). RGBA is Godot's default format.
[codeblocks]
[gdscript]
var color = Color(1, 0.5, 0.2)
@@ -406,7 +406,7 @@
<return type="int">
</return>
<description>
- Returns the color's 64-bit integer in RGBA format (each word represents a component of the RGBA profile). RGBA is Godot's default format.
+ Returns the color converted to a 64-bit integer in RGBA format (each word represents a color channel). RGBA is Godot's default format.
[codeblocks]
[gdscript]
var color = Color(1, 0.5, 0.2)
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 6ea7b79dff..eb0b941da5 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -446,7 +446,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns a color from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]type[/code].
+ Returns a color from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]node_type[/code].
[codeblocks]
[gdscript]
func _ready():
@@ -469,7 +469,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns a constant from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]type[/code].
+ Returns a constant from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]node_type[/code].
</description>
</method>
<method name="get_theme_font" qualifiers="const">
@@ -480,7 +480,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns a font from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]type[/code].
+ Returns a font from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]node_type[/code].
</description>
</method>
<method name="get_theme_icon" qualifiers="const">
@@ -491,7 +491,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns an icon from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]type[/code].
+ Returns an icon from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]node_type[/code].
</description>
</method>
<method name="get_theme_stylebox" qualifiers="const">
@@ -502,7 +502,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns a [StyleBox] from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]type[/code].
+ Returns a [StyleBox] from assigned [Theme] with given [code]name[/code] and associated with [Control] of given [code]node_type[/code].
</description>
</method>
<method name="get_tooltip" qualifiers="const">
@@ -566,7 +566,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns [code]true[/code] if [Color] with given [code]name[/code] and associated with [Control] of given [code]type[/code] exists in assigned [Theme].
+ Returns [code]true[/code] if [Color] with given [code]name[/code] and associated with [Control] of given [code]node_type[/code] exists in assigned [Theme].
</description>
</method>
<method name="has_theme_color_override" qualifiers="const">
@@ -586,7 +586,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns [code]true[/code] if constant with given [code]name[/code] and associated with [Control] of given [code]type[/code] exists in assigned [Theme].
+ Returns [code]true[/code] if constant with given [code]name[/code] and associated with [Control] of given [code]node_type[/code] exists in assigned [Theme].
</description>
</method>
<method name="has_theme_constant_override" qualifiers="const">
@@ -606,7 +606,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns [code]true[/code] if font with given [code]name[/code] and associated with [Control] of given [code]type[/code] exists in assigned [Theme].
+ Returns [code]true[/code] if font with given [code]name[/code] and associated with [Control] of given [code]node_type[/code] exists in assigned [Theme].
</description>
</method>
<method name="has_theme_font_override" qualifiers="const">
@@ -626,7 +626,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns [code]true[/code] if icon with given [code]name[/code] and associated with [Control] of given [code]type[/code] exists in assigned [Theme].
+ Returns [code]true[/code] if icon with given [code]name[/code] and associated with [Control] of given [code]node_type[/code] exists in assigned [Theme].
</description>
</method>
<method name="has_theme_icon_override" qualifiers="const">
@@ -655,7 +655,7 @@
<argument index="1" name="type" type="StringName" default="&quot;&quot;">
</argument>
<description>
- Returns [code]true[/code] if [StyleBox] with given [code]name[/code] and associated with [Control] of given [code]type[/code] exists in assigned [Theme].
+ Returns [code]true[/code] if [StyleBox] with given [code]name[/code] and associated with [Control] of given [code]node_type[/code] exists in assigned [Theme].
</description>
</method>
<method name="has_theme_stylebox_override" qualifiers="const">
diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml
index dc38fdf0e8..cd0b5ac027 100644
--- a/doc/classes/Dictionary.xml
+++ b/doc/classes/Dictionary.xml
@@ -11,17 +11,28 @@
Creating a dictionary:
[codeblocks]
[gdscript]
- var my_dir = {} # Creates an empty dictionary.
- var points_dir = {"White": 50, "Yellow": 75, "Orange": 100}
- var another_dir = {
- key1: value1,
- key2: value2,
- key3: value3,
+ var my_dict = {} # Creates an empty dictionary.
+
+ var dict_variable_key = "Another key name"
+ var dict_variable_value = "value2"
+ var another_dict = {
+ "Some key name": "value1",
+ dict_variable_key: dict_variable_value,
+ }
+
+ var points_dict = {"White": 50, "Yellow": 75, "Orange": 100}
+
+ # Alternative Lua-style syntax.
+ # Doesn't require quotes around keys, but only string constants can be used as key names.
+ # Additionally, key names must start with a letter or an underscore.
+ # Here, `some_key` is a string literal, not a variable!
+ another_dict = {
+ some_key = 42,
}
[/gdscript]
[csharp]
- var myDir = new Godot.Collections.Dictionary(); // Creates an empty dictionary.
- var pointsDir = new Godot.Collections.Dictionary
+ var myDict = new Godot.Collections.Dictionary(); // Creates an empty dictionary.
+ var pointsDict = new Godot.Collections.Dictionary
{
{"White", 50},
{"Yellow", 75},
@@ -33,15 +44,15 @@
[codeblocks]
[gdscript]
export(string, "White", "Yellow", "Orange") var my_color
- var points_dir = {"White": 50, "Yellow": 75, "Orange": 100}
+ var points_dict = {"White": 50, "Yellow": 75, "Orange": 100}
func _ready():
# We can't use dot syntax here as `my_color` is a variable.
- var points = points_dir[my_color]
+ var points = points_dict[my_color]
[/gdscript]
[csharp]
[Export(PropertyHint.Enum, "White,Yellow,Orange")]
public string MyColor { get; set; }
- public Godot.Collections.Dictionary pointsDir = new Godot.Collections.Dictionary
+ public Godot.Collections.Dictionary pointsDict = new Godot.Collections.Dictionary
{
{"White", 50},
{"Yellow", 75},
@@ -50,7 +61,7 @@
public override void _Ready()
{
- int points = (int)pointsDir[MyColor];
+ int points = (int)pointsDict[MyColor];
}
[/csharp]
[/codeblocks]
@@ -58,7 +69,7 @@
Dictionaries can contain more complex data:
[codeblocks]
[gdscript]
- my_dir = {"First Array": [1, 2, 3, 4]} # Assigns an Array to a String key.
+ my_dict = {"First Array": [1, 2, 3, 4]} # Assigns an Array to a String key.
[/gdscript]
[csharp]
var myDir = new Godot.Collections.Dictionary
@@ -70,8 +81,8 @@
To add a key to an existing dictionary, access it like an existing key and assign to it:
[codeblocks]
[gdscript]
- var points_dir = {"White": 50, "Yellow": 75, "Orange": 100}
- points_dir["Blue"] = 150 # Add "Blue" as a key and assign 150 as its value.
+ var points_dict = {"White": 50, "Yellow": 75, "Orange": 100}
+ points_dict["Blue"] = 150 # Add "Blue" as a key and assign 150 as its value.
[/gdscript]
[csharp]
var pointsDir = new Godot.Collections.Dictionary
@@ -80,7 +91,7 @@
{"Yellow", 75},
{"Orange", 100}
};
- pointsDir["blue"] = 150; // Add "Blue" as a key and assign 150 as its value.
+ pointsDict["blue"] = 150; // Add "Blue" as a key and assign 150 as its value.
[/csharp]
[/codeblocks]
Finally, dictionaries can contain different types of keys and values in the same dictionary:
@@ -89,22 +100,22 @@
# This is a valid dictionary.
# To access the string "Nested value" below, use `my_dir.sub_dir.sub_key` or `my_dir["sub_dir"]["sub_key"]`.
# Indexing styles can be mixed and matched depending on your needs.
- var my_dir = {
+ var my_dict = {
"String Key": 5,
4: [1, 2, 3],
7: "Hello",
- "sub_dir": {"sub_key": "Nested value"},
+ "sub_dict": {"sub_key": "Nested value"},
}
[/gdscript]
[csharp]
// This is a valid dictionary.
// To access the string "Nested value" below, use `my_dir.sub_dir.sub_key` or `my_dir["sub_dir"]["sub_key"]`.
// Indexing styles can be mixed and matched depending on your needs.
- var myDir = new Godot.Collections.Dictionary {
+ var myDict = new Godot.Collections.Dictionary {
{"String Key", 5},
{4, new Godot.Collections.Array{1,2,3}},
{7, "Hello"},
- {"sub_dir", new Godot.Collections.Dictionary{{"sub_key", "Nested value"}}}
+ {"sub_dict", new Godot.Collections.Dictionary{{"sub_key", "Nested value"}}}
};
[/csharp]
[/codeblocks]
@@ -117,11 +128,11 @@
func compare_arrays():
print(array1 == array2) # Will print true.
- var dir1 = {"a": 1, "b": 2, "c": 3}
- var dir2 = {"a": 1, "b": 2, "c": 3}
+ var dict1 = {"a": 1, "b": 2, "c": 3}
+ var dict2 = {"a": 1, "b": 2, "c": 3}
func compare_dictionaries():
- print(dir1 == dir2) # Will NOT print true.
+ print(dict1 == dict2) # Will NOT print true.
[/gdscript]
[csharp]
// You have to use GD.Hash().
@@ -135,35 +146,36 @@
GD.Print(GD.Hash(array1) == GD.Hash(array2)); // Will print true.
}
- public Godot.Collections.Dictionary dir1 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}};
- public Godot.Collections.Dictionary dir2 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}};
+ public Godot.Collections.Dictionary dict1 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}};
+ public Godot.Collections.Dictionary dict2 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}};
public void CompareDictionaries()
{
- GD.Print(dir1 == dir2); // Will NOT print true.
+ GD.Print(dict1 == dict2); // Will NOT print true.
}
[/csharp]
[/codeblocks]
You need to first calculate the dictionary's hash with [method hash] before you can compare them:
[codeblocks]
[gdscript]
- var dir1 = {"a": 1, "b": 2, "c": 3}
- var dir2 = {"a": 1, "b": 2, "c": 3}
+ var dict1 = {"a": 1, "b": 2, "c": 3}
+ var dict2 = {"a": 1, "b": 2, "c": 3}
func compare_dictionaries():
- print(dir1.hash() == dir2.hash()) # Will print true.
+ print(dict1.hash() == dict2.hash()) # Will print true.
[/gdscript]
[csharp]
// You have to use GD.Hash().
- public Godot.Collections.Dictionary dir1 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}};
- public Godot.Collections.Dictionary dir2 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}};
+ public Godot.Collections.Dictionary dict1 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}};
+ public Godot.Collections.Dictionary dict2 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}};
public void CompareDictionaries()
{
- GD.Print(GD.Hash(dir1) == GD.Hash(dir2)); // Will print true.
+ GD.Print(GD.Hash(dict1) == GD.Hash(dict2)); // Will print true.
}
[/csharp]
[/codeblocks]
+ [b]Note:[/b] When declaring a dictionary with [code]const[/code], the dictionary itself can still be mutated by defining the values of individual keys. Using [code]const[/code] will only prevent assigning the constant with another value after it was initialized.
</description>
<tutorials>
<link title="GDScript basics: Dictionary">https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/gdscript_basics.html#dictionary</link>
@@ -322,7 +334,7 @@
<return type="int">
</return>
<description>
- Returns the size of the dictionary (in pairs).
+ Returns the number of keys in the dictionary.
</description>
</method>
<method name="values">
diff --git a/doc/classes/EditorNode3DGizmoPlugin.xml b/doc/classes/EditorNode3DGizmoPlugin.xml
index 4dea1bb645..adaaed4f1c 100644
--- a/doc/classes/EditorNode3DGizmoPlugin.xml
+++ b/doc/classes/EditorNode3DGizmoPlugin.xml
@@ -59,8 +59,11 @@
</argument>
<argument index="1" name="billboard" type="bool" default="false">
</argument>
+ <argument index="2" name="texture" type="Texture2D" default="null">
+ </argument>
<description>
Creates a handle material with its variants (selected and/or editable) and adds them to the internal material list. They can then be accessed with [method get_material] and used in [method EditorNode3DGizmo.add_handles]. Should not be overridden.
+ You can optionally provide a texture to use instead of the default icon.
</description>
</method>
<method name="create_icon_material">
diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index fb0ed8ff62..eafae7310c 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -54,6 +54,15 @@
[b]Note:[/b] This method only works on iOS, Android, and UWP. On other platforms, it always returns [constant Vector3.ZERO].
</description>
</method>
+ <method name="get_action_raw_strength" qualifiers="const">
+ <return type="float">
+ </return>
+ <argument index="0" name="action" type="StringName">
+ </argument>
+ <description>
+ Returns a value between 0 and 1 representing the raw intensity of the given action, ignoring the action's deadzone. In most cases, you should use [method get_action_strength] instead.
+ </description>
+ </method>
<method name="get_action_strength" qualifiers="const">
<return type="float">
</return>
@@ -63,6 +72,18 @@
Returns a value between 0 and 1 representing the intensity of the given action. In a joypad, for example, the further away the axis (analog sticks or L2, R2 triggers) is from the dead zone, the closer the value will be to 1. If the action is mapped to a control that has no axis as the keyboard, the value returned will be 0 or 1.
</description>
</method>
+ <method name="get_axis" qualifiers="const">
+ <return type="float">
+ </return>
+ <argument index="0" name="negative_action" type="StringName">
+ </argument>
+ <argument index="1" name="positive_action" type="StringName">
+ </argument>
+ <description>
+ Get axis input by specifying two actions, one negative and one positive.
+ This is a horthand for writing [code]Input.get_action_strength("positive_action") - Input.get_action_strength("negative_action")[/code].
+ </description>
+ </method>
<method name="get_connected_joypads">
<return type="Array">
</return>
@@ -205,6 +226,25 @@
Returns the mouse mode. See the constants for more information.
</description>
</method>
+ <method name="get_vector" qualifiers="const">
+ <return type="Vector2">
+ </return>
+ <argument index="0" name="negative_x" type="StringName">
+ </argument>
+ <argument index="1" name="positive_x" type="StringName">
+ </argument>
+ <argument index="2" name="negative_y" type="StringName">
+ </argument>
+ <argument index="3" name="positive_y" type="StringName">
+ </argument>
+ <argument index="4" name="deadzone" type="float" default="-1.0">
+ </argument>
+ <description>
+ Get vector input by specifying four actions, two for the X axis and two for the Y axis, negative and positive.
+ This method is useful when getting vector input, such as from a joystick, directional pad, arrows, or WASD. The vector has its length limited to 1 and has a circular deadzone, which is useful for using vector input as movement.
+ By default, the deadzone is automatically calculated from the average of the action deadzones. However, you can override the deadzone to be whatever you want (on the range of 0 to 1).
+ </description>
+ </method>
<method name="is_action_just_pressed" qualifiers="const">
<return type="bool">
</return>
diff --git a/doc/classes/InstancePlaceholder.xml b/doc/classes/InstancePlaceholder.xml
index 39827f6604..defd23afb1 100644
--- a/doc/classes/InstancePlaceholder.xml
+++ b/doc/classes/InstancePlaceholder.xml
@@ -18,13 +18,14 @@
<argument index="1" name="custom_scene" type="PackedScene" default="null">
</argument>
<description>
+ Not thread-safe. Use [method Object.call_deferred] if calling from a thread.
</description>
</method>
<method name="get_instance_path" qualifiers="const">
<return type="String">
</return>
<description>
- Gets the path to the [PackedScene] resource file that is loaded by default when calling [method create_instance].
+ Gets the path to the [PackedScene] resource file that is loaded by default when calling [method create_instance]. Not thread-safe. Use [method Object.call_deferred] if calling from a thread.
</description>
</method>
<method name="get_stored_values">
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index f08a15d873..5c2dffd538 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -78,6 +78,13 @@
Returns the [PopupMenu] of this [LineEdit]. By default, this menu is displayed when right-clicking on the [LineEdit].
</description>
</method>
+ <method name="get_scroll_offset" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ Returns the scroll offset due to [member caret_position], as a number of characters.
+ </description>
+ </method>
<method name="menu_option">
<return type="void">
</return>
diff --git a/doc/classes/MarginContainer.xml b/doc/classes/MarginContainer.xml
index fb5f437239..c8eebd4677 100644
--- a/doc/classes/MarginContainer.xml
+++ b/doc/classes/MarginContainer.xml
@@ -6,13 +6,22 @@
<description>
Adds a top, left, bottom, and right margin to all [Control] nodes that are direct children of the container. To control the [MarginContainer]'s margin, use the [code]margin_*[/code] theme properties listed below.
[b]Note:[/b] Be careful, [Control] margin values are different than the constant margin values. If you want to change the custom margin values of the [MarginContainer] by code, you should use the following examples:
- [codeblock]
+ [codeblocks]
+ [gdscript]
var margin_value = 100
set("custom_constants/margin_top", margin_value)
set("custom_constants/margin_left", margin_value)
set("custom_constants/margin_bottom", margin_value)
set("custom_constants/margin_right", margin_value)
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ int marginValue = 100;
+ Set("custom_constants/margin_top", marginValue);
+ Set("custom_constants/margin_left", marginValue);
+ Set("custom_constants/margin_bottom", marginValue);
+ Set("custom_constants/margin_right", marginValue);
+ [/csharp]
+ [/codeblocks]
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/MeshDataTool.xml b/doc/classes/MeshDataTool.xml
index dcc3bbf2a6..e107b1a108 100644
--- a/doc/classes/MeshDataTool.xml
+++ b/doc/classes/MeshDataTool.xml
@@ -7,16 +7,44 @@
MeshDataTool provides access to individual vertices in a [Mesh]. It allows users to read and edit vertex data of meshes. It also creates an array of faces and edges.
To use MeshDataTool, load a mesh with [method create_from_surface]. When you are finished editing the data commit the data to a mesh with [method commit_to_surface].
Below is an example of how MeshDataTool may be used.
- [codeblock]
+ [codeblocks]
+ [gdscript]
+ var mesh = ArrayMesh.new()
+ mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, CubeMesh.new().get_mesh_arrays())
var mdt = MeshDataTool.new()
mdt.create_from_surface(mesh, 0)
for i in range(mdt.get_vertex_count()):
var vertex = mdt.get_vertex(i)
- ...
+ # In this example we extend the mesh by one unit, which results in seperated faces as it is flat shaded.
+ vertex += mdt.get_vertex_normal(i)
+ # Save your change.
mdt.set_vertex(i, vertex)
mesh.surface_remove(0)
mdt.commit_to_surface(mesh)
- [/codeblock]
+ var mi = MeshInstance.new()
+ mi.mesh = mesh
+ add_child(mi)
+ [/gdscript]
+ [csharp]
+ var mesh = new ArrayMesh();
+ mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, new CubeMesh().GetMeshArrays());
+ var mdt = new MeshDataTool();
+ mdt.CreateFromSurface(mesh, 0);
+ for (var i = 0; i &lt; mdt.GetVertexCount(); i++)
+ {
+ Vector3 vertex = mdt.GetVertex(i);
+ // In this example we extend the mesh by one unit, which results in seperated faces as it is flat shaded.
+ vertex += mdt.GetVertexNormal(i);
+ // Save your change.
+ mdt.SetVertex(i, vertex);
+ }
+ mesh.SurfaceRemove(0);
+ mdt.CommitToSurface(mesh);
+ var mi = new MeshInstance();
+ mi.Mesh = mesh;
+ AddChild(mi);
+ [/csharp]
+ [/codeblocks]
See also [ArrayMesh], [ImmediateGeometry3D] and [SurfaceTool] for procedural geometry generation.
[b]Note:[/b] Godot uses clockwise [url=https://learnopengl.com/Advanced-OpenGL/Face-culling]winding order[/url] for front faces of triangle primitive modes.
</description>
diff --git a/doc/classes/NavigationPolygon.xml b/doc/classes/NavigationPolygon.xml
index e75efa3b27..38921078d7 100644
--- a/doc/classes/NavigationPolygon.xml
+++ b/doc/classes/NavigationPolygon.xml
@@ -6,22 +6,41 @@
<description>
There are two ways to create polygons. Either by using the [method add_outline] method, or using the [method add_polygon] method.
Using [method add_outline]:
- [codeblock]
+ [codeblocks]
+ [gdscript]
var polygon = NavigationPolygon.new()
var outline = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
polygon.add_outline(outline)
polygon.make_polygons_from_outlines()
$NavigationRegion2D.navpoly = polygon
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var polygon = new NavigationPolygon();
+ var outline = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
+ polygon.AddOutline(outline);
+ polygon.MakePolygonsFromOutlines();
+ GetNode&lt;NavigationRegion2D&gt;("NavigationRegion2D").Navpoly = polygon;
+ [/csharp]
+ [/codeblocks]
Using [method add_polygon] and indices of the vertices array.
- [codeblock]
+ [codeblocks]
+ [gdscript]
var polygon = NavigationPolygon.new()
var vertices = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)])
- polygon.set_vertices(vertices)
+ polygon.vertices = vertices
var indices = PackedInt32Array(0, 3, 1)
polygon.add_polygon(indices)
$NavigationRegion2D.navpoly = polygon
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var polygon = new NavigationPolygon();
+ var vertices = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) };
+ polygon.Vertices = vertices;
+ var indices = new int[] { 0, 3, 1 };
+ polygon.AddPolygon(indices);
+ GetNode&lt;NavigationRegion2D&gt;("NavigationRegion2D").Navpoly = polygon;
+ [/csharp]
+ [/codeblocks]
</description>
<tutorials>
<link title="2D Navigation Demo">https://godotengine.org/asset-library/asset/117</link>
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 2e8b76865d..3f212fa0f6 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -130,11 +130,22 @@
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 an human-readable name based on the name of the node being instanced instead of its type.
[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:
- [codeblock]
+ [codeblocks]
+ [gdscript]
+ var child_node = get_child(0)
if child_node.get_parent():
child_node.get_parent().remove_child(child_node)
add_child(child_node)
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ Node childNode = GetChild(0);
+ if (childNode.GetParent() != null)
+ {
+ childNode.GetParent().RemoveChild(childNode);
+ }
+ AddChild(childNode);
+ [/csharp]
+ [/codeblocks]
If you need the child node to be added below a specific node in the list of children, use [method add_sibling] instead of this method.
[b]Note:[/b] If you want a child to be persisted to a [PackedScene], you must set [member owner] in addition to calling [method add_child]. This is typically relevant for [url=https://godot.readthedocs.io/en/latest/tutorials/misc/running_code_in_the_editor.html]tool scripts[/url] and [url=https://godot.readthedocs.io/en/latest/tutorials/plugins/editor/index.html]editor plugins[/url]. If [method add_child] is called without setting [member owner], the newly added [Node] will not be visible in the scene tree, though it will be visible in the 2D/3D view.
</description>
@@ -275,12 +286,20 @@
/root/Swamp/Goblin
[/codeblock]
Possible paths are:
- [codeblock]
+ [codeblocks]
+ [gdscript]
get_node("Sword")
get_node("Backpack/Dagger")
get_node("../Swamp/Alligator")
get_node("/root/MyGame")
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ GetNode("Sword");
+ GetNode("Backpack/Dagger");
+ GetNode("../Swamp/Alligator");
+ GetNode("/root/MyGame");
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="get_node_and_resource">
@@ -292,11 +311,18 @@
Fetches a node and one of its resources as specified by the [NodePath]'s subname (e.g. [code]Area2D/CollisionShape2D:shape[/code]). If several nested resources are specified in the [NodePath], the last one will be fetched.
The return value is an array of size 3: the first index points to the [Node] (or [code]null[/code] if not found), the second index points to the [Resource] (or [code]null[/code] if not found), and the third index is the remaining [NodePath], if any.
For example, assuming that [code]Area2D/CollisionShape2D[/code] is a valid node and that its [code]shape[/code] property has been assigned a [RectangleShape2D] resource, one could have this kind of output:
- [codeblock]
+ [codeblocks]
+ [gdscript]
print(get_node_and_resource("Area2D/CollisionShape2D")) # [[CollisionShape2D:1161], Null, ]
print(get_node_and_resource("Area2D/CollisionShape2D:shape")) # [[CollisionShape2D:1161], [RectangleShape2D:1156], ]
print(get_node_and_resource("Area2D/CollisionShape2D:shape:extents")) # [[CollisionShape2D:1161], [RectangleShape2D:1156], :extents]
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ GD.Print(GetNodeAndResource("Area2D/CollisionShape2D")); // [[CollisionShape2D:1161], Null, ]
+ GD.Print(GetNodeAndResource("Area2D/CollisionShape2D:shape")); // [[CollisionShape2D:1161], [RectangleShape2D:1156], ]
+ GD.Print(GetNodeAndResource("Area2D/CollisionShape2D:shape:extents")); // [[CollisionShape2D:1161], [RectangleShape2D:1156], :extents]
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="get_node_or_null" qualifiers="const">
diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml
index 93ede047fd..36835d9e94 100644
--- a/doc/classes/NodePath.xml
+++ b/doc/classes/NodePath.xml
@@ -51,7 +51,7 @@
The "subnames" optionally included after the path to the target node can point to resources or properties, and can also be nested.
Examples of valid NodePaths (assuming that those nodes exist and have the referenced resources or properties):
[codeblock]
- # Points to the Sprite2D node
+ # Points to the Sprite2D node.
"Path2D/PathFollow2D/Sprite2D"
# Points to the Sprite2D node and its "texture" resource.
# get_node() would retrieve "Sprite2D", while get_node_and_resource()
@@ -70,14 +70,23 @@
<return type="NodePath">
</return>
<description>
- Returns a node path with a colon character ([code]:[/code]) prepended, transforming it to a pure property path with no node name (defaults to resolving from the current node).
- [codeblock]
- # This will be parsed as a node path to the "x" property in the "position" node
+ Returns a node path with a colon character ([code]:[/code]) prepended, transforming it to a pure property path with no node name (defaults to resolving from the from the current node).
+ [codeblocks]
+ [gdscript]
+ # This will be parsed as a node path to the "x" property in the "position" node.
var node_path = NodePath("position:x")
- # This will be parsed as a node path to the "x" component of the "position" property in the current node
+ # This will be parsed as a node path to the "x" component of the "position" property in the current node.
var property_path = node_path.get_as_property_path()
print(property_path) # :position:x
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ // This will be parsed as a node path to the "x" property in the "position" node.
+ var nodePath = new NodePath("position:x");
+ // This will be parsed as a node path to the "x" component of the "position" property in the current node.
+ NodePath propertyPath = nodePath.GetAsPropertyPath();
+ GD.Print(propertyPath); // :position:x
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="get_concatenated_subnames">
@@ -85,10 +94,16 @@
</return>
<description>
Returns all subnames concatenated with a colon character ([code]:[/code]) as separator, i.e. the right side of the first colon in a node path.
- [codeblock]
+ [codeblocks]
+ [gdscript]
var nodepath = NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path")
print(nodepath.get_concatenated_subnames()) # texture:load_path
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
+ GD.Print(nodepath.GetConcatenatedSubnames()); // texture:load_path
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="get_name">
@@ -98,12 +113,20 @@
</argument>
<description>
Gets the node name indicated by [code]idx[/code] (0 to [method get_name_count]).
- [codeblock]
+ [codeblocks]
+ [gdscript]
var node_path = NodePath("Path2D/PathFollow2D/Sprite2D")
print(node_path.get_name(0)) # Path2D
print(node_path.get_name(1)) # PathFollow2D
print(node_path.get_name(2)) # Sprite
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D");
+ GD.Print(nodePath.GetName(0)); // Path2D
+ GD.Print(nodePath.GetName(1)); // PathFollow2D
+ GD.Print(nodePath.GetName(2)); // Sprite
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="get_name_count">
@@ -121,11 +144,18 @@
</argument>
<description>
Gets the resource or property name indicated by [code]idx[/code] (0 to [method get_subname_count]).
- [codeblock]
+ [codeblocks]
+ [gdscript]
var node_path = NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path")
print(node_path.get_subname(0)) # texture
print(node_path.get_subname(1)) # load_path
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
+ GD.Print(nodePath.GetSubname(0)); // texture
+ GD.Print(nodePath.GetSubname(1)); // load_path
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="get_subname_count">
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 1487c9e078..1d80695798 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -85,18 +85,36 @@
If [code]blocking[/code] is [code]false[/code], the Godot thread will continue while the new process runs. It is not possible to retrieve the shell output in non-blocking mode, so [code]output[/code] will be empty.
The return value also depends on the blocking mode. When blocking, the method will return an exit code of the process. When non-blocking, the method returns a process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). If the process forking (non-blocking) or opening (blocking) fails, the method will return [code]-1[/code] or another exit code.
Example of blocking mode and retrieving the shell output:
- [codeblock]
+ [codeblocks]
+ [gdscript]
var output = []
var exit_code = OS.execute("ls", ["-l", "/tmp"], true, output)
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var output = new Godot.Collections.Array();
+ int exitCode = OS.Execute("ls", new string[] {"-l", "/tmp"}, true, output);
+ [/csharp]
+ [/codeblocks]
Example of non-blocking mode, running another instance of the project and storing its process ID:
- [codeblock]
+ [codeblocks]
+ [gdscript]
var pid = OS.execute(OS.get_executable_path(), [], false)
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var pid = OS.Execute(OS.GetExecutablePath(), new string[] {}, false);
+ [/csharp]
+ [/codeblocks]
If you wish to access a shell built-in or perform a composite command, a platform-specific shell can be invoked. For example:
- [codeblock]
+ [codeblocks]
+ [gdscript]
+ var output = []
OS.execute("CMD.exe", ["/C", "cd %TEMP% &amp;&amp; dir"], true, output)
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var output = new Godot.Collections.Array();
+ OS.Execute("CMD.exe", new string[] {"/C", "cd %TEMP% &amp;&amp; dir"}, true, output);
+ [/csharp]
+ [/codeblocks]
[b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows.
</description>
</method>
@@ -118,13 +136,26 @@
You can also incorporate environment variables using the [method get_environment] method.
You can set [code]editor/main_run_args[/code] in the Project Settings to define command-line arguments to be passed by the editor when running the project.
Here's a minimal example on how to parse command-line arguments into a dictionary using the [code]--key=value[/code] form for arguments:
- [codeblock]
+ [codeblocks]
+ [gdscript]
var arguments = {}
for argument in OS.get_cmdline_args():
if argument.find("=") &gt; -1:
var key_value = argument.split("=")
arguments[key_value[0].lstrip("--")] = key_value[1]
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var arguments = new Godot.Collections.Dictionary();
+ foreach (var argument in OS.GetCmdlineArgs())
+ {
+ if (argument.Find("=") &gt; -1)
+ {
+ string[] keyValue = argument.Split("=");
+ arguments[keyValue[0].LStrip("--")] = keyValue[1];
+ }
+ }
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="get_connected_midi_inputs">
diff --git a/doc/classes/PCKPacker.xml b/doc/classes/PCKPacker.xml
index 6b500d5ac3..e3c78e08f1 100644
--- a/doc/classes/PCKPacker.xml
+++ b/doc/classes/PCKPacker.xml
@@ -5,12 +5,20 @@
</brief_description>
<description>
The [PCKPacker] is used to create packages that can be loaded into a running project using [method ProjectSettings.load_resource_pack].
- [codeblock]
+ [codeblocks]
+ [gdscript]
var packer = PCKPacker.new()
packer.pck_start("test.pck")
packer.add_file("res://text.txt", "text.txt")
packer.flush()
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var packer = new PCKPacker();
+ packer.PckStart("test.pck");
+ packer.AddFile("res://text.txt", "text.txt");
+ packer.Flush();
+ [/csharp]
+ [/codeblocks]
The above [PCKPacker] creates package [code]test.pck[/code], then adds a file named [code]text.txt[/code] at the root of the package.
</description>
<tutorials>
diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml
index 4a6893879d..91d066260b 100644
--- a/doc/classes/PackedByteArray.xml
+++ b/doc/classes/PackedByteArray.xml
@@ -135,10 +135,16 @@
</return>
<description>
Returns a hexadecimal representation of this array as a [String].
- [codeblock]
+ [codeblocks]
+ [gdscript]
var array = PackedByteArray([11, 46, 255])
print(array.hex_encode()) # Prints: 0b2eff
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var array = new byte[] {11, 46, 255};
+ GD.Print(array.HexEncode()); // Prints: 0b2eff
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="insert">
diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml
index be40ab05de..d15bcfd114 100644
--- a/doc/classes/PackedScene.xml
+++ b/doc/classes/PackedScene.xml
@@ -8,14 +8,23 @@
Can be used to save a node to a file. When saving, the node as well as all the node it owns get saved (see [code]owner[/code] property on [Node]).
[b]Note:[/b] The node doesn't need to own itself.
[b]Example of loading a saved scene:[/b]
- [codeblock]
- # Use `load()` instead of `preload()` if the path isn't known at compile-time.
+ [codeblocks]
+ [gdscript]
+ # Use load() instead of preload() if the path isn't known at compile-time.
var scene = preload("res://scene.tscn").instance()
# Add the node as a child of the node the script is attached to.
add_child(scene)
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ // C# has no preload, so you have to always use ResourceLoader.Load&lt;PackedScene&gt;().
+ var scene = ResourceLoader.Load&lt;PackedScene&gt;("res://scene.tscn").Instance();
+ // Add the node as a child of the node the script is attached to.
+ AddChild(scene);
+ [/csharp]
+ [/codeblocks]
[b]Example of saving a node with different owners:[/b] The following example creates 3 objects: [code]Node2D[/code] ([code]node[/code]), [code]RigidBody2D[/code] ([code]rigid[/code]) and [code]CollisionObject2D[/code] ([code]collision[/code]). [code]collision[/code] is a child of [code]rigid[/code] which is a child of [code]node[/code]. Only [code]rigid[/code] is owned by [code]node[/code] and [code]pack[/code] will therefore only save those two nodes, but not [code]collision[/code].
- [codeblock]
+ [codeblocks]
+ [gdscript]
# Create the objects.
var node = Node2D.new()
var rigid = RigidBody2D.new()
@@ -27,15 +36,41 @@
# Change owner of `rigid`, but not of `collision`.
rigid.owner = node
-
var scene = PackedScene.new()
+
# Only `node` and `rigid` are now packed.
var result = scene.pack(node)
if result == OK:
- var error = ResourceSaver.save("res://path/name.scn", scene) # Or "user://..."
+ var error = ResourceSaver.save("res://path/name.tscn", scene) # Or "user://..."
if error != OK:
push_error("An error occurred while saving the scene to disk.")
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ // Create the objects.
+ var node = new Node2D();
+ var rigid = new RigidBody2D();
+ var collision = new CollisionShape2D();
+
+ // Create the object hierarchy.
+ rigid.AddChild(collision);
+ node.AddChild(rigid);
+
+ // Change owner of `rigid`, but not of `collision`.
+ rigid.Owner = node;
+ var scene = new PackedScene();
+
+ // Only `node` and `rigid` are now packed.
+ Error result = scene.Pack(node);
+ if (result == Error.Ok)
+ {
+ Error error = ResourceSaver.Save("res://path/name.tscn", scene); // Or "user://..."
+ if (error != Error.Ok)
+ {
+ GD.PushError("An error occurred while saving the scene to disk.");
+ }
+ }
+ [/csharp]
+ [/codeblocks]
</description>
<tutorials>
<link title="2D Role Playing Game Demo">https://godotengine.org/asset-library/asset/520</link>
diff --git a/doc/classes/PacketPeerUDP.xml b/doc/classes/PacketPeerUDP.xml
index cab821b4c0..d7cf6cc8c6 100644
--- a/doc/classes/PacketPeerUDP.xml
+++ b/doc/classes/PacketPeerUDP.xml
@@ -124,17 +124,36 @@
<description>
Waits for a packet to arrive on the listening port. See [method listen].
[b]Note:[/b] [method wait] can't be interrupted once it has been called. This can be worked around by allowing the other party to send a specific "death pill" packet like this:
- [codeblock]
- # Server
- socket.set_dest_address("127.0.0.1", 789)
- socket.put_packet("Time to stop".to_ascii())
+ [codeblocks]
+ [gdscript]
+ socket = PacketPeerUDP.new()
+ # Server
+ socket.set_dest_address("127.0.0.1", 789)
+ socket.put_packet("Time to stop".to_ascii())
- # Client
- while socket.wait() == OK:
- var data = socket.get_packet().get_string_from_ascii()
- if data == "Time to stop":
- return
- [/codeblock]
+ # Client
+ while socket.wait() == OK:
+ var data = socket.get_packet().get_string_from_ascii()
+ if data == "Time to stop":
+ return
+ [/gdscript]
+ [csharp]
+ var socket = new PacketPeerUDP();
+ // Server
+ socket.SetDestAddress("127.0.0.1", 789);
+ socket.PutPacket("Time To Stop".ToAscii());
+
+ // Client
+ while (socket.Wait() == OK)
+ {
+ string data = socket.GetPacket().GetStringFromASCII();
+ if (data == "Time to stop")
+ {
+ return;
+ }
+ }
+ [/csharp]
+ [/codeblocks]
</description>
</method>
</methods>
diff --git a/doc/classes/Performance.xml b/doc/classes/Performance.xml
index 0a9079ce71..9e9c5063ae 100644
--- a/doc/classes/Performance.xml
+++ b/doc/classes/Performance.xml
@@ -24,14 +24,53 @@
</argument>
<description>
Adds a custom monitor with name same as id. You can specify the category of monitor using '/' in id. If there are more than one '/' then default category is used. Default category is "Custom".
- [codeblock]
- Performance.add_custom_monitor("MyCategory/MyMonitor", some_callable) # Adds monitor with name "MyName" to category "MyCategory"
- Performance.add_custom_monitor("MyMonitor", some_callable) # Adds monitor with name "MyName" to category "Custom"
- # Note: "MyCategory/MyMonitor" and "MyMonitor" have same name but different ids so above code is valid
- Performance.add_custom_monitor("Custom/MyMonitor", some_callable) # Adds monitor with name "MyName" to category "Custom"
- # Note: "MyMonitor" and "Custom/MyMonitor" have same name and same category but different ids so above code is valid
- Performance.add_custom_monitor("MyCategoryOne/MyCategoryTwo/MyMonitor", some_callable) # Adds monitor with name "MyCategoryOne/MyCategoryTwo/MyMonitor" to category "Custom"
- [/codeblock]
+ [codeblocks]
+ [gdscript]
+ func _ready():
+ var monitor_value = Callable(self, "get_monitor_value")
+
+ # Adds monitor with name "MyName" to category "MyCategory".
+ Performance.add_custom_monitor("MyCategory/MyMonitor", monitor_value)
+
+ # Adds monitor with name "MyName" to category "Custom".
+ # Note: "MyCategory/MyMonitor" and "MyMonitor" have same name but different ids so the code is valid.
+ Performance.add_custom_monitor("MyMonitor", monitor_value)
+
+ # Adds monitor with name "MyName" to category "Custom".
+ # Note: "MyMonitor" and "Custom/MyMonitor" have same name and same category but different ids so the code is valid.
+ Performance.add_custom_monitor("Custom/MyMonitor", monitor_value)
+
+ # Adds monitor with name "MyCategoryOne/MyCategoryTwo/MyMonitor" to category "Custom".
+ Performance.add_custom_monitor("MyCategoryOne/MyCategoryTwo/MyMonitor", monitor_value)
+
+ func get_monitor_value():
+ return randi() % 25
+ [/gdscript]
+ [csharp]
+ public override void _Ready()
+ {
+ var monitorValue = new Callable(this, nameof(GetMonitorValue));
+
+ // Adds monitor with name "MyName" to category "MyCategory".
+ Performance.AddCustomMonitor("MyCategory/MyMonitor", monitorValue);
+ // Adds monitor with name "MyName" to category "Custom".
+ // Note: "MyCategory/MyMonitor" and "MyMonitor" have same name but different ids so the code is valid.
+ Performance.AddCustomMonitor("MyMonitor", monitorValue);
+
+ // Adds monitor with name "MyName" to category "Custom".
+ // Note: "MyMonitor" and "Custom/MyMonitor" have same name and same category but different ids so the code is valid.
+ Performance.AddCustomMonitor("Custom/MyMonitor", monitorValue);
+
+ // Adds monitor with name "MyCategoryOne/MyCategoryTwo/MyMonitor" to category "Custom".
+ Performance.AddCustomMonitor("MyCategoryOne/MyCategoryTwo/MyMonitor", monitorValue);
+ }
+
+ public int GetMonitorValue()
+ {
+ return GD.Randi() % 25;
+ }
+ [/csharp]
+ [/codeblocks]
The debugger calls the callable to get the value of custom monitor. The callable must return a number.
Callables are called with arguments supplied in argument array.
[b]Note:[/b] It throws an error if given id is already present.
@@ -61,9 +100,14 @@
</argument>
<description>
Returns the value of one of the available monitors. You should provide one of the [enum Monitor] constants as the argument, like this:
- [codeblock]
- print(Performance.get_monitor(Performance.TIME_FPS)) # Prints the FPS to the console
- [/codeblock]
+ [codeblocks]
+ [gdscript]
+ print(Performance.get_monitor(Performance.TIME_FPS)) # Prints the FPS to the console.
+ [/gdscript]
+ [csharp]
+ GD.Print(Performance.GetMonitor(Performance.Monitor.TimeFps)); // Prints the FPS to the console.
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="get_monitor_modification_time">
diff --git a/doc/classes/PhysicsShapeQueryParameters2D.xml b/doc/classes/PhysicsShapeQueryParameters2D.xml
index 93ca684b95..4d7fc61517 100644
--- a/doc/classes/PhysicsShapeQueryParameters2D.xml
+++ b/doc/classes/PhysicsShapeQueryParameters2D.xml
@@ -34,19 +34,34 @@
</member>
<member name="shape_rid" type="RID" setter="set_shape_rid" getter="get_shape_rid">
The queried shape's [RID] that will be used for collision/intersection queries. Use this over [member shape] if you want to optimize for performance using the Servers API:
- [codeblock]
- var shape_rid = PhysicsServer2D.circle_shape_create()
- var radius = 64
- PhysicsServer2D.shape_set_data(shape_rid, radius)
+ [codeblocks]
+ [gdscript]
+ var shape_rid = PhysicsServer2D.circle_shape_create()
+ var radius = 64
+ PhysicsServer2D.shape_set_data(shape_rid, radius)
- var params = PhysicsShapeQueryParameters2D.new()
- params.shape_rid = shape_rid
+ var params = PhysicsShapeQueryParameters2D.new()
+ params.shape_rid = shape_rid
- # Execute physics queries here...
+ # Execute physics queries here...
- # Release the shape when done with physics queries.
- PhysicsServer2D.free_rid(shape_rid)
- [/codeblock]
+ # Release the shape when done with physics queries.
+ PhysicsServer2D.free_rid(shape_rid)
+ [/gdscript]
+ [csharp]
+ RID shapeRid = PhysicsServer2D.CircleShapeCreate();
+ int radius = 64;
+ PhysicsServer2D.ShapeSetData(shapeRid, radius);
+
+ var params = new PhysicsShapeQueryParameters2D();
+ params.ShapeRid = shapeRid;
+
+ // Execute physics queries here...
+
+ // Release the shape when done with physics queries.
+ PhysicsServer2D.FreeRid(shapeRid);
+ [/csharp]
+ [/codeblocks]
</member>
<member name="transform" type="Transform2D" setter="set_transform" getter="get_transform" default="Transform2D( 1, 0, 0, 1, 0, 0 )">
The queried shape's transform matrix.
diff --git a/doc/classes/PhysicsShapeQueryParameters3D.xml b/doc/classes/PhysicsShapeQueryParameters3D.xml
index 167fb31bb3..4b43ea66fc 100644
--- a/doc/classes/PhysicsShapeQueryParameters3D.xml
+++ b/doc/classes/PhysicsShapeQueryParameters3D.xml
@@ -31,19 +31,34 @@
</member>
<member name="shape_rid" type="RID" setter="set_shape_rid" getter="get_shape_rid">
The queried shape's [RID] that will be used for collision/intersection queries. Use this over [member shape] if you want to optimize for performance using the Servers API:
- [codeblock]
- var shape_rid = PhysicsServer3D.shape_create(PhysicsServer3D.SHAPE_SPHERE)
- var radius = 2.0
- PhysicsServer3D.shape_set_data(shape_rid, radius)
+ [codeblocks]
+ [gdscript]
+ var shape_rid = PhysicsServer3D.shape_create(PhysicsServer3D.SHAPE_SPHERE)
+ var radius = 2.0
+ PhysicsServer3D.shape_set_data(shape_rid, radius)
- var params = PhysicsShapeQueryParameters3D.new()
- params.shape_rid = shape_rid
+ var params = PhysicsShapeQueryParameters3D.new()
+ params.shape_rid = shape_rid
- # Execute physics queries here...
+ # Execute physics queries here...
- # Release the shape when done with physics queries.
- PhysicsServer3D.free_rid(shape_rid)
- [/codeblock]
+ # Release the shape when done with physics queries.
+ PhysicsServer3D.free_rid(shape_rid)
+ [/gdscript]
+ [csharp]
+ RID shapeRid = PhysicsServer3D.ShapeCreate(PhysicsServer3D.ShapeType.Sphere);
+ float radius = 2.0f;
+ PhysicsServer3D.ShapeSetData(shapeRid, radius);
+
+ var params = new PhysicsShapeQueryParameters3D();
+ params.ShapeRid = shapeRid;
+
+ // Execute physics queries here...
+
+ // Release the shape when done with physics queries.
+ PhysicsServer3D.FreeRid(shapeRid);
+ [/csharp]
+ [/codeblocks]
</member>
<member name="transform" type="Transform" setter="set_transform" getter="get_transform" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )">
The queried shape's transform matrix.
diff --git a/doc/classes/PrimitiveMesh.xml b/doc/classes/PrimitiveMesh.xml
index 9e7f26ed4f..7e9bccc1d7 100644
--- a/doc/classes/PrimitiveMesh.xml
+++ b/doc/classes/PrimitiveMesh.xml
@@ -14,11 +14,18 @@
</return>
<description>
Returns mesh arrays used to constitute surface of [Mesh]. The result can be passed to [method ArrayMesh.add_surface_from_arrays] to create a new surface. For example:
- [codeblock]
- var c := CylinderMesh.new()
- var arr_mesh := ArrayMesh.new()
+ [codeblocks]
+ [gdscript]
+ var c = CylinderMesh.new()
+ var arr_mesh = ArrayMesh.new()
arr_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, c.get_mesh_arrays())
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ var c = new CylinderMesh();
+ var arrMesh = new ArrayMesh();
+ arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, c.GetMeshArrays());
+ [/csharp]
+ [/codeblocks]
</description>
</method>
</methods>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 7ca2dae4d7..96d71db383 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -25,7 +25,8 @@
- [code]type[/code]: [int] (see [enum Variant.Type])
- optionally [code]hint[/code]: [int] (see [enum PropertyHint]) and [code]hint_string[/code]: [String]
[b]Example:[/b]
- [codeblock]
+ [codeblocks]
+ [gdscript]
ProjectSettings.set("category/property_name", 0)
var property_info = {
@@ -36,7 +37,21 @@
}
ProjectSettings.add_property_info(property_info)
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ ProjectSettings.Singleton.Set("category/property_name", 0);
+
+ var propertyInfo = new Godot.Collections.Dictionary
+ {
+ {"name", "category/propertyName"},
+ {"type", Variant.Type.Int},
+ {"hint", PropertyHint.Enum},
+ {"hint_string", "one,two,three"},
+ };
+
+ ProjectSettings.AddPropertyInfo(propertyInfo);
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="clear">
@@ -65,9 +80,14 @@
<description>
Returns the value of a setting.
[b]Example:[/b]
- [codeblock]
+ [codeblocks]
+ [gdscript]
print(ProjectSettings.get_setting("application/config/name"))
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ GD.Print(ProjectSettings.GetSetting("application/config/name"));
+ [/csharp]
+ [/codeblocks]
</description>
</method>
<method name="globalize_path" qualifiers="const">
@@ -178,9 +198,14 @@
<description>
Sets the value of a setting.
[b]Example:[/b]
- [codeblock]
+ [codeblocks]
+ [gdscript]
ProjectSettings.set_setting("application/config/name", "Example")
- [/codeblock]
+ [/gdscript]
+ [csharp]
+ ProjectSettings.SetSetting("application/config/name", "Example");
+ [/csharp]
+ [/codeblocks]
</description>
</method>
</methods>
@@ -858,7 +883,7 @@
Maximum number of warnings allowed to be sent from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
</member>
<member name="network/limits/packet_peer_stream/max_buffer_po2" type="int" setter="" getter="" default="16">
- Default size of packet peer stream for deserializing Godot data. Over this size, data is dropped.
+ Default size of packet peer stream for deserializing Godot data (in bytes, specified as a power of two). The default value [code]16[/code] is equal to 65,536 bytes. Over this size, data is dropped.
</member>
<member name="network/limits/tcp/connect_timeout_seconds" type="int" setter="" getter="" default="30">
Timeout (in seconds) for connection attempts using TCP.
@@ -895,18 +920,30 @@
<member name="physics/2d/default_gravity" type="int" setter="" getter="" default="98">
The default gravity strength in 2D.
[b]Note:[/b] This property is only read when the project starts. To change the default gravity at runtime, use the following code sample:
- [codeblock]
+ [codeblocks]
+ [gdscript]
# Set the default gravity strength to 98.
- PhysicsServer2D.area_set_param(get_viewport().find_world_2d().get_space(), PhysicsServer2D.AREA_PARAM_GRAVITY, 98)
- [/codeblock]
+ PhysicsServer2D.area_set_param(get_viewport().find_world_2d().space, PhysicsServer2D.AREA_PARAM_GRAVITY, 98)
+ [/gdscript]
+ [csharp]
+ // Set the default gravity strength to 98.
+ PhysicsServer2D.AreaSetParam(GetViewport().FindWorld2d().Space, PhysicsServer2D.AreaParameter.Gravity, 98);
+ [/csharp]
+ [/codeblocks]
</member>
<member name="physics/2d/default_gravity_vector" type="Vector2" setter="" getter="" default="Vector2( 0, 1 )">
The default gravity direction in 2D.
[b]Note:[/b] This property is only read when the project starts. To change the default gravity vector at runtime, use the following code sample:
- [codeblock]
+ [codeblocks]
+ [gdscript]
# Set the default gravity direction to `Vector2(0, 1)`.
- PhysicsServer2D.area_set_param(get_viewport().find_world_2d().get_space(), PhysicsServer2D.AREA_PARAM_GRAVITY_VECTOR, Vector2(0, 1))
- [/codeblock]
+ PhysicsServer2D.area_set_param(get_viewport().find_world_2d().space, PhysicsServer2D.AREA_PARAM_GRAVITY_VECTOR, Vector2.DOWN)
+ [/gdscript]
+ [csharp]
+ // Set the default gravity direction to `Vector2(0, 1)`.
+ PhysicsServer2D.AreaSetParam(GetViewport().FindWorld2d().Space, PhysicsServer2D.AreaParameter.GravityVector, Vector2.Down)
+ [/csharp]
+ [/codeblocks]
</member>
<member name="physics/2d/default_linear_damp" type="float" setter="" getter="" default="0.1">
The default linear damp in 2D.
@@ -942,18 +979,30 @@
<member name="physics/3d/default_gravity" type="float" setter="" getter="" default="9.8">
The default gravity strength in 3D.
[b]Note:[/b] This property is only read when the project starts. To change the default gravity at runtime, use the following code sample:
- [codeblock]
+ [codeblocks]
+ [gdscript]
# Set the default gravity strength to 9.8.
- PhysicsServer3D.area_set_param(get_viewport().find_world().get_space(), PhysicsServer3D.AREA_PARAM_GRAVITY, 9.8)
- [/codeblock]
+ PhysicsServer3D.area_set_param(get_viewport().find_world().space, PhysicsServer3D.AREA_PARAM_GRAVITY, 9.8)
+ [/gdscript]
+ [csharp]
+ // Set the default gravity strength to 9.8.
+ PhysicsServer3D.AreaSetParam(GetViewport().FindWorld().Space, PhysicsServer3D.AreaParameter.Gravity, 9.8);
+ [/csharp]
+ [/codeblocks]
</member>
<member name="physics/3d/default_gravity_vector" type="Vector3" setter="" getter="" default="Vector3( 0, -1, 0 )">
The default gravity direction in 3D.
[b]Note:[/b] This property is only read when the project starts. To change the default gravity vector at runtime, use the following code sample:
- [codeblock]
+ [codeblocks]
+ [gdscript]
# Set the default gravity direction to `Vector3(0, -1, 0)`.
- PhysicsServer3D.area_set_param(get_viewport().find_world().get_space(), PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR, Vector3(0, -1, 0))
- [/codeblock]
+ PhysicsServer3D.area_set_param(get_viewport().find_world().get_space(), PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR, Vector3.DOWN)
+ [/gdscript]
+ [csharp]
+ // Set the default gravity direction to `Vector3(0, -1, 0)`.
+ PhysicsServer3D.AreaSetParam(GetViewport().FindWorld().Space, PhysicsServer3D.AreaParameter.GravityVector, Vector3.Down)
+ [/csharp]
+ [/codeblocks]
</member>
<member name="physics/3d/default_linear_damp" type="float" setter="" getter="" default="0.1">
The default linear damp in 3D.
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index 53d706db2d..168cc8a1c3 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -270,7 +270,7 @@
<return type="String">
</return>
<description>
- Returns a [String] text with the word under the mouse cursor location.
+ Returns a [String] text with the word under the caret (text cursor) location.
</description>
</method>
<method name="insert_text_at_cursor">
diff --git a/doc/classes/Theme.xml b/doc/classes/Theme.xml
index 2824159f0e..783614c4af 100644
--- a/doc/classes/Theme.xml
+++ b/doc/classes/Theme.xml
@@ -23,10 +23,10 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Clears the [Color] at [code]name[/code] if the theme has [code]type[/code].
+ Clears the [Color] at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="clear_constant">
@@ -34,10 +34,10 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Clears the constant at [code]name[/code] if the theme has [code]type[/code].
+ Clears the constant at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="clear_font">
@@ -45,10 +45,10 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Clears the [Font] at [code]name[/code] if the theme has [code]type[/code].
+ Clears the [Font] at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="clear_icon">
@@ -56,10 +56,10 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Clears the icon at [code]name[/code] if the theme has [code]type[/code].
+ Clears the icon at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="clear_stylebox">
@@ -67,10 +67,10 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Clears [StyleBox] at [code]name[/code] if the theme has [code]type[/code].
+ Clears [StyleBox] at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="copy_default_theme">
@@ -94,19 +94,19 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns the [Color] at [code]name[/code] if the theme has [code]type[/code].
+ Returns the [Color] at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_color_list" qualifiers="const">
<return type="PackedStringArray">
</return>
- <argument index="0" name="type" type="String">
+ <argument index="0" name="node_type" type="String">
</argument>
<description>
- Returns all the [Color]s as a [PackedStringArray] filled with each [Color]'s name, for use in [method get_color], if the theme has [code]type[/code].
+ Returns all the [Color]s as a [PackedStringArray] filled with each [Color]'s name, for use in [method get_color], if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_constant" qualifiers="const">
@@ -114,19 +114,19 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns the constant at [code]name[/code] if the theme has [code]type[/code].
+ Returns the constant at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_constant_list" qualifiers="const">
<return type="PackedStringArray">
</return>
- <argument index="0" name="type" type="String">
+ <argument index="0" name="node_type" type="String">
</argument>
<description>
- Returns all the constants as a [PackedStringArray] filled with each constant's name, for use in [method get_constant], if the theme has [code]type[/code].
+ Returns all the constants as a [PackedStringArray] filled with each constant's name, for use in [method get_constant], if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_font" qualifiers="const">
@@ -134,19 +134,19 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns the [Font] at [code]name[/code] if the theme has [code]type[/code].
+ Returns the [Font] at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_font_list" qualifiers="const">
<return type="PackedStringArray">
</return>
- <argument index="0" name="type" type="String">
+ <argument index="0" name="node_type" type="String">
</argument>
<description>
- Returns all the [Font]s as a [PackedStringArray] filled with each [Font]'s name, for use in [method get_font], if the theme has [code]type[/code].
+ Returns all the [Font]s as a [PackedStringArray] filled with each [Font]'s name, for use in [method get_font], if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_icon" qualifiers="const">
@@ -154,19 +154,19 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns the icon [Texture2D] at [code]name[/code] if the theme has [code]type[/code].
+ Returns the icon [Texture2D] at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_icon_list" qualifiers="const">
<return type="PackedStringArray">
</return>
- <argument index="0" name="type" type="String">
+ <argument index="0" name="node_type" type="String">
</argument>
<description>
- Returns all the icons as a [PackedStringArray] filled with each [Texture2D]'s name, for use in [method get_icon], if the theme has [code]type[/code].
+ Returns all the icons as a [PackedStringArray] filled with each [Texture2D]'s name, for use in [method get_icon], if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_stylebox" qualifiers="const">
@@ -174,35 +174,35 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns the icon [StyleBox] at [code]name[/code] if the theme has [code]type[/code].
+ Returns the icon [StyleBox] at [code]name[/code] if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_stylebox_list" qualifiers="const">
<return type="PackedStringArray">
</return>
- <argument index="0" name="type" type="String">
+ <argument index="0" name="node_type" type="String">
</argument>
<description>
- Returns all the [StyleBox]s as a [PackedStringArray] filled with each [StyleBox]'s name, for use in [method get_stylebox], if the theme has [code]type[/code].
+ Returns all the [StyleBox]s as a [PackedStringArray] filled with each [StyleBox]'s name, for use in [method get_stylebox], if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_stylebox_types" qualifiers="const">
<return type="PackedStringArray">
</return>
<description>
- Returns all the [StyleBox] types as a [PackedStringArray] filled with each [StyleBox]'s type, for use in [method get_stylebox] and/or [method get_stylebox_list], if the theme has [code]type[/code].
+ Returns all the [StyleBox] types as a [PackedStringArray] filled with each [StyleBox]'s type, for use in [method get_stylebox] and/or [method get_stylebox_list], if the theme has [code]node_type[/code].
</description>
</method>
<method name="get_type_list" qualifiers="const">
<return type="PackedStringArray">
</return>
- <argument index="0" name="type" type="String">
+ <argument index="0" name="node_type" type="String">
</argument>
<description>
- Returns all the types in [code]type[/code] as a [PackedStringArray] for use in any of the [code]get_*[/code] functions, if the theme has [code]type[/code].
+ Returns all the types in [code]node_type[/code] as a [PackedStringArray] for use in any of the [code]get_*[/code] functions, if the theme has [code]node_type[/code].
</description>
</method>
<method name="has_color" qualifiers="const">
@@ -210,11 +210,11 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns [code]true[/code] if [Color] with [code]name[/code] is in [code]type[/code].
- Returns [code]false[/code] if the theme does not have [code]type[/code].
+ Returns [code]true[/code] if [Color] with [code]name[/code] is in [code]node_type[/code].
+ Returns [code]false[/code] if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="has_constant" qualifiers="const">
@@ -222,11 +222,11 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns [code]true[/code] if constant with [code]name[/code] is in [code]type[/code].
- Returns [code]false[/code] if the theme does not have [code]type[/code].
+ Returns [code]true[/code] if constant with [code]name[/code] is in [code]node_type[/code].
+ Returns [code]false[/code] if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="has_font" qualifiers="const">
@@ -234,11 +234,11 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns [code]true[/code] if [Font] with [code]name[/code] is in [code]type[/code].
- Returns [code]false[/code] if the theme does not have [code]type[/code].
+ Returns [code]true[/code] if [Font] with [code]name[/code] is in [code]node_type[/code].
+ Returns [code]false[/code] if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="has_icon" qualifiers="const">
@@ -246,11 +246,11 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns [code]true[/code] if icon [Texture2D] with [code]name[/code] is in [code]type[/code].
- Returns [code]false[/code] if the theme does not have [code]type[/code].
+ Returns [code]true[/code] if icon [Texture2D] with [code]name[/code] is in [code]node_type[/code].
+ Returns [code]false[/code] if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="has_stylebox" qualifiers="const">
@@ -258,11 +258,11 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<description>
- Returns [code]true[/code] if [StyleBox] with [code]name[/code] is in [code]type[/code].
- Returns [code]false[/code] if the theme does not have [code]type[/code].
+ Returns [code]true[/code] if [StyleBox] with [code]name[/code] is in [code]node_type[/code].
+ Returns [code]false[/code] if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="set_color">
@@ -270,13 +270,13 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<argument index="2" name="color" type="Color">
</argument>
<description>
- Sets the theme's [Color] to [code]color[/code] at [code]name[/code] in [code]type[/code].
- Does nothing if the theme does not have [code]type[/code].
+ Sets the theme's [Color] to [code]color[/code] at [code]name[/code] in [code]node_type[/code].
+ Does nothing if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="set_constant">
@@ -284,13 +284,13 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<argument index="2" name="constant" type="int">
</argument>
<description>
- Sets the theme's constant to [code]constant[/code] at [code]name[/code] in [code]type[/code].
- Does nothing if the theme does not have [code]type[/code].
+ Sets the theme's constant to [code]constant[/code] at [code]name[/code] in [code]node_type[/code].
+ Does nothing if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="set_font">
@@ -298,13 +298,13 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<argument index="2" name="font" type="Font">
</argument>
<description>
- Sets the theme's [Font] to [code]font[/code] at [code]name[/code] in [code]type[/code].
- Does nothing if the theme does not have [code]type[/code].
+ Sets the theme's [Font] to [code]font[/code] at [code]name[/code] in [code]node_type[/code].
+ Does nothing if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="set_icon">
@@ -312,13 +312,13 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<argument index="2" name="texture" type="Texture2D">
</argument>
<description>
- Sets the theme's icon [Texture2D] to [code]texture[/code] at [code]name[/code] in [code]type[/code].
- Does nothing if the theme does not have [code]type[/code].
+ Sets the theme's icon [Texture2D] to [code]texture[/code] at [code]name[/code] in [code]node_type[/code].
+ Does nothing if the theme does not have [code]node_type[/code].
</description>
</method>
<method name="set_stylebox">
@@ -326,13 +326,13 @@
</return>
<argument index="0" name="name" type="StringName">
</argument>
- <argument index="1" name="type" type="StringName">
+ <argument index="1" name="node_type" type="StringName">
</argument>
<argument index="2" name="texture" type="StyleBox">
</argument>
<description>
- Sets theme's [StyleBox] to [code]stylebox[/code] at [code]name[/code] in [code]type[/code].
- Does nothing if the theme does not have [code]type[/code].
+ Sets theme's [StyleBox] to [code]stylebox[/code] at [code]name[/code] in [code]node_type[/code].
+ Does nothing if the theme does not have [code]node_type[/code].
</description>
</method>
</methods>
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index 0b2fb80480..73575b4309 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -106,14 +106,21 @@
<return type="TreeItem">
</return>
<description>
- Returns the currently edited item. This is only available for custom cell mode.
+ Returns the currently edited item. Can be used with [signal item_edited] to get the item that was modified.
+ [codeblock]
+ func _ready():
+ $Tree.item_edited.connect(on_Tree_item_edited)
+
+ func on_Tree_item_edited():
+ print($Tree.get_edited()) # This item just got edited (e.g. checked).
+ [/codeblock]
</description>
</method>
<method name="get_edited_column" qualifiers="const">
<return type="int">
</return>
<description>
- Returns the column for the currently edited item. This is only available for custom cell mode.
+ Returns the column for the currently edited item.
</description>
</method>
<method name="get_item_area_rect" qualifiers="const">
diff --git a/doc/classes/World2D.xml b/doc/classes/World2D.xml
index b0bfd7f418..25033cdb09 100644
--- a/doc/classes/World2D.xml
+++ b/doc/classes/World2D.xml
@@ -16,7 +16,7 @@
The [RID] of this world's canvas resource. Used by the [RenderingServer] for 2D drawing.
</member>
<member name="direct_space_state" type="PhysicsDirectSpaceState2D" setter="" getter="get_direct_space_state">
- Direct access to the world's physics 2D space state. Used for querying current and potential collisions. Must only be accessed from the main thread within [code]_physics_process(delta)[/code].
+ Direct access to the world's physics 2D space state. Used for querying current and potential collisions. When using multi-threaded physics, access is limited to [code]_physics_process(delta)[/code] in the main thread.
</member>
<member name="space" type="RID" setter="" getter="get_space">
The [RID] of this world's physics space resource. Used by the [PhysicsServer2D] for 2D physics, treating it as both a space and an area.
diff --git a/doc/classes/World3D.xml b/doc/classes/World3D.xml
index d804485d4e..fe92077432 100644
--- a/doc/classes/World3D.xml
+++ b/doc/classes/World3D.xml
@@ -15,7 +15,7 @@
<member name="camera_effects" type="CameraEffects" setter="set_camera_effects" getter="get_camera_effects">
</member>
<member name="direct_space_state" type="PhysicsDirectSpaceState3D" setter="" getter="get_direct_space_state">
- Direct access to the world's physics 3D space state. Used for querying current and potential collisions. Must only be accessed from within [code]_physics_process(delta)[/code].
+ Direct access to the world's physics 3D space state. Used for querying current and potential collisions.
</member>
<member name="environment" type="Environment" setter="set_environment" getter="get_environment">
The World3D's [Environment].
diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h
index f0e62de144..2d1c4f8d85 100644
--- a/drivers/dummy/rasterizer_dummy.h
+++ b/drivers/dummy/rasterizer_dummy.h
@@ -98,7 +98,7 @@ public:
void environment_set_adjustment(RID p_env, bool p_enable, float p_brightness, float p_contrast, float p_saturation, RID p_ramp) override {}
- void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density) override {}
+ void environment_set_fog(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective) override {}
void environment_set_volumetric_fog(RID p_env, bool p_enable, float p_density, const Color &p_light, float p_light_energy, float p_length, float p_detail_spread, float p_gi_inject, RS::EnvVolumetricFogShadowFilter p_shadow_filter) override {}
void environment_set_volumetric_fog_volume_size(int p_size, int p_depth) override {}
void environment_set_volumetric_fog_filter_active(bool p_enable) override {}
@@ -161,7 +161,7 @@ public:
void set_debug_draw_mode(RS::ViewportDebugDraw p_debug_draw) override {}
RID render_buffers_create() override { return RID(); }
- void render_buffers_configure(RID p_render_buffers, RID p_render_target, int p_width, int p_height, RS::ViewportMSAA p_msaa, RS::ViewportScreenSpaceAA p_screen_space_aa) override {}
+ void render_buffers_configure(RID p_render_buffers, RID p_render_target, int p_width, int p_height, RS::ViewportMSAA p_msaa, RS::ViewportScreenSpaceAA p_screen_space_aa, bool p_use_debanding) override {}
void screen_space_roughness_limiter_set_active(bool p_enable, float p_amount, float p_curve) override {}
bool screen_space_roughness_limiter_is_active() const override { return false; }
@@ -251,6 +251,15 @@ public:
void texture_add_to_decal_atlas(RID p_texture, bool p_panorama_to_dp = false) override {}
void texture_remove_from_decal_atlas(RID p_texture, bool p_panorama_to_dp = false) override {}
+ /* CANVAS TEXTURE API */
+
+ RID canvas_texture_create() override { return RID(); }
+ void canvas_texture_set_channel(RID p_canvas_texture, RS::CanvasTextureChannel p_channel, RID p_texture) override {}
+ void canvas_texture_set_shading_parameters(RID p_canvas_texture, const Color &p_base_color, float p_shininess) override {}
+
+ void canvas_texture_set_texture_filter(RID p_item, RS::CanvasItemTextureFilter p_filter) override {}
+ void canvas_texture_set_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat) override {}
+
#if 0
RID texture_create() override {
@@ -935,23 +944,22 @@ public:
class RasterizerCanvasDummy : public RasterizerCanvas {
public:
- TextureBindingID request_texture_binding(RID p_texture, RID p_normalmap, RID p_specular, RS::CanvasItemTextureFilter p_filter, RS::CanvasItemTextureRepeat p_repeat, RID p_multimesh) override { return 0; }
- void free_texture_binding(TextureBindingID p_binding) override {}
-
PolygonID request_polygon(const Vector<int> &p_indices, const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), const Vector<int> &p_bones = Vector<int>(), const Vector<float> &p_weights = Vector<float>()) override { return 0; }
void free_polygon(PolygonID p_polygon) override {}
- void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, const Transform2D &p_canvas_transform) override {}
+ void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel) override {}
void canvas_debug_viewport_shadows(Light *p_lights_with_shadow) override {}
RID light_create() override { return RID(); }
void light_set_texture(RID p_rid, RID p_texture) override {}
- void light_set_use_shadow(RID p_rid, bool p_enable, int p_resolution) override {}
- void light_update_shadow(RID p_rid, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders) override {}
+ void light_set_use_shadow(RID p_rid, bool p_enable) override {}
+ void light_update_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_near, float p_far, LightOccluderInstance *p_occluders) override {}
+ void light_update_directional_shadow(RID p_rid, int p_shadow_index, const Transform2D &p_light_xform, int p_light_mask, float p_cull_distance, const Rect2 &p_clip_rect, LightOccluderInstance *p_occluders) override {}
RID occluder_polygon_create() override { return RID(); }
void occluder_polygon_set_shape_as_lines(RID p_occluder, const Vector<Vector2> &p_lines) override {}
void occluder_polygon_set_cull_mode(RID p_occluder, RS::CanvasOccluderPolygonCullMode p_mode) override {}
+ void set_shadow_texture_size(int p_size) override {}
void draw_window_margins(int *p_margins, RID *p_margin_textures) override {}
diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp
index ebfd3e0454..28634d5e70 100644
--- a/drivers/vulkan/vulkan_context.cpp
+++ b/drivers/vulkan/vulkan_context.cpp
@@ -707,7 +707,8 @@ Error VulkanContext::_window_create(DisplayServer::WindowID p_window_id, VkSurfa
// We use a single GPU, but we need a surface to initialize the
// queues, so this process must be deferred until a surface
// is created.
- _initialize_queues(p_surface);
+ Error err = _initialize_queues(p_surface);
+ ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
Window window;
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 236d1e884e..3182bca0eb 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -1138,6 +1138,7 @@ void CodeTextEditor::move_lines_up() {
int from_col = text_editor->get_selection_from_column();
int to_line = text_editor->get_selection_to_line();
int to_column = text_editor->get_selection_to_column();
+ int cursor_line = text_editor->cursor_get_line();
for (int i = from_line; i <= to_line; i++) {
int line_id = i;
@@ -1155,7 +1156,9 @@ void CodeTextEditor::move_lines_up() {
}
int from_line_up = from_line > 0 ? from_line - 1 : from_line;
int to_line_up = to_line > 0 ? to_line - 1 : to_line;
+ int cursor_line_up = cursor_line > 0 ? cursor_line - 1 : cursor_line;
text_editor->select(from_line_up, from_col, to_line_up, to_column);
+ text_editor->cursor_set_line(cursor_line_up);
} else {
int line_id = text_editor->cursor_get_line();
int next_id = line_id - 1;
@@ -1181,6 +1184,7 @@ void CodeTextEditor::move_lines_down() {
int from_col = text_editor->get_selection_from_column();
int to_line = text_editor->get_selection_to_line();
int to_column = text_editor->get_selection_to_column();
+ int cursor_line = text_editor->cursor_get_line();
for (int i = to_line; i >= from_line; i--) {
int line_id = i;
@@ -1198,7 +1202,9 @@ void CodeTextEditor::move_lines_down() {
}
int from_line_down = from_line < text_editor->get_line_count() ? from_line + 1 : from_line;
int to_line_down = to_line < text_editor->get_line_count() ? to_line + 1 : to_line;
+ int cursor_line_down = cursor_line < text_editor->get_line_count() ? cursor_line + 1 : cursor_line;
text_editor->select(from_line_down, from_col, to_line_down, to_column);
+ text_editor->cursor_set_line(cursor_line_down);
} else {
int line_id = text_editor->cursor_get_line();
int next_id = line_id + 1;
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 248073c5a2..fd33115cda 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -487,8 +487,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
error->set_text_align(0, TreeItem::ALIGN_LEFT);
String error_title;
- // Include method name, when given, in error title.
- if (!oe.source_func.empty()) {
+ if (oe.callstack.size() > 0) {
+ // If available, use the script's stack in the error title.
+ error_title = oe.callstack[oe.callstack.size() - 1].func + ": ";
+ } else if (!oe.source_func.empty()) {
+ // Otherwise try to use the C++ source function.
error_title += oe.source_func + ": ";
}
// If we have a (custom) error message, use it as title, and add a C++ Error
@@ -529,9 +532,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
cpp_source->set_metadata(0, source_meta);
}
- error->set_tooltip(0, tooltip);
- error->set_tooltip(1, tooltip);
-
// Format stack trace.
// stack_items_count is the number of elements to parse, with 3 items per frame
// of the stack trace (script, method, line).
@@ -548,10 +548,17 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
stack_trace->set_text(0, "<" + TTR("Stack Trace") + ">");
stack_trace->set_text_align(0, TreeItem::ALIGN_LEFT);
error->set_metadata(0, meta);
+ tooltip += TTR("Stack Trace:") + "\n";
}
- stack_trace->set_text(1, infos[i].file.get_file() + ":" + itos(infos[i].line) + " @ " + infos[i].func + "()");
+
+ String frame_txt = infos[i].file.get_file() + ":" + itos(infos[i].line) + " @ " + infos[i].func + "()";
+ tooltip += frame_txt + "\n";
+ stack_trace->set_text(1, frame_txt);
}
+ error->set_tooltip(0, tooltip);
+ error->set_tooltip(1, tooltip);
+
if (oe.warning) {
warning_count++;
} else {
diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp
index 5e87f866d8..527286583e 100644
--- a/editor/dependency_editor.cpp
+++ b/editor/dependency_editor.cpp
@@ -450,13 +450,13 @@ void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector<
removed_deps.sort();
if (removed_deps.empty()) {
owners->hide();
- text->set_text(TTR("Remove selected files from the project? (Can't be restored)"));
+ text->set_text(TTR("Remove selected files from the project? (no undo)\nYou can find the removed files in the system trash to restore them."));
set_size(Size2());
popup_centered();
} else {
_build_removed_dependency_tree(removed_deps);
owners->show();
- text->set_text(TTR("The files being removed are required by other resources in order for them to work.\nRemove them anyway? (no undo)"));
+ text->set_text(TTR("The files being removed are required by other resources in order for them to work.\nRemove them anyway? (no undo)\nYou can find the removed files in the system trash to restore them."));
popup_centered(Size2(500, 350));
}
EditorFileSystem::get_singleton()->scan_changes();
diff --git a/editor/doc_data.cpp b/editor/doc_data.cpp
index 78c601d4bc..8504d61d2f 100644
--- a/editor/doc_data.cpp
+++ b/editor/doc_data.cpp
@@ -737,16 +737,16 @@ void DocData::generate(bool p_basic_types) {
c.properties.push_back(pd);
}
- List<StringName> builtin_funcs;
- Variant::get_builtin_function_list(&builtin_funcs);
- builtin_funcs.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = builtin_funcs.front(); E; E = E->next()) {
+ List<StringName> utility_functions;
+ Variant::get_utility_function_list(&utility_functions);
+ utility_functions.sort_custom<StringName::AlphCompare>();
+ for (List<StringName>::Element *E = utility_functions.front(); E; E = E->next()) {
MethodDoc md;
md.name = E->get();
//return
- if (Variant::has_builtin_func_return_value(E->get())) {
+ if (Variant::has_utility_function_return_value(E->get())) {
PropertyInfo pi;
- pi.type = Variant::get_builtin_func_return_type(E->get());
+ pi.type = Variant::get_utility_function_return_type(E->get());
if (pi.type == Variant::NIL) {
pi.usage = PROPERTY_USAGE_NIL_IS_VARIANT;
}
@@ -755,13 +755,13 @@ void DocData::generate(bool p_basic_types) {
md.return_type = ad.type;
}
- if (Variant::is_builtin_func_vararg(E->get())) {
+ if (Variant::is_utility_function_vararg(E->get())) {
md.qualifiers = "vararg";
} else {
- for (int i = 0; i < Variant::get_builtin_func_argument_count(E->get()); i++) {
+ for (int i = 0; i < Variant::get_utility_function_argument_count(E->get()); i++) {
PropertyInfo pi;
- pi.type = Variant::get_builtin_func_argument_type(E->get(), i);
- pi.name = Variant::get_builtin_func_argument_name(E->get(), i);
+ pi.type = Variant::get_utility_function_argument_type(E->get(), i);
+ pi.name = Variant::get_utility_function_argument_name(E->get(), i);
if (pi.type == Variant::NIL) {
pi.usage = PROPERTY_USAGE_NIL_IS_VARIANT;
}
diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp
index 97800fe961..3aeffede82 100644
--- a/editor/editor_export.cpp
+++ b/editor/editor_export.cpp
@@ -737,6 +737,9 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
_edit_filter_list(paths, p_preset->get_include_filter(), false);
_edit_filter_list(paths, p_preset->get_exclude_filter(), true);
+ // Ignore import files, since these are automatically added to the jar later with the resources
+ _edit_filter_list(paths, String("*.import"), true);
+
// Get encryption filters.
bool enc_pck = p_preset->get_enc_pck();
Vector<String> enc_in_filters;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index d0d99e071a..2e796b2dcd 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -1679,7 +1679,7 @@ void EditorNode::_dialog_action(String p_file) {
if (err == ERR_FILE_CANT_OPEN || err == ERR_FILE_NOT_FOUND) {
config.instance(); // new config
} else if (err != OK) {
- show_warning(TTR("Error trying to save layout!"));
+ show_warning(TTR("An error occurred while trying to save the editor layout.\nMake sure the editor's user data path is writable."));
return;
}
@@ -1691,7 +1691,7 @@ void EditorNode::_dialog_action(String p_file) {
_update_layouts_menu();
if (p_file == "Default") {
- show_warning(TTR("Default editor layout overridden."));
+ show_warning(TTR("Default editor layout overridden.\nTo restore the Default layout to its base settings, use the Delete Layout option and delete the Default layout."));
}
} break;
@@ -1722,7 +1722,7 @@ void EditorNode::_dialog_action(String p_file) {
_update_layouts_menu();
if (p_file == "Default") {
- show_warning(TTR("Restored default layout to base settings."));
+ show_warning(TTR("Restored the Default layout to its base settings."));
}
} break;
@@ -2705,10 +2705,14 @@ void EditorNode::_screenshot(bool p_use_utc) {
}
void EditorNode::_save_screenshot(NodePath p_path) {
- SubViewport *viewport = Object::cast_to<SubViewport>(EditorInterface::get_singleton()->get_editor_viewport()->get_viewport());
- viewport->set_clear_mode(SubViewport::CLEAR_MODE_ONLY_NEXT_FRAME);
- Ref<Image> img = viewport->get_texture()->get_data();
- viewport->set_clear_mode(SubViewport::CLEAR_MODE_ALWAYS);
+ Control *editor_viewport = EditorInterface::get_singleton()->get_editor_viewport();
+ ERR_FAIL_COND_MSG(!editor_viewport, "Cannot get editor viewport.");
+ Viewport *viewport = editor_viewport->get_viewport();
+ ERR_FAIL_COND_MSG(!viewport, "Cannot get editor viewport.");
+ Ref<ViewportTexture> texture = viewport->get_texture();
+ ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get editor viewport texture.");
+ Ref<Image> img = texture->get_data();
+ ERR_FAIL_COND_MSG(img.is_null(), "Cannot get editor viewport texture image.");
Error error = img->save_png(p_path);
ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'.");
}
@@ -5561,46 +5565,51 @@ EditorNode::EditorNode() {
{
int display_scale = EditorSettings::get_singleton()->get("interface/editor/display_scale");
- float custom_display_scale = EditorSettings::get_singleton()->get("interface/editor/custom_display_scale");
switch (display_scale) {
case 0: {
- // Try applying a suitable display scale automatically
+ // Try applying a suitable display scale automatically.
#ifdef OSX_ENABLED
editor_set_scale(DisplayServer::get_singleton()->screen_get_max_scale());
#else
const int screen = DisplayServer::get_singleton()->window_get_current_screen();
- editor_set_scale(DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).x > 2000 ? 2.0 : 1.0);
+ float scale;
+ if (DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).y >= 1400) {
+ // hiDPI display.
+ scale = 2.0;
+ } else if (DisplayServer::get_singleton()->screen_get_size(screen).y <= 800) {
+ // Small loDPI display. Use a smaller display scale so that editor elements fit more easily.
+ // Icons won't look great, but this is better than having editor elements overflow from its window.
+ scale = 0.75;
+ } else {
+ scale = 1.0;
+ }
+
+ editor_set_scale(scale);
#endif
} break;
- case 1: {
+ case 1:
editor_set_scale(0.75);
- } break;
-
- case 2: {
+ break;
+ case 2:
editor_set_scale(1.0);
- } break;
-
- case 3: {
+ break;
+ case 3:
editor_set_scale(1.25);
- } break;
-
- case 4: {
+ break;
+ case 4:
editor_set_scale(1.5);
- } break;
-
- case 5: {
+ break;
+ case 5:
editor_set_scale(1.75);
- } break;
-
- case 6: {
+ break;
+ case 6:
editor_set_scale(2.0);
- } break;
-
- default: {
- editor_set_scale(custom_display_scale);
- } break;
+ break;
+ default:
+ editor_set_scale(EditorSettings::get_singleton()->get("interface/editor/custom_display_scale"));
+ break;
}
}
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 79525ced51..768e5bccbc 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -441,7 +441,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// Highlighted tabs and border width
Color tab_color = highlight_tabs ? base_color.lerp(font_color, contrast) : base_color;
- const int border_width = CLAMP(border_size, 0, 3) * EDSCALE;
+ // Ensure borders are visible when using an editor scale below 100%.
+ const int border_width = CLAMP(border_size, 0, 3) * MAX(1, EDSCALE);
const int default_margin_size = 4;
const int margin_size_extra = default_margin_size + CLAMP(border_size, 0, 3);
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index ee0ee91893..543546b152 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2363,7 +2363,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str
if (p_paths.size() > 1 || p_paths[0] != "res://") {
p_popup->add_icon_item(get_theme_icon("MoveUp", "EditorIcons"), TTR("Move To..."), FILE_MOVE);
- p_popup->add_icon_item(get_theme_icon("Remove", "EditorIcons"), TTR("Delete"), FILE_REMOVE);
+ p_popup->add_icon_item(get_theme_icon("Remove", "EditorIcons"), TTR("Move to Trash"), FILE_REMOVE);
}
if (p_paths.size() == 1) {
diff --git a/editor/icons/CanvasGroup.svg b/editor/icons/CanvasGroup.svg
new file mode 100644
index 0000000000..232ae53231
--- /dev/null
+++ b/editor/icons/CanvasGroup.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v6h-6v8h8v-6h6v-8zm2 2h4v4h-4z" fill="#a5b8f3" fill-opacity=".588235"/><path d="m1 1v2c0 .0000234.446 0 1 0s1 .0000234 1 0v-2c0-.00002341-.446 0-1 0s-1-.00002341-1 0zm12 0v2c0 .0000234.446 0 1 0s1 .0000234 1 0v-2c0-.00002341-.446 0-1 0s-1-.00002341-1 0zm-12 12v2c0 .000023.446 0 1 0s1 .000023 1 0v-2c0-.000023-.446 0-1 0s-1-.000023-1 0zm12 0v2c0 .000023.446 0 1 0s1 .000023 1 0v-2c0-.000023-.446 0-1 0s-1-.000023-1 0z" fill="#a5b7f4"/></svg>
diff --git a/editor/icons/CodeEdit.svg b/editor/icons/CodeEdit.svg
new file mode 100644
index 0000000000..0750b072e7
--- /dev/null
+++ b/editor/icons/CodeEdit.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.4)"><path d="m29 1042.4h1v1h-1z" fill="#fefeff"/><path d="m3 1c-1.1046 0-2 .8954-2 2v10c0 1.1046.89543 2 2 2h10c1.1046 0 2-.8954 2-2v-10c0-1.1046-.89543-2-2-2zm0 2h10v10h-10zm2 1-1 1 1 1-1 1 1 1 2-2zm2 3v1h2v-1z" fill="#a5efac" transform="translate(0 1036.4)"/></g></svg>
diff --git a/editor/icons/EditorCurveHandle.svg b/editor/icons/EditorCurveHandle.svg
index ea69f4e4cc..e0f3256807 100644
--- a/editor/icons/EditorCurveHandle.svg
+++ b/editor/icons/EditorCurveHandle.svg
@@ -1 +1 @@
-<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><circle cx="5" cy="5" fill="#fefefe" r="2.75" stroke="#000" stroke-linecap="square"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="8" fill="#fefefe" r="4.4" stroke="#000" stroke-linecap="square" stroke-width="1.6"/></svg>
diff --git a/editor/icons/EditorPathSharpHandle.svg b/editor/icons/EditorPathSharpHandle.svg
index 328dc04677..5166930cca 100644
--- a/editor/icons/EditorPathSharpHandle.svg
+++ b/editor/icons/EditorPathSharpHandle.svg
@@ -1 +1 @@
-<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><path d="m-3.035534-10.106602h6.071068v6.071068h-6.071068z" fill="#fefefe" stroke="#000" stroke-linecap="square" transform="matrix(-.70710678 .70710678 -.70710678 -.70710678 0 0)"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m14.868629 8.0000002-6.8686288 6.8686288-6.8686293-6.8686288 6.8686293-6.8686293z" fill="#fefefe" stroke="#000" stroke-linecap="square" stroke-width="1.6"/></svg>
diff --git a/editor/icons/EditorPathSmoothHandle.svg b/editor/icons/EditorPathSmoothHandle.svg
index b498345d5a..2ab4f3a96a 100644
--- a/editor/icons/EditorPathSmoothHandle.svg
+++ b/editor/icons/EditorPathSmoothHandle.svg
@@ -1 +1 @@
-<svg height="10" viewBox="0 0 10 10" width="10" xmlns="http://www.w3.org/2000/svg"><path d="m1.5-8.5h7v7h-7z" fill="#fefefe" stroke="#000" stroke-linecap="square" transform="rotate(90)"/></svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m13.6 2.4v11.2h-11.2v-11.2z" fill="#fefefe" stroke="#000" stroke-linecap="square" stroke-width="1.6"/></svg>
diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp
index 3a0e624a8f..ac2485fe31 100644
--- a/editor/import/resource_importer_texture.cpp
+++ b/editor/import/resource_importer_texture.cpp
@@ -205,7 +205,7 @@ void ResourceImporterTexture::get_import_options(List<ImportOption> *r_options,
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "mipmaps/generate"), (p_preset == PRESET_3D ? true : false)));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "mipmaps/limit", PROPERTY_HINT_RANGE, "-1,256"), -1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "roughness/mode", PROPERTY_HINT_ENUM, "Detect,Disabled,Red,Green,Blue,Alpha,Gray"), 0));
- r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "roughness/src_normal", PROPERTY_HINT_FILE, "*.png,*.jpg"), ""));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "roughness/src_normal", PROPERTY_HINT_FILE, "*.bmp,*.dds,*.exr,*.jpeg,*.jpg,*.hdr,*.png,*.svg,*.svgz,*.tga,*.webp"), ""));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/fix_alpha_border"), p_preset != PRESET_3D));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/premult_alpha"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/invert_color"), false));
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index fdbf3415db..28acb26012 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -6753,12 +6753,12 @@ void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const R
materials[p_name] = icons;
}
-void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool p_billboard) {
+void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool p_billboard, const Ref<Texture2D> &p_icon) {
Ref<StandardMaterial3D> handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true);
- Ref<Texture2D> handle_t = Node3DEditor::get_singleton()->get_theme_icon("Editor3DHandle", "EditorIcons");
+ Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : Node3DEditor::get_singleton()->get_theme_icon("Editor3DHandle", "EditorIcons");
handle_material->set_point_size(handle_t->get_width());
handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t);
handle_material->set_albedo(Color(1, 1, 1));
@@ -6842,7 +6842,7 @@ void EditorNode3DGizmoPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_icon_material", "name", "texture", "on_top", "color"), &EditorNode3DGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1)));
- ClassDB::bind_method(D_METHOD("create_handle_material", "name", "billboard"), &EditorNode3DGizmoPlugin::create_handle_material, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("create_handle_material", "name", "billboard", "texture"), &EditorNode3DGizmoPlugin::create_handle_material, DEFVAL(false), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("add_material", "name", "material"), &EditorNode3DGizmoPlugin::add_material);
ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material); //, DEFVAL(Ref<EditorNode3DGizmo>()));
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 4c4faef07f..07f6d69d76 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -866,7 +866,7 @@ protected:
public:
void create_material(const String &p_name, const Color &p_color, bool p_billboard = false, bool p_on_top = false, bool p_use_vertex_color = false);
void create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top = false, const Color &p_albedo = Color(1, 1, 1, 1));
- void create_handle_material(const String &p_name, bool p_billboard = false);
+ void create_handle_material(const String &p_name, bool p_billboard = false, const Ref<Texture2D> &p_texture = nullptr);
void add_material(const String &p_name, Ref<StandardMaterial3D> p_material);
Ref<StandardMaterial3D> get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo = Ref<EditorNode3DGizmo>());
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index f53130c24d..280f6fafd8 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -224,6 +224,7 @@ void Path3DGizmo::redraw() {
Ref<StandardMaterial3D> path_material = gizmo_plugin->get_material("path_material", this);
Ref<StandardMaterial3D> path_thin_material = gizmo_plugin->get_material("path_thin_material", this);
Ref<StandardMaterial3D> handles_material = gizmo_plugin->get_material("handles");
+ Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles");
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
@@ -281,7 +282,7 @@ void Path3DGizmo::redraw() {
add_handles(handles, handles_material);
}
if (sec_handles.size()) {
- add_handles(sec_handles, handles_material, false, true);
+ add_handles(sec_handles, sec_handles_material, false, true);
}
}
}
@@ -641,5 +642,6 @@ Path3DGizmoPlugin::Path3DGizmoPlugin() {
Color path_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/path", Color(0.5, 0.5, 1.0, 0.8));
create_material("path_material", path_color);
create_material("path_thin_material", Color(0.5, 0.5, 0.5));
- create_handle_material("handles");
+ create_handle_material("handles", false, Node3DEditor::get_singleton()->get_theme_icon("EditorPathSmoothHandle", "EditorIcons"));
+ create_handle_material("sec_handles", false, Node3DEditor::get_singleton()->get_theme_icon("EditorCurveHandle", "EditorIcons"));
}
diff --git a/editor/project_export.cpp b/editor/project_export.cpp
index e8c2b1f954..75509c7544 100644
--- a/editor/project_export.cpp
+++ b/editor/project_export.cpp
@@ -721,14 +721,19 @@ void ProjectExportDialog::_fill_resource_tree() {
}
bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> &current, bool p_only_scenes) {
+ p_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
p_item->set_icon(0, presets->get_theme_icon("folder", "FileDialog"));
p_item->set_text(0, p_dir->get_name() + "/");
+ p_item->set_editable(0, true);
+ p_item->set_metadata(0, p_dir->get_path());
bool used = false;
+ bool checked = true;
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
TreeItem *subdir = include_files->create_item(p_item);
if (_fill_tree(p_dir->get_subdir(i), subdir, current, p_only_scenes)) {
used = true;
+ checked = checked && subdir->is_checked(0);
} else {
memdelete(subdir);
}
@@ -750,10 +755,12 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem
file->set_editable(0, true);
file->set_checked(0, current->has_export_file(path));
file->set_metadata(0, path);
+ checked = checked && file->is_checked(0);
used = true;
}
+ p_item->set_checked(0, checked);
return used;
}
@@ -775,13 +782,53 @@ void ProjectExportDialog::_tree_changed() {
String path = item->get_metadata(0);
bool added = item->is_checked(0);
- if (added) {
- current->add_export_file(path);
+ if (path.ends_with("/")) {
+ _check_dir_recursive(item, added);
} else {
- current->remove_export_file(path);
+ if (added) {
+ current->add_export_file(path);
+ } else {
+ current->remove_export_file(path);
+ }
+ }
+ _refresh_parent_checks(item); // Makes parent folder checked if all files/folders are checked.
+}
+
+void ProjectExportDialog::_check_dir_recursive(TreeItem *p_dir, bool p_checked) {
+ for (TreeItem *child = p_dir->get_children(); child; child = child->get_next()) {
+ String path = child->get_metadata(0);
+
+ child->set_checked(0, p_checked);
+ if (path.ends_with("/")) {
+ _check_dir_recursive(child, p_checked);
+ } else {
+ if (p_checked) {
+ get_current_preset()->add_export_file(path);
+ } else {
+ get_current_preset()->remove_export_file(path);
+ }
+ }
}
}
+void ProjectExportDialog::_refresh_parent_checks(TreeItem *p_item) {
+ TreeItem *parent = p_item->get_parent();
+ if (!parent) {
+ return;
+ }
+
+ bool checked = true;
+ for (TreeItem *child = parent->get_children(); child; child = child->get_next()) {
+ checked = checked && child->is_checked(0);
+ if (!checked) {
+ break;
+ }
+ }
+ parent->set_checked(0, checked);
+
+ _refresh_parent_checks(parent);
+}
+
void ProjectExportDialog::_export_pck_zip() {
export_pck_zip->popup_file_dialog();
}
diff --git a/editor/project_export.h b/editor/project_export.h
index 026daac2ad..b8ca0dd9f2 100644
--- a/editor/project_export.h
+++ b/editor/project_export.h
@@ -123,6 +123,8 @@ private:
void _fill_resource_tree();
bool _fill_tree(EditorFileSystemDirectory *p_dir, TreeItem *p_item, Ref<EditorExportPreset> &current, bool p_only_scenes);
void _tree_changed();
+ void _check_dir_recursive(TreeItem *p_dir, bool p_checked);
+ void _refresh_parent_checks(TreeItem *p_item);
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index e3c2ba83f2..167cd3ac35 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -61,6 +61,7 @@ class ProjectDialog : public ConfirmationDialog {
GDCLASS(ProjectDialog, ConfirmationDialog);
public:
+ bool is_folder_empty = true;
enum Mode {
MODE_NEW,
MODE_IMPORT,
@@ -218,7 +219,7 @@ private:
// check if the specified install folder is empty, even though this is not an error, it is good to check here
d->list_dir_begin();
- bool is_empty = true;
+ is_folder_empty = true;
String n = d->get_next();
while (n != String()) {
if (!n.begins_with(".")) {
@@ -226,14 +227,14 @@ private:
// and hidden files/folders to be present.
// For instance, this lets users initialize a Git repository
// and still be able to create a project in the directory afterwards.
- is_empty = false;
+ is_folder_empty = false;
break;
}
n = d->get_next();
}
d->list_dir_end();
- if (!is_empty) {
+ if (!is_folder_empty) {
set_message(TTR("Please choose an empty folder."), MESSAGE_WARNING, INSTALL_PATH);
memdelete(d);
get_ok()->set_disabled(true);
@@ -258,7 +259,7 @@ private:
} else {
// check if the specified folder is empty, even though this is not an error, it is good to check here
d->list_dir_begin();
- bool is_empty = true;
+ is_folder_empty = true;
String n = d->get_next();
while (n != String()) {
if (!n.begins_with(".")) {
@@ -266,18 +267,18 @@ private:
// and hidden files/folders to be present.
// For instance, this lets users initialize a Git repository
// and still be able to create a project in the directory afterwards.
- is_empty = false;
+ is_folder_empty = false;
break;
}
n = d->get_next();
}
d->list_dir_end();
- if (!is_empty) {
- set_message(TTR("Please choose an empty folder."), MESSAGE_ERROR);
+ if (!is_folder_empty) {
+ set_message(TTR("The selected path is not empty. Choosing an empty folder is highly recommended."), MESSAGE_WARNING);
memdelete(d);
- get_ok()->set_disabled(true);
- return "";
+ get_ok()->set_disabled(false);
+ return valid_path;
}
}
@@ -416,6 +417,11 @@ private:
}
}
+ void _nonempty_confirmation_ok_pressed() {
+ is_folder_empty = true;
+ ok_pressed();
+ }
+
void ok_pressed() override {
String dir = project_path->get_text();
@@ -454,6 +460,18 @@ private:
} else {
if (mode == MODE_NEW) {
+ // Before we create a project, check that the target folder is empty.
+ // If not, we need to ask the user if they're sure they want to do this.
+ if (!is_folder_empty) {
+ ConfirmationDialog *cd = memnew(ConfirmationDialog);
+ cd->set_title(TTR("Warning: This folder is not empty"));
+ cd->set_text(TTR("You are about to create a Godot project in a non-empty folder.\nThe entire contents of this folder will be imported as project resources!\n\nAre you sure you wish to continue?"));
+ cd->get_ok()->connect("pressed", callable_mp(this, &ProjectDialog::_nonempty_confirmation_ok_pressed));
+ get_parent()->add_child(cd);
+ cd->popup_centered();
+ cd->grab_focus();
+ return;
+ }
ProjectSettings::CustomMap initial_settings;
if (rasterizer_button_group->get_pressed_button()->get_meta("driver_name") == "Vulkan") {
initial_settings["rendering/quality/driver/driver_name"] = "Vulkan";
@@ -2348,12 +2366,24 @@ ProjectManager::ProjectManager() {
switch (display_scale) {
case 0: {
- // Try applying a suitable display scale automatically
+ // Try applying a suitable display scale automatically.
#ifdef OSX_ENABLED
editor_set_scale(DisplayServer::get_singleton()->screen_get_max_scale());
#else
const int screen = DisplayServer::get_singleton()->window_get_current_screen();
- editor_set_scale(DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).x > 2000 ? 2.0 : 1.0);
+ float scale;
+ if (DisplayServer::get_singleton()->screen_get_dpi(screen) >= 192 && DisplayServer::get_singleton()->screen_get_size(screen).y >= 1400) {
+ // hiDPI display.
+ scale = 2.0;
+ } else if (DisplayServer::get_singleton()->screen_get_size(screen).y <= 800) {
+ // Small loDPI display. Use a smaller display scale so that editor elements fit more easily.
+ // Icons won't look great, but this is better than having editor elements overflow from its window.
+ scale = 0.75;
+ } else {
+ scale = 1.0;
+ }
+
+ editor_set_scale(scale);
#endif
} break;
@@ -2375,9 +2405,9 @@ ProjectManager::ProjectManager() {
case 6:
editor_set_scale(2.0);
break;
- default: {
+ default:
editor_set_scale(EditorSettings::get_singleton()->get("interface/editor/custom_display_scale"));
- } break;
+ break;
}
// Define a minimum window size to prevent UI elements from overlapping or being cut off
diff --git a/editor/project_manager.h b/editor/project_manager.h
index 407dba0c94..212d693f1d 100644
--- a/editor/project_manager.h
+++ b/editor/project_manager.h
@@ -98,6 +98,7 @@ class ProjectManager : public Control {
void _restart_confirm();
void _exit_dialog();
void _confirm_update_settings();
+ void _nonempty_confirmation_ok_pressed();
void _load_recent_projects();
void _on_project_created(const String &dir);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index add5047c99..038b18a648 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1270,10 +1270,6 @@ void SceneTreeDock::_fill_path_renames(Vector<StringName> base_path, Vector<Stri
}
void SceneTreeDock::fill_path_renames(Node *p_node, Node *p_new_parent, List<Pair<NodePath, NodePath>> *p_renames) {
- if (!bool(EDITOR_DEF("editors/animation/autorename_animation_tracks", true))) {
- return;
- }
-
Vector<StringName> base_path;
Node *n = p_node->get_parent();
while (n) {
diff --git a/main/main.cpp b/main/main.cpp
index 3905366598..29497cd1cf 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2396,7 +2396,6 @@ bool Main::iteration() {
for (int iters = 0; iters < advance.physics_steps; ++iters) {
uint64_t physics_begin = OS::get_singleton()->get_ticks_usec();
- PhysicsServer3D::get_singleton()->sync();
PhysicsServer3D::get_singleton()->flush_queries();
PhysicsServer2D::get_singleton()->sync();
diff --git a/misc/dist/windows/.gitignore b/misc/dist/windows/.gitignore
new file mode 100644
index 0000000000..b615268279
--- /dev/null
+++ b/misc/dist/windows/.gitignore
@@ -0,0 +1,2 @@
+# Ignore both the Godot executable and generated installers.
+*.exe
diff --git a/misc/dist/windows/README.md b/misc/dist/windows/README.md
new file mode 100644
index 0000000000..6df66437a7
--- /dev/null
+++ b/misc/dist/windows/README.md
@@ -0,0 +1,17 @@
+# Windows installer
+
+`godot.iss` is an [Inno Setup](https://jrsoftware.org/isinfo.php) installer file
+that can be used to build a Windows installer. The generated installer is able
+to run without Administrator privileges and can optionally add Godot to the
+user's `PATH` environment variable.
+
+To use Inno Setup on Linux, use [innoextract](https://constexpr.org/innoextract/)
+to extract the Inno Setup installer then run `ISCC.exe` using
+[WINE](https://www.winehq.org/).
+
+## Building
+
+- Place a Godot editor executable in this folder and rename it to `godot.exe`.
+- Run the Inno Setup Compiler (part of the Inno Setup suite) on the `godot.iss` file.
+
+If everything succeeds, an installer will be generated in this folder.
diff --git a/misc/dist/windows/godot.iss b/misc/dist/windows/godot.iss
new file mode 100644
index 0000000000..f7aa8249bc
--- /dev/null
+++ b/misc/dist/windows/godot.iss
@@ -0,0 +1,63 @@
+#define MyAppName "Godot Engine"
+#define MyAppVersion "4.0.dev"
+#define MyAppPublisher "Godot Engine contributors"
+#define MyAppURL "https://godotengine.org/"
+#define MyAppExeName "godot.exe"
+
+[Setup]
+AppId={{60D07AAA-400E-40F5-B073-A796C34D9D78}
+AppName={#MyAppName}
+AppVersion={#MyAppVersion}
+; Don't add "version {version}" to the installed app name in the Add/Remove Programs
+; dialog as it's redundant with the Version field in that same dialog.
+AppVerName={#MyAppName}
+AppPublisher={#MyAppPublisher}
+AppPublisherURL={#MyAppURL}
+AppSupportURL={#MyAppURL}
+AppUpdatesURL={#MyAppURL}
+AppComments=Godot Engine editor
+ChangesEnvironment=yes
+DefaultDirName={localappdata}\Godot
+DefaultGroupName=Godot Engine
+AllowNoIcons=yes
+UninstallDisplayIcon={app}\{#MyAppExeName}
+#ifdef App32Bit
+ OutputBaseFilename=godot-setup-x86
+#else
+ OutputBaseFilename=godot-setup-x86_64
+ ArchitecturesAllowed=x64
+ ArchitecturesInstallIn64BitMode=x64
+#endif
+Compression=lzma
+SolidCompression=yes
+PrivilegesRequired=lowest
+
+[Languages]
+Name: "english"; MessagesFile: "compiler:Default.isl"
+
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+Name: "modifypath"; Description: "Add Godot to PATH environment variable"
+
+[Files]
+Source: "{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
+
+[Icons]
+Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
+Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
+
+[Run]
+Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
+
+[Code]
+const
+ ModPathName = 'modifypath';
+ ModPathType = 'user';
+
+function ModPathDir(): TArrayOfString;
+begin
+ setArrayLength(Result, 1)
+ Result[0] := ExpandConstant('{app}');
+end;
+
+#include "modpath.pas"
diff --git a/misc/dist/windows/modpath.pas b/misc/dist/windows/modpath.pas
new file mode 100644
index 0000000000..c55ec60163
--- /dev/null
+++ b/misc/dist/windows/modpath.pas
@@ -0,0 +1,219 @@
+// ----------------------------------------------------------------------------
+//
+// Inno Setup Ver: 5.4.2
+// Script Version: 1.4.2
+// Author: Jared Breland <jbreland@legroom.net>
+// Homepage: http://www.legroom.net/software
+// License: GNU Lesser General Public License (LGPL), version 3
+// http://www.gnu.org/licenses/lgpl.html
+//
+// Script Function:
+// Allow modification of environmental path directly from Inno Setup installers
+//
+// Instructions:
+// Copy modpath.iss to the same directory as your setup script
+//
+// Add this statement to your [Setup] section
+// ChangesEnvironment=true
+//
+// Add this statement to your [Tasks] section
+// You can change the Description or Flags
+// You can change the Name, but it must match the ModPathName setting below
+// Name: modifypath; Description: &Add application directory to your environmental path; Flags: unchecked
+//
+// Add the following to the end of your [Code] section
+// ModPathName defines the name of the task defined above
+// ModPathType defines whether the 'user' or 'system' path will be modified;
+// this will default to user if anything other than system is set
+// setArrayLength must specify the total number of dirs to be added
+// Result[0] contains first directory, Result[1] contains second, etc.
+// const
+// ModPathName = 'modifypath';
+// ModPathType = 'user';
+//
+// function ModPathDir(): TArrayOfString;
+// begin
+// setArrayLength(Result, 1);
+// Result[0] := ExpandConstant('{app}');
+// end;
+// #include "modpath.iss"
+// ----------------------------------------------------------------------------
+
+procedure ModPath();
+var
+ oldpath: String;
+ newpath: String;
+ updatepath: Boolean;
+ pathArr: TArrayOfString;
+ aExecFile: String;
+ aExecArr: TArrayOfString;
+ i, d: Integer;
+ pathdir: TArrayOfString;
+ regroot: Integer;
+ regpath: String;
+
+begin
+ // Get constants from main script and adjust behavior accordingly
+ // ModPathType MUST be 'system' or 'user'; force 'user' if invalid
+ if ModPathType = 'system' then begin
+ regroot := HKEY_LOCAL_MACHINE;
+ regpath := 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
+ end else begin
+ regroot := HKEY_CURRENT_USER;
+ regpath := 'Environment';
+ end;
+
+ // Get array of new directories and act on each individually
+ pathdir := ModPathDir();
+ for d := 0 to GetArrayLength(pathdir)-1 do begin
+ updatepath := true;
+
+ // Modify WinNT path
+ if UsingWinNT() = true then begin
+
+ // Get current path, split into an array
+ RegQueryStringValue(regroot, regpath, 'Path', oldpath);
+ oldpath := oldpath + ';';
+ i := 0;
+
+ while (Pos(';', oldpath) > 0) do begin
+ SetArrayLength(pathArr, i+1);
+ pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath)-1);
+ oldpath := Copy(oldpath, Pos(';', oldpath)+1, Length(oldpath));
+ i := i + 1;
+
+ // Check if current directory matches app dir
+ if pathdir[d] = pathArr[i-1] then begin
+ // if uninstalling, remove dir from path
+ if IsUninstaller() = true then begin
+ continue;
+ // if installing, flag that dir already exists in path
+ end else begin
+ updatepath := false;
+ end;
+ end;
+
+ // Add current directory to new path
+ if i = 1 then begin
+ newpath := pathArr[i-1];
+ end else begin
+ newpath := newpath + ';' + pathArr[i-1];
+ end;
+ end;
+
+ // Append app dir to path if not already included
+ if (IsUninstaller() = false) AND (updatepath = true) then
+ newpath := newpath + ';' + pathdir[d];
+
+ // Write new path
+ RegWriteStringValue(regroot, regpath, 'Path', newpath);
+
+ // Modify Win9x path
+ end else begin
+
+ // Convert to shortened dirname
+ pathdir[d] := GetShortName(pathdir[d]);
+
+ // If autoexec.bat exists, check if app dir already exists in path
+ aExecFile := 'C:\AUTOEXEC.BAT';
+ if FileExists(aExecFile) then begin
+ LoadStringsFromFile(aExecFile, aExecArr);
+ for i := 0 to GetArrayLength(aExecArr)-1 do begin
+ if IsUninstaller() = false then begin
+ // If app dir already exists while installing, skip add
+ if (Pos(pathdir[d], aExecArr[i]) > 0) then
+ updatepath := false;
+ break;
+ end else begin
+ // If app dir exists and = what we originally set, then delete at uninstall
+ if aExecArr[i] = 'SET PATH=%PATH%;' + pathdir[d] then
+ aExecArr[i] := '';
+ end;
+ end;
+ end;
+
+ // If app dir not found, or autoexec.bat didn't exist, then (create and) append to current path
+ if (IsUninstaller() = false) AND (updatepath = true) then begin
+ SaveStringToFile(aExecFile, #13#10 + 'SET PATH=%PATH%;' + pathdir[d], True);
+
+ // If uninstalling, write the full autoexec out
+ end else begin
+ SaveStringsToFile(aExecFile, aExecArr, False);
+ end;
+ end;
+ end;
+end;
+
+// Split a string into an array using passed delimeter
+procedure MPExplode(var Dest: TArrayOfString; Text: String; Separator: String);
+var
+ i: Integer;
+begin
+ i := 0;
+ repeat
+ SetArrayLength(Dest, i+1);
+ if Pos(Separator,Text) > 0 then begin
+ Dest[i] := Copy(Text, 1, Pos(Separator, Text)-1);
+ Text := Copy(Text, Pos(Separator,Text) + Length(Separator), Length(Text));
+ i := i + 1;
+ end else begin
+ Dest[i] := Text;
+ Text := '';
+ end;
+ until Length(Text)=0;
+end;
+
+
+procedure CurStepChanged(CurStep: TSetupStep);
+var
+ taskname: String;
+begin
+ taskname := ModPathName;
+ if CurStep = ssPostInstall then
+ if IsTaskSelected(taskname) then
+ ModPath();
+end;
+
+procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
+var
+ aSelectedTasks: TArrayOfString;
+ i: Integer;
+ taskname: String;
+ regpath: String;
+ regstring: String;
+ appid: String;
+begin
+ // only run during actual uninstall
+ if CurUninstallStep = usUninstall then begin
+ // get list of selected tasks saved in registry at install time
+ appid := '{#emit SetupSetting("AppId")}';
+ if appid = '' then appid := '{#emit SetupSetting("AppName")}';
+ regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1');
+ RegQueryStringValue(HKLM, regpath, 'Inno Setup: Selected Tasks', regstring);
+ if regstring = '' then RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring);
+
+ // check each task; if matches modpath taskname, trigger patch removal
+ if regstring <> '' then begin
+ taskname := ModPathName;
+ MPExplode(aSelectedTasks, regstring, ',');
+ if GetArrayLength(aSelectedTasks) > 0 then begin
+ for i := 0 to GetArrayLength(aSelectedTasks)-1 do begin
+ if comparetext(aSelectedTasks[i], taskname) = 0 then
+ ModPath();
+ end;
+ end;
+ end;
+ end;
+end;
+
+function NeedRestart(): Boolean;
+var
+ taskname: String;
+begin
+ taskname := ModPathName;
+ if IsTaskSelected(taskname) and not UsingWinNT() then begin
+ Result := True;
+ end else begin
+ Result := False;
+ end;
+end;
diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp
index f7290666ad..663ad6e3e1 100644
--- a/modules/bullet/bullet_physics_server.cpp
+++ b/modules/bullet/bullet_physics_server.cpp
@@ -1553,9 +1553,6 @@ void BulletPhysicsServer3D::step(float p_deltaTime) {
}
}
-void BulletPhysicsServer3D::sync() {
-}
-
void BulletPhysicsServer3D::flush_queries() {
if (!active) {
return;
diff --git a/modules/bullet/bullet_physics_server.h b/modules/bullet/bullet_physics_server.h
index 02ba5458d8..dca9339c44 100644
--- a/modules/bullet/bullet_physics_server.h
+++ b/modules/bullet/bullet_physics_server.h
@@ -397,7 +397,6 @@ public:
virtual void init() override;
virtual void step(float p_deltaTime) override;
- virtual void sync() override;
virtual void flush_queries() override;
virtual void finish() override;
diff --git a/modules/gdnative/gdnative/variant.cpp b/modules/gdnative/gdnative/variant.cpp
index 8e30eaae4d..417abeaad3 100644
--- a/modules/gdnative/gdnative/variant.cpp
+++ b/modules/gdnative/gdnative/variant.cpp
@@ -576,7 +576,9 @@ godot_variant GDAPI godot_variant_call(godot_variant *p_self, const godot_string
godot_variant raw_dest;
Variant *dest = (Variant *)&raw_dest;
Callable::CallError error;
- memnew_placement_custom(dest, Variant, Variant(self->call(*method, args, p_argcount, error)));
+ Variant ret;
+ self->call(*method, args, p_argcount, ret, error);
+ memnew_placement_custom(dest, Variant, Variant(ret));
if (r_error) {
r_error->error = (godot_variant_call_error_error)error.error;
r_error->argument = error.argument;
diff --git a/modules/gdnative/net/webrtc_gdnative.cpp b/modules/gdnative/net/webrtc_gdnative.cpp
index a7355e4d12..d8c3ddc5f8 100644
--- a/modules/gdnative/net/webrtc_gdnative.cpp
+++ b/modules/gdnative/net/webrtc_gdnative.cpp
@@ -54,7 +54,7 @@ godot_error GDAPI godot_net_set_webrtc_library(const godot_net_webrtc_library *p
#ifdef WEBRTC_GDNATIVE_ENABLED
return (godot_error)WebRTCPeerConnectionGDNative::set_default_library(p_lib);
#else
- return ERR_UNAVAILABLE;
+ return (godot_error)ERR_UNAVAILABLE;
#endif
}
}
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index bad450c9f9..a64a05fcba 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1963,6 +1963,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
}
p_script->member_indices = base->member_indices;
+ native = base->native;
+ p_script->native = native;
} break;
default: {
_set_error("Parser bug: invalid inheritance.", p_class);
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index 59f3deb736..8372672cf7 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -1048,7 +1048,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Callable::CallError err;
if (call_ret) {
GET_VARIANT_PTR(ret, argc);
- base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err);
+ base->call(*methodname, (const Variant **)argptrs, argc, *ret, err);
#ifdef DEBUG_ENABLED
if (!call_async && ret->get_type() == Variant::OBJECT) {
// Check if getting a function state without await.
@@ -1066,7 +1066,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
#endif
} else {
- base->call_ptr(*methodname, (const Variant **)argptrs, argc, nullptr, err);
+ Variant ret;
+ base->call(*methodname, (const Variant **)argptrs, argc, ret, err);
}
#ifdef DEBUG_ENABLED
if (GDScriptLanguage::get_singleton()->profiling) {
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index 0942552ba8..3a7c1a8676 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -285,7 +285,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
int64_t i = *p_args[0];
r_ret = i < 0 ? -1 : (i > 0 ? +1 : 0);
} else if (p_args[0]->get_type() == Variant::FLOAT) {
- real_t r = *p_args[0];
+ double r = *p_args[0];
r_ret = r < 0.0 ? -1.0 : (r > 0.0 ? +1.0 : 0.0);
} else {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
@@ -510,8 +510,8 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
- real_t a = *p_args[0];
- real_t b = *p_args[1];
+ double a = *p_args[0];
+ double b = *p_args[1];
r_ret = MAX(a, b);
}
@@ -527,8 +527,8 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(0);
VALIDATE_ARG_NUM(1);
- real_t a = *p_args[0];
- real_t b = *p_args[1];
+ double a = *p_args[0];
+ double b = *p_args[1];
r_ret = MIN(a, b);
}
@@ -545,9 +545,9 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(1);
VALIDATE_ARG_NUM(2);
- real_t a = *p_args[0];
- real_t b = *p_args[1];
- real_t c = *p_args[2];
+ double a = *p_args[0];
+ double b = *p_args[1];
+ double c = *p_args[2];
r_ret = CLAMP(a, b, c);
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
index d0add835c0..90141928ca 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
@@ -337,8 +337,8 @@ namespace Godot
}
/// <summary>
- /// Returns the color's 32-bit integer in ABGR format
- /// (each byte represents a component of the ABGR profile).
+ /// Returns the color converted to an unsigned 32-bit integer in ABGR
+ /// format (each byte represents a color channel).
/// ABGR is the reversed version of the default format.
/// </summary>
/// <returns>A uint representing this color in ABGR32 format.</returns>
@@ -356,8 +356,8 @@ namespace Godot
}
/// <summary>
- /// Returns the color's 64-bit integer in ABGR format
- /// (each word represents a component of the ABGR profile).
+ /// Returns the color converted to an unsigned 64-bit integer in ABGR
+ /// format (each word represents a color channel).
/// ABGR is the reversed version of the default format.
/// </summary>
/// <returns>A ulong representing this color in ABGR64 format.</returns>
@@ -375,8 +375,8 @@ namespace Godot
}
/// <summary>
- /// Returns the color's 32-bit integer in ARGB format
- /// (each byte represents a component of the ARGB profile).
+ /// Returns the color converted to an unsigned 32-bit integer in ARGB
+ /// format (each byte represents a color channel).
/// ARGB is more compatible with DirectX, but not used much in Godot.
/// </summary>
/// <returns>A uint representing this color in ARGB32 format.</returns>
@@ -394,8 +394,8 @@ namespace Godot
}
/// <summary>
- /// Returns the color's 64-bit integer in ARGB format
- /// (each word represents a component of the ARGB profile).
+ /// Returns the color converted to an unsigned 64-bit integer in ARGB
+ /// format (each word represents a color channel).
/// ARGB is more compatible with DirectX, but not used much in Godot.
/// </summary>
/// <returns>A ulong representing this color in ARGB64 format.</returns>
@@ -413,8 +413,8 @@ namespace Godot
}
/// <summary>
- /// Returns the color's 32-bit integer in RGBA format
- /// (each byte represents a component of the RGBA profile).
+ /// Returns the color converted to an unsigned 32-bit integer in RGBA
+ /// format (each byte represents a color channel).
/// RGBA is Godot's default and recommended format.
/// </summary>
/// <returns>A uint representing this color in RGBA32 format.</returns>
@@ -432,8 +432,8 @@ namespace Godot
}
/// <summary>
- /// Returns the color's 64-bit integer in RGBA format
- /// (each word represents a component of the RGBA profile).
+ /// Returns the color converted to an unsigned 64-bit integer in RGBA
+ /// format (each word represents a color channel).
/// RGBA is Godot's default and recommended format.
/// </summary>
/// <returns>A ulong representing this color in RGBA64 format.</returns>
@@ -472,7 +472,7 @@ namespace Godot
}
/// <summary>
- /// Constructs a color from RGBA values on the range of 0 to 1.
+ /// Constructs a color from RGBA values, typically on the range of 0 to 1.
/// </summary>
/// <param name="r">The color's red component, typically on the range of 0 to 1.</param>
/// <param name="g">The color's green component, typically on the range of 0 to 1.</param>
@@ -500,8 +500,8 @@ namespace Godot
}
/// <summary>
- /// Constructs a color from a 32-bit integer
- /// (each byte represents a component of the RGBA profile).
+ /// Constructs a color from an unsigned 32-bit integer in RGBA format
+ /// (each byte represents a color channel).
/// </summary>
/// <param name="rgba">The uint representing the color.</param>
public Color(uint rgba)
@@ -516,8 +516,8 @@ namespace Godot
}
/// <summary>
- /// Constructs a color from a 64-bit integer
- /// (each word represents a component of the RGBA profile).
+ /// Constructs a color from an unsigned 64-bit integer in RGBA format
+ /// (each word represents a color channel).
/// </summary>
/// <param name="rgba">The ulong representing the color.</param>
public Color(ulong rgba)
@@ -777,31 +777,10 @@ namespace Godot
return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1);
}
- private String ToHex32(float val)
+ private string ToHex32(float val)
{
- int v = Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255));
-
- var ret = string.Empty;
-
- for (int i = 0; i < 2; i++)
- {
- char c;
- int lv = v & 0xF;
-
- if (lv < 10)
- {
- c = (char)('0' + lv);
- }
- else
- {
- c = (char)('a' + lv - 10);
- }
-
- v >>= 4;
- ret = c + ret;
- }
-
- return ret;
+ byte b = (byte)Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255));
+ return b.HexEncode();
}
internal static bool HtmlIsValid(string color)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
index 7f4777777c..6699c5992c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
@@ -39,14 +39,6 @@ namespace Godot
return val * sgn;
}
- public static FuncRef FuncRef(Object instance, StringName funcName)
- {
- var ret = new FuncRef();
- ret.SetInstance(instance);
- ret.SetFunction(funcName);
- return ret;
- }
-
public static int Hash(object var)
{
return godot_icall_GD_hash(var);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index d63db0f905..0700f197ff 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
@@ -322,6 +322,15 @@ namespace Godot
return instance.IndexOf(what, from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
}
+ /// <summary>Find the first occurrence of a char. Optionally, the search starting position can be passed.</summary>
+ /// <returns>The first instance of the char, or -1 if not found.</returns>
+ public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true)
+ {
+ // TODO: Could be more efficient if we get a char version of `IndexOf`.
+ // See https://github.com/dotnet/runtime/issues/44116
+ return instance.IndexOf(what.ToString(), from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
+ }
+
/// <summary>Find the last occurrence of a substring.</summary>
/// <returns>The starting position of the substring, or -1 if not found.</returns>
public static int FindLast(this string instance, string what, bool caseSensitive = true)
@@ -437,6 +446,53 @@ namespace Godot
return hashv;
}
+ /// <summary>
+ /// Returns a hexadecimal representation of this byte as a string.
+ /// </summary>
+ /// <param name="bytes">The byte to encode.</param>
+ /// <returns>The hexadecimal representation of this byte.</returns>
+ internal static string HexEncode(this byte b)
+ {
+ var ret = string.Empty;
+
+ for (int i = 0; i < 2; i++)
+ {
+ char c;
+ int lv = b & 0xF;
+
+ if (lv < 10)
+ {
+ c = (char)('0' + lv);
+ }
+ else
+ {
+ c = (char)('a' + lv - 10);
+ }
+
+ b >>= 4;
+ ret = c + ret;
+ }
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Returns a hexadecimal representation of this byte array as a string.
+ /// </summary>
+ /// <param name="bytes">The byte array to encode.</param>
+ /// <returns>The hexadecimal representation of this byte array.</returns>
+ public static string HexEncode(this byte[] bytes)
+ {
+ var ret = string.Empty;
+
+ foreach (byte b in bytes)
+ {
+ ret += b.HexEncode();
+ }
+
+ return ret;
+ }
+
// <summary>
// Convert a string containing an hexadecimal number into an int.
// </summary>
@@ -659,6 +715,33 @@ namespace Godot
}
/// <summary>
+ /// Returns a copy of the string with characters removed from the left.
+ /// </summary>
+ /// <param name="instance">The string to remove characters from.</param>
+ /// <param name="chars">The characters to be removed.</param>
+ /// <returns>A copy of the string with characters removed from the left.</returns>
+ public static string LStrip(this string instance, string chars)
+ {
+ int len = instance.Length;
+ int beg;
+
+ for (beg = 0; beg < len; beg++)
+ {
+ if (chars.Find(instance[beg]) == -1)
+ {
+ break;
+ }
+ }
+
+ if (beg == 0)
+ {
+ return instance;
+ }
+
+ return instance.Substr(beg, len - beg);
+ }
+
+ /// <summary>
/// Do a simple expression match, where '*' matches zero or more arbitrary characters and '?' matches any single character except '.'.
/// </summary>
private static bool ExprMatch(this string instance, string expr, bool caseSensitive)
@@ -886,6 +969,33 @@ namespace Godot
return instance.Substring(pos, instance.Length - pos);
}
+ /// <summary>
+ /// Returns a copy of the string with characters removed from the right.
+ /// </summary>
+ /// <param name="instance">The string to remove characters from.</param>
+ /// <param name="chars">The characters to be removed.</param>
+ /// <returns>A copy of the string with characters removed from the right.</returns>
+ public static string RStrip(this string instance, string chars)
+ {
+ int len = instance.Length;
+ int end;
+
+ for (end = len - 1; end >= 0; end--)
+ {
+ if (chars.Find(instance[end]) == -1)
+ {
+ break;
+ }
+ }
+
+ if (end == len - 1)
+ {
+ return instance;
+ }
+
+ return instance.Substr(0, end + 1);
+ }
+
public static byte[] SHA256Buffer(this string instance)
{
return godot_icall_String_sha256_buffer(instance);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index 06bbe98497..bc0f81b2a7 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -221,8 +221,7 @@ namespace Godot
real_t dot = v1.Dot(v2);
- // Clamp dot to [-1, 1]
- dot = dot < -1.0f ? -1.0f : (dot > 1.0f ? 1.0f : dot);
+ dot = Mathf.Clamp(dot, -1.0f, 1.0f);
Vector2 v;
diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp
index bb015b118d..10a18dfd5e 100644
--- a/modules/visual_script/visual_script_expression.cpp
+++ b/modules/visual_script/visual_script_expression.cpp
@@ -1463,7 +1463,7 @@ public:
argp.write[i] = &arr[i];
}
- r_ret = base.call(call->method, (const Variant **)argp.ptr(), argp.size(), ce);
+ base.call(call->method, (const Variant **)argp.ptr(), argp.size(), r_ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
r_error_str = "On call to '" + String(call->method) + "':";
diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp
index 3b46af3cbd..34a1cfc4fc 100644
--- a/modules/visual_script/visual_script_func_nodes.cpp
+++ b/modules/visual_script/visual_script_func_nodes.cpp
@@ -42,7 +42,7 @@
//////////////////////////////////////////
int VisualScriptFunctionCall::get_output_sequence_port_count() const {
- if ((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function))) {
+ if ((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_builtin_method_const(basic_type, function))) {
return 0;
} else {
return 1;
@@ -50,7 +50,7 @@ int VisualScriptFunctionCall::get_output_sequence_port_count() const {
}
bool VisualScriptFunctionCall::has_input_sequence_port() const {
- return !((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_method_const(basic_type, function)));
+ return !((method_cache.flags & METHOD_FLAG_CONST && call_mode != CALL_MODE_INSTANCE) || (call_mode == CALL_MODE_BASIC_TYPE && Variant::is_builtin_method_const(basic_type, function)));
}
#ifdef TOOLS_ENABLED
@@ -130,7 +130,11 @@ StringName VisualScriptFunctionCall::_get_base_type() const {
int VisualScriptFunctionCall::get_input_value_port_count() const {
if (call_mode == CALL_MODE_BASIC_TYPE) {
- Vector<Variant::Type> types = Variant::get_method_argument_types(basic_type, function);
+ Vector<Variant::Type> types;
+ int argc = Variant::get_builtin_method_argument_count(basic_type, function);
+ for (int i = 0; i < argc; i++) {
+ types.push_back(Variant::get_builtin_method_argument_type(basic_type, function, i));
+ }
return types.size() + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) + 1;
} else {
@@ -147,8 +151,7 @@ int VisualScriptFunctionCall::get_input_value_port_count() const {
int VisualScriptFunctionCall::get_output_value_port_count() const {
if (call_mode == CALL_MODE_BASIC_TYPE) {
- bool returns = false;
- Variant::get_method_return_type(basic_type, function, &returns);
+ bool returns = Variant::has_builtin_method_return_value(basic_type, function);
return returns ? 1 : 0;
} else {
@@ -195,10 +198,7 @@ PropertyInfo VisualScriptFunctionCall::get_input_value_port_info(int p_idx) cons
#ifdef DEBUG_METHODS_ENABLED
if (call_mode == CALL_MODE_BASIC_TYPE) {
- Vector<StringName> names = Variant::get_method_argument_names(basic_type, function);
- Vector<Variant::Type> types = Variant::get_method_argument_types(basic_type, function);
- return PropertyInfo(types[p_idx], names[p_idx]);
-
+ return PropertyInfo(Variant::get_builtin_method_argument_type(basic_type, function, p_idx), Variant::get_builtin_method_argument_name(basic_type, function, p_idx));
} else {
MethodBind *mb = ClassDB::get_method(_get_base_type(), function);
if (mb) {
@@ -220,7 +220,7 @@ PropertyInfo VisualScriptFunctionCall::get_output_value_port_info(int p_idx) con
#ifdef DEBUG_METHODS_ENABLED
if (call_mode == CALL_MODE_BASIC_TYPE) {
- return PropertyInfo(Variant::get_method_return_type(basic_type, function), "");
+ return PropertyInfo(Variant::get_builtin_method_return_type(basic_type, function), "");
} else {
if (call_mode == CALL_MODE_INSTANCE) {
if (p_idx == 0) {
@@ -419,7 +419,7 @@ void VisualScriptFunctionCall::set_function(const StringName &p_type) {
function = p_type;
if (call_mode == CALL_MODE_BASIC_TYPE) {
- use_default_args = Variant::get_method_default_arguments(basic_type, function).size();
+ use_default_args = Variant::get_builtin_method_default_arguments(basic_type, function).size();
} else {
//update all caches
@@ -606,7 +606,7 @@ void VisualScriptFunctionCall::_validate_property(PropertyInfo &property) const
int mc = 0;
if (call_mode == CALL_MODE_BASIC_TYPE) {
- mc = Variant::get_method_default_arguments(basic_type, function).size();
+ mc = Variant::get_builtin_method_default_arguments(basic_type, function).size();
} else {
MethodBind *mb = ClassDB::get_method(_get_base_type(), function);
if (mb) {
@@ -805,19 +805,21 @@ public:
} else if (returns) {
if (call_mode == VisualScriptFunctionCall::CALL_MODE_INSTANCE) {
if (returns >= 2) {
- *p_outputs[1] = v.call(function, p_inputs + 1, input_args, r_error);
+ v.call(function, p_inputs + 1, input_args, *p_outputs[1], r_error);
} else if (returns == 1) {
- v.call(function, p_inputs + 1, input_args, r_error);
+ Variant ret;
+ v.call(function, p_inputs + 1, input_args, ret, r_error);
} else {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
r_error_str = "Invalid returns count for call_mode == CALL_MODE_INSTANCE";
return 0;
}
} else {
- *p_outputs[0] = v.call(function, p_inputs + 1, input_args, r_error);
+ v.call(function, p_inputs + 1, input_args, *p_outputs[0], r_error);
}
} else {
- v.call(function, p_inputs + 1, input_args, r_error);
+ Variant ret;
+ v.call(function, p_inputs + 1, input_args, ret, r_error);
}
if (call_mode == VisualScriptFunctionCall::CALL_MODE_INSTANCE) {
diff --git a/modules/webrtc/SCsub b/modules/webrtc/SCsub
index 20b4c8f8d2..4f870ddb2f 100644
--- a/modules/webrtc/SCsub
+++ b/modules/webrtc/SCsub
@@ -12,4 +12,8 @@ if use_gdnative: # GDNative is retained in Javascript for export compatibility
env_webrtc.Append(CPPDEFINES=["WEBRTC_GDNATIVE_ENABLED"])
env_webrtc.Prepend(CPPPATH=["#modules/gdnative/include/"])
+if env["platform"] == "javascript":
+ # Our JavaScript/C++ interface.
+ env.AddJSLibraries(["library_godot_webrtc.js"])
+
env_webrtc.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/webrtc/library_godot_webrtc.js b/modules/webrtc/library_godot_webrtc.js
new file mode 100644
index 0000000000..b75996b1f3
--- /dev/null
+++ b/modules/webrtc/library_godot_webrtc.js
@@ -0,0 +1,407 @@
+/*************************************************************************/
+/* library_godot_webrtc.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+var GodotRTCDataChannel = {
+ // Our socket implementation that forwards events to C++.
+ $GodotRTCDataChannel__deps: ['$IDHandler', '$GodotOS'],
+ $GodotRTCDataChannel: {
+
+ connect: function(p_id, p_on_open, p_on_message, p_on_error, p_on_close) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+
+ ref.binaryType = 'arraybuffer';
+ ref.onopen = function (event) {
+ p_on_open();
+ };
+ ref.onclose = function (event) {
+ p_on_close();
+ };
+ ref.onerror = function (event) {
+ p_on_error();
+ };
+ ref.onmessage = function(event) {
+ var buffer;
+ var is_string = 0;
+ if (event.data instanceof ArrayBuffer) {
+ buffer = new Uint8Array(event.data);
+ } else if (event.data instanceof Blob) {
+ console.error("Blob type not supported");
+ return;
+ } else if (typeof event.data === "string") {
+ is_string = 1;
+ var enc = new TextEncoder("utf-8");
+ buffer = new Uint8Array(enc.encode(event.data));
+ } else {
+ console.error("Unknown message type");
+ return;
+ }
+ var len = buffer.length*buffer.BYTES_PER_ELEMENT;
+ var out = _malloc(len);
+ HEAPU8.set(buffer, out);
+ p_on_message(out, len, is_string);
+ _free(out);
+ }
+ },
+
+ close: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ ref.onopen = null;
+ ref.onmessage = null;
+ ref.onerror = null;
+ ref.onclose = null;
+ ref.close();
+ },
+
+ get_prop: function(p_id, p_prop, p_def) {
+ const ref = IDHandler.get(p_id);
+ return (ref && ref[p_prop] !== undefined) ? ref[p_prop] : p_def;
+ },
+ },
+
+ godot_js_rtc_datachannel_ready_state_get: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return 3; // CLOSED
+ }
+
+ switch(ref.readyState) {
+ case "connecting":
+ return 0;
+ case "open":
+ return 1;
+ case "closing":
+ return 2;
+ case "closed":
+ return 3;
+ }
+ return 3; // CLOSED
+ },
+
+ godot_js_rtc_datachannel_send: function(p_id, p_buffer, p_length, p_raw) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return 1;
+ }
+
+ const bytes_array = new Uint8Array(p_length);
+ for (var i = 0; i < p_length; i++) {
+ bytes_array[i] = getValue(p_buffer + i, 'i8');
+ }
+
+ if (p_raw) {
+ ref.send(bytes_array.buffer);
+ } else {
+ const string = new TextDecoder('utf-8').decode(bytes_array);
+ ref.send(string);
+ }
+ },
+
+ godot_js_rtc_datachannel_is_ordered: function(p_id) {
+ return IDHandler.get_prop(p_id, 'ordered', true);
+ },
+
+ godot_js_rtc_datachannel_id_get: function(p_id) {
+ return IDHandler.get_prop(p_id, 'id', 65535);
+ },
+
+ godot_js_rtc_datachannel_max_packet_lifetime_get: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return 65535;
+ }
+ if (ref['maxPacketLifeTime'] !== undefined) {
+ return ref['maxPacketLifeTime'];
+ } else if (ref['maxRetransmitTime'] !== undefined) {
+ // Guess someone didn't appreciate the standardization process.
+ return ref['maxRetransmitTime'];
+ }
+ return 65535;
+ },
+
+ godot_js_rtc_datachannel_max_retransmits_get: function(p_id) {
+ return IDHandler.get_prop(p_id, 'maxRetransmits', 65535);
+ },
+
+ godot_js_rtc_datachannel_is_negotiated: function(p_id, p_def) {
+ return IDHandler.get_prop(p_id, 'negotiated', 65535);
+ },
+
+ godot_js_rtc_datachannel_label_get: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref || !ref.label) {
+ return 0;
+ }
+ return GodotOS.allocString(ref.label);
+ },
+
+ godot_js_rtc_datachannel_protocol_get: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref || !ref.protocol) {
+ return 0;
+ }
+ return GodotOS.allocString(ref.protocol);
+ },
+
+ godot_js_rtc_datachannel_destroy: function(p_id) {
+ GodotRTCDataChannel.close(p_id);
+ IDHandler.remove(p_id);
+ },
+
+ godot_js_rtc_datachannel_connect: function(p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) {
+ const onopen = GodotOS.get_func(p_on_open).bind(null, p_ref);
+ const onmessage = GodotOS.get_func(p_on_message).bind(null, p_ref);
+ const onerror = GodotOS.get_func(p_on_error).bind(null, p_ref);
+ const onclose = GodotOS.get_func(p_on_close).bind(null, p_ref);
+ GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose);
+ },
+
+ godot_js_rtc_datachannel_close: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ GodotRTCDataChannel.close(p_id);
+ },
+};
+
+autoAddDeps(GodotRTCDataChannel, '$GodotRTCDataChannel');
+mergeInto(LibraryManager.library, GodotRTCDataChannel);
+
+var GodotRTCPeerConnection = {
+
+ $GodotRTCPeerConnection__deps: ['$IDHandler', '$GodotOS', '$GodotRTCDataChannel'],
+ $GodotRTCPeerConnection: {
+ onstatechange: function(p_id, p_conn, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ var state = 5; // CLOSED
+ switch(p_conn.iceConnectionState) {
+ case "new":
+ state = 0;
+ case "checking":
+ state = 1;
+ case "connected":
+ case "completed":
+ state = 2;
+ case "disconnected":
+ state = 3;
+ case "failed":
+ state = 4;
+ case "closed":
+ state = 5;
+ }
+ callback(state);
+ },
+
+ onicecandidate: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref || !event.candidate) {
+ return;
+ }
+
+ let c = event.candidate;
+ let candidate_str = GodotOS.allocString(c.candidate);
+ let mid_str = GodotOS.allocString(c.sdpMid);
+ callback(mid_str, c.sdpMLineIndex, candidate_str);
+ _free(candidate_str);
+ _free(mid_str);
+ },
+
+ ondatachannel: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+
+ const cid = IDHandler.add(event.channel);
+ callback(cid);
+ },
+
+ onsession: function(p_id, callback, session) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ let type_str = GodotOS.allocString(session.type);
+ let sdp_str = GodotOS.allocString(session.sdp);
+ callback(type_str, sdp_str);
+ _free(type_str);
+ _free(sdp_str);
+ },
+
+ onerror: function(p_id, callback, error) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ console.error(error);
+ callback();
+ },
+ },
+
+ godot_js_rtc_pc_create: function(p_config, p_ref, p_on_state_change, p_on_candidate, p_on_datachannel) {
+ const onstatechange = GodotOS.get_func(p_on_state_change).bind(null, p_ref);
+ const oncandidate = GodotOS.get_func(p_on_candidate).bind(null, p_ref);
+ const ondatachannel = GodotOS.get_func(p_on_datachannel).bind(null, p_ref);
+
+ var config = JSON.parse(UTF8ToString(p_config));
+ var conn = null;
+ try {
+ conn = new RTCPeerConnection(config);
+ } catch (e) {
+ console.error(e);
+ return 0;
+ }
+
+ const base = GodotRTCPeerConnection;
+ const id = IDHandler.add(conn);
+ conn.oniceconnectionstatechange = base.onstatechange.bind(null, id, conn, onstatechange);
+ conn.onicecandidate = base.onicecandidate.bind(null, id, oncandidate);
+ conn.ondatachannel = base.ondatachannel.bind(null, id, ondatachannel);
+ return id;
+ },
+
+ godot_js_rtc_pc_close: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ ref.close();
+ },
+
+ godot_js_rtc_pc_destroy: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ ref.oniceconnectionstatechange = null;
+ ref.onicecandidate = null;
+ ref.ondatachannel = null;
+ IDHandler.remove(p_id);
+ },
+
+ godot_js_rtc_pc_offer_create: function(p_id, p_obj, p_on_session, p_on_error) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ const onsession = GodotOS.get_func(p_on_session).bind(null, p_obj);
+ const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
+ ref.createOffer().then(function(session) {
+ GodotRTCPeerConnection.onsession(p_id, onsession, session);
+ }).catch(function(error) {
+ GodotRTCPeerConnection.onerror(p_id, onerror, error);
+ });
+ },
+
+ godot_js_rtc_pc_local_description_set: function(p_id, p_type, p_sdp, p_obj, p_on_error) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ const type = UTF8ToString(p_type);
+ const sdp = UTF8ToString(p_sdp);
+ const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
+ ref.setLocalDescription({
+ 'sdp': sdp,
+ 'type': type
+ }).catch(function(error) {
+ GodotRTCPeerConnection.onerror(p_id, onerror, error);
+ });
+ },
+
+ godot_js_rtc_pc_remote_description_set: function(p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ const type = UTF8ToString(p_type);
+ const sdp = UTF8ToString(p_sdp);
+ const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
+ const onsession = GodotOS.get_func(p_session_created).bind(null, p_obj);
+ ref.setRemoteDescription({
+ 'sdp': sdp,
+ 'type': type
+ }).then(function() {
+ if (type != 'offer') {
+ return;
+ }
+ return ref.createAnswer().then(function(session) {
+ GodotRTCPeerConnection.onsession(p_id, onsession, session);
+ });
+ }).catch(function(error) {
+ GodotRTCPeerConnection.onerror(p_id, onerror, error);
+ });
+ },
+
+ godot_js_rtc_pc_ice_candidate_add: function(p_id, p_mid_name, p_mline_idx, p_sdp) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ var sdpMidName = UTF8ToString(p_mid_name);
+ var sdpName = UTF8ToString(p_sdp);
+ ref.addIceCandidate(new RTCIceCandidate({
+ "candidate": sdpName,
+ "sdpMid": sdpMidName,
+ "sdpMlineIndex": p_mline_idx,
+ }));
+ },
+
+ godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'],
+ godot_js_rtc_pc_datachannel_create: function(p_id, p_label, p_config) {
+ try {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return 0;
+ }
+
+ const label = UTF8ToString(p_label);
+ const config = JSON.parse(UTF8ToString(p_config));
+
+ const channel = ref.createDataChannel(label, config);
+ return IDHandler.add(channel);
+ } catch (e) {
+ console.error(e);
+ return 0;
+ }
+ },
+};
+
+autoAddDeps(GodotRTCPeerConnection, '$GodotRTCPeerConnection')
+mergeInto(LibraryManager.library, GodotRTCPeerConnection);
diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp
index 2c648ba9f9..3a63001a56 100644
--- a/modules/webrtc/webrtc_data_channel_js.cpp
+++ b/modules/webrtc/webrtc_data_channel_js.cpp
@@ -34,65 +34,58 @@
#include "emscripten.h"
extern "C" {
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_error(void *obj) {
- WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(obj);
- peer->_on_error();
-}
+typedef void (*RTCChOnOpen)(void *p_obj);
+typedef void (*RTCChOnMessage)(void *p_obj, const uint8_t *p_buffer, int p_size, int p_is_string);
+typedef void (*RTCChOnClose)(void *p_obj);
+typedef void (*RTCChOnError)(void *p_obj);
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_open(void *obj) {
- WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(obj);
- peer->_on_open();
+extern int godot_js_rtc_datachannel_ready_state_get(int p_id);
+extern int godot_js_rtc_datachannel_send(int p_id, const uint8_t *p_buffer, int p_length, int p_raw);
+extern int godot_js_rtc_datachannel_is_ordered(int p_id);
+extern int godot_js_rtc_datachannel_id_get(int p_id);
+extern int godot_js_rtc_datachannel_max_packet_lifetime_get(int p_id);
+extern int godot_js_rtc_datachannel_max_retransmits_get(int p_id);
+extern int godot_js_rtc_datachannel_is_negotiated(int p_id);
+extern char *godot_js_rtc_datachannel_label_get(int p_id); // Must free the returned string.
+extern char *godot_js_rtc_datachannel_protocol_get(int p_id); // Must free the returned string.
+extern void godot_js_rtc_datachannel_destroy(int p_id);
+extern void godot_js_rtc_datachannel_connect(int p_id, void *p_obj, RTCChOnOpen p_on_open, RTCChOnMessage p_on_message, RTCChOnError p_on_error, RTCChOnClose p_on_close);
+extern void godot_js_rtc_datachannel_close(int p_id);
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_close(void *obj) {
- WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(obj);
- peer->_on_close();
+void WebRTCDataChannelJS::_on_open(void *p_obj) {
+ WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(p_obj);
+ peer->in_buffer.resize(peer->_in_buffer_shift);
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_message(void *obj, uint8_t *p_data, uint32_t p_size, bool p_is_string) {
- WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(obj);
- peer->_on_message(p_data, p_size, p_is_string);
-}
+void WebRTCDataChannelJS::_on_close(void *p_obj) {
+ WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(p_obj);
+ peer->close();
}
-void WebRTCDataChannelJS::_on_open() {
- in_buffer.resize(_in_buffer_shift);
+void WebRTCDataChannelJS::_on_error(void *p_obj) {
+ WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(p_obj);
+ peer->close();
}
-void WebRTCDataChannelJS::_on_close() {
- close();
-}
+void WebRTCDataChannelJS::_on_message(void *p_obj, const uint8_t *p_data, int p_size, int p_is_string) {
+ WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(p_obj);
+ RingBuffer<uint8_t> &in_buffer = peer->in_buffer;
-void WebRTCDataChannelJS::_on_error() {
- close();
-}
-
-void WebRTCDataChannelJS::_on_message(uint8_t *p_data, uint32_t p_size, bool p_is_string) {
ERR_FAIL_COND_MSG(in_buffer.space_left() < (int)(p_size + 5), "Buffer full! Dropping data.");
uint8_t is_string = p_is_string ? 1 : 0;
in_buffer.write((uint8_t *)&p_size, 4);
in_buffer.write((uint8_t *)&is_string, 1);
in_buffer.write(p_data, p_size);
- queue_count++;
+ peer->queue_count++;
}
void WebRTCDataChannelJS::close() {
in_buffer.resize(0);
queue_count = 0;
_was_string = false;
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- if (!dict) return;
- var channel = dict["channel"];
- channel.onopen = null;
- channel.onclose = null;
- channel.onerror = null;
- channel.onmessage = null;
- channel.close();
- }, _js_id);
- /* clang-format on */
+ godot_js_rtc_datachannel_close(_js_id);
}
Error WebRTCDataChannelJS::poll() {
@@ -100,24 +93,7 @@ Error WebRTCDataChannelJS::poll() {
}
WebRTCDataChannelJS::ChannelState WebRTCDataChannelJS::get_ready_state() const {
- /* clang-format off */
- return (ChannelState) EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict) return 3; // CLOSED
- var channel = dict["channel"];
- switch(channel.readyState) {
- case "connecting":
- return 0;
- case "open":
- return 1;
- case "closing":
- return 2;
- case "closed":
- return 3;
- }
- return 3; // CLOSED
- }, _js_id);
- /* clang-format on */
+ return (ChannelState)godot_js_rtc_datachannel_ready_state_get(_js_id);
}
int WebRTCDataChannelJS::get_available_packet_count() const {
@@ -157,27 +133,7 @@ Error WebRTCDataChannelJS::put_packet(const uint8_t *p_buffer, int p_buffer_size
ERR_FAIL_COND_V(get_ready_state() != STATE_OPEN, ERR_UNCONFIGURED);
int is_bin = _write_mode == WebRTCDataChannel::WRITE_MODE_BINARY ? 1 : 0;
-
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var channel = dict["channel"];
- var bytes_array = new Uint8Array($2);
- var i = 0;
-
- for(i=0; i<$2; i++) {
- bytes_array[i] = getValue($1+i, 'i8');
- }
-
- if ($3) {
- channel.send(bytes_array.buffer);
- } else {
- var string = new TextDecoder("utf-8").decode(bytes_array);
- channel.send(string);
- }
- }, _js_id, p_buffer, p_buffer_size, is_bin);
- /* clang-format on */
-
+ godot_js_rtc_datachannel_send(_js_id, p_buffer, p_buffer_size, is_bin);
return OK;
}
@@ -201,46 +157,20 @@ String WebRTCDataChannelJS::get_label() const {
return _label;
}
-/* clang-format off */
-#define _JS_GET(PROP, DEF) \
-EM_ASM_INT({ \
- var dict = Module.IDHandler.get($0); \
- if (!dict || !dict["channel"]) { \
- return DEF; \
- } \
- var out = dict["channel"].PROP; \
- return out === null ? DEF : out; \
-}, _js_id)
-/* clang-format on */
-
bool WebRTCDataChannelJS::is_ordered() const {
- return _JS_GET(ordered, true);
+ return godot_js_rtc_datachannel_is_ordered(_js_id);
}
int WebRTCDataChannelJS::get_id() const {
- return _JS_GET(id, 65535);
+ return godot_js_rtc_datachannel_id_get(_js_id);
}
int WebRTCDataChannelJS::get_max_packet_life_time() const {
- // Can't use macro, webkit workaround.
- /* clang-format off */
- return EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict || !dict["channel"]) {
- return 65535;
- }
- if (dict["channel"].maxRetransmitTime !== undefined) {
- // Guess someone didn't appreciate the standardization process.
- return dict["channel"].maxRetransmitTime;
- }
- var out = dict["channel"].maxPacketLifeTime;
- return out === null ? 65535 : out;
- }, _js_id);
- /* clang-format on */
+ return godot_js_rtc_datachannel_max_packet_lifetime_get(_js_id);
}
int WebRTCDataChannelJS::get_max_retransmits() const {
- return _JS_GET(maxRetransmits, 65535);
+ return godot_js_rtc_datachannel_max_retransmits_get(_js_id);
}
String WebRTCDataChannelJS::get_protocol() const {
@@ -248,7 +178,7 @@ String WebRTCDataChannelJS::get_protocol() const {
}
bool WebRTCDataChannelJS::is_negotiated() const {
- return _JS_GET(negotiated, false);
+ return godot_js_rtc_datachannel_is_negotiated(_js_id);
}
WebRTCDataChannelJS::WebRTCDataChannelJS() {
@@ -264,101 +194,22 @@ WebRTCDataChannelJS::WebRTCDataChannelJS(int js_id) {
_write_mode = WRITE_MODE_BINARY;
_js_id = js_id;
- /* clang-format off */
- EM_ASM({
- var c_ptr = $0;
- var dict = Module.IDHandler.get($1);
- if (!dict) return;
- var channel = dict["channel"];
- dict["ptr"] = c_ptr;
-
- channel.binaryType = "arraybuffer";
- channel.onopen = function (evt) {
- ccall("_emrtc_on_ch_open",
- "void",
- ["number"],
- [c_ptr]
- );
- };
- channel.onclose = function (evt) {
- ccall("_emrtc_on_ch_close",
- "void",
- ["number"],
- [c_ptr]
- );
- };
- channel.onerror = function (evt) {
- ccall("_emrtc_on_ch_error",
- "void",
- ["number"],
- [c_ptr]
- );
- };
- channel.onmessage = function(event) {
- var buffer;
- var is_string = 0;
- if (event.data instanceof ArrayBuffer) {
- buffer = new Uint8Array(event.data);
- } else if (event.data instanceof Blob) {
- console.error("Blob type not supported");
- return;
- } else if (typeof event.data === "string") {
- is_string = 1;
- var enc = new TextEncoder("utf-8");
- buffer = new Uint8Array(enc.encode(event.data));
- } else {
- console.error("Unknown message type");
- return;
- }
- var len = buffer.length*buffer.BYTES_PER_ELEMENT;
- var out = _malloc(len);
- HEAPU8.set(buffer, out);
- ccall("_emrtc_on_ch_message",
- "void",
- ["number", "number", "number", "number"],
- [c_ptr, out, len, is_string]
- );
- _free(out);
- }
-
- }, this, js_id);
+ godot_js_rtc_datachannel_connect(js_id, this, &_on_open, &_on_message, &_on_error, &_on_close);
// Parse label
- char *str;
- str = (char *)EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict || !dict["channel"]) return 0;
- var str = dict["channel"].label;
- var len = lengthBytesUTF8(str)+1;
- var ptr = _malloc(str);
- stringToUTF8(str, ptr, len+1);
- return ptr;
- }, js_id);
- if(str != nullptr) {
- _label.parse_utf8(str);
- EM_ASM({ _free($0) }, str);
+ char *label = godot_js_rtc_datachannel_label_get(js_id);
+ if (label) {
+ _label.parse_utf8(label);
+ free(label);
}
- str = (char *)EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict || !dict["channel"]) return 0;
- var str = dict["channel"].protocol;
- var len = lengthBytesUTF8(str)+1;
- var ptr = _malloc(str);
- stringToUTF8(str, ptr, len+1);
- return ptr;
- }, js_id);
- if(str != nullptr) {
- _protocol.parse_utf8(str);
- EM_ASM({ _free($0) }, str);
+ char *protocol = godot_js_rtc_datachannel_protocol_get(js_id);
+ if (protocol) {
+ _protocol.parse_utf8(protocol);
+ free(protocol);
}
- /* clang-format on */
}
WebRTCDataChannelJS::~WebRTCDataChannelJS() {
close();
- /* clang-format off */
- EM_ASM({
- Module.IDHandler.remove($0);
- }, _js_id);
- /* clang-format on */
-};
+ godot_js_rtc_datachannel_destroy(_js_id);
+}
#endif
diff --git a/modules/webrtc/webrtc_data_channel_js.h b/modules/webrtc/webrtc_data_channel_js.h
index 7545910e66..e251760019 100644
--- a/modules/webrtc/webrtc_data_channel_js.h
+++ b/modules/webrtc/webrtc_data_channel_js.h
@@ -54,12 +54,12 @@ private:
int queue_count;
uint8_t packet_buffer[PACKET_BUFFER_SIZE];
-public:
- void _on_open();
- void _on_close();
- void _on_error();
- void _on_message(uint8_t *p_data, uint32_t p_size, bool p_is_string);
+ static void _on_open(void *p_obj);
+ static void _on_close(void *p_obj);
+ static void _on_error(void *p_obj);
+ static void _on_message(void *p_obj, const uint8_t *p_data, int p_size, int p_is_string);
+public:
virtual void set_write_mode(WriteMode mode) override;
virtual WriteMode get_write_mode() const override;
virtual bool was_string_packet() const override;
diff --git a/modules/webrtc/webrtc_peer_connection_js.cpp b/modules/webrtc/webrtc_peer_connection_js.cpp
index 593c3a5162..ad9b46a8af 100644
--- a/modules/webrtc/webrtc_peer_connection_js.cpp
+++ b/modules/webrtc/webrtc_peer_connection_js.cpp
@@ -37,116 +37,32 @@
#include "core/io/json.h"
#include "emscripten.h"
-extern "C" {
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ice_candidate(void *obj, char *p_MidName, int p_MlineIndexName, char *p_sdpName) {
- WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(obj);
- peer->emit_signal("ice_candidate_created", String(p_MidName), p_MlineIndexName, String(p_sdpName));
+void WebRTCPeerConnectionJS::_on_ice_candidate(void *p_obj, const char *p_mid_name, int p_mline_idx, const char *p_candidate) {
+ WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
+ peer->emit_signal("ice_candidate_created", String(p_mid_name), p_mline_idx, String(p_candidate));
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_session_description_created(void *obj, char *p_type, char *p_offer) {
- WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(obj);
- peer->emit_signal("session_description_created", String(p_type), String(p_offer));
+void WebRTCPeerConnectionJS::_on_session_created(void *p_obj, const char *p_type, const char *p_session) {
+ WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
+ peer->emit_signal("session_description_created", String(p_type), String(p_session));
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_connection_state_changed(void *obj) {
- WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(obj);
- peer->_on_connection_state_changed();
+void WebRTCPeerConnectionJS::_on_connection_state_changed(void *p_obj, int p_state) {
+ WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
+ peer->_conn_state = (ConnectionState)p_state;
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_error() {
+void WebRTCPeerConnectionJS::_on_error(void *p_obj) {
ERR_PRINT("RTCPeerConnection error!");
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_emit_channel(void *obj, int p_id) {
- WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(obj);
+void WebRTCPeerConnectionJS::_on_data_channel(void *p_obj, int p_id) {
+ WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
peer->emit_signal("data_channel_received", Ref<WebRTCDataChannelJS>(new WebRTCDataChannelJS(p_id)));
}
-}
-
-void _emrtc_create_pc(int p_id, const Dictionary &p_config) {
- String config = JSON::print(p_config);
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var c_ptr = dict["ptr"];
- var config = JSON.parse(UTF8ToString($1));
- // Setup local connaction
- var conn = null;
- try {
- conn = new RTCPeerConnection(config);
- } catch (e) {
- console.log(e);
- return;
- }
- conn.oniceconnectionstatechange = function(event) {
- if (!Module.IDHandler.get($0)) return;
- ccall("_emrtc_on_connection_state_changed", "void", ["number"], [c_ptr]);
- };
- conn.onicecandidate = function(event) {
- if (!Module.IDHandler.get($0)) return;
- if (!event.candidate) return;
-
- var c = event.candidate;
- // should emit on ice candidate
- ccall("_emrtc_on_ice_candidate",
- "void",
- ["number", "string", "number", "string"],
- [c_ptr, c.sdpMid, c.sdpMLineIndex, c.candidate]
- );
- };
- conn.ondatachannel = function (evt) {
- var dict = Module.IDHandler.get($0);
- if (!dict) {
- return;
- }
- var id = Module.IDHandler.add({"channel": evt.channel, "ptr": null});
- ccall("_emrtc_emit_channel",
- "void",
- ["number", "number"],
- [c_ptr, id]
- );
- };
- dict["conn"] = conn;
- }, p_id, config.utf8().get_data());
- /* clang-format on */
-}
-
-void WebRTCPeerConnectionJS::_on_connection_state_changed() {
- /* clang-format off */
- _conn_state = (ConnectionState)EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict) return 5; // CLOSED
- var conn = dict["conn"];
- switch(conn.iceConnectionState) {
- case "new":
- return 0;
- case "checking":
- return 1;
- case "connected":
- case "completed":
- return 2;
- case "disconnected":
- return 3;
- case "failed":
- return 4;
- case "closed":
- return 5;
- }
- return 5; // CLOSED
- }, _js_id);
- /* clang-format on */
-}
void WebRTCPeerConnectionJS::close() {
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- if (!dict) return;
- if (dict["conn"]) {
- dict["conn"].close();
- }
- }, _js_id);
- /* clang-format on */
+ godot_js_rtc_pc_close(_js_id);
_conn_state = STATE_CLOSED;
}
@@ -154,46 +70,12 @@ Error WebRTCPeerConnectionJS::create_offer() {
ERR_FAIL_COND_V(_conn_state != STATE_NEW, FAILED);
_conn_state = STATE_CONNECTING;
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var conn = dict["conn"];
- var c_ptr = dict["ptr"];
- var onError = function(error) {
- console.error(error);
- ccall("_emrtc_on_error", "void", [], []);
- };
- var onCreated = function(offer) {
- ccall("_emrtc_session_description_created",
- "void",
- ["number", "string", "string"],
- [c_ptr, offer.type, offer.sdp]
- );
- };
- conn.createOffer().then(onCreated).catch(onError);
- }, _js_id);
- /* clang-format on */
+ godot_js_rtc_pc_offer_create(_js_id, this, &_on_session_created, &_on_error);
return OK;
}
Error WebRTCPeerConnectionJS::set_local_description(String type, String sdp) {
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var conn = dict["conn"];
- var c_ptr = dict["ptr"];
- var type = UTF8ToString($1);
- var sdp = UTF8ToString($2);
- var onError = function(error) {
- console.error(error);
- ccall("_emrtc_on_error", "void", [], []);
- };
- conn.setLocalDescription({
- "sdp": sdp,
- "type": type
- }).catch(onError);
- }, _js_id, type.utf8().get_data(), sdp.utf8().get_data());
- /* clang-format on */
+ godot_js_rtc_pc_local_description_set(_js_id, type.utf8().get_data(), sdp.utf8().get_data(), this, &_on_error);
return OK;
}
@@ -202,83 +84,32 @@ Error WebRTCPeerConnectionJS::set_remote_description(String type, String sdp) {
ERR_FAIL_COND_V(_conn_state != STATE_NEW, FAILED);
_conn_state = STATE_CONNECTING;
}
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var conn = dict["conn"];
- var c_ptr = dict["ptr"];
- var type = UTF8ToString($1);
- var sdp = UTF8ToString($2);
-
- var onError = function(error) {
- console.error(error);
- ccall("_emrtc_on_error", "void", [], []);
- };
- var onCreated = function(offer) {
- ccall("_emrtc_session_description_created",
- "void",
- ["number", "string", "string"],
- [c_ptr, offer.type, offer.sdp]
- );
- };
- var onSet = function() {
- if (type != "offer") {
- return;
- }
- conn.createAnswer().then(onCreated);
- };
- conn.setRemoteDescription({
- "sdp": sdp,
- "type": type
- }).then(onSet).catch(onError);
- }, _js_id, type.utf8().get_data(), sdp.utf8().get_data());
- /* clang-format on */
+ godot_js_rtc_pc_remote_description_set(_js_id, type.utf8().get_data(), sdp.utf8().get_data(), this, &_on_session_created, &_on_error);
return OK;
}
Error WebRTCPeerConnectionJS::add_ice_candidate(String sdpMidName, int sdpMlineIndexName, String sdpName) {
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var conn = dict["conn"];
- var c_ptr = dict["ptr"];
- var sdpMidName = UTF8ToString($1);
- var sdpMlineIndexName = UTF8ToString($2);
- var sdpName = UTF8ToString($3);
- conn.addIceCandidate(new RTCIceCandidate({
- "candidate": sdpName,
- "sdpMid": sdpMidName,
- "sdpMlineIndex": sdpMlineIndexName
- }));
- }, _js_id, sdpMidName.utf8().get_data(), sdpMlineIndexName, sdpName.utf8().get_data());
- /* clang-format on */
+ godot_js_rtc_pc_ice_candidate_add(_js_id, sdpMidName.utf8().get_data(), sdpMlineIndexName, sdpName.utf8().get_data());
return OK;
}
Error WebRTCPeerConnectionJS::initialize(Dictionary p_config) {
- _emrtc_create_pc(_js_id, p_config);
- return OK;
+ if (_js_id) {
+ godot_js_rtc_pc_destroy(_js_id);
+ _js_id = 0;
+ }
+ _conn_state = STATE_NEW;
+
+ String config = JSON::print(p_config);
+ _js_id = godot_js_rtc_pc_create(config.utf8().get_data(), this, &_on_connection_state_changed, &_on_ice_candidate, &_on_data_channel);
+ return _js_id ? OK : FAILED;
}
Ref<WebRTCDataChannel> WebRTCPeerConnectionJS::create_data_channel(String p_channel, Dictionary p_channel_config) {
+ ERR_FAIL_COND_V(_conn_state != STATE_NEW, nullptr);
+
String config = JSON::print(p_channel_config);
- /* clang-format off */
- int id = EM_ASM_INT({
- try {
- var dict = Module.IDHandler.get($0);
- if (!dict) return 0;
- var label = UTF8ToString($1);
- var config = JSON.parse(UTF8ToString($2));
- var conn = dict["conn"];
- return Module.IDHandler.add({
- "channel": conn.createDataChannel(label, config),
- "ptr": null
- })
- } catch (e) {
- return 0;
- }
- }, _js_id, p_channel.utf8().get_data(), config.utf8().get_data());
- /* clang-format on */
+ int id = godot_js_rtc_pc_datachannel_create(_js_id, p_channel.utf8().get_data(), config.utf8().get_data());
ERR_FAIL_COND_V(id == 0, nullptr);
return memnew(WebRTCDataChannelJS(id));
}
@@ -293,22 +124,17 @@ WebRTCPeerConnection::ConnectionState WebRTCPeerConnectionJS::get_connection_sta
WebRTCPeerConnectionJS::WebRTCPeerConnectionJS() {
_conn_state = STATE_NEW;
+ _js_id = 0;
- /* clang-format off */
- _js_id = EM_ASM_INT({
- return Module.IDHandler.add({"conn": null, "ptr": $0});
- }, this);
- /* clang-format on */
Dictionary config;
- _emrtc_create_pc(_js_id, config);
+ initialize(config);
}
WebRTCPeerConnectionJS::~WebRTCPeerConnectionJS() {
close();
- /* clang-format off */
- EM_ASM({
- Module.IDHandler.remove($0);
- }, _js_id);
- /* clang-format on */
+ if (_js_id) {
+ godot_js_rtc_pc_destroy(_js_id);
+ _js_id = 0;
+ }
};
#endif
diff --git a/modules/webrtc/webrtc_peer_connection_js.h b/modules/webrtc/webrtc_peer_connection_js.h
index cdaf3068e3..e33dd5f259 100644
--- a/modules/webrtc/webrtc_peer_connection_js.h
+++ b/modules/webrtc/webrtc_peer_connection_js.h
@@ -35,16 +35,37 @@
#include "webrtc_peer_connection.h"
+extern "C" {
+typedef void (*RTCOnIceConnectionStateChange)(void *p_obj, int p_state);
+typedef void (*RTCOnIceCandidate)(void *p_obj, const char *p_mid, int p_mline_idx, const char *p_candidate);
+typedef void (*RTCOnDataChannel)(void *p_obj, int p_id);
+typedef void (*RTCOnSession)(void *p_obj, const char *p_type, const char *p_sdp);
+typedef void (*RTCOnError)(void *p_obj);
+extern int godot_js_rtc_pc_create(const char *p_config, void *p_obj, RTCOnIceConnectionStateChange p_on_state_change, RTCOnIceCandidate p_on_candidate, RTCOnDataChannel p_on_datachannel);
+extern void godot_js_rtc_pc_close(int p_id);
+extern void godot_js_rtc_pc_destroy(int p_id);
+extern void godot_js_rtc_pc_offer_create(int p_id, void *p_obj, RTCOnSession p_on_session, RTCOnError p_on_error);
+extern void godot_js_rtc_pc_local_description_set(int p_id, const char *p_type, const char *p_sdp, void *p_obj, RTCOnError p_on_error);
+extern void godot_js_rtc_pc_remote_description_set(int p_id, const char *p_type, const char *p_sdp, void *p_obj, RTCOnSession p_on_session, RTCOnError p_on_error);
+extern void godot_js_rtc_pc_ice_candidate_add(int p_id, const char *p_mid_name, int p_mline_idx, const char *p_sdo);
+extern int godot_js_rtc_pc_datachannel_create(int p_id, const char *p_label, const char *p_config);
+}
+
class WebRTCPeerConnectionJS : public WebRTCPeerConnection {
private:
int _js_id;
ConnectionState _conn_state;
+ static void _on_connection_state_changed(void *p_obj, int p_state);
+ static void _on_ice_candidate(void *p_obj, const char *p_mid_name, int p_mline_idx, const char *p_candidate);
+ static void _on_data_channel(void *p_obj, int p_channel);
+ static void _on_session_created(void *p_obj, const char *p_type, const char *p_session);
+ static void _on_error(void *p_obj);
+
public:
static WebRTCPeerConnection *_create() { return memnew(WebRTCPeerConnectionJS); }
static void make_default() { WebRTCPeerConnection::_create = WebRTCPeerConnectionJS::_create; }
- void _on_connection_state_changed();
virtual ConnectionState get_connection_state() const;
virtual Error initialize(Dictionary configuration = Dictionary());
diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub
index af60055855..13e51a39c0 100644
--- a/modules/websocket/SCsub
+++ b/modules/websocket/SCsub
@@ -3,11 +3,13 @@
Import("env")
Import("env_modules")
-# Thirdparty source files
-
env_ws = env_modules.Clone()
-if env["builtin_wslay"] and not env["platform"] == "javascript": # already builtin for javascript
+if env["platform"] == "javascript":
+ # Our JavaScript/C++ interface.
+ env.AddJSLibraries(["library_godot_websocket.js"])
+elif env["builtin_wslay"]:
+ # Thirdparty source files
wslay_dir = "#thirdparty/wslay/"
wslay_sources = [
"wslay_net.c",
diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp
index 93d60dca08..d6e00a26af 100644
--- a/modules/websocket/emws_client.cpp
+++ b/modules/websocket/emws_client.cpp
@@ -35,14 +35,13 @@
#include "core/io/ip.h"
#include "emscripten.h"
-extern "C" {
-EMSCRIPTEN_KEEPALIVE void _esws_on_connect(void *obj, char *proto) {
+void EMWSClient::_esws_on_connect(void *obj, char *proto) {
EMWSClient *client = static_cast<EMWSClient *>(obj);
client->_is_connecting = false;
client->_on_connect(String(proto));
}
-EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_data_size, int p_is_string) {
+void EMWSClient::_esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string) {
EMWSClient *client = static_cast<EMWSClient *>(obj);
Error err = static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1);
@@ -50,21 +49,26 @@ EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_dat
client->_on_peer_packet();
}
-EMSCRIPTEN_KEEPALIVE void _esws_on_error(void *obj) {
+void EMWSClient::_esws_on_error(void *obj) {
EMWSClient *client = static_cast<EMWSClient *>(obj);
client->_is_connecting = false;
client->_on_error();
}
-EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int was_clean) {
+void EMWSClient::_esws_on_close(void *obj, int code, const char *reason, int was_clean) {
EMWSClient *client = static_cast<EMWSClient *>(obj);
client->_on_close_request(code, String(reason));
client->_is_connecting = false;
+ client->disconnect_from_host();
client->_on_disconnect(was_clean != 0);
}
-}
Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) {
+ if (_js_id) {
+ godot_js_websocket_destroy(_js_id);
+ _js_id = 0;
+ }
+
String proto_string;
for (int i = 0; i < p_protocols.size(); i++) {
if (i != 0)
@@ -84,106 +88,17 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
}
}
str += p_host + ":" + itos(p_port) + p_path;
-
_is_connecting = true;
- /* clang-format off */
- int peer_sock = EM_ASM_INT({
- var proto_str = UTF8ToString($2);
- var socket = null;
- try {
- if (proto_str) {
- socket = new WebSocket(UTF8ToString($1), proto_str.split(","));
- } else {
- socket = new WebSocket(UTF8ToString($1));
- }
- } catch (e) {
- return -1;
- }
- var c_ptr = Module.IDHandler.get($0);
- socket.binaryType = "arraybuffer";
-
- // Connection opened
- socket.addEventListener("open", function (event) {
- if (!Module.IDHandler.has($0))
- return; // Godot Object is gone!
- ccall("_esws_on_connect",
- "void",
- ["number", "string"],
- [c_ptr, socket.protocol]
- );
- });
-
- // Listen for messages
- socket.addEventListener("message", function (event) {
- if (!Module.IDHandler.has($0))
- return; // Godot Object is gone!
- var buffer;
- var is_string = 0;
- if (event.data instanceof ArrayBuffer) {
-
- buffer = new Uint8Array(event.data);
-
- } else if (event.data instanceof Blob) {
-
- alert("Blob type not supported");
- return;
-
- } else if (typeof event.data === "string") {
-
- is_string = 1;
- var enc = new TextEncoder("utf-8");
- buffer = new Uint8Array(enc.encode(event.data));
-
- } else {
-
- alert("Unknown message type");
- return;
-
- }
- var len = buffer.length*buffer.BYTES_PER_ELEMENT;
- var out = _malloc(len);
- HEAPU8.set(buffer, out);
- ccall("_esws_on_message",
- "void",
- ["number", "number", "number", "number"],
- [c_ptr, out, len, is_string]
- );
- _free(out);
- });
-
- socket.addEventListener("error", function (event) {
- if (!Module.IDHandler.has($0))
- return; // Godot Object is gone!
- ccall("_esws_on_error",
- "void",
- ["number"],
- [c_ptr]
- );
- });
-
- socket.addEventListener("close", function (event) {
- if (!Module.IDHandler.has($0))
- return; // Godot Object is gone!
- var was_clean = 0;
- if (event.wasClean)
- was_clean = 1;
- ccall("_esws_on_close",
- "void",
- ["number", "number", "string", "number"],
- [c_ptr, event.code, event.reason, was_clean]
- );
- });
-
- return Module.IDHandler.add(socket);
- }, _js_id, str.utf8().get_data(), proto_string.utf8().get_data());
- /* clang-format on */
- if (peer_sock == -1)
+
+ _js_id = godot_js_websocket_create(this, str.utf8().get_data(), proto_string.utf8().get_data(), &_esws_on_connect, &_esws_on_message, &_esws_on_error, &_esws_on_close);
+ if (!_js_id) {
return FAILED;
+ }
- static_cast<Ref<EMWSPeer>>(_peer)->set_sock(peer_sock, _in_buf_size, _in_pkt_size);
+ static_cast<Ref<EMWSPeer>>(_peer)->set_sock(_js_id, _in_buf_size, _in_pkt_size);
return OK;
-};
+}
void EMWSClient::poll() {
}
@@ -200,19 +115,19 @@ NetworkedMultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() c
}
return CONNECTION_DISCONNECTED;
-};
+}
void EMWSClient::disconnect_from_host(int p_code, String p_reason) {
_peer->close(p_code, p_reason);
-};
+}
IP_Address EMWSClient::get_connected_host() const {
ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export.");
-};
+}
uint16_t EMWSClient::get_connected_port() const {
ERR_FAIL_V_MSG(0, "Not supported in HTML5 export.");
-};
+}
int EMWSClient::get_max_packet_size() const {
return (1 << _in_buf_size) - PROTO_SIZE;
@@ -227,24 +142,17 @@ Error EMWSClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffe
EMWSClient::EMWSClient() {
_in_buf_size = DEF_BUF_SHIFT;
_in_pkt_size = DEF_PKT_SHIFT;
-
_is_connecting = false;
_peer = Ref<EMWSPeer>(memnew(EMWSPeer));
- /* clang-format off */
- _js_id = EM_ASM_INT({
- return Module.IDHandler.add($0);
- }, this);
- /* clang-format on */
-};
+ _js_id = 0;
+}
EMWSClient::~EMWSClient() {
disconnect_from_host();
_peer = Ref<EMWSPeer>();
- /* clang-format off */
- EM_ASM({
- Module.IDHandler.remove($0);
- }, _js_id);
- /* clang-format on */
-};
+ if (_js_id) {
+ godot_js_websocket_destroy(_js_id);
+ }
+}
#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h
index 58973c52fa..0123c37457 100644
--- a/modules/websocket/emws_client.h
+++ b/modules/websocket/emws_client.h
@@ -41,13 +41,17 @@ class EMWSClient : public WebSocketClient {
GDCIIMPL(EMWSClient, WebSocketClient);
private:
+ int _js_id;
+ bool _is_connecting;
int _in_buf_size;
int _in_pkt_size;
- int _js_id;
-public:
- bool _is_connecting;
+ static void _esws_on_connect(void *obj, char *proto);
+ static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string);
+ static void _esws_on_error(void *obj);
+ static void _esws_on_close(void *obj, int code, const char *reason, int was_clean);
+public:
Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets);
Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>());
Ref<WebSocketPeer> get_peer(int p_peer_id) const;
diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp
index 749f45451a..5dcfba5567 100644
--- a/modules/websocket/emws_peer.cpp
+++ b/modules/websocket/emws_peer.cpp
@@ -47,38 +47,14 @@ EMWSPeer::WriteMode EMWSPeer::get_write_mode() const {
return write_mode;
}
-Error EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) {
+Error EMWSPeer::read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string) {
uint8_t is_string = p_is_string ? 1 : 0;
return _in_buffer.write_packet(p_data, p_size, &is_string);
}
Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0;
-
- /* clang-format off */
- EM_ASM({
- var sock = Module.IDHandler.get($0);
- var bytes_array = new Uint8Array($2);
- var i = 0;
-
- for(i=0; i<$2; i++) {
- bytes_array[i] = getValue($1+i, 'i8');
- }
-
- try {
- if ($3) {
- sock.send(bytes_array.buffer);
- } else {
- var string = new TextDecoder("utf-8").decode(bytes_array);
- sock.send(string);
- }
- } catch (e) {
- return 1;
- }
- return 0;
- }, peer_sock, p_buffer, p_buffer_size, is_bin);
- /* clang-format on */
-
+ godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, is_bin);
return OK;
};
@@ -110,15 +86,7 @@ bool EMWSPeer::is_connected_to_host() const {
void EMWSPeer::close(int p_code, String p_reason) {
if (peer_sock != -1) {
- /* clang-format off */
- EM_ASM({
- var sock = Module.IDHandler.get($0);
- var code = $1;
- var reason = UTF8ToString($2);
- sock.close(code, reason);
- Module.IDHandler.remove($0);
- }, peer_sock, p_code, p_reason.utf8().get_data());
- /* clang-format on */
+ godot_js_websocket_close(peer_sock, p_code, p_reason.utf8().get_data());
}
_is_string = 0;
_in_buffer.clear();
diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h
index c94d7e9148..2291a32bbc 100644
--- a/modules/websocket/emws_peer.h
+++ b/modules/websocket/emws_peer.h
@@ -40,6 +40,18 @@
#include "packet_buffer.h"
#include "websocket_peer.h"
+extern "C" {
+typedef void (*WSOnOpen)(void *p_ref, char *p_protocol);
+typedef void (*WSOnMessage)(void *p_ref, const uint8_t *p_buf, int p_buf_len, int p_is_string);
+typedef void (*WSOnClose)(void *p_ref, int p_code, const char *p_reason, int p_is_clean);
+typedef void (*WSOnError)(void *p_ref);
+
+extern int godot_js_websocket_create(void *p_ref, const char *p_url, const char *p_proto, WSOnOpen p_on_open, WSOnMessage p_on_message, WSOnError p_on_error, WSOnClose p_on_close);
+extern int godot_js_websocket_send(int p_id, const uint8_t *p_buf, int p_buf_len, int p_raw);
+extern void godot_js_websocket_close(int p_id, int p_code, const char *p_reason);
+extern void godot_js_websocket_destroy(int p_id);
+}
+
class EMWSPeer : public WebSocketPeer {
GDCIIMPL(EMWSPeer, WebSocketPeer);
@@ -52,7 +64,7 @@ private:
uint8_t _is_string;
public:
- Error read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string);
+ Error read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string);
void set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size);
virtual int get_available_packet_count() const;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size);
diff --git a/modules/websocket/library_godot_websocket.js b/modules/websocket/library_godot_websocket.js
new file mode 100644
index 0000000000..f7d3195807
--- /dev/null
+++ b/modules/websocket/library_godot_websocket.js
@@ -0,0 +1,187 @@
+/*************************************************************************/
+/* library_godot_websocket.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+var GodotWebSocket = {
+
+ // Our socket implementation that forwards events to C++.
+ $GodotWebSocket__deps: ['$IDHandler'],
+ $GodotWebSocket: {
+ // Connection opened, report selected protocol
+ _onopen: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return; // Godot object is gone.
+ }
+ let c_str = GodotOS.allocString(ref.protocol);
+ callback(c_str);
+ _free(c_str);
+ },
+
+ // Message received, report content and type (UTF8 vs binary)
+ _onmessage: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return; // Godot object is gone.
+ }
+ var buffer;
+ var is_string = 0;
+ if (event.data instanceof ArrayBuffer) {
+ buffer = new Uint8Array(event.data);
+ } else if (event.data instanceof Blob) {
+ alert("Blob type not supported");
+ return;
+ } else if (typeof event.data === "string") {
+ is_string = 1;
+ var enc = new TextEncoder("utf-8");
+ buffer = new Uint8Array(enc.encode(event.data));
+ } else {
+ alert("Unknown message type");
+ return;
+ }
+ var len = buffer.length*buffer.BYTES_PER_ELEMENT;
+ var out = _malloc(len);
+ HEAPU8.set(buffer, out);
+ callback(out, len, is_string);
+ _free(out);
+ },
+
+ // An error happened, 'onclose' will be called after this.
+ _onerror: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return; // Godot object is gone.
+ }
+ callback();
+ },
+
+ // Connection is closed, this is always fired. Report close code, reason, and clean status.
+ _onclose: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return; // Godot object is gone.
+ }
+ let c_str = GodotOS.allocString(event.reason);
+ callback(event.code, c_str, event.wasClean ? 1 : 0);
+ _free(c_str);
+ },
+
+ // Send a message
+ send: function(p_id, p_data) {
+ const ref = IDHandler.get(p_id);
+ if (!ref || ref.readyState != ref.OPEN) {
+ return 1; // Godot object is gone or socket is not in a ready state.
+ }
+ ref.send(p_data);
+ return 0;
+ },
+
+ create: function(socket, p_on_open, p_on_message, p_on_error, p_on_close) {
+ const id = IDHandler.add(socket);
+ socket.onopen = GodotWebSocket._onopen.bind(null, id, p_on_open);
+ socket.onmessage = GodotWebSocket._onmessage.bind(null, id, p_on_message);
+ socket.onerror = GodotWebSocket._onerror.bind(null, id, p_on_error);
+ socket.onclose = GodotWebSocket._onclose.bind(null, id, p_on_close);
+ return id;
+ },
+
+ // Closes the JavaScript WebSocket (if not already closing) associated to a given C++ object.
+ close: function(p_id, p_code, p_reason) {
+ const ref = IDHandler.get(p_id);
+ if (ref && ref.readyState < ref.CLOSING) {
+ const code = p_code;
+ const reason = UTF8ToString(p_reason);
+ ref.close(code, reason);
+ }
+ },
+
+ // Deletes the reference to a C++ object (closing any connected socket if necessary).
+ destroy: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ GodotWebSocket.close(p_id, 1001, '');
+ IDHandler.remove(p_id);
+ ref.onopen = null;
+ ref.onmessage = null;
+ ref.onerror = null;
+ ref.onclose = null;
+ },
+ },
+
+ godot_js_websocket_create: function(p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) {
+ const on_open = GodotOS.get_func(p_on_open).bind(null, p_ref);
+ const on_message = GodotOS.get_func(p_on_message).bind(null, p_ref);
+ const on_error = GodotOS.get_func(p_on_error).bind(null, p_ref);
+ const on_close = GodotOS.get_func(p_on_close).bind(null, p_ref);
+ const url = UTF8ToString(p_url);
+ const protos = UTF8ToString(p_proto);
+ var socket = null;
+ try {
+ if (protos) {
+ socket = new WebSocket(url, protos.split(","));
+ } else {
+ socket = new WebSocket(url);
+ }
+ } catch (e) {
+ return 0;
+ }
+ socket.binaryType = "arraybuffer";
+ return GodotWebSocket.create(socket, on_open, on_message, on_error, on_close);
+ },
+
+ godot_js_websocket_send: function(p_id, p_buf, p_buf_len, p_raw) {
+ var bytes_array = new Uint8Array(p_buf_len);
+ var i = 0;
+ for(i = 0; i < p_buf_len; i++) {
+ bytes_array[i] = getValue(p_buf + i, 'i8');
+ }
+ var out = bytes_array;
+ if (p_raw) {
+ out = bytes_array.buffer;
+ } else {
+ out = new TextDecoder("utf-8").decode(bytes_array);
+ }
+ return GodotWebSocket.send(p_id, out);
+ },
+
+ godot_js_websocket_close: function(p_id, p_code, p_reason) {
+ const code = p_code;
+ const reason = UTF8ToString(p_reason);
+ GodotWebSocket.close(p_id, code, reason);
+ },
+
+ godot_js_websocket_destroy: function(p_id) {
+ GodotWebSocket.destroy(p_id);
+ },
+};
+
+autoAddDeps(GodotWebSocket, '$GodotWebSocket')
+mergeInto(LibraryManager.library, GodotWebSocket);
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 5007b3f570..53b43ae0e8 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -29,7 +29,6 @@
/*************************************************************************/
#include "export.h"
-#include "gradle_export_util.h"
#include "core/config/project_settings.h"
#include "core/io/image_loader.h"
@@ -827,7 +826,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
int version_code = p_preset->get("version/code");
String package_name = p_preset->get("package/unique_name");
- int orientation = p_preset->get("screen/orientation");
+ const int screen_orientation = _get_android_orientation_value(_get_screen_orientation());
bool screen_support_small = p_preset->get("screen/support_small");
bool screen_support_normal = p_preset->get("screen/support_normal");
@@ -937,7 +936,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
if (tname == "activity" && attrname == "screenOrientation") {
- encode_uint32(orientation == 0 ? 0 : 1, &p_manifest.write[iofs + 16]);
+ encode_uint32(screen_orientation, &p_manifest.write[iofs + 16]);
}
if (tname == "supports-screens") {
@@ -1636,7 +1635,6 @@ public:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name [default if blank]"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/signed"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/immersive_mode"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "screen/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_small"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_normal"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true));
@@ -1645,10 +1643,10 @@ public:
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_foreground_option, PROPERTY_HINT_FILE, "*.png"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_adaptive_icon_background_option, PROPERTY_HINT_FILE, "*.png"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "apk_expansion/enable"), false));
@@ -1968,9 +1966,21 @@ public:
valid = false;
} else {
Error errn;
+ // Check for the platform-tools directory.
DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
if (errn != OK) {
- err += TTR("Invalid Android SDK path for custom build in Editor Settings.") + "\n";
+ err += TTR("Invalid Android SDK path for custom build in Editor Settings.");
+ err += TTR("Missing 'platform-tools' directory!");
+ err += "\n";
+ valid = false;
+ }
+
+ // Check for the build-tools directory.
+ DirAccessRef build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn);
+ if (errn != OK) {
+ err += TTR("Invalid Android SDK path for custom build in Editor Settings.");
+ err += TTR("Missing 'build-tools' directory!");
+ err += "\n";
valid = false;
}
}
@@ -2159,14 +2169,16 @@ public:
}
}
- Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String apk_path, EditorProgress ep) {
+ Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String export_path, EditorProgress ep) {
+ int export_format = int(p_preset->get("custom_template/export_format"));
+ String export_label = export_format == 1 ? "AAB" : "APK";
String release_keystore = p_preset->get("keystore/release");
String release_username = p_preset->get("keystore/release_user");
String release_password = p_preset->get("keystore/release_password");
String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner");
if (!FileAccess::exists(jarsigner)) {
- EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned.");
+ EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting " + export_label + " is unsigned.");
return OK;
}
@@ -2184,7 +2196,7 @@ public:
user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
}
- if (ep.step("Signing debug APK...", 103)) {
+ if (ep.step("Signing debug " + export_label + "...", 103)) {
return ERR_SKIP;
}
@@ -2193,7 +2205,7 @@ public:
password = release_password;
user = release_username;
- if (ep.step("Signing release APK...", 103)) {
+ if (ep.step("Signing release " + export_label + "...", 103)) {
return ERR_SKIP;
}
}
@@ -2218,7 +2230,7 @@ public:
args.push_back(keystore);
args.push_back("-storepass");
args.push_back(password);
- args.push_back(apk_path);
+ args.push_back(export_path);
args.push_back(user);
int retval;
OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
@@ -2227,7 +2239,7 @@ public:
return ERR_CANT_CREATE;
}
- if (ep.step("Verifying APK...", 104)) {
+ if (ep.step("Verifying " + export_label + "...", 104)) {
return ERR_SKIP;
}
@@ -2235,17 +2247,78 @@ public:
args.push_back("-verify");
args.push_back("-keystore");
args.push_back(keystore);
- args.push_back(apk_path);
+ args.push_back(export_path);
args.push_back("-verbose");
OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval);
if (retval) {
- EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8.");
+ EditorNode::add_io_error("'jarsigner' verification of " + export_label + " failed. Make sure to use a jarsigner from OpenJDK 8.");
return ERR_CANT_CREATE;
}
return OK;
}
+ void _clear_assets_directory() {
+ DirAccessRef da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ if (da_res->dir_exists("res://android/build/assets")) {
+ DirAccessRef da_assets = DirAccess::open("res://android/build/assets");
+ da_assets->erase_contents_recursive();
+ da_res->remove("res://android/build/assets");
+ }
+ }
+
+ Error _zip_align_project(const String &sdk_path, const String &unaligned_file_path, const String &aligned_file_path) {
+ // Look for the zipalign tool.
+ String zipalign_command;
+ Error errn;
+ String build_tools_dir = sdk_path.plus_file("build-tools");
+ DirAccessRef da = DirAccess::open(build_tools_dir, &errn);
+ if (errn != OK) {
+ return errn;
+ }
+
+ // There are additional versions directories we need to go through.
+ da->list_dir_begin();
+ String sub_dir = da->get_next();
+ while (!sub_dir.empty()) {
+ if (!sub_dir.begins_with(".") && da->current_is_dir()) {
+ // Check if the tool is here.
+ String tool_path = build_tools_dir.plus_file(sub_dir).plus_file("zipalign");
+ if (FileAccess::exists(tool_path)) {
+ zipalign_command = tool_path;
+ break;
+ }
+ }
+ sub_dir = da->get_next();
+ }
+ da->list_dir_end();
+
+ if (zipalign_command.empty()) {
+ EditorNode::get_singleton()->show_warning(TTR("Unable to find the zipalign tool."));
+ return ERR_CANT_CREATE;
+ }
+
+ List<String> zipalign_args;
+ zipalign_args.push_back("-f");
+ zipalign_args.push_back("-v");
+ zipalign_args.push_back("4");
+ zipalign_args.push_back(unaligned_file_path); // source file
+ zipalign_args.push_back(aligned_file_path); // destination file
+
+ int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Aligning APK..."), zipalign_command, zipalign_args);
+ if (result != 0) {
+ EditorNode::get_singleton()->show_warning(TTR("Unable to complete APK alignment."));
+ return ERR_CANT_CREATE;
+ }
+
+ // Delete the unaligned path.
+ errn = da->remove(unaligned_file_path);
+ if (errn != OK) {
+ EditorNode::get_singleton()->show_warning(TTR("Unable to delete unaligned APK."));
+ }
+ return OK;
+ }
+
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
@@ -2325,13 +2398,8 @@ public:
_write_tmp_manifest(p_preset, p_give_internet, p_debug);
//stores all the project files inside the Gradle project directory. Also includes all ABIs
+ _clear_assets_directory();
if (!apk_expansion) {
- DirAccess *da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- if (da_res->dir_exists("res://android/build/assets")) {
- DirAccess *da_assets = DirAccess::open("res://android/build/assets");
- da_assets->erase_contents_recursive();
- da_res->remove("res://android/build/assets");
- }
err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file);
if (err != OK) {
EditorNode::add_io_error("Could not export project files to gradle project\n");
@@ -2419,7 +2487,16 @@ public:
copy_args.push_back(build_path); // start directory.
String export_filename = p_path.get_file();
+ if (export_format == 0) {
+ // By default, generated apk are not aligned.
+ export_filename += ".unaligned";
+ }
String export_path = p_path.get_base_dir();
+ if (export_path.is_rel_path()) {
+ export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path);
+ }
+ export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path();
+ String export_file_path = export_path.plus_file(export_filename);
copy_args.push_back("-Pexport_path=file:" + export_path);
copy_args.push_back("-Pexport_filename=" + export_filename);
@@ -2430,11 +2507,20 @@ public:
return ERR_CANT_CREATE;
}
if (_signed) {
- err = sign_apk(p_preset, p_debug, p_path, ep);
+ err = sign_apk(p_preset, p_debug, export_file_path, ep);
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ if (export_format == 0) {
+ // Perform zip alignment
+ err = _zip_align_project(sdk_path, export_file_path, export_path.plus_file(p_path.get_file()));
if (err != OK) {
return err;
}
}
+
return OK;
}
// This is the start of the Legacy build system
@@ -2781,7 +2867,7 @@ void register_android_exporter() {
EDITOR_DEF("export/android/jarsigner", "");
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/jarsigner", PROPERTY_HINT_GLOBAL_FILE, exe_ext));
EDITOR_DEF("export/android/debug_keystore", "");
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore"));
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks"));
EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey");
EDITOR_DEF("export/android/debug_keystore_pass", "android");
EDITOR_DEF("export/android/force_system_user", false);
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
index 95f870bc35..3bc3651712 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -44,6 +44,67 @@ const String godot_project_name_xml_string = R"(<?xml version="1.0" encoding="ut
</resources>
)";
+DisplayServer::ScreenOrientation _get_screen_orientation() {
+ String orientation_settings = ProjectSettings::get_singleton()->get("display/window/handheld/orientation");
+ DisplayServer::ScreenOrientation screen_orientation;
+ if (orientation_settings == "portrait")
+ screen_orientation = DisplayServer::SCREEN_PORTRAIT;
+ else if (orientation_settings == "reverse_landscape")
+ screen_orientation = DisplayServer::SCREEN_REVERSE_LANDSCAPE;
+ else if (orientation_settings == "reverse_portrait")
+ screen_orientation = DisplayServer::SCREEN_REVERSE_PORTRAIT;
+ else if (orientation_settings == "sensor_landscape")
+ screen_orientation = DisplayServer::SCREEN_SENSOR_LANDSCAPE;
+ else if (orientation_settings == "sensor_portrait")
+ screen_orientation = DisplayServer::SCREEN_SENSOR_PORTRAIT;
+ else if (orientation_settings == "sensor")
+ screen_orientation = DisplayServer::SCREEN_SENSOR;
+ else
+ screen_orientation = DisplayServer::SCREEN_LANDSCAPE;
+
+ return screen_orientation;
+}
+
+int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation) {
+ switch (screen_orientation) {
+ case DisplayServer::SCREEN_PORTRAIT:
+ return 1;
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
+ return 8;
+ case DisplayServer::SCREEN_REVERSE_PORTRAIT:
+ return 9;
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+ return 11;
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
+ return 12;
+ case DisplayServer::SCREEN_SENSOR:
+ return 13;
+ case DisplayServer::SCREEN_LANDSCAPE:
+ default:
+ return 0;
+ }
+}
+
+String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_orientation) {
+ switch (screen_orientation) {
+ case DisplayServer::SCREEN_PORTRAIT:
+ return "portrait";
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
+ return "reverseLandscape";
+ case DisplayServer::SCREEN_REVERSE_PORTRAIT:
+ return "reversePortrait";
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+ return "userLandscape";
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
+ return "userPortrait";
+ case DisplayServer::SCREEN_SENSOR:
+ return "fullUser";
+ case DisplayServer::SCREEN_LANDSCAPE:
+ default:
+ return "landscape";
+ }
+}
+
// Utility method used to create a directory.
Error create_directory(const String &p_dir) {
if (!DirAccess::exists(p_dir)) {
@@ -209,7 +270,7 @@ String _get_plugins_tag(const String &plugins_names) {
String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
- String orientation = (int)(p_preset->get("screen/orientation")) == 1 ? "portrait" : "landscape";
+ String orientation = _get_android_orientation_label(_get_screen_orientation());
String manifest_activity_text = vformat(
" <activity android:name=\"com.godot.game.GodotApp\" "
"tools:replace=\"android:screenOrientation\" "
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index 821a4dc584..73c136ed0e 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -22,8 +22,6 @@ allprojects {
}
ext {
- sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : ""
-
supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"]
supportedTargets = ["release", "debug"]
diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle
index e3c5a02203..89ce3d15e6 100644
--- a/platform/android/java/lib/build.gradle
+++ b/platform/android/java/lib/build.gradle
@@ -64,10 +64,42 @@ android {
throw new GradleException("Invalid default abi: " + defaultAbi)
}
+ // Find scons' executable path
+ File sconsExecutableFile = null
+ def sconsName = "scons"
+ def sconsExts = (org.gradle.internal.os.OperatingSystem.current().isWindows()
+ ? [".bat", ".exe"]
+ : [""])
+ logger.lifecycle("Looking for $sconsName executable path")
+ for (ext in sconsExts) {
+ String sconsNameExt = sconsName + ext
+ logger.lifecycle("Checking $sconsNameExt")
+
+ sconsExecutableFile = org.gradle.internal.os.OperatingSystem.current().findInPath(sconsNameExt)
+ if (sconsExecutableFile != null) {
+ // We're done!
+ break
+ }
+
+ // Check all the options in path
+ List<File> allOptions = org.gradle.internal.os.OperatingSystem.current().findAllInPath(sconsNameExt)
+ if (!allOptions.isEmpty()) {
+ // Pick the first option and we're done!
+ sconsExecutableFile = allOptions.get(0)
+ break
+ }
+ }
+
+ if (sconsExecutableFile == null) {
+ throw new GradleException("Unable to find executable path for the '$sconsName' command.")
+ } else {
+ logger.lifecycle("Found executable path for $sconsName: ${sconsExecutableFile.absolutePath}")
+ }
+
// Creating gradle task to generate the native libraries for the default abi.
def taskName = getSconsTaskName(buildType)
tasks.create(name: taskName, type: Exec) {
- executable "scons" + sconsExt
+ executable sconsExecutableFile.absolutePath
args "--directory=${pathToRootDir}", "platform=android", "target=${releaseTarget}", "android_arch=${defaultAbi}", "-j" + Runtime.runtime.availableProcessors()
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 874fd88848..894009e30f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -515,13 +515,13 @@ public class GodotIO {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
} break;
case SCREEN_SENSOR_LANDSCAPE: {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE);
} break;
case SCREEN_SENSOR_PORTRAIT: {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT);
} break;
case SCREEN_SENSOR: {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
} break;
}
}
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index 0a558f8e3d..ee7b2f4ab5 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -14,9 +14,11 @@ iphone_lib = [
"joypad_iphone.mm",
"godot_view.mm",
"display_layer.mm",
+ "godot_app_delegate.m",
"godot_view_renderer.mm",
"godot_view_gesture_recognizer.mm",
"device_metrics.m",
+ "keyboard_input_view.mm",
"native_video_view.m",
]
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
index 6fa5e6ee4a..d47d131719 100644
--- a/platform/iphone/display_server_iphone.mm
+++ b/platform/iphone/display_server_iphone.mm
@@ -35,6 +35,7 @@
#import "device_metrics.h"
#import "godot_view.h"
#include "ios.h"
+#import "keyboard_input_view.h"
#import "native_video_view.h"
#include "os_iphone.h"
#import "view_controller.h"
@@ -529,11 +530,17 @@ bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
}
void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
- [AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
+ NSString *existingString = [[NSString alloc] initWithUTF8String:p_existing_text.utf8().get_data()];
+
+ [AppDelegate.viewController.keyboardView
+ becomeFirstResponderWithString:existingString
+ multiline:p_multiline
+ cursorStart:p_cursor_start
+ cursorEnd:p_cursor_end];
}
void DisplayServerIPhone::virtual_keyboard_hide() {
- [AppDelegate.viewController.godotView resignFirstResponder];
+ [AppDelegate.viewController.keyboardView resignFirstResponder];
}
void DisplayServerIPhone::virtual_keyboard_set_height(int height) {
diff --git a/platform/javascript/native/id_handler.js b/platform/iphone/godot_app_delegate.h
index 67d29075b8..ebb21c499b 100644
--- a/platform/javascript/native/id_handler.js
+++ b/platform/iphone/godot_app_delegate.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* id_handler.js */
+/* godot_app_delegate.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,36 +28,14 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-var IDHandler = /** @constructor */ function() {
+#import <UIKit/UIKit.h>
- var ids = {};
- var size = 0;
+typedef NSObject<UIApplicationDelegate> ApplicationDelegateService;
- this.has = function(id) {
- return ids.hasOwnProperty(id);
- }
+@interface GodotApplicalitionDelegate : NSObject <UIApplicationDelegate>
- this.add = function(obj) {
- size += 1;
- var id = crypto.getRandomValues(new Int32Array(32))[0];
- ids[id] = obj;
- return id;
- }
+@property(class, readonly, strong) NSArray<ApplicationDelegateService *> *services;
- this.get = function(id) {
- return ids[id];
- }
++ (void)addService:(ApplicationDelegateService *)service;
- this.remove = function(id) {
- size -= 1;
- delete ids[id];
- }
-
- this.size = function() {
- return size;
- }
-
- this.ids = ids;
-};
-
-Module.IDHandler = new IDHandler;
+@end
diff --git a/platform/iphone/godot_app_delegate.m b/platform/iphone/godot_app_delegate.m
new file mode 100644
index 0000000000..a5aad26bd5
--- /dev/null
+++ b/platform/iphone/godot_app_delegate.m
@@ -0,0 +1,497 @@
+/*************************************************************************/
+/* godot_app_delegate.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+#import "godot_app_delegate.h"
+
+#import "app_delegate.h"
+
+@interface GodotApplicalitionDelegate ()
+
+@end
+
+@implementation GodotApplicalitionDelegate
+
+static NSMutableArray<ApplicationDelegateService *> *services = nil;
+
++ (NSArray<ApplicationDelegateService *> *)services {
+ return services;
+}
+
++ (void)load {
+ services = [NSMutableArray new];
+ [services addObject:[AppDelegate new]];
+}
+
++ (void)addService:(ApplicationDelegateService *)service {
+ if (!services || !service) {
+ return;
+ }
+ [services addObject:service];
+}
+
+// UIApplicationDelegate documantation can be found here: https://developer.apple.com/documentation/uikit/uiapplicationdelegate
+
+// MARK: Window
+
+- (UIWindow *)window {
+ UIWindow *result = nil;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ UIWindow *value = [service window];
+
+ if (value) {
+ result = value;
+ }
+ }
+
+ return result;
+}
+
+// MARK: Initializing
+
+- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application willFinishLaunchingWithOptions:launchOptions]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application didFinishLaunchingWithOptions:launchOptions]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+/* Can be handled by Info.plist. Not yet supported by Godot.
+
+// MARK: Scene
+
+- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {}
+
+- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {}
+
+*/
+
+// MARK: Life-Cycle
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationDidBecomeActive:application];
+ }
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationWillResignActive:application];
+ }
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationDidEnterBackground:application];
+ }
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationWillEnterForeground:application];
+ }
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationWillTerminate:application];
+ }
+}
+
+// MARK: Environment Changes
+
+- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationProtectedDataDidBecomeAvailable:application];
+ }
+}
+
+- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationProtectedDataWillBecomeUnavailable:application];
+ }
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationDidReceiveMemoryWarning:application];
+ }
+}
+
+- (void)applicationSignificantTimeChange:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationSignificantTimeChange:application];
+ }
+}
+
+// MARK: App State Restoration
+
+- (BOOL)application:(UIApplication *)application shouldSaveSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application shouldSaveSecureApplicationState:coder]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (BOOL)application:(UIApplication *)application shouldRestoreSecureApplicationState:(NSCoder *)coder API_AVAILABLE(ios(13.2)) {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application shouldRestoreSecureApplicationState:coder]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ UIViewController *controller = [service application:application viewControllerWithRestorationIdentifierPath:identifierComponents coder:coder];
+
+ if (controller) {
+ return controller;
+ }
+ }
+
+ return nil;
+}
+
+- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application willEncodeRestorableStateWithCoder:coder];
+ }
+}
+
+- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didDecodeRestorableStateWithCoder:coder];
+ }
+}
+
+// MARK: Download Data in Background
+
+- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler];
+ }
+
+ completionHandler();
+}
+
+// MARK: Remote Notification
+
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
+ }
+}
+
+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didFailToRegisterForRemoteNotificationsWithError:error];
+ }
+}
+
+- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
+ }
+
+ completionHandler(UIBackgroundFetchResultNoData);
+}
+
+// MARK: User Activity and Handling Quick Actions
+
+- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application willContinueUserActivityWithType:userActivityType]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didUpdateUserActivity:userActivity];
+ }
+}
+
+- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application didFailToContinueUserActivityWithType:userActivityType error:error];
+ }
+}
+
+- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler];
+ }
+}
+
+// MARK: WatchKit
+
+- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application handleWatchKitExtensionRequest:userInfo reply:reply];
+ }
+}
+
+// MARK: HealthKit
+
+- (void)applicationShouldRequestHealthAuthorization:(UIApplication *)application {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service applicationShouldRequestHealthAuthorization:application];
+ }
+}
+
+// MARK: Opening an URL
+
+- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:app openURL:url options:options]) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+// MARK: Disallowing Specified App Extension Types
+
+- (BOOL)application:(UIApplication *)application shouldAllowExtensionPointIdentifier:(UIApplicationExtensionPointIdentifier)extensionPointIdentifier {
+ BOOL result = NO;
+
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ if ([service application:application shouldAllowExtensionPointIdentifier:extensionPointIdentifier]) {
+ result = YES;
+ }
+ }
+
+ return result;
+}
+
+// MARK: SiriKit
+
+- (id)application:(UIApplication *)application handlerForIntent:(INIntent *)intent API_AVAILABLE(ios(14.0)) {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ id result = [service application:application handlerForIntent:intent];
+
+ if (result) {
+ return result;
+ }
+ }
+
+ return nil;
+}
+
+// MARK: CloudKit
+
+- (void)application:(UIApplication *)application userDidAcceptCloudKitShareWithMetadata:(CKShareMetadata *)cloudKitShareMetadata {
+ for (ApplicationDelegateService *service in services) {
+ if (![service respondsToSelector:_cmd]) {
+ continue;
+ }
+
+ [service application:application userDidAcceptCloudKitShareWithMetadata:cloudKitShareMetadata];
+ }
+}
+
+/* Handled By Info.plist file for now
+
+// MARK: Interface Geometry
+
+- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {}
+
+*/
+
+@end
diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h
index 62fa2f5a32..a8f4cb38d9 100644
--- a/platform/iphone/godot_view.h
+++ b/platform/iphone/godot_view.h
@@ -35,7 +35,7 @@ class String;
@protocol DisplayLayer;
@protocol GodotViewRendererProtocol;
-@interface GodotView : UIView <UIKeyInput>
+@interface GodotView : UIView
@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
@@ -51,6 +51,4 @@ class String;
- (void)stopRendering;
- (void)startRendering;
-- (BOOL)becomeFirstResponderWithString:(String)p_existing;
-
@end
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
index eaf7e962ce..0c50842cfa 100644
--- a/platform/iphone/godot_view.mm
+++ b/platform/iphone/godot_view.mm
@@ -42,7 +42,6 @@ static const int max_touches = 8;
@interface GodotView () {
UITouch *godot_touches[max_touches];
- String keyboard_text;
}
@property(assign, nonatomic) BOOL isActive;
@@ -278,40 +277,6 @@ static const int max_touches = 8;
// MARK: - Input
-// MARK: Keyboard
-
-- (BOOL)canBecomeFirstResponder {
- return YES;
-}
-
-- (BOOL)becomeFirstResponderWithString:(String)p_existing {
- keyboard_text = p_existing;
- return [self becomeFirstResponder];
-}
-
-- (BOOL)resignFirstResponder {
- keyboard_text = String();
- return [super resignFirstResponder];
-}
-
-- (void)deleteBackward {
- if (keyboard_text.length()) {
- keyboard_text.erase(keyboard_text.length() - 1, 1);
- }
- DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
-}
-
-- (BOOL)hasText {
- return keyboard_text.length() > 0;
-}
-
-- (void)insertText:(NSString *)p_text {
- String character;
- character.parse_utf8([p_text UTF8String]);
- keyboard_text = keyboard_text + character;
- DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
-}
-
// MARK: Touches
- (void)initTouches {
diff --git a/platform/iphone/keyboard_input_view.h b/platform/iphone/keyboard_input_view.h
new file mode 100644
index 0000000000..5382a2e9a9
--- /dev/null
+++ b/platform/iphone/keyboard_input_view.h
@@ -0,0 +1,37 @@
+/*************************************************************************/
+/* keyboard_input_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+@interface GodotKeyboardInputView : UITextView
+
+- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end;
+
+@end
diff --git a/platform/iphone/keyboard_input_view.mm b/platform/iphone/keyboard_input_view.mm
new file mode 100644
index 0000000000..1a37403de7
--- /dev/null
+++ b/platform/iphone/keyboard_input_view.mm
@@ -0,0 +1,195 @@
+/*************************************************************************/
+/* keyboard_input_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+#import "keyboard_input_view.h"
+
+#include "core/os/keyboard.h"
+#include "display_server_iphone.h"
+#include "os_iphone.h"
+
+@interface GodotKeyboardInputView () <UITextViewDelegate>
+
+@property(nonatomic, copy) NSString *previousText;
+@property(nonatomic, assign) NSRange previousSelectedRange;
+
+@end
+
+@implementation GodotKeyboardInputView
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer {
+ self = [super initWithFrame:frame textContainer:textContainer];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.hidden = YES;
+ self.delegate = self;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(observeTextChange:)
+ name:UITextViewTextDidChangeNotification
+ object:self];
+}
+
+- (void)dealloc {
+ self.delegate = nil;
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+// MARK: Keyboard
+
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+- (BOOL)becomeFirstResponderWithString:(NSString *)existingString multiline:(BOOL)flag cursorStart:(NSInteger)start cursorEnd:(NSInteger)end {
+ self.text = existingString;
+ self.previousText = existingString;
+
+ NSRange textRange;
+
+ // Either a simple cursor or a selection.
+ if (end > 0) {
+ textRange = NSMakeRange(start, end - start);
+ } else {
+ textRange = NSMakeRange(start, 0);
+ }
+
+ self.selectedRange = textRange;
+ self.previousSelectedRange = textRange;
+
+ return [self becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+ self.text = nil;
+ self.previousText = nil;
+ return [super resignFirstResponder];
+}
+
+// MARK: OS Messages
+
+- (void)deleteText:(NSInteger)charactersToDelete {
+ for (int i = 0; i < charactersToDelete; i++) {
+ DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
+ DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, false);
+ }
+}
+
+- (void)enterText:(NSString *)substring {
+ String characters;
+ characters.parse_utf8([substring UTF8String]);
+
+ for (int i = 0; i < characters.size(); i++) {
+ int character = characters[i];
+
+ switch (character) {
+ case 10:
+ character = KEY_ENTER;
+ break;
+ case 8198:
+ character = KEY_SPACE;
+ break;
+ default:
+ break;
+ }
+
+ DisplayServerIPhone::get_singleton()->key(character, true);
+ DisplayServerIPhone::get_singleton()->key(character, false);
+ }
+}
+
+// MARK: Observer
+
+- (void)observeTextChange:(NSNotification *)notification {
+ if (notification.object != self) {
+ return;
+ }
+
+ if (self.previousSelectedRange.length == 0) {
+ // We are deleting all text before cursor if no range was selected.
+ // This way any inserted or changed text will be updated.
+ NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
+ [self deleteText:substringToDelete.length];
+ } else {
+ // If text was previously selected
+ // we are sending only one `backspace`.
+ // It will remove all text from text input.
+ [self deleteText:1];
+ }
+
+ NSString *substringToEnter;
+
+ if (self.selectedRange.length == 0) {
+ // If previous cursor had a selection
+ // we have to calculate an inserted text.
+ if (self.previousSelectedRange.length != 0) {
+ NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length;
+ NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location);
+ NSInteger rangeLength = MAX(0, rangeEnd - rangeStart);
+
+ NSRange calculatedRange;
+
+ if (rangeLength >= 0) {
+ calculatedRange = NSMakeRange(rangeStart, rangeLength);
+ } else {
+ calculatedRange = NSMakeRange(rangeStart, 0);
+ }
+
+ substringToEnter = [self.text substringWithRange:calculatedRange];
+ } else {
+ substringToEnter = [self.text substringToIndex:self.selectedRange.location];
+ }
+ } else {
+ substringToEnter = [self.text substringWithRange:self.selectedRange];
+ }
+
+ [self enterText:substringToEnter];
+
+ self.previousText = self.text;
+ self.previousSelectedRange = self.selectedRange;
+}
+
+@end
diff --git a/platform/iphone/main.m b/platform/iphone/main.m
index c292f02822..351b40a881 100644
--- a/platform/iphone/main.m
+++ b/platform/iphone/main.m
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#import "app_delegate.h"
+#import "godot_app_delegate.h"
#import <UIKit/UIKit.h>
#include <stdio.h>
@@ -49,7 +49,8 @@ int main(int argc, char *argv[]) {
printf("running app main\n");
@autoreleasepool {
- UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]);
+ UIApplicationMain(argc, argv, nil, className);
}
printf("main done\n");
return 0;
diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h
index 2cc4e4c388..ff76359842 100644
--- a/platform/iphone/view_controller.h
+++ b/platform/iphone/view_controller.h
@@ -32,11 +32,13 @@
@class GodotView;
@class GodotNativeVideoView;
+@class GodotKeyboardInputView;
@interface ViewController : UIViewController
@property(nonatomic, readonly, strong) GodotView *godotView;
@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView;
+@property(nonatomic, readonly, strong) GodotKeyboardInputView *keyboardView;
// MARK: Native Video Player
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm
index 5d18a72be1..d3969e6b02 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/view_controller.mm
@@ -33,6 +33,7 @@
#include "display_server_iphone.h"
#import "godot_view.h"
#import "godot_view_renderer.h"
+#import "keyboard_input_view.h"
#import "native_video_view.h"
#include "os_iphone.h"
@@ -43,6 +44,7 @@
@property(strong, nonatomic) GodotViewRenderer *renderer;
@property(strong, nonatomic) GodotNativeVideoView *videoView;
+@property(strong, nonatomic) GodotKeyboardInputView *keyboardView;
@end
@@ -102,6 +104,10 @@
}
- (void)observeKeyboard {
+ printf("******** setting up keyboard input view\n");
+ self.keyboardView = [GodotKeyboardInputView new];
+ [self.view addSubview:self.keyboardView];
+
printf("******** adding observer for keyboard show/hide\n");
[[NSNotificationCenter defaultCenter]
addObserver:self
@@ -119,6 +125,9 @@
[self.videoView stopVideo];
self.videoView = nil;
+
+ self.keyboardView = nil;
+
self.renderer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub
index 7381ea13b7..a0e6fa0e18 100644
--- a/platform/javascript/SCsub
+++ b/platform/javascript/SCsub
@@ -18,21 +18,22 @@ if env["threads_enabled"]:
build = env.add_program(build_targets, javascript_files)
-js_libraries = [
- "native/http_request.js",
- "native/library_godot_audio.js",
-]
-for lib in js_libraries:
- env.Append(LINKFLAGS=["--js-library", env.File(lib).path])
-env.Depends(build, js_libraries)
+env.AddJSLibraries(
+ [
+ "native/http_request.js",
+ "native/library_godot_audio.js",
+ "native/library_godot_display.js",
+ "native/library_godot_os.js",
+ ]
+)
-js_pre = [
- "native/id_handler.js",
- "native/utils.js",
-]
-for js in js_pre:
- env.Append(LINKFLAGS=["--pre-js", env.File(js).path])
-env.Depends(build, js_pre)
+if env["tools"]:
+ env.AddJSLibraries(["native/library_godot_editor_tools.js"])
+if env["javascript_eval"]:
+ env.AddJSLibraries(["native/library_godot_eval.js"])
+for lib in env["JS_LIBS"]:
+ env.Append(LINKFLAGS=["--js-library", lib])
+env.Depends(build, env["JS_LIBS"])
engine = [
"engine/preloader.js",
@@ -55,9 +56,10 @@ out_files = [
zip_dir.File(binary_name + ".js"),
zip_dir.File(binary_name + ".wasm"),
zip_dir.File(binary_name + ".html"),
+ zip_dir.File(binary_name + ".audio.worklet.js"),
]
html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html"
-in_files = [js_wrapped, build[1], html_file]
+in_files = [js_wrapped, build[1], html_file, "#platform/javascript/native/audio.worklet.js"]
if env["threads_enabled"]:
in_files.append(build[2])
out_files.append(zip_dir.File(binary_name + ".worker.js"))
diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp
index d44e8139a1..8d781703ed 100644
--- a/platform/javascript/api/javascript_tools_editor_plugin.cpp
+++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp
@@ -39,6 +39,11 @@
#include <emscripten/emscripten.h>
+// JavaScript functions defined in library_godot_editor_tools.js
+extern "C" {
+extern void godot_js_editor_download_file(const char *p_path, const char *p_name, const char *p_mime);
+}
+
static void _javascript_editor_init_callback() {
EditorNode::get_singleton()->add_editor_plugin(memnew(JavaScriptToolsEditorPlugin(EditorNode::get_singleton())));
}
@@ -65,25 +70,7 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) {
String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/";
_zip_recursive(resource_path, base_path, zip);
zipClose(zip, NULL);
- EM_ASM({
- const path = "/tmp/project.zip";
- const size = FS.stat(path)["size"];
- const buf = new Uint8Array(size);
- const fd = FS.open(path, "r");
- FS.read(fd, buf, 0, size);
- FS.close(fd);
- FS.unlink(path);
- const blob = new Blob([buf], { type: "application/zip" });
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement("a");
- a.href = url;
- a.download = "project.zip";
- a.style.display = "none";
- document.body.appendChild(a);
- a.click();
- a.remove();
- window.URL.revokeObjectURL(url);
- });
+ godot_js_editor_download_file("/tmp/project.zip", "project.zip", "application/zip");
}
void JavaScriptToolsEditorPlugin::_bind_methods() {
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp
index ab6ed54cc8..dd982bc3a8 100644
--- a/platform/javascript/audio_driver_javascript.cpp
+++ b/platform/javascript/audio_driver_javascript.cpp
@@ -31,7 +31,6 @@
#include "audio_driver_javascript.h"
#include "core/config/project_settings.h"
-#include "godot_audio.h"
#include <emscripten.h>
@@ -45,92 +44,109 @@ const char *AudioDriverJavaScript::get_name() const {
return "JavaScript";
}
-#ifndef NO_THREADS
-void AudioDriverJavaScript::_audio_thread_func(void *p_data) {
- AudioDriverJavaScript *obj = static_cast<AudioDriverJavaScript *>(p_data);
- while (!obj->quit) {
- obj->lock();
- if (!obj->needs_process) {
- obj->unlock();
- OS::get_singleton()->delay_usec(1000); // Give the browser some slack.
- continue;
- }
- obj->_js_driver_process();
- obj->needs_process = false;
- obj->unlock();
- }
+void AudioDriverJavaScript::_state_change_callback(int p_state) {
+ singleton->state = p_state;
}
-#endif
-extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_process_start() {
-#ifndef NO_THREADS
- AudioDriverJavaScript::singleton->lock();
-#else
- AudioDriverJavaScript::singleton->_js_driver_process();
-#endif
+void AudioDriverJavaScript::_latency_update_callback(float p_latency) {
+ singleton->output_latency = p_latency;
}
-extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_process_end() {
-#ifndef NO_THREADS
- AudioDriverJavaScript::singleton->needs_process = true;
- AudioDriverJavaScript::singleton->unlock();
-#endif
-}
+void AudioDriverJavaScript::_audio_driver_process(int p_from, int p_samples) {
+ int32_t *stream_buffer = reinterpret_cast<int32_t *>(output_rb);
+ const int max_samples = memarr_len(output_rb);
-extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_process_capture(float sample) {
- AudioDriverJavaScript::singleton->process_capture(sample);
+ int write_pos = p_from;
+ int to_write = p_samples;
+ if (to_write == 0) {
+ to_write = max_samples;
+ }
+ // High part
+ if (write_pos + to_write > max_samples) {
+ const int samples_high = max_samples - write_pos;
+ audio_server_process(samples_high / channel_count, &stream_buffer[write_pos]);
+ for (int i = write_pos; i < max_samples; i++) {
+ output_rb[i] = float(stream_buffer[i] >> 16) / 32768.f;
+ }
+ to_write -= samples_high;
+ write_pos = 0;
+ }
+ // Leftover
+ audio_server_process(to_write / channel_count, &stream_buffer[write_pos]);
+ for (int i = write_pos; i < write_pos + to_write; i++) {
+ output_rb[i] = float(stream_buffer[i] >> 16) / 32768.f;
+ }
}
-void AudioDriverJavaScript::_js_driver_process() {
- int sample_count = memarr_len(internal_buffer) / channel_count;
- int32_t *stream_buffer = reinterpret_cast<int32_t *>(internal_buffer);
- audio_server_process(sample_count, stream_buffer);
- for (int i = 0; i < sample_count * channel_count; i++) {
- internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.f;
+void AudioDriverJavaScript::_audio_driver_capture(int p_from, int p_samples) {
+ if (get_input_buffer().size() == 0) {
+ return; // Input capture stopped.
}
-}
+ const int max_samples = memarr_len(input_rb);
-void AudioDriverJavaScript::process_capture(float sample) {
- int32_t sample32 = int32_t(sample * 32768.f) * (1U << 16);
- input_buffer_write(sample32);
+ int read_pos = p_from;
+ int to_read = p_samples;
+ if (to_read == 0) {
+ to_read = max_samples;
+ }
+ // High part
+ if (read_pos + to_read > max_samples) {
+ const int samples_high = max_samples - read_pos;
+ for (int i = read_pos; i < max_samples; i++) {
+ input_buffer_write(int32_t(input_rb[i] * 32768.f) * (1U << 16));
+ }
+ to_read -= samples_high;
+ read_pos = 0;
+ }
+ // Leftover
+ for (int i = read_pos; i < read_pos + to_read; i++) {
+ input_buffer_write(int32_t(input_rb[i] * 32768.f) * (1U << 16));
+ }
}
Error AudioDriverJavaScript::init() {
mix_rate = GLOBAL_GET("audio/mix_rate");
int latency = GLOBAL_GET("audio/output_latency");
- channel_count = godot_audio_init(mix_rate, latency);
- buffer_length = closest_power_of_2(latency * mix_rate / 1000);
- buffer_length = godot_audio_create_processor(buffer_length, channel_count);
- if (!buffer_length) {
- return FAILED;
+ channel_count = godot_audio_init(mix_rate, latency, &_state_change_callback, &_latency_update_callback);
+ buffer_length = closest_power_of_2((latency * mix_rate / 1000));
+#ifndef NO_THREADS
+ node = memnew(WorkletNode);
+#else
+ node = memnew(ScriptProcessorNode);
+#endif
+ buffer_length = node->create(buffer_length, channel_count);
+ if (output_rb) {
+ memdelete_arr(output_rb);
}
-
- if (!internal_buffer || (int)memarr_len(internal_buffer) != buffer_length * channel_count) {
- if (internal_buffer)
- memdelete_arr(internal_buffer);
- internal_buffer = memnew_arr(float, buffer_length *channel_count);
+ output_rb = memnew_arr(float, buffer_length *channel_count);
+ if (!output_rb) {
+ return ERR_OUT_OF_MEMORY;
}
-
- if (!internal_buffer) {
+ if (input_rb) {
+ memdelete_arr(input_rb);
+ }
+ input_rb = memnew_arr(float, buffer_length *channel_count);
+ if (!input_rb) {
return ERR_OUT_OF_MEMORY;
}
return OK;
}
void AudioDriverJavaScript::start() {
-#ifndef NO_THREADS
- thread = Thread::create(_audio_thread_func, this);
-#endif
- godot_audio_start(internal_buffer);
+ if (node) {
+ node->start(output_rb, memarr_len(output_rb), input_rb, memarr_len(input_rb));
+ }
}
void AudioDriverJavaScript::resume() {
- godot_audio_resume();
+ if (state == 0) { // 'suspended'
+ godot_audio_resume();
+ }
}
float AudioDriverJavaScript::get_latency() {
- return godot_audio_get_latency();
+ return output_latency + (float(buffer_length) / mix_rate);
}
int AudioDriverJavaScript::get_mix_rate() const {
@@ -142,48 +158,128 @@ AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const {
}
void AudioDriverJavaScript::lock() {
-#ifndef NO_THREADS
- mutex.lock();
-#endif
+ if (node) {
+ node->unlock();
+ }
}
void AudioDriverJavaScript::unlock() {
-#ifndef NO_THREADS
- mutex.unlock();
-#endif
-}
-
-void AudioDriverJavaScript::finish_async() {
-#ifndef NO_THREADS
- quit = true; // Ask thread to quit.
-#endif
- godot_audio_finish_async();
+ if (node) {
+ node->unlock();
+ }
}
void AudioDriverJavaScript::finish() {
-#ifndef NO_THREADS
- Thread::wait_to_finish(thread);
- memdelete(thread);
- thread = NULL;
-#endif
- if (internal_buffer) {
- memdelete_arr(internal_buffer);
- internal_buffer = nullptr;
+ if (node) {
+ node->finish();
+ memdelete(node);
+ node = nullptr;
+ }
+ if (output_rb) {
+ memdelete_arr(output_rb);
+ output_rb = nullptr;
+ }
+ if (input_rb) {
+ memdelete_arr(input_rb);
+ input_rb = nullptr;
}
}
Error AudioDriverJavaScript::capture_start() {
- godot_audio_capture_stop();
+ lock();
input_buffer_init(buffer_length);
+ unlock();
godot_audio_capture_start();
return OK;
}
Error AudioDriverJavaScript::capture_stop() {
+ godot_audio_capture_stop();
+ lock();
input_buffer.clear();
+ unlock();
return OK;
}
AudioDriverJavaScript::AudioDriverJavaScript() {
singleton = this;
}
+
+#ifdef NO_THREADS
+/// ScriptProcessorNode implementation
+void AudioDriverJavaScript::ScriptProcessorNode::_process_callback() {
+ AudioDriverJavaScript::singleton->_audio_driver_capture();
+ AudioDriverJavaScript::singleton->_audio_driver_process();
+}
+
+int AudioDriverJavaScript::ScriptProcessorNode::create(int p_buffer_samples, int p_channels) {
+ return godot_audio_script_create(p_buffer_samples, p_channels);
+}
+
+void AudioDriverJavaScript::ScriptProcessorNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
+ godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback);
+}
+#else
+/// AudioWorkletNode implementation
+void AudioDriverJavaScript::WorkletNode::_audio_thread_func(void *p_data) {
+ AudioDriverJavaScript::WorkletNode *obj = static_cast<AudioDriverJavaScript::WorkletNode *>(p_data);
+ AudioDriverJavaScript *driver = AudioDriverJavaScript::singleton;
+ const int out_samples = memarr_len(driver->output_rb);
+ const int in_samples = memarr_len(driver->input_rb);
+ int wpos = 0;
+ int to_write = out_samples;
+ int rpos = 0;
+ int to_read = 0;
+ int32_t step = 0;
+ while (!obj->quit) {
+ if (to_read) {
+ driver->lock();
+ driver->_audio_driver_capture(rpos, to_read);
+ godot_audio_worklet_state_add(obj->state, STATE_SAMPLES_IN, -to_read);
+ driver->unlock();
+ rpos += to_read;
+ if (rpos >= in_samples) {
+ rpos -= in_samples;
+ }
+ }
+ if (to_write) {
+ driver->lock();
+ driver->_audio_driver_process(wpos, to_write);
+ godot_audio_worklet_state_add(obj->state, STATE_SAMPLES_OUT, to_write);
+ driver->unlock();
+ wpos += to_write;
+ if (wpos >= out_samples) {
+ wpos -= out_samples;
+ }
+ }
+ step = godot_audio_worklet_state_wait(obj->state, STATE_PROCESS, step, 1);
+ to_write = out_samples - godot_audio_worklet_state_get(obj->state, STATE_SAMPLES_OUT);
+ to_read = godot_audio_worklet_state_get(obj->state, STATE_SAMPLES_IN);
+ }
+}
+
+int AudioDriverJavaScript::WorkletNode::create(int p_buffer_size, int p_channels) {
+ godot_audio_worklet_create(p_channels);
+ return p_buffer_size;
+}
+
+void AudioDriverJavaScript::WorkletNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
+ godot_audio_worklet_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, state);
+ thread = Thread::create(_audio_thread_func, this);
+}
+
+void AudioDriverJavaScript::WorkletNode::lock() {
+ mutex.lock();
+}
+
+void AudioDriverJavaScript::WorkletNode::unlock() {
+ mutex.unlock();
+}
+
+void AudioDriverJavaScript::WorkletNode::finish() {
+ quit = true; // Ask thread to quit.
+ Thread::wait_to_finish(thread);
+ memdelete(thread);
+ thread = nullptr;
+}
+#endif
diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h
index 56a7da0307..f112a1ede4 100644
--- a/platform/javascript/audio_driver_javascript.h
+++ b/platform/javascript/audio_driver_javascript.h
@@ -31,53 +31,96 @@
#ifndef AUDIO_DRIVER_JAVASCRIPT_H
#define AUDIO_DRIVER_JAVASCRIPT_H
-#include "servers/audio_server.h"
-
#include "core/os/mutex.h"
#include "core/os/thread.h"
+#include "servers/audio_server.h"
+
+#include "godot_audio.h"
class AudioDriverJavaScript : public AudioDriver {
+public:
+ class AudioNode {
+ public:
+ virtual int create(int p_buffer_size, int p_output_channels) = 0;
+ virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) = 0;
+ virtual void finish() {}
+ virtual void lock() {}
+ virtual void unlock() {}
+ virtual ~AudioNode() {}
+ };
+
+ class WorkletNode : public AudioNode {
+ private:
+ enum {
+ STATE_LOCK,
+ STATE_PROCESS,
+ STATE_SAMPLES_IN,
+ STATE_SAMPLES_OUT,
+ STATE_MAX,
+ };
+ Mutex mutex;
+ Thread *thread = nullptr;
+ bool quit = false;
+ int32_t state[STATE_MAX] = { 0 };
+
+ static void _audio_thread_func(void *p_data);
+
+ public:
+ int create(int p_buffer_size, int p_output_channels) override;
+ void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
+ void finish() override;
+ void lock() override;
+ void unlock() override;
+ };
+
+ class ScriptProcessorNode : public AudioNode {
+ private:
+ static void _process_callback();
+
+ public:
+ int create(int p_buffer_samples, int p_channels) override;
+ void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
+ };
+
private:
- float *internal_buffer = nullptr;
+ AudioNode *node = nullptr;
+
+ float *output_rb = nullptr;
+ float *input_rb = nullptr;
int buffer_length = 0;
int mix_rate = 0;
int channel_count = 0;
+ int state = 0;
+ float output_latency = 0.0;
-public:
-#ifndef NO_THREADS
- Mutex mutex;
- Thread *thread = nullptr;
- bool quit = false;
- bool needs_process = true;
-
- static void _audio_thread_func(void *p_data);
-#endif
+ static void _state_change_callback(int p_state);
+ static void _latency_update_callback(float p_latency);
- void _js_driver_process();
+protected:
+ void _audio_driver_process(int p_from = 0, int p_samples = 0);
+ void _audio_driver_capture(int p_from = 0, int p_samples = 0);
+public:
static bool is_available();
- void process_capture(float sample);
static AudioDriverJavaScript *singleton;
- const char *get_name() const override;
+ virtual const char *get_name() const;
- Error init() override;
- void start() override;
+ virtual Error init();
+ virtual void start();
void resume();
- float get_latency() override;
- int get_mix_rate() const override;
- SpeakerMode get_speaker_mode() const override;
- void lock() override;
- void unlock() override;
- void finish() override;
- void finish_async();
+ virtual float get_latency();
+ virtual int get_mix_rate() const;
+ virtual SpeakerMode get_speaker_mode() const;
+ virtual void lock();
+ virtual void unlock();
+ virtual void finish();
- Error capture_start() override;
- Error capture_stop() override;
+ virtual Error capture_start();
+ virtual Error capture_stop();
AudioDriverJavaScript();
};
-
#endif
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 8f2961b33d..e6e35f6aa9 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -1,6 +1,6 @@
import os
-from emscripten_helpers import run_closure_compiler, create_engine_file
+from emscripten_helpers import run_closure_compiler, create_engine_file, add_js_libraries
from SCons.Util import WhereIs
@@ -85,7 +85,8 @@ def configure(env):
if env["use_lto"]:
env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"])
env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"])
- env.Append(LINKFLAGS=["--llvm-lto", "1"])
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto"])
# Closure compiler
if env["use_closure_compiler"]:
@@ -95,6 +96,9 @@ def configure(env):
jscc = env.Builder(generator=run_closure_compiler, suffix=".cc.js", src_suffix=".js")
env.Append(BUILDERS={"BuildJS": jscc})
+ # Add helper method for adding libraries.
+ env.AddMethod(add_js_libraries, "AddJSLibraries")
+
# Add method that joins/compiles our Engine files.
env.AddMethod(create_engine_file, "CreateEngineFile")
@@ -165,6 +169,6 @@ def configure(env):
env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"])
# callMain for manual start, FS for preloading, PATH and ERRNO_CODES for BrowserFS.
- env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain', 'FS', 'PATH']"])
+ env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"])
# Add code that allow exiting runtime.
env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"])
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index 8dc33bdf64..768e326e80 100644
--- a/platform/javascript/display_server_javascript.cpp
+++ b/platform/javascript/display_server_javascript.cpp
@@ -37,6 +37,7 @@
#include <png.h>
#include "dom_keys.inc"
+#include "godot_js.h"
#define DOM_BUTTON_LEFT 0
#define DOM_BUTTON_MIDDLE 1
@@ -44,28 +45,17 @@
#define DOM_BUTTON_XBUTTON1 3
#define DOM_BUTTON_XBUTTON2 4
-char DisplayServerJavaScript::canvas_id[256] = { 0 };
-static bool cursor_inside_canvas = true;
-
DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() {
return static_cast<DisplayServerJavaScript *>(DisplayServer::get_singleton());
}
// Window (canvas)
void DisplayServerJavaScript::focus_canvas() {
- /* clang-format off */
- EM_ASM(
- Module['canvas'].focus();
- );
- /* clang-format on */
+ godot_js_display_canvas_focus();
}
bool DisplayServerJavaScript::is_canvas_focused() {
- /* clang-format off */
- return EM_ASM_INT_V(
- return document.activeElement == Module['canvas'];
- );
- /* clang-format on */
+ return godot_js_display_canvas_is_focused() != 0;
}
bool DisplayServerJavaScript::check_size_force_redraw() {
@@ -83,19 +73,17 @@ bool DisplayServerJavaScript::check_size_force_redraw() {
}
Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) {
- int canvas_x = EM_ASM_INT({
- return Module['canvas'].getBoundingClientRect().x;
- });
- int canvas_y = EM_ASM_INT({
- return Module['canvas'].getBoundingClientRect().y;
- });
+ DisplayServerJavaScript *display = get_singleton();
+ int canvas_x;
+ int canvas_y;
+ godot_js_display_canvas_bounding_rect_position_get(&canvas_x, &canvas_y);
int canvas_width;
int canvas_height;
- emscripten_get_canvas_element_size(canvas_id, &canvas_width, &canvas_height);
+ emscripten_get_canvas_element_size(display->canvas_id, &canvas_width, &canvas_height);
double element_width;
double element_height;
- emscripten_get_element_css_size(canvas_id, &element_width, &element_height);
+ emscripten_get_element_css_size(display->canvas_id, &element_width, &element_height);
return Point2((int)(canvas_width / element_width * (p_x - canvas_x)),
(int)(canvas_height / element_height * (p_y - canvas_y)));
@@ -105,8 +93,7 @@ EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, co
DisplayServerJavaScript *display = get_singleton();
// Empty ID is canvas.
String target_id = String::utf8(p_event->id);
- String canvas_str_id = String::utf8(canvas_id);
- if (target_id.empty() || target_id == canvas_str_id) {
+ if (target_id.empty() || target_id == String::utf8(display->canvas_id)) {
// This event property is the only reliable data on
// browser fullscreen state.
if (p_event->isFullscreen) {
@@ -118,14 +105,15 @@ EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, co
return false;
}
-// Drag and drop callback (see native/utils.js).
-extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p_filec) {
- DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton();
+// Drag and drop callback.
+void DisplayServerJavaScript::drop_files_js_callback(char **p_filev, int p_filec) {
+ DisplayServerJavaScript *ds = get_singleton();
if (!ds) {
ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active");
}
- if (ds->drop_files_callback.is_null())
+ if (ds->drop_files_callback.is_null()) {
return;
+ }
Vector<String> files;
for (int i = 0; i < p_filec; i++) {
files.push_back(String::utf8(p_filev[i]));
@@ -137,6 +125,18 @@ extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p
ds->drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
}
+// JavaScript quit request callback.
+void DisplayServerJavaScript::request_quit_callback() {
+ DisplayServerJavaScript *ds = get_singleton();
+ if (ds && !ds->window_event_callback.is_null()) {
+ Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce);
+ }
+}
+
// Keys
template <typename T>
@@ -272,12 +272,13 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E
}
EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *ds = get_singleton();
Input *input = Input::get_singleton();
int input_mask = input->get_mouse_button_mask();
Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY);
// For motion outside the canvas, only read mouse movement if dragging
// started inside the canvas; imitating desktop app behaviour.
- if (!cursor_inside_canvas && !input_mask)
+ if (!ds->cursor_inside_canvas && !input_mask)
return false;
Ref<InputEventMouseMotion> ev;
@@ -339,35 +340,13 @@ const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape
}
}
-void DisplayServerJavaScript::set_css_cursor(const char *p_cursor) {
- /* clang-format off */
- EM_ASM_({
- Module['canvas'].style.cursor = UTF8ToString($0);
- }, p_cursor);
- /* clang-format on */
-}
-
-bool DisplayServerJavaScript::is_css_cursor_hidden() const {
- /* clang-format off */
- return EM_ASM_INT({
- return Module['canvas'].style.cursor === 'none';
- });
- /* clang-format on */
-}
-
void DisplayServerJavaScript::cursor_set_shape(CursorShape p_shape) {
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
-
- if (mouse_get_mode() == MOUSE_MODE_VISIBLE) {
- if (cursors[p_shape] != "") {
- Vector<String> url = cursors[p_shape].split("?");
- set_css_cursor(("url(\"" + url[0] + "\") " + url[1] + ", auto").utf8());
- } else {
- set_css_cursor(godot2dom_cursor(p_shape));
- }
+ if (cursor_shape == p_shape) {
+ return;
}
-
cursor_shape = p_shape;
+ godot_js_display_cursor_set_shape(godot2dom_cursor(cursor_shape));
}
DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const {
@@ -376,17 +355,6 @@ DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const {
void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
if (p_cursor.is_valid()) {
- Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
-
- if (cursor_c) {
- if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
- cursor_set_shape(p_shape);
- return;
- }
-
- cursors_cache.erase(p_shape);
- }
-
Ref<Texture2D> texture = p_cursor;
Ref<AtlasTexture> atlas_texture = p_cursor;
Ref<Image> image;
@@ -449,53 +417,10 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso
png.resize(len);
ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));
- char *object_url;
- /* clang-format off */
- EM_ASM({
- var PNG_PTR = $0;
- var PNG_LEN = $1;
- var PTR = $2;
-
- var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: 'image/png' });
- var url = URL.createObjectURL(png);
- var length_bytes = lengthBytesUTF8(url) + 1;
- var string_on_wasm_heap = _malloc(length_bytes);
- setValue(PTR, string_on_wasm_heap, '*');
- stringToUTF8(url, string_on_wasm_heap, length_bytes);
- }, png.ptr(), len, &object_url);
- /* clang-format on */
-
- String url = String::utf8(object_url) + "?" + itos(p_hotspot.x) + " " + itos(p_hotspot.y);
-
- /* clang-format off */
- EM_ASM({ _free($0); }, object_url);
- /* clang-format on */
-
- if (cursors[p_shape] != "") {
- /* clang-format off */
- EM_ASM({
- URL.revokeObjectURL(UTF8ToString($0).split('?')[0]);
- }, cursors[p_shape].utf8().get_data());
- /* clang-format on */
- cursors[p_shape] = "";
- }
-
- cursors[p_shape] = url;
+ godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), png.ptr(), len, p_hotspot.x, p_hotspot.y);
- Vector<Variant> params;
- params.push_back(p_cursor);
- params.push_back(p_hotspot);
- cursors_cache.insert(p_shape, params);
-
- } else if (cursors[p_shape] != "") {
- /* clang-format off */
- EM_ASM({
- URL.revokeObjectURL(UTF8ToString($0).split('?')[0]);
- }, cursors[p_shape].utf8().get_data());
- /* clang-format on */
- cursors[p_shape] = "";
-
- cursors_cache.erase(p_shape);
+ } else {
+ godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), NULL, 0, 0, 0);
}
cursor_set_shape(cursor_shape);
@@ -508,40 +433,37 @@ void DisplayServerJavaScript::mouse_set_mode(MouseMode p_mode) {
return;
if (p_mode == MOUSE_MODE_VISIBLE) {
- // set_css_cursor must be called before set_cursor_shape to make the cursor visible
- set_css_cursor(godot2dom_cursor(cursor_shape));
- cursor_set_shape(cursor_shape);
+ godot_js_display_cursor_set_visible(1);
emscripten_exit_pointerlock();
} else if (p_mode == MOUSE_MODE_HIDDEN) {
- set_css_cursor("none");
+ godot_js_display_cursor_set_visible(0);
emscripten_exit_pointerlock();
} else if (p_mode == MOUSE_MODE_CAPTURED) {
- EMSCRIPTEN_RESULT result = emscripten_request_pointerlock("canvas", false);
+ godot_js_display_cursor_set_visible(1);
+ EMSCRIPTEN_RESULT result = emscripten_request_pointerlock(canvas_id, false);
ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
- // set_css_cursor must be called before cursor_set_shape to make the cursor visible
- set_css_cursor(godot2dom_cursor(cursor_shape));
- cursor_set_shape(cursor_shape);
}
}
DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_mode() const {
- if (is_css_cursor_hidden())
+ if (godot_js_display_cursor_is_hidden()) {
return MOUSE_MODE_HIDDEN;
+ }
EmscriptenPointerlockChangeEvent ev;
emscripten_get_pointerlock_status(&ev);
- return (ev.isActive && String::utf8(ev.id) == "canvas") ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
+ return (ev.isActive && String::utf8(ev.id) == String::utf8(canvas_id)) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
}
// Wheel
-
EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) {
ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false);
+ DisplayServerJavaScript *ds = get_singleton();
if (!is_canvas_focused()) {
- if (cursor_inside_canvas) {
+ if (ds->cursor_inside_canvas) {
focus_canvas();
} else {
return false;
@@ -632,7 +554,7 @@ EM_BOOL DisplayServerJavaScript::touchmove_callback(int p_event_type, const Emsc
}
bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const {
- return EM_ASM_INT({ return 'ontouchstart' in window; });
+ return godot_js_display_touchscreen_is_available();
}
// Gamepad
@@ -696,53 +618,30 @@ Vector<String> DisplayServerJavaScript::get_rendering_drivers_func() {
}
// Clipboard
-extern "C" EMSCRIPTEN_KEEPALIVE void update_clipboard(const char *p_text) {
- // Only call set_clipboard from OS (sets local clipboard)
- DisplayServerJavaScript::get_singleton()->clipboard = p_text;
+void DisplayServerJavaScript::update_clipboard_callback(const char *p_text) {
+ get_singleton()->clipboard = p_text;
}
void DisplayServerJavaScript::clipboard_set(const String &p_text) {
- /* clang-format off */
- int err = EM_ASM_INT({
- var text = UTF8ToString($0);
- if (!navigator.clipboard || !navigator.clipboard.writeText)
- return 1;
- navigator.clipboard.writeText(text).catch(function(e) {
- // Setting OS clipboard is only possible from an input callback.
- console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e);
- });
- return 0;
- }, p_text.utf8().get_data());
- /* clang-format on */
+ clipboard = p_text;
+ int err = godot_js_display_clipboard_set(p_text.utf8().get_data());
ERR_FAIL_COND_MSG(err, "Clipboard API is not supported.");
}
String DisplayServerJavaScript::clipboard_get() const {
- /* clang-format off */
- EM_ASM({
- try {
- navigator.clipboard.readText().then(function (result) {
- ccall('update_clipboard', 'void', ['string'], [result]);
- }).catch(function (e) {
- // Fail graciously.
- });
- } catch (e) {
- // Fail graciously.
- }
- });
- /* clang-format on */
+ godot_js_display_clipboard_get(update_clipboard_callback);
return clipboard;
}
-extern "C" EMSCRIPTEN_KEEPALIVE void send_window_event(int p_notification) {
+void DisplayServerJavaScript::send_window_event_callback(int p_notification) {
+ DisplayServerJavaScript *ds = get_singleton();
+ if (!ds) {
+ return;
+ }
if (p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER || p_notification == DisplayServer::WINDOW_EVENT_MOUSE_EXIT) {
- cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+ ds->cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
}
- OS_JavaScript *os = OS_JavaScript::get_singleton();
- if (os->is_finalizing())
- return; // We don't want events anymore.
- DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton();
- if (ds && !ds->window_event_callback.is_null()) {
+ if (!ds->window_event_callback.is_null()) {
Variant event = int(p_notification);
Variant *eventp = &event;
Variant ret;
@@ -752,11 +651,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE void send_window_event(int p_notification) {
}
void DisplayServerJavaScript::alert(const String &p_alert, const String &p_title) {
- /* clang-format off */
- EM_ASM_({
- window.alert(UTF8ToString($0));
- }, p_alert.utf8().get_data());
- /* clang-format on */
+ godot_js_display_alert(p_alert.utf8().get_data());
}
void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) {
@@ -787,29 +682,11 @@ void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) {
png.resize(len);
ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));
- /* clang-format off */
- EM_ASM({
- var PNG_PTR = $0;
- var PNG_LEN = $1;
-
- var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: "image/png" });
- var url = URL.createObjectURL(png);
- var link = document.getElementById('-gd-engine-icon');
- if (link === null) {
- link = document.createElement('link');
- link.rel = 'icon';
- link.id = '-gd-engine-icon';
- document.head.appendChild(link);
- }
- link.href = url;
- }, png.ptr(), len);
- /* clang-format on */
+ godot_js_display_window_icon_set(png.ptr(), len);
}
void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_event) {
OS_JavaScript *os = OS_JavaScript::get_singleton();
- if (os->is_finalizing())
- return; // We don't want events anymore.
// Resume audio context after input in case autoplay was denied.
os->resume_audio();
@@ -831,16 +708,14 @@ DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_dr
DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
r_error = OK; // Always succeeds for now.
- /* clang-format off */
- swap_cancel_ok = EM_ASM_INT({
- const win = (['Windows', 'Win64', 'Win32', 'WinCE']);
- const plat = navigator.platform || "";
- if (win.indexOf(plat) !== -1) {
- return 1;
- }
- return 0;
- }) == 1;
- /* clang-format on */
+ // Ensure the canvas ID.
+ godot_js_config_canvas_id_get(canvas_id, 256);
+
+ // Check if it's windows.
+ swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1;
+
+ // Expose method for requesting quit.
+ godot_js_os_request_quit_cb(request_quit_callback);
RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu?
#if 0
@@ -878,10 +753,8 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive
video_driver_index = p_video_driver;
#endif
- /* clang-format off */
window_set_mode(p_mode);
- if (EM_ASM_INT_V({ return Module['resizeCanvasOnStart'] })) {
- /* clang-format on */
+ if (godot_js_config_is_resize_on_start()) {
window_set_size(p_resolution);
}
@@ -899,8 +772,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive
result = emscripten_set_##ev##_callback(nullptr, true, &cb); \
EM_CHECK(ev)
// These callbacks from Emscripten's html5.h suffice to access most
- // JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM
- // is used below.
+ // JavaScript APIs.
SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback)
SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback)
SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback)
@@ -919,42 +791,20 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive
#undef SET_EM_CALLBACK
#undef EM_CHECK
- /* clang-format off */
- EM_ASM_ARGS({
- // Bind native event listeners.
- // Module.listeners, and Module.drop_handler are defined in native/utils.js
- const canvas = Module['canvas'];
- const send_window_event = cwrap('send_window_event', null, ['number']);
- const notifications = arguments;
- (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) {
- Module.listeners.add(canvas, event, send_window_event.bind(null, notifications[index]), true);
- });
- // Clipboard
- const update_clipboard = cwrap('update_clipboard', null, ['string']);
- Module.listeners.add(window, 'paste', function(evt) {
- update_clipboard(evt.clipboardData.getData('text'));
- }, false);
- // Drag an drop
- Module.listeners.add(canvas, 'dragover', function(ev) {
- // Prevent default behavior (which would try to open the file(s))
- ev.preventDefault();
- }, false);
- Module.listeners.add(canvas, 'drop', Module.drop_handler, false);
- },
- WINDOW_EVENT_MOUSE_ENTER,
- WINDOW_EVENT_MOUSE_EXIT,
- WINDOW_EVENT_FOCUS_IN,
- WINDOW_EVENT_FOCUS_OUT
- );
- /* clang-format on */
+ // For APIs that are not (sufficiently) exposed, a
+ // library is used below (implemented in library_godot_display.js).
+ godot_js_display_notification_cb(&send_window_event_callback,
+ WINDOW_EVENT_MOUSE_ENTER,
+ WINDOW_EVENT_MOUSE_EXIT,
+ WINDOW_EVENT_FOCUS_IN,
+ WINDOW_EVENT_FOCUS_OUT);
+ godot_js_display_paste_cb(update_clipboard_callback);
+ godot_js_display_drop_files_cb(drop_files_js_callback);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event);
}
DisplayServerJavaScript::~DisplayServerJavaScript() {
- EM_ASM({
- Module.listeners.clear();
- });
//emscripten_webgl_commit_frame();
//emscripten_webgl_destroy_context(webgl_ctx);
}
@@ -1057,11 +907,7 @@ void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_c
}
void DisplayServerJavaScript::window_set_title(const String &p_title, WindowID p_window) {
- /* clang-format off */
- EM_ASM_({
- document.title = UTF8ToString($0);
- }, p_title.utf8().get_data());
- /* clang-format on */
+ godot_js_display_window_title_set(p_title.utf8().get_data());
}
int DisplayServerJavaScript::window_get_current_screen(WindowID p_window) const {
@@ -1103,9 +949,7 @@ Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const {
void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) {
last_width = p_size.x;
last_height = p_size.y;
- double scale = EM_ASM_DOUBLE({
- return window.devicePixelRatio || 1;
- });
+ double scale = godot_js_display_pixel_ratio_get();
emscripten_set_canvas_element_size(canvas_id, p_size.x * scale, p_size.y * scale);
emscripten_set_element_css_size(canvas_id, p_size.x, p_size.y);
}
@@ -1130,7 +974,7 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind
emscripten_exit_fullscreen();
}
window_mode = WINDOW_MODE_WINDOWED;
- window_set_size(windowed_size);
+ window_set_size(Size2i(last_width, last_height));
} break;
case WINDOW_MODE_FULLSCREEN: {
EmscriptenFullscreenStrategy strategy;
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
index d7116be36f..1f00295d48 100644
--- a/platform/javascript/display_server_javascript.h
+++ b/platform/javascript/display_server_javascript.h
@@ -37,18 +37,22 @@
#include <emscripten/html5.h>
class DisplayServerJavaScript : public DisplayServer {
- //int video_driver_index;
-
- Vector2 windowed_size;
-
+private:
+ WindowMode window_mode = WINDOW_MODE_WINDOWED;
ObjectID window_attached_instance_id = {};
+ Callable window_event_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+ Callable drop_files_callback;
+
+ String clipboard;
Ref<InputEventKey> deferred_key_event;
- CursorShape cursor_shape = CURSOR_ARROW;
- String cursors[CURSOR_MAX];
- Map<CursorShape, Vector<Variant>> cursors_cache;
Point2 touches[32];
+ char canvas_id[256] = { 0 };
+ bool cursor_inside_canvas = true;
+ CursorShape cursor_shape = CURSOR_ARROW;
Point2i last_click_pos = Point2(-100, -100); // TODO check this again.
double last_click_ms = 0;
int last_click_button_index = -1;
@@ -66,8 +70,6 @@ class DisplayServerJavaScript : public DisplayServer {
static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event);
static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event);
static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape);
- static void set_css_cursor(const char *p_cursor);
- bool is_css_cursor_hidden() const;
// events
static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data);
@@ -92,22 +94,17 @@ class DisplayServerJavaScript : public DisplayServer {
static void _dispatch_input_event(const Ref<InputEvent> &p_event);
+ static void request_quit_callback();
+ static void update_clipboard_callback(const char *p_text);
+ static void send_window_event_callback(int p_notification);
+ static void drop_files_js_callback(char **p_filev, int p_filec);
+
protected:
int get_current_video_driver() const;
public:
// Override return type to make writing static callbacks less tedious.
static DisplayServerJavaScript *get_singleton();
- static char canvas_id[256];
-
- WindowMode window_mode = WINDOW_MODE_WINDOWED;
-
- String clipboard;
-
- Callable window_event_callback;
- Callable input_event_callback;
- Callable input_text_callback;
- Callable drop_files_callback;
// utilities
bool check_size_force_redraw();
diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py
index f6db10fbbd..cc874c432e 100644
--- a/platform/javascript/emscripten_helpers.py
+++ b/platform/javascript/emscripten_helpers.py
@@ -19,3 +19,9 @@ def create_engine_file(env, target, source, externs):
if env["use_closure_compiler"]:
return env.BuildJS(target, source, JSEXTERNS=externs)
return env.Textfile(target, [env.File(s) for s in source])
+
+
+def add_js_libraries(env, libraries):
+ if "JS_LIBS" not in env:
+ env["JS_LIBS"] = []
+ env.Append(JS_LIBS=env.File(libraries))
diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js
index 05a11701c0..3745e04479 100644
--- a/platform/javascript/engine/engine.js
+++ b/platform/javascript/engine/engine.js
@@ -33,7 +33,7 @@ Function('return this')()['Engine'] = (function() {
this.resizeCanvasOnStart = false;
this.onExecute = null;
this.onExit = null;
- this.persistentPaths = [];
+ this.persistentPaths = ['/userfs'];
};
Engine.prototype.init = /** @param {string=} basePath */ function(basePath) {
@@ -114,18 +114,30 @@ Function('return this')()['Engine'] = (function() {
locale = navigator.languages ? navigator.languages[0] : navigator.language;
locale = locale.split('.')[0];
}
- me.rtenv['locale'] = locale;
- me.rtenv['canvas'] = me.canvas;
+ // Emscripten configuration.
me.rtenv['thisProgram'] = me.executableName;
- me.rtenv['resizeCanvasOnStart'] = me.resizeCanvasOnStart;
me.rtenv['noExitRuntime'] = true;
- me.rtenv['onExecute'] = me.onExecute;
- me.rtenv['onExit'] = function(code) {
- me.rtenv['deinitFS']();
- if (me.onExit)
- me.onExit(code);
- me.rtenv = null;
- };
+ // Godot configuration.
+ me.rtenv['initConfig']({
+ 'resizeCanvasOnStart': me.resizeCanvasOnStart,
+ 'canvas': me.canvas,
+ 'locale': locale,
+ 'onExecute': function(p_args) {
+ if (me.onExecute) {
+ me.onExecute(p_args);
+ return 0;
+ }
+ return 1;
+ },
+ 'onExit': function(p_code) {
+ me.rtenv['deinitFS']();
+ if (me.onExit) {
+ me.onExit(p_code);
+ }
+ me.rtenv = null;
+ },
+ });
+
return new Promise(function(resolve, reject) {
preloader.preloadedFiles.forEach(function(file) {
me.rtenv['copyToFS'](file.path, file.buffer);
@@ -208,8 +220,6 @@ Function('return this')()['Engine'] = (function() {
};
Engine.prototype.setOnExecute = function(onExecute) {
- if (this.rtenv)
- this.rtenv.onExecute = onExecute;
this.onExecute = onExecute;
};
diff --git a/platform/javascript/engine/utils.js b/platform/javascript/engine/utils.js
index 0c97b38199..10e3abe91e 100644
--- a/platform/javascript/engine/utils.js
+++ b/platform/javascript/engine/utils.js
@@ -4,6 +4,8 @@ var Utils = {
function rw(path) {
if (path.endsWith('.worker.js')) {
return execName + '.worker.js';
+ } else if (path.endsWith('.audio.worklet.js')) {
+ return execName + '.audio.worklet.js';
} else if (path.endsWith('.js')) {
return execName + '.js';
} else if (path.endsWith('.wasm')) {
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index a83ff44d20..d520931067 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -94,6 +94,9 @@ public:
} else if (req[1] == basereq + ".js") {
filepath += ".js";
ctype = "application/javascript";
+ } else if (req[1] == basereq + ".audio.worklet.js") {
+ filepath += ".audio.worklet.js";
+ ctype = "application/javascript";
} else if (req[1] == basereq + ".worker.js") {
filepath += ".worker.js";
ctype = "application/javascript";
@@ -440,6 +443,9 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
} else if (file == "godot.worker.js") {
file = p_path.get_file().get_basename() + ".worker.js";
+ } else if (file == "godot.audio.worklet.js") {
+ file = p_path.get_file().get_basename() + ".audio.worklet.js";
+
} else if (file == "godot.wasm") {
file = p_path.get_file().get_basename() + ".wasm";
}
@@ -566,6 +572,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
DirAccess::remove_file_or_error(basepath + ".html");
DirAccess::remove_file_or_error(basepath + ".js");
DirAccess::remove_file_or_error(basepath + ".worker.js");
+ DirAccess::remove_file_or_error(basepath + ".audio.worklet.js");
DirAccess::remove_file_or_error(basepath + ".pck");
DirAccess::remove_file_or_error(basepath + ".png");
DirAccess::remove_file_or_error(basepath + ".wasm");
diff --git a/platform/javascript/godot_audio.h b/platform/javascript/godot_audio.h
index f7f26e5262..7ebda3ad39 100644
--- a/platform/javascript/godot_audio.h
+++ b/platform/javascript/godot_audio.h
@@ -38,19 +38,24 @@ extern "C" {
#include "stddef.h"
extern int godot_audio_is_available();
-
-extern int godot_audio_init(int p_mix_rate, int p_latency);
-extern int godot_audio_create_processor(int p_buffer_length, int p_channel_count);
-
-extern void godot_audio_start(float *r_buffer_ptr);
+extern int godot_audio_init(int p_mix_rate, int p_latency, void (*_state_cb)(int), void (*_latency_cb)(float));
extern void godot_audio_resume();
-extern void godot_audio_finish_async();
-
-extern float godot_audio_get_latency();
extern void godot_audio_capture_start();
extern void godot_audio_capture_stop();
+// Worklet
+typedef int32_t GodotAudioState[4];
+extern void godot_audio_worklet_create(int p_channels);
+extern void godot_audio_worklet_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, GodotAudioState p_state);
+extern void godot_audio_worklet_state_add(GodotAudioState p_state, int p_idx, int p_value);
+extern int godot_audio_worklet_state_get(GodotAudioState p_state, int p_idx);
+extern int godot_audio_worklet_state_wait(int32_t *p_state, int p_idx, int32_t p_expected, int p_timeout);
+
+// Script
+extern int godot_audio_script_create(int p_buffer_size, int p_channels);
+extern void godot_audio_script_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, void (*p_cb)());
+
#ifdef __cplusplus
}
#endif
diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h
new file mode 100644
index 0000000000..23596a0897
--- /dev/null
+++ b/platform/javascript/godot_js.h
@@ -0,0 +1,87 @@
+/*************************************************************************/
+/* godot_js.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 GODOT_JS_H
+#define GODOT_JS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "stddef.h"
+
+// Config
+extern void godot_js_config_locale_get(char *p_ptr, int p_ptr_max);
+extern void godot_js_config_canvas_id_get(char *p_ptr, int p_ptr_max);
+extern int godot_js_config_is_resize_on_start();
+
+// OS
+extern void godot_js_os_finish_async(void (*p_callback)());
+extern void godot_js_os_request_quit_cb(void (*p_callback)());
+extern int godot_js_os_fs_is_persistent();
+extern void godot_js_os_fs_sync(void (*p_callback)());
+extern int godot_js_os_execute(const char *p_json);
+extern void godot_js_os_shell_open(const char *p_uri);
+
+// Display
+extern double godot_js_display_pixel_ratio_get();
+extern void godot_js_display_alert(const char *p_text);
+extern int godot_js_display_touchscreen_is_available();
+extern int godot_js_display_is_swap_ok_cancel();
+
+// Display canvas
+extern void godot_js_display_canvas_focus();
+extern int godot_js_display_canvas_is_focused();
+extern void godot_js_display_canvas_bounding_rect_position_get(int32_t *p_x, int32_t *p_y);
+
+// Display window
+extern void godot_js_display_window_request_fullscreen();
+extern void godot_js_display_window_title_set(const char *p_text);
+extern void godot_js_display_window_icon_set(const uint8_t *p_ptr, int p_len);
+
+// Display clipboard
+extern int godot_js_display_clipboard_set(const char *p_text);
+extern int godot_js_display_clipboard_get(void (*p_callback)(const char *p_text));
+
+// Display cursor
+extern void godot_js_display_cursor_set_shape(const char *p_cursor);
+extern int godot_js_display_cursor_is_hidden();
+extern void godot_js_display_cursor_set_custom_shape(const char *p_shape, const uint8_t *p_ptr, int p_len, int p_hotspot_x, int p_hotspot_y);
+extern void godot_js_display_cursor_set_visible(int p_visible);
+
+// Display listeners
+extern void godot_js_display_notification_cb(void (*p_callback)(int p_notification), int p_enter, int p_exit, int p_in, int p_out);
+extern void godot_js_display_paste_cb(void (*p_callback)(const char *p_text));
+extern void godot_js_display_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec));
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GODOT_JS_H */
diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp
index 3a72b10dd4..b203253a39 100644
--- a/platform/javascript/javascript_eval.cpp
+++ b/platform/javascript/javascript_eval.cpp
@@ -33,95 +33,30 @@
#include "api/javascript_eval.h"
#include "emscripten.h"
-extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_PackedByteArray_and_open_write(PackedByteArray *p_arr, VectorWriteProxy<uint8_t> *r_write, int p_len) {
- p_arr->resize(p_len);
- *r_write = p_arr->write;
- return p_arr->ptrw();
+extern "C" {
+union js_eval_ret {
+ uint32_t b;
+ double d;
+ char *s;
+};
+
+extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len));
}
-Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
- union {
- bool b;
- double d;
- char *s;
- } js_data;
+void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) {
+ PackedByteArray *arr = (PackedByteArray *)p_arr;
+ VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write;
+ arr->resize(p_len);
+ *write = arr->write;
+ return arr->ptrw();
+}
+Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
+ union js_eval_ret js_data;
PackedByteArray arr;
VectorWriteProxy<uint8_t> arr_write;
- /* clang-format off */
- Variant::Type return_type = static_cast<Variant::Type>(EM_ASM_INT({
-
- const CODE = $0;
- const USE_GLOBAL_EXEC_CONTEXT = $1;
- const PTR = $2;
- const BYTEARRAY_PTR = $3;
- const BYTEARRAY_WRITE_PTR = $4;
- var eval_ret;
- try {
- if (USE_GLOBAL_EXEC_CONTEXT) {
- // indirect eval call grants global execution context
- var global_eval = eval;
- eval_ret = global_eval(UTF8ToString(CODE));
- } else {
- eval_ret = eval(UTF8ToString(CODE));
- }
- } catch (e) {
- err(e);
- eval_ret = null;
- }
-
- switch (typeof eval_ret) {
-
- case 'boolean':
- setValue(PTR, eval_ret, 'i32');
- return 1; // BOOL
-
- case 'number':
- setValue(PTR, eval_ret, 'double');
- return 3; // FLOAT
-
- case 'string':
- var array_len = lengthBytesUTF8(eval_ret)+1;
- var array_ptr = _malloc(array_len);
- try {
- if (array_ptr===0) {
- throw new Error('String allocation failed (probably out of memory)');
- }
- setValue(PTR, array_ptr , '*');
- stringToUTF8(eval_ret, array_ptr, array_len);
- return 4; // STRING
- } catch (e) {
- if (array_ptr!==0) {
- _free(array_ptr)
- }
- err(e);
- // fall through
- }
- break;
-
- case 'object':
- if (eval_ret === null) {
- break;
- }
-
- if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) {
- eval_ret = new Uint8Array(eval_ret.buffer);
- }
- else if (eval_ret instanceof ArrayBuffer) {
- eval_ret = new Uint8Array(eval_ret);
- }
- if (eval_ret instanceof Uint8Array) {
- var bytes_ptr = ccall('resize_PackedByteArray_and_open_write', 'number', ['number', 'number' ,'number'], [BYTEARRAY_PTR, BYTEARRAY_WRITE_PTR, eval_ret.length]);
- HEAPU8.set(eval_ret, bytes_ptr);
- return 20; // PACKED_BYTE_ARRAY
- }
- break;
- }
- return 0; // NIL
-
- }, p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write));
- /* clang-format on */
+ Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write));
switch (return_type) {
case Variant::BOOL:
@@ -130,9 +65,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
return js_data.d;
case Variant::STRING: {
String str = String::utf8(js_data.s);
- /* clang-format off */
- EM_ASM_({ _free($0); }, js_data.s);
- /* clang-format on */
+ free(js_data.s); // Must free the string allocated in JS.
return str;
}
case Variant::PACKED_BYTE_ARRAY:
diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp
index 01722c4bc8..2d28a63566 100644
--- a/platform/javascript/javascript_main.cpp
+++ b/platform/javascript/javascript_main.cpp
@@ -36,20 +36,11 @@
#include <emscripten/emscripten.h>
#include <stdlib.h>
+#include "godot_js.h"
+
static OS_JavaScript *os = nullptr;
static uint64_t target_ticks = 0;
-extern "C" EMSCRIPTEN_KEEPALIVE void _request_quit_callback(char *p_filev[], int p_filec) {
- DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton();
- if (ds) {
- Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST);
- Variant *eventp = &event;
- Variant ret;
- Callable::CallError ce;
- ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce);
- }
-}
-
void exit_callback() {
emscripten_cancel_main_loop(); // After this, we can exit!
Main::cleanup();
@@ -59,6 +50,10 @@ void exit_callback() {
emscripten_force_exit(exit_code); // No matter that we call cancel_main_loop, regular "exit" will not work, forcing.
}
+void cleanup_after_sync() {
+ emscripten_set_main_loop(exit_callback, -1, false);
+}
+
void main_loop_callback() {
uint64_t current_ticks = os->get_ticks_usec();
@@ -74,68 +69,14 @@ void main_loop_callback() {
target_ticks += (uint64_t)(1000000 / target_fps);
}
if (os->main_loop_iterate()) {
- emscripten_cancel_main_loop(); // Cancel current loop and wait for finalize_async.
- /* clang-format off */
- EM_ASM({
- // This will contain the list of operations that need to complete before cleanup.
- Module.async_finish = [
- // Always contains at least one async promise, to avoid firing immediately if nothing is added.
- new Promise(function(accept, reject) {
- setTimeout(accept, 0);
- })
- ];
- });
- /* clang-format on */
- os->get_main_loop()->finish();
- os->finalize_async(); // Will add all the async finish functions.
- /* clang-format off */
- EM_ASM({
- Promise.all(Module.async_finish).then(function() {
- Module.async_finish = [];
- return new Promise(function(accept, reject) {
- if (!Module.idbfs) {
- accept();
- return;
- }
- FS.syncfs(function(error) {
- if (error) {
- err('Failed to save IDB file system: ' + error.message);
- }
- accept();
- });
- });
- }).then(function() {
- ccall("cleanup_after_sync", null, []);
- });
- });
- /* clang-format on */
+ emscripten_cancel_main_loop(); // Cancel current loop and wait for cleanup_after_sync.
+ godot_js_os_finish_async(cleanup_after_sync);
}
}
-extern "C" EMSCRIPTEN_KEEPALIVE void cleanup_after_sync() {
- emscripten_set_main_loop(exit_callback, -1, false);
-}
-
/// When calling main, it is assumed FS is setup and synced.
int main(int argc, char *argv[]) {
- // Configure locale.
- char locale_ptr[16];
- /* clang-format off */
- EM_ASM({
- stringToUTF8(Module['locale'], $0, 16);
- }, locale_ptr);
- /* clang-format on */
- setenv("LANG", locale_ptr, true);
-
- // Ensure the canvas ID.
- /* clang-format off */
- EM_ASM({
- stringToUTF8("#" + Module['canvas'].id, $0, 255);
- }, DisplayServerJavaScript::canvas_id);
- /* clang-format on */
-
os = new OS_JavaScript();
- os->set_idb_available((bool)EM_ASM_INT({ return Module.idbfs }));
// We must override main when testing is enabled
TEST_MAIN_OVERRIDE
@@ -147,14 +88,6 @@ int main(int argc, char *argv[]) {
Main::start();
os->get_main_loop()->init();
- // Expose method for requesting quit.
- /* clang-format off */
- EM_ASM({
- Module['request_quit'] = function() {
- ccall("_request_quit_callback", null, []);
- };
- });
- /* clang-format on */
emscripten_set_main_loop(main_loop_callback, -1, false);
// Immediately run the first iteration.
// We are inside an animation frame, we want to immediately draw on the newly setup canvas.
diff --git a/platform/javascript/native/audio.worklet.js b/platform/javascript/native/audio.worklet.js
new file mode 100644
index 0000000000..ad7957e45c
--- /dev/null
+++ b/platform/javascript/native/audio.worklet.js
@@ -0,0 +1,186 @@
+/*************************************************************************/
+/* audio.worklet.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+class RingBuffer {
+
+ constructor(p_buffer, p_state) {
+ this.buffer = p_buffer;
+ this.avail = p_state;
+ this.rpos = 0;
+ this.wpos = 0;
+ }
+
+ data_left() {
+ return Atomics.load(this.avail, 0);
+ }
+
+ space_left() {
+ return this.buffer.length - this.data_left();
+ }
+
+ read(output) {
+ const size = this.buffer.length;
+ let from = 0
+ let to_write = output.length;
+ if (this.rpos + to_write > size) {
+ const high = size - this.rpos;
+ output.set(this.buffer.subarray(this.rpos, size));
+ from = high;
+ to_write -= high;
+ this.rpos = 0;
+ }
+ output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from);
+ this.rpos += to_write;
+ Atomics.add(this.avail, 0, -output.length);
+ Atomics.notify(this.avail, 0);
+ }
+
+ write(p_buffer) {
+ const to_write = p_buffer.length;
+ const mw = this.buffer.length - this.wpos;
+ if (mw >= to_write) {
+ this.buffer.set(p_buffer, this.wpos);
+ } else {
+ const high = p_buffer.subarray(0, to_write - mw);
+ const low = p_buffer.subarray(to_write - mw);
+ this.buffer.set(high, this.wpos);
+ this.buffer.set(low);
+ }
+ let diff = to_write;
+ if (this.wpos + diff >= this.buffer.length) {
+ diff -= this.buffer.length;
+ }
+ this.wpos += diff;
+ Atomics.add(this.avail, 0, to_write);
+ Atomics.notify(this.avail, 0);
+ }
+}
+
+class GodotProcessor extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ this.running = true;
+ this.lock = null;
+ this.notifier = null;
+ this.output = null;
+ this.output_buffer = new Float32Array();
+ this.input = null;
+ this.input_buffer = new Float32Array();
+ this.port.onmessage = (event) => {
+ const cmd = event.data['cmd'];
+ const data = event.data['data'];
+ this.parse_message(cmd, data);
+ };
+ }
+
+ process_notify() {
+ Atomics.add(this.notifier, 0, 1);
+ Atomics.notify(this.notifier, 0);
+ }
+
+ parse_message(p_cmd, p_data) {
+ if (p_cmd == "start" && p_data) {
+ const state = p_data[0];
+ let idx = 0;
+ this.lock = state.subarray(idx, ++idx);
+ this.notifier = state.subarray(idx, ++idx);
+ const avail_in = state.subarray(idx, ++idx);
+ const avail_out = state.subarray(idx, ++idx);
+ this.input = new RingBuffer(p_data[1], avail_in);
+ this.output = new RingBuffer(p_data[2], avail_out);
+ } else if (p_cmd == "stop") {
+ this.runing = false;
+ this.output = null;
+ this.input = null;
+ }
+ }
+
+ array_has_data(arr) {
+ return arr.length && arr[0].length && arr[0][0].length;
+ }
+
+ process(inputs, outputs, parameters) {
+ if (!this.running) {
+ return false; // Stop processing.
+ }
+ if (this.output === null) {
+ return true; // Not ready yet, keep processing.
+ }
+ const process_input = this.array_has_data(inputs);
+ if (process_input) {
+ const input = inputs[0];
+ const chunk = input[0].length * input.length;
+ if (this.input_buffer.length != chunk) {
+ this.input_buffer = new Float32Array(chunk);
+ }
+ if (this.input.space_left() >= chunk) {
+ this.write_input(this.input_buffer, input);
+ this.input.write(this.input_buffer);
+ } else {
+ this.port.postMessage("Input buffer is full! Skipping input frame.");
+ }
+ }
+ const process_output = this.array_has_data(outputs);
+ if (process_output) {
+ const output = outputs[0];
+ const chunk = output[0].length * output.length;
+ if (this.output_buffer.length != chunk) {
+ this.output_buffer = new Float32Array(chunk)
+ }
+ if (this.output.data_left() >= chunk) {
+ this.output.read(this.output_buffer);
+ this.write_output(output, this.output_buffer);
+ } else {
+ this.port.postMessage("Output buffer has not enough frames! Skipping output frame.");
+ }
+ }
+ this.process_notify();
+ return true;
+ }
+
+ write_output(dest, source) {
+ const channels = dest.length;
+ for (let ch = 0; ch < channels; ch++) {
+ for (let sample = 0; sample < dest[ch].length; sample++) {
+ dest[ch][sample] = source[sample * channels + ch];
+ }
+ }
+ }
+
+ write_input(dest, source) {
+ const channels = source.length;
+ for (let ch = 0; ch < channels; ch++) {
+ for (let sample = 0; sample < source[ch].length; sample++) {
+ dest[sample * channels + ch] = source[ch][sample];
+ }
+ }
+ }
+}
+
+registerProcessor('godot-processor', GodotProcessor);
diff --git a/platform/javascript/native/library_godot_audio.js b/platform/javascript/native/library_godot_audio.js
index 4e7f3e2af5..846359b8b2 100644
--- a/platform/javascript/native/library_godot_audio.js
+++ b/platform/javascript/native/library_godot_audio.js
@@ -27,13 +27,109 @@
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-var GodotAudio = {
- $GodotAudio: {
+const GodotAudio = {
+ $GodotAudio__deps: ['$GodotOS'],
+ $GodotAudio: {
ctx: null,
input: null,
- script: null,
+ driver: null,
+ interval: 0,
+
+ init: function(mix_rate, latency, onstatechange, onlatencyupdate) {
+ const ctx = new (window.AudioContext || window.webkitAudioContext)({
+ sampleRate: mix_rate,
+ // latencyHint: latency / 1000 // Do not specify, leave 'interactive' for good performance.
+ });
+ GodotAudio.ctx = ctx;
+ onstatechange(ctx.state); // Immeditately notify state.
+ ctx.onstatechange = function() {
+ let state = 0;
+ switch (ctx.state) {
+ case 'suspended':
+ state = 0;
+ break;
+ case 'running':
+ state = 1;
+ break;
+ case 'closed':
+ state = 2;
+ break;
+ }
+ onstatechange(state);
+ }
+ // Update computed latency
+ GodotAudio.interval = setInterval(function() {
+ let latency = 0;
+ if (ctx.baseLatency) {
+ latency += GodotAudio.ctx.baseLatency;
+ }
+ if (ctx.outputLatency) {
+ latency += GodotAudio.ctx.outputLatency;
+ }
+ onlatencyupdate(latency);
+ }, 1000);
+ GodotOS.atexit(GodotAudio.close_async);
+ return ctx.destination.channelCount;
+ },
+
+ create_input: function(callback) {
+ if (GodotAudio.input) {
+ return; // Already started.
+ }
+ function gotMediaInput(stream) {
+ GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream);
+ callback(GodotAudio.input)
+ }
+ if (navigator.mediaDevices.getUserMedia) {
+ navigator.mediaDevices.getUserMedia({
+ "audio": true
+ }).then(gotMediaInput, function(e) { out(e) });
+ } else {
+ if (!navigator.getUserMedia) {
+ navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
+ }
+ navigator.getUserMedia({
+ "audio": true
+ }, gotMediaInput, function(e) { out(e) });
+ }
+ },
+
+ close_async: function(resolve, reject) {
+ const ctx = GodotAudio.ctx;
+ GodotAudio.ctx = null;
+ // Audio was not initialized.
+ if (!ctx) {
+ resolve();
+ return;
+ }
+ // Remove latency callback
+ if (GodotAudio.interval) {
+ clearInterval(GodotAudio.interval);
+ GodotAudio.interval = 0;
+ }
+ // Disconnect input, if it was started.
+ if (GodotAudio.input) {
+ GodotAudio.input.disconnect();
+ GodotAudio.input = null;
+ }
+ // Disconnect output
+ let closed = Promise.resolve();
+ if (GodotAudio.driver) {
+ closed = GodotAudio.driver.close();
+ }
+ closed.then(function() {
+ return ctx.close();
+ }).then(function() {
+ ctx.onstatechange = null;
+ resolve();
+ }).catch(function(e) {
+ ctx.onstatechange = null;
+ console.error("Error closing AudioContext", e);
+ resolve();
+ });
+ },
},
godot_audio_is_available__proxy: 'sync',
@@ -44,50 +140,10 @@ var GodotAudio = {
return 1;
},
- godot_audio_init: function(mix_rate, latency) {
- GodotAudio.ctx = new (window.AudioContext || window.webkitAudioContext)({
- sampleRate: mix_rate,
- // latencyHint: latency / 1000 // Do not specify, leave 'interactive' for good performance.
- });
- return GodotAudio.ctx.destination.channelCount;
- },
-
- godot_audio_create_processor: function(buffer_length, channel_count) {
- GodotAudio.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count);
- GodotAudio.script.connect(GodotAudio.ctx.destination);
- return GodotAudio.script.bufferSize;
- },
-
- godot_audio_start: function(buffer_ptr) {
- var audioDriverProcessStart = cwrap('audio_driver_process_start');
- var audioDriverProcessEnd = cwrap('audio_driver_process_end');
- var audioDriverProcessCapture = cwrap('audio_driver_process_capture', null, ['number']);
- GodotAudio.script.onaudioprocess = function(audioProcessingEvent) {
- audioDriverProcessStart();
-
- var input = audioProcessingEvent.inputBuffer;
- var output = audioProcessingEvent.outputBuffer;
- var internalBuffer = HEAPF32.subarray(
- buffer_ptr / HEAPF32.BYTES_PER_ELEMENT,
- buffer_ptr / HEAPF32.BYTES_PER_ELEMENT + output.length * output.numberOfChannels);
- for (var channel = 0; channel < output.numberOfChannels; channel++) {
- var outputData = output.getChannelData(channel);
- // Loop through samples.
- for (var sample = 0; sample < outputData.length; sample++) {
- outputData[sample] = internalBuffer[sample * output.numberOfChannels + channel];
- }
- }
-
- if (GodotAudio.input) {
- var inputDataL = input.getChannelData(0);
- var inputDataR = input.getChannelData(1);
- for (var i = 0; i < inputDataL.length; i++) {
- audioDriverProcessCapture(inputDataL[i]);
- audioDriverProcessCapture(inputDataR[i]);
- }
- }
- audioDriverProcessEnd();
- };
+ godot_audio_init: function(p_mix_rate, p_latency, p_state_change, p_latency_update) {
+ const statechange = GodotOS.get_func(p_state_change);
+ const latencyupdate = GodotOS.get_func(p_latency_update);
+ return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate);
},
godot_audio_resume: function() {
@@ -96,72 +152,22 @@ var GodotAudio = {
}
},
- godot_audio_finish_async: function() {
- Module.async_finish.push(new Promise(function(accept, reject) {
- if (!GodotAudio.ctx) {
- setTimeout(accept, 0);
- } else {
- if (GodotAudio.script) {
- GodotAudio.script.disconnect();
- GodotAudio.script = null;
- }
- if (GodotAudio.input) {
- GodotAudio.input.disconnect();
- GodotAudio.input = null;
- }
- GodotAudio.ctx.close().then(function() {
- accept();
- }).catch(function(e) {
- accept();
- });
- GodotAudio.ctx = null;
- }
- }));
- },
-
- godot_audio_get_latency__proxy: 'sync',
- godot_audio_get_latency: function() {
- var latency = 0;
- if (GodotAudio.ctx) {
- if (GodotAudio.ctx.baseLatency) {
- latency += GodotAudio.ctx.baseLatency;
- }
- if (GodotAudio.ctx.outputLatency) {
- latency += GodotAudio.ctx.outputLatency;
- }
- }
- return latency;
- },
-
godot_audio_capture_start__proxy: 'sync',
godot_audio_capture_start: function() {
if (GodotAudio.input) {
return; // Already started.
}
- function gotMediaInput(stream) {
- GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream);
- GodotAudio.input.connect(GodotAudio.script);
- }
-
- function gotMediaInputError(e) {
- out(e);
- }
-
- if (navigator.mediaDevices.getUserMedia) {
- navigator.mediaDevices.getUserMedia({"audio": true}).then(gotMediaInput, gotMediaInputError);
- } else {
- if (!navigator.getUserMedia)
- navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
- navigator.getUserMedia({"audio": true}, gotMediaInput, gotMediaInputError);
- }
+ GodotAudio.create_input(function(input) {
+ input.connect(GodotAudio.driver.get_node());
+ });
},
godot_audio_capture_stop__proxy: 'sync',
godot_audio_capture_stop: function() {
if (GodotAudio.input) {
- const tracks = GodotAudio.input.mediaStream.getTracks();
- for (var i = 0; i < tracks.length; i++) {
- tracks[i].stop();
+ const tracks = GodotAudio.input['mediaStream']['getTracks']();
+ for (let i = 0; i < tracks.length; i++) {
+ tracks[i]['stop']();
}
GodotAudio.input.disconnect();
GodotAudio.input = null;
@@ -171,3 +177,165 @@ var GodotAudio = {
autoAddDeps(GodotAudio, "$GodotAudio");
mergeInto(LibraryManager.library, GodotAudio);
+
+/**
+ * The AudioWorklet API driver, used when threads are available.
+ */
+const GodotAudioWorklet = {
+
+ $GodotAudioWorklet__deps: ['$GodotAudio'],
+ $GodotAudioWorklet: {
+ promise: null,
+ worklet: null,
+
+ create: function(channels) {
+ const path = Module['locateFile']('godot.audio.worklet.js');
+ GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet.addModule(path).then(function() {
+ GodotAudioWorklet.worklet = new AudioWorkletNode(
+ GodotAudio.ctx,
+ 'godot-processor',
+ {
+ 'outputChannelCount': [channels]
+ }
+ );
+ return Promise.resolve();
+ });
+ GodotAudio.driver = GodotAudioWorklet;
+ },
+
+ start: function(in_buf, out_buf, state) {
+ GodotAudioWorklet.promise.then(function() {
+ const node = GodotAudioWorklet.worklet;
+ node.connect(GodotAudio.ctx.destination);
+ node.port.postMessage({
+ 'cmd': 'start',
+ 'data': [state, in_buf, out_buf],
+ });
+ node.port.onmessage = function(event) {
+ console.error(event.data);
+ };
+ });
+ },
+
+ get_node: function() {
+ return GodotAudioWorklet.worklet;
+ },
+
+ close: function() {
+ return new Promise(function(resolve, reject) {
+ GodotAudioWorklet.promise.then(function() {
+ GodotAudioWorklet.worklet.port.postMessage({
+ 'cmd': 'stop',
+ 'data': null,
+ });
+ GodotAudioWorklet.worklet.disconnect();
+ GodotAudioWorklet.worklet = null;
+ GodotAudioWorklet.promise = null;
+ resolve();
+ });
+ });
+ },
+ },
+
+ godot_audio_worklet_create: function(channels) {
+ GodotAudioWorklet.create(channels);
+ },
+
+ godot_audio_worklet_start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) {
+ const out_buffer = GodotOS.heapSub(HEAPF32, p_out_buf, p_out_size);
+ const in_buffer = GodotOS.heapSub(HEAPF32, p_in_buf, p_in_size);
+ const state = GodotOS.heapSub(HEAP32, p_state, 4);
+ GodotAudioWorklet.start(in_buffer, out_buffer, state);
+ },
+
+ godot_audio_worklet_state_wait: function(p_state, p_idx, p_expected, p_timeout) {
+ Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout);
+ return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
+ },
+
+ godot_audio_worklet_state_add: function(p_state, p_idx, p_value) {
+ return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value);
+ },
+
+ godot_audio_worklet_state_get: function(p_state, p_idx) {
+ return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
+ },
+};
+
+autoAddDeps(GodotAudioWorklet, "$GodotAudioWorklet");
+mergeInto(LibraryManager.library, GodotAudioWorklet);
+
+/*
+ * The deprecated ScriptProcessorNode API, used when threads are disabled.
+ */
+const GodotAudioScript = {
+
+ $GodotAudioScript__deps: ['$GodotAudio'],
+ $GodotAudioScript: {
+ script: null,
+
+ create: function(buffer_length, channel_count) {
+ GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count);
+ GodotAudio.driver = GodotAudioScript;
+ return GodotAudioScript.script.bufferSize;
+ },
+
+ start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) {
+ GodotAudioScript.script.onaudioprocess = function(event) {
+ // Read input
+ const inb = GodotOS.heapSub(HEAPF32, p_in_buf, p_in_size);
+ const input = event.inputBuffer;
+ if (GodotAudio.input) {
+ const inlen = input.getChannelData(0).length;
+ for (let ch = 0; ch < 2; ch++) {
+ const data = input.getChannelData(ch);
+ for (let s = 0; s < inlen; s++) {
+ inb[s * 2 + ch] = data[s];
+ }
+ }
+ }
+
+ // Let Godot process the input/output.
+ onprocess();
+
+ // Write the output.
+ const outb = GodotOS.heapSub(HEAPF32, p_out_buf, p_out_size);
+ const output = event.outputBuffer;
+ const channels = output.numberOfChannels;
+ for (let ch = 0; ch < channels; ch++) {
+ const data = output.getChannelData(ch);
+ // Loop through samples and assign computed values.
+ for (let sample = 0; sample < data.length; sample++) {
+ data[sample] = outb[sample * channels + ch];
+ }
+ }
+ };
+ GodotAudioScript.script.connect(GodotAudio.ctx.destination);
+ },
+
+ get_node: function() {
+ return GodotAudioScript.script;
+ },
+
+ close: function() {
+ return new Promise(function(resolve, reject) {
+ GodotAudioScript.script.disconnect();
+ GodotAudioScript.script.onaudioprocess = null;
+ GodotAudioScript.script = null;
+ resolve();
+ });
+ },
+ },
+
+ godot_audio_script_create: function(buffer_length, channel_count) {
+ return GodotAudioScript.create(buffer_length, channel_count);
+ },
+
+ godot_audio_script_start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) {
+ const onprocess = GodotOS.get_func(p_cb);
+ GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess);
+ },
+};
+
+autoAddDeps(GodotAudioScript, "$GodotAudioScript");
+mergeInto(LibraryManager.library, GodotAudioScript);
diff --git a/platform/javascript/native/library_godot_display.js b/platform/javascript/native/library_godot_display.js
new file mode 100644
index 0000000000..490b9181d0
--- /dev/null
+++ b/platform/javascript/native/library_godot_display.js
@@ -0,0 +1,478 @@
+/*************************************************************************/
+/* library_godot_display.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+/*
+ * Display Server listeners.
+ * Keeps track of registered event listeners so it can remove them on shutdown.
+ */
+const GodotDisplayListeners = {
+ $GodotDisplayListeners__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayListeners.clear(); resolve(); });',
+ $GodotDisplayListeners: {
+ handlers: [],
+
+ has: function(target, event, method, capture) {
+ return GodotDisplayListeners.handlers.findIndex(function(e) {
+ return e.target === target && e.event === event && e.method === method && e.capture == capture;
+ }) !== -1;
+ },
+
+ add: function(target, event, method, capture) {
+ if (GodotDisplayListeners.has(target, event, method, capture)) {
+ return;
+ }
+ function Handler(target, event, method, capture) {
+ this.target = target;
+ this.event = event;
+ this.method = method;
+ this.capture = capture;
+ };
+ GodotDisplayListeners.handlers.push(new Handler(target, event, method, capture));
+ target.addEventListener(event, method, capture);
+ },
+
+ clear: function() {
+ GodotDisplayListeners.handlers.forEach(function(h) {
+ h.target.removeEventListener(h.event, h.method, h.capture);
+ });
+ GodotDisplayListeners.handlers.length = 0;
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotDisplayListeners);
+
+/*
+ * Drag and drop handler.
+ * This is pretty big, but basically detect dropped files on GodotConfig.canvas,
+ * process them one by one (recursively for directories), and copies them to
+ * the temporary FS path '/tmp/drop-[random]/' so it can be emitted as a godot
+ * event (that requires a string array of paths).
+ *
+ * NOTE: The temporary files are removed after the callback. This means that
+ * deferred callbacks won't be able to access the files.
+ */
+const GodotDisplayDragDrop = {
+
+ $GodotDisplayDragDrop__deps: ['$FS', '$GodotFS'],
+ $GodotDisplayDragDrop: {
+ promises: [],
+ pending_files: [],
+
+ add_entry: function(entry) {
+ if (entry.isDirectory) {
+ GodotDisplayDragDrop.add_dir(entry);
+ } else if (entry.isFile) {
+ GodotDisplayDragDrop.add_file(entry);
+ } else {
+ console.error("Unrecognized entry...", entry);
+ }
+ },
+
+ add_dir: function(entry) {
+ GodotDisplayDragDrop.promises.push(new Promise(function(resolve, reject) {
+ const reader = entry.createReader();
+ reader.readEntries(function(entries) {
+ for (let i = 0; i < entries.length; i++) {
+ GodotDisplayDragDrop.add_entry(entries[i]);
+ }
+ resolve();
+ });
+ }));
+ },
+
+ add_file: function(entry) {
+ GodotDisplayDragDrop.promises.push(new Promise(function(resolve, reject) {
+ entry.file(function(file) {
+ const reader = new FileReader();
+ reader.onload = function() {
+ const f = {
+ "path": file.relativePath || file.webkitRelativePath,
+ "name": file.name,
+ "type": file.type,
+ "size": file.size,
+ "data": reader.result
+ };
+ if (!f['path']) {
+ f['path'] = f['name'];
+ }
+ GodotDisplayDragDrop.pending_files.push(f);
+ resolve()
+ };
+ reader.onerror = function() {
+ console.log("Error reading file");
+ reject();
+ }
+ reader.readAsArrayBuffer(file);
+ }, function(err) {
+ console.log("Error!");
+ reject();
+ });
+ }));
+ },
+
+ process: function(resolve, reject) {
+ if (GodotDisplayDragDrop.promises.length == 0) {
+ resolve();
+ return;
+ }
+ GodotDisplayDragDrop.promises.pop().then(function() {
+ setTimeout(function() {
+ GodotDisplayDragDrop.process(resolve, reject);
+ }, 0);
+ });
+ },
+
+ _process_event: function(ev, callback) {
+ ev.preventDefault();
+ if (ev.dataTransfer.items) {
+ // Use DataTransferItemList interface to access the file(s)
+ for (let i = 0; i < ev.dataTransfer.items.length; i++) {
+ const item = ev.dataTransfer.items[i];
+ let entry = null;
+ if ("getAsEntry" in item) {
+ entry = item.getAsEntry();
+ } else if ("webkitGetAsEntry" in item) {
+ entry = item.webkitGetAsEntry();
+ }
+ if (entry) {
+ GodotDisplayDragDrop.add_entry(entry);
+ }
+ }
+ } else {
+ console.error("File upload not supported");
+ }
+ new Promise(GodotDisplayDragDrop.process).then(function() {
+ const DROP = "/tmp/drop-" + parseInt(Math.random() * Math.pow(2, 31)) + "/";
+ const drops = [];
+ const files = [];
+ FS.mkdir(DROP);
+ GodotDisplayDragDrop.pending_files.forEach((elem) => {
+ const path = elem['path'];
+ GodotFS.copy_to_fs(DROP + path, elem['data']);
+ let idx = path.indexOf("/");
+ if (idx == -1) {
+ // Root file
+ drops.push(DROP + path);
+ } else {
+ // Subdir
+ const sub = path.substr(0, idx);
+ idx = sub.indexOf("/");
+ if (idx < 0 && drops.indexOf(DROP + sub) == -1) {
+ drops.push(DROP + sub);
+ }
+ }
+ files.push(DROP + path);
+ });
+ GodotDisplayDragDrop.promises = [];
+ GodotDisplayDragDrop.pending_files = [];
+ callback(drops);
+ const dirs = [DROP.substr(0, DROP.length -1)];
+ // Remove temporary files
+ files.forEach(function (file) {
+ FS.unlink(file);
+ let dir = file.replace(DROP, "");
+ let idx = dir.lastIndexOf("/");
+ while (idx > 0) {
+ dir = dir.substr(0, idx);
+ if (dirs.indexOf(DROP + dir) == -1) {
+ dirs.push(DROP + dir);
+ }
+ idx = dir.lastIndexOf("/");
+ }
+ });
+ // Remove dirs.
+ dirs.sort(function(a, b) {
+ const al = (a.match(/\//g) || []).length;
+ const bl = (b.match(/\//g) || []).length;
+ if (al > bl)
+ return -1;
+ else if (al < bl)
+ return 1;
+ return 0;
+ }).forEach(function(dir) {
+ FS.rmdir(dir);
+ });
+ });
+ },
+
+ handler: function(callback) {
+ return function(ev) {
+ GodotDisplayDragDrop._process_event(ev, callback);
+ };
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotDisplayDragDrop);
+
+/*
+ * Display server cursor helper.
+ * Keeps track of cursor status and custom shapes.
+ */
+const GodotDisplayCursor = {
+ $GodotDisplayCursor__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayCursor.clear(); resolve(); });',
+ $GodotDisplayCursor__deps: ['$GodotConfig', '$GodotOS'],
+ $GodotDisplayCursor: {
+ shape: 'auto',
+ visible: true,
+ cursors: {},
+ set_style: function(style) {
+ GodotConfig.canvas.style.cursor = style;
+ },
+ set_shape: function(shape) {
+ GodotDisplayCursor.shape = shape;
+ let css = shape;
+ if (shape in GodotDisplayCursor.cursors) {
+ const c = GodotDisplayCursor.cursors[shape];
+ css = 'url("' + c.url + '") ' + c.x + ' ' + c.y + ', auto';
+ }
+ if (GodotDisplayCursor.visible) {
+ GodotDisplayCursor.set_style(css);
+ }
+ },
+ clear: function() {
+ GodotDisplayCursor.set_style('');
+ GodotDisplayCursor.shape = 'auto';
+ GodotDisplayCursor.visible = true;
+ Object.keys(GodotDisplayCursor.cursors).forEach(function(key) {
+ URL.revokeObjectURL(GodotDisplayCursor.cursors[key]);
+ delete GodotDisplayCursor.cursors[key];
+ });
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotDisplayCursor);
+
+/**
+ * Display server interface.
+ *
+ * Exposes all the functions needed by DisplayServer implementation.
+ */
+const GodotDisplay = {
+ $GodotDisplay__deps: ['$GodotConfig', '$GodotOS', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop'],
+ $GodotDisplay: {
+ window_icon: '',
+ },
+
+ godot_js_display_is_swap_ok_cancel: function() {
+ const win = (['Windows', 'Win64', 'Win32', 'WinCE']);
+ const plat = navigator.platform || "";
+ if (win.indexOf(plat) !== -1) {
+ return 1;
+ }
+ return 0;
+ },
+
+ godot_js_display_alert: function(p_text) {
+ window.alert(UTF8ToString(p_text));
+ },
+
+ godot_js_display_pixel_ratio_get: function() {
+ return window.devicePixelRatio || 1;
+ },
+
+ /*
+ * Canvas
+ */
+ godot_js_display_canvas_focus: function() {
+ GodotConfig.canvas.focus();
+ },
+
+ godot_js_display_canvas_is_focused: function() {
+ return document.activeElement == GodotConfig.canvas;
+ },
+
+ godot_js_display_canvas_bounding_rect_position_get: function(r_x, r_y) {
+ const brect = GodotConfig.canvas.getBoundingClientRect();
+ setValue(r_x, brect.x, 'i32');
+ setValue(r_y, brect.y, 'i32');
+ },
+
+ /*
+ * Touchscreen
+ */
+ godot_js_display_touchscreen_is_available: function() {
+ return 'ontouchstart' in window;
+ },
+
+ /*
+ * Clipboard
+ */
+ godot_js_display_clipboard_set: function(p_text) {
+ const text = UTF8ToString(p_text);
+ if (!navigator.clipboard || !navigator.clipboard.writeText) {
+ return 1;
+ }
+ navigator.clipboard.writeText(text).catch(function(e) {
+ // Setting OS clipboard is only possible from an input callback.
+ console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e);
+ });
+ return 0;
+ },
+
+ godot_js_display_clipboard_get_deps: ['$GodotOS'],
+ godot_js_display_clipboard_get: function(callback) {
+ const func = GodotOS.get_func(callback);
+ try {
+ navigator.clipboard.readText().then(function (result) {
+ const ptr = allocate(intArrayFromString(result), ALLOC_NORMAL);
+ func(ptr);
+ _free(ptr);
+ }).catch(function (e) {
+ // Fail graciously.
+ });
+ } catch (e) {
+ // Fail graciously.
+ }
+ },
+
+ /*
+ * Window
+ */
+ godot_js_display_window_request_fullscreen: function() {
+ const canvas = GodotConfig.canvas;
+ (canvas.requestFullscreen || canvas.msRequestFullscreen ||
+ canvas.mozRequestFullScreen || canvas.mozRequestFullscreen ||
+ canvas.webkitRequestFullscreen
+ ).call(canvas);
+ },
+
+ godot_js_display_window_title_set: function(p_data) {
+ document.title = UTF8ToString(p_data);
+ },
+
+ godot_js_display_window_icon_set: function(p_ptr, p_len) {
+ let link = document.getElementById('-gd-engine-icon');
+ if (link === null) {
+ link = document.createElement('link');
+ link.rel = 'icon';
+ link.id = '-gd-engine-icon';
+ document.head.appendChild(link);
+ }
+ const old_icon = GodotDisplay.window_icon;
+ const png = new Blob([GodotOS.heapCopy(HEAPU8, p_ptr, p_len)], { type: "image/png" });
+ GodotDisplay.window_icon = URL.createObjectURL(png);
+ link.href = GodotDisplay.window_icon;
+ if (old_icon) {
+ URL.revokeObjectURL(old_icon);
+ }
+ },
+
+ /*
+ * Cursor
+ */
+ godot_js_display_cursor_set_visible: function(p_visible) {
+ const visible = p_visible != 0;
+ if (visible == GodotDisplayCursor.visible) {
+ return;
+ }
+ GodotDisplayCursor.visible = visible;
+ if (visible) {
+ GodotDisplayCursor.set_shape(GodotDisplayCursor.shape);
+ } else {
+ GodotDisplayCursor.set_style('none');
+ }
+ },
+
+ godot_js_display_cursor_is_hidden: function() {
+ return !GodotDisplayCursor.visible;
+ },
+
+ godot_js_display_cursor_set_shape: function(p_string) {
+ GodotDisplayCursor.set_shape(UTF8ToString(p_string));
+ },
+
+ godot_js_display_cursor_set_custom_shape: function(p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) {
+ const shape = UTF8ToString(p_shape);
+ const old_shape = GodotDisplayCursor.cursors[shape];
+ if (p_len > 0) {
+ const png = new Blob([GodotOS.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
+ const url = URL.createObjectURL(png);
+ GodotDisplayCursor.cursors[shape] = {
+ url: url,
+ x: p_hotspot_x,
+ y: p_hotspot_y,
+ };
+ } else {
+ delete GodotDisplayCursor.cursors[shape];
+ }
+ if (shape == GodotDisplayCursor.shape) {
+ GodotDisplayCursor.set_shape(GodotDisplayCursor.shape);
+ }
+ if (old_shape) {
+ URL.revokeObjectURL(old_shape.url);
+ }
+ },
+
+ /*
+ * Listeners
+ */
+ godot_js_display_notification_cb: function(callback, p_enter, p_exit, p_in, p_out) {
+ const canvas = GodotConfig.canvas;
+ const func = GodotOS.get_func(callback);
+ const notif = [p_enter, p_exit, p_in, p_out];
+ ['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function(evt_name, idx) {
+ GodotDisplayListeners.add(canvas, evt_name, function() {
+ func.bind(null, notif[idx]);
+ }, true);
+ });
+ },
+
+ godot_js_display_paste_cb: function(callback) {
+ const func = GodotOS.get_func(callback);
+ GodotDisplayListeners.add(window, 'paste', function(evt) {
+ const text = evt.clipboardData.getData('text');
+ const ptr = allocate(intArrayFromString(text), ALLOC_NORMAL);
+ func(ptr);
+ _free(ptr);
+ }, false);
+ },
+
+ godot_js_display_drop_files_cb: function(callback) {
+ const func = GodotOS.get_func(callback)
+ const dropFiles = function(files) {
+ const args = files || [];
+ if (!args.length) {
+ return;
+ }
+ const argc = args.length;
+ const argv = GodotOS.allocStringArray(args);
+ func(argv, argc);
+ GodotOS.freeStringArray(argv, argc);
+ };
+ const canvas = GodotConfig.canvas;
+ GodotDisplayListeners.add(canvas, 'dragover', function(ev) {
+ // Prevent default behavior (which would try to open the file(s))
+ ev.preventDefault();
+ }, false);
+ GodotDisplayListeners.add(canvas, 'drop', GodotDisplayDragDrop.handler(dropFiles));
+ },
+};
+
+autoAddDeps(GodotDisplay, '$GodotDisplay');
+mergeInto(LibraryManager.library, GodotDisplay);
diff --git a/platform/javascript/native/library_godot_editor_tools.js b/platform/javascript/native/library_godot_editor_tools.js
new file mode 100644
index 0000000000..bd62bbf4e1
--- /dev/null
+++ b/platform/javascript/native/library_godot_editor_tools.js
@@ -0,0 +1,57 @@
+/*************************************************************************/
+/* library_godot_editor_tools.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+const GodotEditorTools = {
+
+ godot_js_editor_download_file__deps: ['$FS'],
+ godot_js_editor_download_file: function(p_path, p_name, p_mime) {
+ const path = UTF8ToString(p_path);
+ const name = UTF8ToString(p_name);
+ const mime = UTF8ToString(p_mime);
+ const size = FS.stat(path)['size'];
+ const buf = new Uint8Array(size);
+ const fd = FS.open(path, 'r');
+ FS.read(fd, buf, 0, size);
+ FS.close(fd);
+ FS.unlink(path);
+ const blob = new Blob([buf], { type: mime });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = name;
+ a.style.display = 'none';
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ window.URL.revokeObjectURL(url);
+ },
+};
+
+mergeInto(LibraryManager.library, GodotEditorTools);
diff --git a/platform/javascript/native/library_godot_eval.js b/platform/javascript/native/library_godot_eval.js
new file mode 100644
index 0000000000..e83c61dd9d
--- /dev/null
+++ b/platform/javascript/native/library_godot_eval.js
@@ -0,0 +1,87 @@
+/*************************************************************************/
+/* library_godot_eval.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+const GodotEval = {
+
+ godot_js_eval__deps: ['$GodotOS'],
+ godot_js_eval: function(p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) {
+ const js_code = UTF8ToString(p_js);
+ let eval_ret = null;
+ try {
+ if (p_use_global_ctx) {
+ // indirect eval call grants global execution context
+ const global_eval = eval;
+ eval_ret = global_eval(js_code);
+ } else {
+ eval_ret = eval(js_code);
+ }
+ } catch (e) {
+ err(e);
+ }
+
+ switch (typeof eval_ret) {
+
+ case 'boolean':
+ setValue(p_union_ptr, eval_ret, 'i32');
+ return 1; // BOOL
+
+ case 'number':
+ setValue(p_union_ptr, eval_ret, 'double');
+ return 3; // REAL
+
+ case 'string':
+ let array_ptr = GodotOS.allocString(eval_ret);
+ setValue(p_union_ptr, array_ptr , '*');
+ return 4; // STRING
+
+ case 'object':
+ if (eval_ret === null) {
+ break;
+ }
+
+ if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) {
+ eval_ret = new Uint8Array(eval_ret.buffer);
+ }
+ else if (eval_ret instanceof ArrayBuffer) {
+ eval_ret = new Uint8Array(eval_ret);
+ }
+ if (eval_ret instanceof Uint8Array) {
+ const func = GodotOS.get_func(p_callback);
+ const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length);
+ HEAPU8.set(eval_ret, bytes_ptr);
+ return 20; // POOL_BYTE_ARRAY
+ }
+ break;
+ }
+ return 0; // NIL
+ },
+}
+
+mergeInto(LibraryManager.library, GodotEval);
diff --git a/platform/javascript/native/library_godot_os.js b/platform/javascript/native/library_godot_os.js
new file mode 100644
index 0000000000..ed48280674
--- /dev/null
+++ b/platform/javascript/native/library_godot_os.js
@@ -0,0 +1,313 @@
+/*************************************************************************/
+/* library_godot_os.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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. */
+/*************************************************************************/
+
+const IDHandler = {
+ $IDHandler: {
+ _last_id: 0,
+ _references: {},
+
+ get: function(p_id) {
+ return IDHandler._references[p_id];
+ },
+
+ add: function(p_data) {
+ const id = ++IDHandler._last_id;
+ IDHandler._references[id] = p_data;
+ return id;
+ },
+
+ remove: function(p_id) {
+ delete IDHandler._references[p_id];
+ },
+ },
+};
+
+autoAddDeps(IDHandler, "$IDHandler");
+mergeInto(LibraryManager.library, IDHandler);
+
+const GodotConfig = {
+
+ $GodotConfig__postset: 'Module["initConfig"] = GodotConfig.init_config;',
+ $GodotConfig: {
+ canvas: null,
+ locale: "en",
+ resize_on_start: false,
+ on_execute: null,
+
+ init_config: function(p_opts) {
+ GodotConfig.resize_on_start = p_opts['resizeCanvasOnStart'] ? true : false;
+ GodotConfig.canvas = p_opts['canvas'];
+ GodotConfig.locale = p_opts['locale'] || GodotConfig.locale;
+ GodotConfig.on_execute = p_opts['onExecute'];
+ // This is called by emscripten, even if undocumented.
+ Module['onExit'] = p_opts['onExit'];
+ },
+ },
+
+ godot_js_config_canvas_id_get: function(p_ptr, p_ptr_max) {
+ stringToUTF8('#' + GodotConfig.canvas.id, p_ptr, p_ptr_max);
+ },
+
+ godot_js_config_locale_get: function(p_ptr, p_ptr_max) {
+ stringToUTF8(GodotConfig.locale, p_ptr, p_ptr_max);
+ },
+
+ godot_js_config_is_resize_on_start: function() {
+ return GodotConfig.resize_on_start ? 1 : 0;
+ },
+};
+
+autoAddDeps(GodotConfig, '$GodotConfig');
+mergeInto(LibraryManager.library, GodotConfig);
+
+const GodotFS = {
+ $GodotFS__deps: ['$FS', '$IDBFS'],
+ $GodotFS__postset: [
+ 'Module["initFS"] = GodotFS.init;',
+ 'Module["deinitFS"] = GodotFS.deinit;',
+ 'Module["copyToFS"] = GodotFS.copy_to_fs;',
+ ].join(''),
+ $GodotFS: {
+ _idbfs: false,
+ _syncing: false,
+ _mount_points: [],
+
+ is_persistent: function() {
+ return GodotFS._idbfs ? 1 : 0;
+ },
+
+ // Initialize godot file system, setting up persistent paths.
+ // Returns a promise that resolves when the FS is ready.
+ // We keep track of mount_points, so that we can properly close the IDBFS
+ // since emscripten is not doing it by itself. (emscripten GH#12516).
+ init: function(persistentPaths) {
+ GodotFS._idbfs = false;
+ if (!Array.isArray(persistentPaths)) {
+ return Promise.reject(new Error('Persistent paths must be an array'));
+ }
+ if (!persistentPaths.length) {
+ return Promise.resolve();
+ }
+ GodotFS._mount_points = persistentPaths.slice();
+
+ function createRecursive(dir) {
+ try {
+ FS.stat(dir);
+ } catch (e) {
+ if (e.errno !== ERRNO_CODES.ENOENT) {
+ throw e;
+ }
+ FS.mkdirTree(dir);
+ }
+ }
+
+ GodotFS._mount_points.forEach(function(path) {
+ createRecursive(path);
+ FS.mount(IDBFS, {}, path);
+ });
+ return new Promise(function(resolve, reject) {
+ FS.syncfs(true, function(err) {
+ if (err) {
+ GodotFS._mount_points = [];
+ GodotFS._idbfs = false;
+ console.log("IndexedDB not available: " + err.message);
+ } else {
+ GodotFS._idbfs = true;
+ }
+ resolve(err);
+ });
+ });
+ },
+
+ // Deinit godot file system, making sure to unmount file systems, and close IDBFS(s).
+ deinit: function() {
+ GodotFS._mount_points.forEach(function(path) {
+ try {
+ FS.unmount(path);
+ } catch (e) {
+ console.log("Already unmounted", e);
+ }
+ if (GodotFS._idbfs && IDBFS.dbs[path]) {
+ IDBFS.dbs[path].close();
+ delete IDBFS.dbs[path];
+ }
+ });
+ GodotFS._mount_points = [];
+ GodotFS._idbfs = false;
+ GodotFS._syncing = false;
+ },
+
+ sync: function() {
+ if (GodotFS._syncing) {
+ err('Already syncing!');
+ return Promise.resolve();
+ }
+ GodotFS._syncing = true;
+ return new Promise(function (resolve, reject) {
+ FS.syncfs(false, function(error) {
+ if (error) {
+ err('Failed to save IDB file system: ' + error.message);
+ }
+ GodotFS._syncing = false;
+ resolve(error);
+ });
+ });
+ },
+
+ // Copies a buffer to the internal file system. Creating directories recursively.
+ copy_to_fs: function(path, buffer) {
+ const idx = path.lastIndexOf("/");
+ let dir = "/";
+ if (idx > 0) {
+ dir = path.slice(0, idx);
+ }
+ try {
+ FS.stat(dir);
+ } catch (e) {
+ if (e.errno !== ERRNO_CODES.ENOENT) {
+ throw e;
+ }
+ FS.mkdirTree(dir);
+ }
+ FS.writeFile(path, new Uint8Array(buffer), {'flags': 'wx+'});
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotFS);
+
+const GodotOS = {
+ $GodotOS__deps: ['$GodotFS'],
+ $GodotOS__postset: [
+ 'Module["request_quit"] = function() { GodotOS.request_quit() };',
+ 'GodotOS._fs_sync_promise = Promise.resolve();',
+ ].join(''),
+ $GodotOS: {
+
+ request_quit: function() {},
+ _async_cbs: [],
+ _fs_sync_promise: null,
+
+ get_func: function(ptr) {
+ return wasmTable.get(ptr);
+ },
+
+ atexit: function(p_promise_cb) {
+ GodotOS._async_cbs.push(p_promise_cb);
+ },
+
+ finish_async: function(callback) {
+ GodotOS._fs_sync_promise.then(function(err) {
+ const promises = [];
+ GodotOS._async_cbs.forEach(function(cb) {
+ promises.push(new Promise(cb));
+ });
+ return Promise.all(promises);
+ }).then(function() {
+ return GodotFS.sync(); // Final FS sync.
+ }).then(function(err) {
+ // Always deferred.
+ setTimeout(function() {
+ callback();
+ }, 0);
+ });
+ },
+
+ allocString: function(p_str) {
+ const length = lengthBytesUTF8(p_str)+1;
+ const c_str = _malloc(length);
+ stringToUTF8(p_str, c_str, length);
+ return c_str;
+ },
+
+ allocStringArray: function(strings) {
+ const size = strings.length;
+ const c_ptr = _malloc(size * 4);
+ for (let i = 0; i < size; i++) {
+ HEAP32[(c_ptr >> 2) + i] = GodotOS.allocString(strings[i]);
+ }
+ return c_ptr;
+ },
+
+ freeStringArray: function(c_ptr, size) {
+ for (let i = 0; i < size; i++) {
+ _free(HEAP32[(c_ptr >> 2) + i]);
+ }
+ _free(c_ptr);
+ },
+
+ heapSub: function(heap, ptr, size) {
+ const bytes = heap.BYTES_PER_ELEMENT;
+ return heap.subarray(ptr / bytes, ptr / bytes + size);
+ },
+
+ heapCopy: function(heap, ptr, size) {
+ const bytes = heap.BYTES_PER_ELEMENT;
+ return heap.slice(ptr / bytes, ptr / bytes + size);
+ },
+ },
+
+ godot_js_os_finish_async: function(p_callback) {
+ const func = GodotOS.get_func(p_callback);
+ GodotOS.finish_async(func);
+ },
+
+ godot_js_os_request_quit_cb: function(p_callback) {
+ GodotOS.request_quit = GodotOS.get_func(p_callback);
+ },
+
+ godot_js_os_fs_is_persistent: function() {
+ return GodotFS.is_persistent();
+ },
+
+ godot_js_os_fs_sync: function(callback) {
+ const func = GodotOS.get_func(callback);
+ GodotOS._fs_sync_promise = GodotFS.sync();
+ GodotOS._fs_sync_promise.then(function(err) {
+ func();
+ });
+ },
+
+ godot_js_os_execute: function(p_json) {
+ const json_args = UTF8ToString(p_json);
+ const args = JSON.parse(json_args);
+ if (GodotConfig.on_execute) {
+ GodotConfig.on_execute(args);
+ return 0;
+ }
+ return 1;
+ },
+
+ godot_js_os_shell_open: function(p_uri) {
+ window.open(UTF8ToString(p_uri), '_blank');
+ },
+};
+
+autoAddDeps(GodotOS, '$GodotOS');
+mergeInto(LibraryManager.library, GodotOS);
diff --git a/platform/javascript/native/utils.js b/platform/javascript/native/utils.js
deleted file mode 100644
index 8d0beba454..0000000000
--- a/platform/javascript/native/utils.js
+++ /dev/null
@@ -1,292 +0,0 @@
-/*************************************************************************/
-/* utils.js */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 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. */
-/*************************************************************************/
-
-Module['initFS'] = function(persistentPaths) {
- Module.mount_points = ['/userfs'].concat(persistentPaths);
-
- function createRecursive(dir) {
- try {
- FS.stat(dir);
- } catch (e) {
- if (e.errno !== ERRNO_CODES.ENOENT) {
- throw e;
- }
- FS.mkdirTree(dir);
- }
- }
-
- Module.mount_points.forEach(function(path) {
- createRecursive(path);
- FS.mount(IDBFS, {}, path);
- });
- return new Promise(function(resolve, reject) {
- FS.syncfs(true, function(err) {
- if (err) {
- Module.mount_points = [];
- Module.idbfs = false;
- console.log("IndexedDB not available: " + err.message);
- } else {
- Module.idbfs = true;
- }
- resolve(err);
- });
- });
-};
-
-Module['deinitFS'] = function() {
- Module.mount_points.forEach(function(path) {
- try {
- FS.unmount(path);
- } catch (e) {
- console.log("Already unmounted", e);
- }
- if (Module.idbfs && IDBFS.dbs[path]) {
- IDBFS.dbs[path].close();
- delete IDBFS.dbs[path];
- }
- });
- Module.mount_points = [];
-};
-
-Module['copyToFS'] = function(path, buffer) {
- var p = path.lastIndexOf("/");
- var dir = "/";
- if (p > 0) {
- dir = path.slice(0, path.lastIndexOf("/"));
- }
- try {
- FS.stat(dir);
- } catch (e) {
- if (e.errno !== ERRNO_CODES.ENOENT) {
- throw e;
- }
- FS.mkdirTree(dir);
- }
- // With memory growth, canOwn should be false.
- FS.writeFile(path, new Uint8Array(buffer), {'flags': 'wx+'});
-}
-
-Module.drop_handler = (function() {
- var upload = [];
- var uploadPromises = [];
- var uploadCallback = null;
-
- function readFilePromise(entry, path) {
- return new Promise(function(resolve, reject) {
- entry.file(function(file) {
- var reader = new FileReader();
- reader.onload = function() {
- var f = {
- "path": file.relativePath || file.webkitRelativePath,
- "name": file.name,
- "type": file.type,
- "size": file.size,
- "data": reader.result
- };
- if (!f['path'])
- f['path'] = f['name'];
- upload.push(f);
- resolve()
- };
- reader.onerror = function() {
- console.log("Error reading file");
- reject();
- }
-
- reader.readAsArrayBuffer(file);
-
- }, function(err) {
- console.log("Error!");
- reject();
- });
- });
- }
-
- function readDirectoryPromise(entry) {
- return new Promise(function(resolve, reject) {
- var reader = entry.createReader();
- reader.readEntries(function(entries) {
- for (var i = 0; i < entries.length; i++) {
- var ent = entries[i];
- if (ent.isDirectory) {
- uploadPromises.push(readDirectoryPromise(ent));
- } else if (ent.isFile) {
- uploadPromises.push(readFilePromise(ent));
- }
- }
- resolve();
- });
- });
- }
-
- function processUploadsPromises(resolve, reject) {
- if (uploadPromises.length == 0) {
- resolve();
- return;
- }
- uploadPromises.pop().then(function() {
- setTimeout(function() {
- processUploadsPromises(resolve, reject);
- //processUploadsPromises.bind(null, resolve, reject)
- }, 0);
- });
- }
-
- function dropFiles(files) {
- var args = files || [];
- var argc = args.length;
- var argv = stackAlloc((argc + 1) * 4);
- for (var i = 0; i < argc; i++) {
- HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i]);
- }
- HEAP32[(argv >> 2) + argc] = 0;
- // Defined in display_server_javascript.cpp
- ccall('_drop_files_callback', 'void', ['number', 'number'], [argv, argc]);
- }
-
- return function(ev) {
- ev.preventDefault();
- if (ev.dataTransfer.items) {
- // Use DataTransferItemList interface to access the file(s)
- for (var i = 0; i < ev.dataTransfer.items.length; i++) {
- const item = ev.dataTransfer.items[i];
- var entry = null;
- if ("getAsEntry" in item) {
- entry = item.getAsEntry();
- } else if ("webkitGetAsEntry" in item) {
- entry = item.webkitGetAsEntry();
- }
- if (!entry) {
- console.error("File upload not supported");
- } else if (entry.isDirectory) {
- uploadPromises.push(readDirectoryPromise(entry));
- } else if (entry.isFile) {
- uploadPromises.push(readFilePromise(entry));
- } else {
- console.error("Unrecognized entry...", entry);
- }
- }
- } else {
- console.error("File upload not supported");
- }
- uploadCallback = new Promise(processUploadsPromises).then(function() {
- const DROP = "/tmp/drop-" + parseInt(Math.random() * Math.pow(2, 31)) + "/";
- var drops = [];
- var files = [];
- upload.forEach((elem) => {
- var path = elem['path'];
- Module['copyToFS'](DROP + path, elem['data']);
- var idx = path.indexOf("/");
- if (idx == -1) {
- // Root file
- drops.push(DROP + path);
- } else {
- // Subdir
- var sub = path.substr(0, idx);
- idx = sub.indexOf("/");
- if (idx < 0 && drops.indexOf(DROP + sub) == -1) {
- drops.push(DROP + sub);
- }
- }
- files.push(DROP + path);
- });
- uploadPromises = [];
- upload = [];
- dropFiles(drops);
- var dirs = [DROP.substr(0, DROP.length -1)];
- files.forEach(function (file) {
- FS.unlink(file);
- var dir = file.replace(DROP, "");
- var idx = dir.lastIndexOf("/");
- while (idx > 0) {
- dir = dir.substr(0, idx);
- if (dirs.indexOf(DROP + dir) == -1) {
- dirs.push(DROP + dir);
- }
- idx = dir.lastIndexOf("/");
- }
- });
- // Remove dirs.
- dirs = dirs.sort(function(a, b) {
- var al = (a.match(/\//g) || []).length;
- var bl = (b.match(/\//g) || []).length;
- if (al > bl)
- return -1;
- else if (al < bl)
- return 1;
- return 0;
- });
- dirs.forEach(function(dir) {
- FS.rmdir(dir);
- });
- });
- }
-})();
-
-function EventHandlers() {
- function Handler(target, event, method, capture) {
- this.target = target;
- this.event = event;
- this.method = method;
- this.capture = capture;
- }
-
- var listeners = [];
-
- function has(target, event, method, capture) {
- return listeners.findIndex(function(e) {
- return e.target === target && e.event === event && e.method === method && e.capture == capture;
- }) !== -1;
- }
-
- this.add = function(target, event, method, capture) {
- if (has(target, event, method, capture)) {
- return;
- }
- listeners.push(new Handler(target, event, method, capture));
- target.addEventListener(event, method, capture);
- };
-
- this.remove = function(target, event, method, capture) {
- if (!has(target, event, method, capture)) {
- return;
- }
- target.removeEventListener(event, method, capture);
- };
-
- this.clear = function() {
- listeners.forEach(function(h) {
- h.target.removeEventListener(h.event, h.method, h.capture);
- });
- listeners.length = 0;
- };
-}
-
-Module.listeners = new EventHandlers();
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index cf5751f384..80723d54fc 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -46,6 +46,8 @@
#include <emscripten.h>
#include <stdlib.h>
+#include "godot_js.h"
+
// Lifecycle
void OS_JavaScript::initialize() {
OS_Unix::initialize_core();
@@ -72,24 +74,15 @@ MainLoop *OS_JavaScript::get_main_loop() const {
return main_loop;
}
-extern "C" EMSCRIPTEN_KEEPALIVE void _idb_synced() {
- OS_JavaScript::get_singleton()->idb_is_syncing = false;
+void OS_JavaScript::fs_sync_callback() {
+ get_singleton()->idb_is_syncing = false;
}
bool OS_JavaScript::main_loop_iterate() {
if (is_userfs_persistent() && idb_needs_sync && !idb_is_syncing) {
idb_is_syncing = true;
idb_needs_sync = false;
- /* clang-format off */
- EM_ASM({
- FS.syncfs(function(error) {
- if (error) {
- err('Failed to save IDB file system: ' + error.message);
- }
- ccall("_idb_synced", 'void', [], []);
- });
- });
- /* clang-format on */
+ godot_js_os_fs_sync(&fs_sync_callback);
}
DisplayServer::get_singleton()->process_events();
@@ -104,13 +97,6 @@ void OS_JavaScript::delete_main_loop() {
main_loop = nullptr;
}
-void OS_JavaScript::finalize_async() {
- finalizing = true;
- if (audio_driver_javascript) {
- audio_driver_javascript->finish_async();
- }
-}
-
void OS_JavaScript::finalize() {
delete_main_loop();
if (audio_driver_javascript) {
@@ -127,17 +113,7 @@ Error OS_JavaScript::execute(const String &p_path, const List<String> &p_argumen
args.push_back(E->get());
}
String json_args = JSON::print(args);
- /* clang-format off */
- int failed = EM_ASM_INT({
- const json_args = UTF8ToString($0);
- const args = JSON.parse(json_args);
- if (Module["onExecute"]) {
- Module["onExecute"](args);
- return 0;
- }
- return 1;
- }, json_args.utf8().get_data());
- /* clang-format on */
+ int failed = godot_js_os_execute(json_args.utf8().get_data());
ERR_FAIL_COND_V_MSG(failed, ERR_UNAVAILABLE, "OS::execute() must be implemented in JavaScript via 'engine.setOnExecute' if required.");
return OK;
}
@@ -168,11 +144,7 @@ String OS_JavaScript::get_executable_path() const {
Error OS_JavaScript::shell_open(String p_uri) {
// Open URI in a new tab, browser will deal with it by protocol.
- /* clang-format off */
- EM_ASM_({
- window.open(UTF8ToString($0), '_blank');
- }, p_uri.utf8().get_data());
- /* clang-format on */
+ godot_js_os_shell_open(p_uri.utf8().get_data());
return OK;
}
@@ -211,10 +183,6 @@ void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags
}
}
-void OS_JavaScript::set_idb_available(bool p_idb_available) {
- idb_available = p_idb_available;
-}
-
bool OS_JavaScript::is_userfs_persistent() const {
return idb_available;
}
@@ -227,11 +195,17 @@ void OS_JavaScript::initialize_joypads() {
}
OS_JavaScript::OS_JavaScript() {
+ char locale_ptr[16];
+ godot_js_config_locale_get(locale_ptr, 16);
+ setenv("LANG", locale_ptr, true);
+
if (AudioDriverJavaScript::is_available()) {
audio_driver_javascript = memnew(AudioDriverJavaScript);
AudioDriverManager::add_driver(audio_driver_javascript);
}
+ idb_available = godot_js_os_fs_is_persistent();
+
Vector<Logger *> loggers;
loggers.push_back(memnew(StdLogger));
_set_logger(memnew(CompositeLogger(loggers)));
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index 85551d708b..03a3053367 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -42,13 +42,14 @@ class OS_JavaScript : public OS_Unix {
MainLoop *main_loop = nullptr;
AudioDriverJavaScript *audio_driver_javascript = nullptr;
- bool finalizing = false;
+ bool idb_is_syncing = false;
bool idb_available = false;
bool idb_needs_sync = false;
static void main_loop_callback();
static void file_access_close_callback(const String &p_file, int p_flags);
+ static void fs_sync_callback();
protected:
void initialize() override;
@@ -61,15 +62,12 @@ protected:
bool _check_internal_feature_support(const String &p_feature) override;
public:
- bool idb_is_syncing = false;
-
// Override return type to make writing static callbacks less tedious.
static OS_JavaScript *get_singleton();
void initialize_joypads() override;
MainLoop *get_main_loop() const override;
- void finalize_async();
bool main_loop_iterate();
Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override;
@@ -88,11 +86,9 @@ public:
String get_data_path() const override;
String get_user_data_dir() const override;
- void set_idb_available(bool p_idb_available);
bool is_userfs_persistent() const override;
void resume_audio();
- bool is_finalizing() { return finalizing; }
OS_JavaScript();
};
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 35418116b4..5fa737af8e 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -794,7 +794,9 @@ void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window
Atom _net_wm_name = XInternAtom(x11_display, "_NET_WM_NAME", false);
Atom utf8_string = XInternAtom(x11_display, "UTF8_STRING", false);
- XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length());
+ if (_net_wm_name != None && utf8_string != None) {
+ XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length());
+ }
}
void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
@@ -1184,6 +1186,10 @@ bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_a
unsigned char *data = nullptr;
bool retval = false;
+ if (property == None) {
+ return false;
+ }
+
int result = XGetWindowProperty(
x11_display,
wd.x11_window,
@@ -1272,7 +1278,9 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) {
hints.flags = 2;
hints.decorations = 0;
property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ if (property != None) {
+ XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ }
}
if (p_enabled) {
@@ -1299,7 +1307,9 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) {
// set bypass compositor hint
Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False);
unsigned long compositing_disable_on = p_enabled ? 1 : 0;
- XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);
+ if (bypass_compositor != None) {
+ XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1);
+ }
XFlush(x11_display);
@@ -1313,7 +1323,9 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) {
hints.flags = 2;
hints.decorations = window_get_flag(WINDOW_FLAG_BORDERLESS, p_window) ? 0 : 1;
property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ if (property != None) {
+ XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ }
}
}
@@ -1448,6 +1460,10 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c
{ // Test minimized.
// Using ICCCM -- Inter-Client Communication Conventions Manual
Atom property = XInternAtom(x11_display, "WM_STATE", True);
+ if (property == None) {
+ return WINDOW_MODE_WINDOWED;
+ }
+
Atom type;
int format;
unsigned long len;
@@ -1503,7 +1519,9 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
hints.flags = 2;
hints.decorations = p_enabled ? 0 : 1;
property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ if (property != None) {
+ XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ }
// Preserve window size
window_set_size(window_get_size(p_window), p_window);
@@ -1943,28 +1961,29 @@ String DisplayServerX11::keyboard_get_layout_name(int p_index) const {
}
DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) {
- Atom actual_type;
- int actual_format;
- unsigned long nitems;
- unsigned long bytes_after;
+ Atom actual_type = None;
+ int actual_format = 0;
+ unsigned long nitems = 0;
+ unsigned long bytes_after = 0;
unsigned char *ret = nullptr;
int read_bytes = 1024;
- //Keep trying to read the property until there are no
- //bytes unread.
- do {
- if (ret != nullptr) {
- XFree(ret);
- }
+ // Keep trying to read the property until there are no bytes unread.
+ if (p_property != None) {
+ do {
+ if (ret != nullptr) {
+ XFree(ret);
+ }
- XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,
- &actual_type, &actual_format, &nitems, &bytes_after,
- &ret);
+ XGetWindowProperty(p_display, p_window, p_property, 0, read_bytes, False, AnyPropertyType,
+ &actual_type, &actual_format, &nitems, &bytes_after,
+ &ret);
- read_bytes *= 2;
+ read_bytes *= 2;
- } while (bytes_after != 0);
+ } while (bytes_after != 0);
+ }
Property p = { ret, actual_format, (int)nitems, actual_type };
@@ -3376,7 +3395,9 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) {
pr += 4;
}
- XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());
+ if (net_wm_icon != None) {
+ XChangeProperty(x11_display, wd.x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)pd.ptr(), pd.size());
+ }
if (!g_set_icon_error) {
break;
@@ -3469,7 +3490,9 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u
{
const long pid = OS::get_singleton()->get_process_id();
Atom net_wm_pid = XInternAtom(x11_display, "_NET_WM_PID", False);
- XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
+ if (net_wm_pid != None) {
+ XChangeProperty(x11_display, wd.x11_window, net_wm_pid, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
+ }
}
long im_event_mask = 0;
@@ -3517,7 +3540,9 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u
/* set the titlebar name */
XStoreName(x11_display, wd.x11_window, "Godot");
XSetWMProtocols(x11_display, wd.x11_window, &wm_delete, 1);
- XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);
+ if (xdnd_aware != None) {
+ XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);
+ }
if (xim && xim_style) {
// Block events polling while changing input focus
@@ -3548,20 +3573,25 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u
hints.flags = 2;
hints.decorations = 0;
property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
- XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ if (property != None) {
+ XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
+ }
}
if (wd.menu_type) {
// Set Utility type to disable fade animations.
Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
-
- XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);
+ if (wt_atom != None && type_atom != None) {
+ XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);
+ }
} else {
Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False);
Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
- XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);
+ if (wt_atom != None && type_atom != None) {
+ XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);
+ }
}
_update_size_hints(id);
@@ -3843,6 +3873,10 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
(screen_get_size(0).width - p_resolution.width) / 2,
(screen_get_size(0).height - p_resolution.height) / 2);
WindowID main_window = _create_window(p_mode, p_flags, Rect2i(window_position, p_resolution));
+ if (main_window == INVALID_WINDOW_ID) {
+ r_error = ERR_CANT_CREATE;
+ return;
+ }
for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
if (p_flags & (1 << i)) {
window_set_flag(WindowFlags(i), true, main_window);
diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp
index fda1358dfd..4a9d0a8181 100644
--- a/platform/linuxbsd/joypad_linux.cpp
+++ b/platform/linuxbsd/joypad_linux.cpp
@@ -459,9 +459,9 @@ void JoypadLinux::process_joypads() {
case ABS_HAT0X:
if (ev.value != 0) {
if (ev.value < 0) {
- joy->dpad |= Input::HAT_MASK_LEFT;
+ joy->dpad = (joy->dpad | Input::HAT_MASK_LEFT) & ~Input::HAT_MASK_RIGHT;
} else {
- joy->dpad |= Input::HAT_MASK_RIGHT;
+ joy->dpad = (joy->dpad | Input::HAT_MASK_RIGHT) & ~Input::HAT_MASK_LEFT;
}
} else {
joy->dpad &= ~(Input::HAT_MASK_LEFT | Input::HAT_MASK_RIGHT);
@@ -473,9 +473,9 @@ void JoypadLinux::process_joypads() {
case ABS_HAT0Y:
if (ev.value != 0) {
if (ev.value < 0) {
- joy->dpad |= Input::HAT_MASK_UP;
+ joy->dpad = (joy->dpad | Input::HAT_MASK_UP) & ~Input::HAT_MASK_DOWN;
} else {
- joy->dpad |= Input::HAT_MASK_DOWN;
+ joy->dpad = (joy->dpad | Input::HAT_MASK_DOWN) & ~Input::HAT_MASK_UP;
}
} else {
joy->dpad &= ~(Input::HAT_MASK_UP | Input::HAT_MASK_DOWN);
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index b108d74b2e..646bc3aa4c 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -466,8 +466,10 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK);
if (p_blocking) {
- DWORD ret2 = WaitForSingleObject(pi.pi.hProcess, INFINITE);
+ WaitForSingleObject(pi.pi.hProcess, INFINITE);
if (r_exitcode) {
+ DWORD ret2;
+ GetExitCodeProcess(pi.pi.hProcess, &ret2);
*r_exitcode = ret2;
}
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index a6433f8f58..191159448a 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -519,8 +519,8 @@ void Camera3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fov", PROPERTY_HINT_RANGE, "1,179,0.1"), "set_fov", "get_fov");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.1,16384,0.01"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frustum_offset"), "set_frustum_offset", "get_frustum_offset");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "near", PROPERTY_HINT_EXP_RANGE, "0.01,8192,0.01,or_greater"), "set_znear", "get_znear");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "far", PROPERTY_HINT_EXP_RANGE, "0.1,8192,0.1,or_greater"), "set_zfar", "get_zfar");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "near", PROPERTY_HINT_EXP_RANGE, "0.001,8192,0.001,or_greater"), "set_znear", "get_znear");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "far", PROPERTY_HINT_EXP_RANGE, "0.01,8192,0.01,or_greater"), "set_zfar", "get_zfar");
BIND_ENUM_CONSTANT(PROJECTION_PERSPECTIVE);
BIND_ENUM_CONSTANT(PROJECTION_ORTHOGONAL);
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 3414b04978..50d29a3a02 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -728,13 +728,13 @@ Size2 Control::get_minimum_size() const {
}
template <class T>
-bool Control::_find_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, T &r_ret, T (Theme::*get_func)(const StringName &, const StringName &) const, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_type) {
+bool Control::_find_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, T &r_ret, T (Theme::*get_func)(const StringName &, const StringName &) const, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type) {
// try with custom themes
Control *theme_owner = p_theme_owner;
Window *theme_owner_window = p_theme_owner_window;
while (theme_owner || theme_owner_window) {
- StringName class_name = p_type;
+ StringName class_name = p_node_type;
while (class_name != StringName()) {
if (theme_owner && (theme_owner->data.theme.operator->()->*has_func)(p_name, class_name)) {
@@ -771,13 +771,13 @@ bool Control::_find_theme_item(Control *p_theme_owner, Window *p_theme_owner_win
return false;
}
-bool Control::_has_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_type) {
+bool Control::_has_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type) {
// try with custom themes
Control *theme_owner = p_theme_owner;
Window *theme_owner_window = p_theme_owner_window;
while (theme_owner || theme_owner_window) {
- StringName class_name = p_type;
+ StringName class_name = p_node_type;
while (class_name != StringName()) {
if (theme_owner && (theme_owner->data.theme.operator->()->*has_func)(p_name, class_name)) {
@@ -812,176 +812,176 @@ bool Control::_has_theme_item(Control *p_theme_owner, Window *p_theme_owner_wind
return false;
}
-Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
const Ref<Texture2D> *tex = data.icon_override.getptr(p_name);
if (tex) {
return *tex;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return get_icons(data.theme_owner, data.theme_owner_window, p_name, type);
}
-Ref<Texture2D> Control::get_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
+Ref<Texture2D> Control::get_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
Ref<Texture2D> icon;
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, icon, &Theme::get_icon, &Theme::has_icon, p_name, p_type)) {
+ if (_find_theme_item(p_theme_owner, p_theme_owner_window, icon, &Theme::get_icon, &Theme::has_icon, p_name, p_node_type)) {
return icon;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_icon(p_name, p_type)) {
- return Theme::get_project_default()->get_icon(p_name, p_type);
+ if (Theme::get_project_default()->has_icon(p_name, p_node_type)) {
+ return Theme::get_project_default()->get_icon(p_name, p_node_type);
}
}
- return Theme::get_default()->get_icon(p_name, p_type);
+ return Theme::get_default()->get_icon(p_name, p_node_type);
}
-Ref<Shader> Control::get_theme_shader(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+Ref<Shader> Control::get_theme_shader(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
const Ref<Shader> *sdr = data.shader_override.getptr(p_name);
if (sdr) {
return *sdr;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return get_shaders(data.theme_owner, data.theme_owner_window, p_name, type);
}
-Ref<Shader> Control::get_shaders(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
+Ref<Shader> Control::get_shaders(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
Ref<Shader> shader;
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, shader, &Theme::get_shader, &Theme::has_shader, p_name, p_type)) {
+ if (_find_theme_item(p_theme_owner, p_theme_owner_window, shader, &Theme::get_shader, &Theme::has_shader, p_name, p_node_type)) {
return shader;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_shader(p_name, p_type)) {
- return Theme::get_project_default()->get_shader(p_name, p_type);
+ if (Theme::get_project_default()->has_shader(p_name, p_node_type)) {
+ return Theme::get_project_default()->get_shader(p_name, p_node_type);
}
}
- return Theme::get_default()->get_shader(p_name, p_type);
+ return Theme::get_default()->get_shader(p_name, p_node_type);
}
-Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
const Ref<StyleBox> *style = data.style_override.getptr(p_name);
if (style) {
return *style;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return get_styleboxs(data.theme_owner, data.theme_owner_window, p_name, type);
}
-Ref<StyleBox> Control::get_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
+Ref<StyleBox> Control::get_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
Ref<StyleBox> stylebox;
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, stylebox, &Theme::get_stylebox, &Theme::has_stylebox, p_name, p_type)) {
+ if (_find_theme_item(p_theme_owner, p_theme_owner_window, stylebox, &Theme::get_stylebox, &Theme::has_stylebox, p_name, p_node_type)) {
return stylebox;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_stylebox(p_name, p_type)) {
- return Theme::get_project_default()->get_stylebox(p_name, p_type);
+ if (Theme::get_project_default()->has_stylebox(p_name, p_node_type)) {
+ return Theme::get_project_default()->get_stylebox(p_name, p_node_type);
}
}
- return Theme::get_default()->get_stylebox(p_name, p_type);
+ return Theme::get_default()->get_stylebox(p_name, p_node_type);
}
-Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
const Ref<Font> *font = data.font_override.getptr(p_name);
if (font) {
return *font;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return get_fonts(data.theme_owner, data.theme_owner_window, p_name, type);
}
-Ref<Font> Control::get_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
+Ref<Font> Control::get_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
Ref<Font> font;
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, font, &Theme::get_font, &Theme::has_font, p_name, p_type)) {
+ if (_find_theme_item(p_theme_owner, p_theme_owner_window, font, &Theme::get_font, &Theme::has_font, p_name, p_node_type)) {
return font;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_font(p_name, p_type)) {
- return Theme::get_project_default()->get_font(p_name, p_type);
+ if (Theme::get_project_default()->has_font(p_name, p_node_type)) {
+ return Theme::get_project_default()->get_font(p_name, p_node_type);
}
}
- return Theme::get_default()->get_font(p_name, p_type);
+ return Theme::get_default()->get_font(p_name, p_node_type);
}
-Color Control::get_theme_color(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+Color Control::get_theme_color(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
const Color *color = data.color_override.getptr(p_name);
if (color) {
return *color;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return get_colors(data.theme_owner, data.theme_owner_window, p_name, type);
}
-Color Control::get_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
+Color Control::get_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
Color color;
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, color, &Theme::get_color, &Theme::has_color, p_name, p_type)) {
+ if (_find_theme_item(p_theme_owner, p_theme_owner_window, color, &Theme::get_color, &Theme::has_color, p_name, p_node_type)) {
return color;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_color(p_name, p_type)) {
- return Theme::get_project_default()->get_color(p_name, p_type);
+ if (Theme::get_project_default()->has_color(p_name, p_node_type)) {
+ return Theme::get_project_default()->get_color(p_name, p_node_type);
}
}
- return Theme::get_default()->get_color(p_name, p_type);
+ return Theme::get_default()->get_color(p_name, p_node_type);
}
-int Control::get_theme_constant(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+int Control::get_theme_constant(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
const int *constant = data.constant_override.getptr(p_name);
if (constant) {
return *constant;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return get_constants(data.theme_owner, data.theme_owner_window, p_name, type);
}
-int Control::get_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
+int Control::get_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
int constant;
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, constant, &Theme::get_constant, &Theme::has_constant, p_name, p_type)) {
+ if (_find_theme_item(p_theme_owner, p_theme_owner_window, constant, &Theme::get_constant, &Theme::has_constant, p_name, p_node_type)) {
return constant;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_constant(p_name, p_type)) {
- return Theme::get_project_default()->get_constant(p_name, p_type);
+ if (Theme::get_project_default()->has_constant(p_name, p_node_type)) {
+ return Theme::get_project_default()->get_constant(p_name, p_node_type);
}
}
- return Theme::get_default()->get_constant(p_name, p_type);
+ return Theme::get_default()->get_constant(p_name, p_node_type);
}
bool Control::has_theme_icon_override(const StringName &p_name) const {
@@ -1014,154 +1014,154 @@ bool Control::has_theme_constant_override(const StringName &p_name) const {
return constant != nullptr;
}
-bool Control::has_theme_icon(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+bool Control::has_theme_icon(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
if (has_theme_icon_override(p_name)) {
return true;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return has_icons(data.theme_owner, data.theme_owner_window, p_name, type);
}
-bool Control::has_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_icon, p_name, p_type)) {
+bool Control::has_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
+ if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_icon, p_name, p_node_type)) {
return true;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_color(p_name, p_type)) {
+ if (Theme::get_project_default()->has_color(p_name, p_node_type)) {
return true;
}
}
- return Theme::get_default()->has_icon(p_name, p_type);
+ return Theme::get_default()->has_icon(p_name, p_node_type);
}
-bool Control::has_theme_shader(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+bool Control::has_theme_shader(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
if (has_theme_shader_override(p_name)) {
return true;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return has_shaders(data.theme_owner, data.theme_owner_window, p_name, type);
}
-bool Control::has_shaders(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_shader, p_name, p_type)) {
+bool Control::has_shaders(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
+ if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_shader, p_name, p_node_type)) {
return true;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_shader(p_name, p_type)) {
+ if (Theme::get_project_default()->has_shader(p_name, p_node_type)) {
return true;
}
}
- return Theme::get_default()->has_shader(p_name, p_type);
+ return Theme::get_default()->has_shader(p_name, p_node_type);
}
-bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
if (has_theme_stylebox_override(p_name)) {
return true;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return has_styleboxs(data.theme_owner, data.theme_owner_window, p_name, type);
}
-bool Control::has_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_stylebox, p_name, p_type)) {
+bool Control::has_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
+ if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_stylebox, p_name, p_node_type)) {
return true;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_stylebox(p_name, p_type)) {
+ if (Theme::get_project_default()->has_stylebox(p_name, p_node_type)) {
return true;
}
}
- return Theme::get_default()->has_stylebox(p_name, p_type);
+ return Theme::get_default()->has_stylebox(p_name, p_node_type);
}
-bool Control::has_theme_font(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+bool Control::has_theme_font(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
if (has_theme_font_override(p_name)) {
return true;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return has_fonts(data.theme_owner, data.theme_owner_window, p_name, type);
}
-bool Control::has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_font, p_name, p_type)) {
+bool Control::has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
+ if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_font, p_name, p_node_type)) {
return true;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_font(p_name, p_type)) {
+ if (Theme::get_project_default()->has_font(p_name, p_node_type)) {
return true;
}
}
- return Theme::get_default()->has_font(p_name, p_type);
+ return Theme::get_default()->has_font(p_name, p_node_type);
}
-bool Control::has_theme_color(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+bool Control::has_theme_color(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
if (has_theme_color_override(p_name)) {
return true;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
return has_colors(data.theme_owner, data.theme_owner_window, p_name, type);
}
-bool Control::has_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_color, p_name, p_type)) {
+bool Control::has_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
+ if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_color, p_name, p_node_type)) {
return true;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_color(p_name, p_type)) {
+ if (Theme::get_project_default()->has_color(p_name, p_node_type)) {
return true;
}
}
- return Theme::get_default()->has_color(p_name, p_type);
+ return Theme::get_default()->has_color(p_name, p_node_type);
}
-bool Control::has_theme_constant(const StringName &p_name, const StringName &p_type) const {
- if (p_type == StringName() || p_type == get_class_name()) {
+bool Control::has_theme_constant(const StringName &p_name, const StringName &p_node_type) const {
+ if (p_node_type == StringName() || p_node_type == get_class_name()) {
if (has_theme_constant_override(p_name)) {
return true;
}
}
- StringName type = p_type ? p_type : get_class_name();
+ StringName type = p_node_type ? p_node_type : get_class_name();
- return has_constants(data.theme_owner, data.theme_owner_window, p_name, p_type);
+ return has_constants(data.theme_owner, data.theme_owner_window, p_name, p_node_type);
}
-bool Control::has_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_constant, p_name, p_type)) {
+bool Control::has_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
+ if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_constant, p_name, p_node_type)) {
return true;
}
if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_constant(p_name, p_type)) {
+ if (Theme::get_project_default()->has_constant(p_name, p_node_type)) {
return true;
}
}
- return Theme::get_default()->has_constant(p_name, p_type);
+ return Theme::get_default()->has_constant(p_name, p_node_type);
}
Rect2 Control::get_parent_anchorable_rect() const {
diff --git a/scene/gui/control.h b/scene/gui/control.h
index f2f558cf4f..3be839a5fb 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -236,23 +236,23 @@ private:
static void _propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign = true);
template <class T>
- _FORCE_INLINE_ static bool _find_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, T &, T (Theme::*get_func)(const StringName &, const StringName &) const, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_type);
+ _FORCE_INLINE_ static bool _find_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, T &, T (Theme::*get_func)(const StringName &, const StringName &) const, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type);
- _FORCE_INLINE_ static bool _has_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_type);
+ _FORCE_INLINE_ static bool _has_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type);
- static Ref<Texture2D> get_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static Ref<Shader> get_shaders(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static Ref<StyleBox> get_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static Ref<Font> get_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static Color get_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static int get_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
+ static Ref<Texture2D> get_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static Ref<Shader> get_shaders(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static Ref<StyleBox> get_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static Ref<Font> get_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static Color get_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static int get_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static bool has_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static bool has_shaders(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static bool has_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static bool has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static bool has_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
- static bool has_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_type = StringName());
+ static bool has_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static bool has_shaders(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static bool has_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static bool has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static bool has_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static bool has_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
protected:
virtual void add_child_notify(Node *p_child) override;
@@ -429,12 +429,12 @@ public:
void add_theme_color_override(const StringName &p_name, const Color &p_color);
void add_theme_constant_override(const StringName &p_name, int p_constant);
- Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_type = StringName()) const;
- Ref<Shader> get_theme_shader(const StringName &p_name, const StringName &p_type = StringName()) const;
- Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_type = StringName()) const;
- Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_type = StringName()) const;
- Color get_theme_color(const StringName &p_name, const StringName &p_type = StringName()) const;
- int get_theme_constant(const StringName &p_name, const StringName &p_type = StringName()) const;
+ Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ Ref<Shader> get_theme_shader(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ Color get_theme_color(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ int get_theme_constant(const StringName &p_name, const StringName &p_node_type = StringName()) const;
bool has_theme_icon_override(const StringName &p_name) const;
bool has_theme_shader_override(const StringName &p_name) const;
@@ -443,12 +443,12 @@ public:
bool has_theme_color_override(const StringName &p_name) const;
bool has_theme_constant_override(const StringName &p_name) const;
- bool has_theme_icon(const StringName &p_name, const StringName &p_type = StringName()) const;
- bool has_theme_shader(const StringName &p_name, const StringName &p_type = StringName()) const;
- bool has_theme_stylebox(const StringName &p_name, const StringName &p_type = StringName()) const;
- bool has_theme_font(const StringName &p_name, const StringName &p_type = StringName()) const;
- bool has_theme_color(const StringName &p_name, const StringName &p_type = StringName()) const;
- bool has_theme_constant(const StringName &p_name, const StringName &p_type = StringName()) const;
+ bool has_theme_icon(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ bool has_theme_shader(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ bool has_theme_stylebox(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ bool has_theme_font(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ bool has_theme_color(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ bool has_theme_constant(const StringName &p_name, const StringName &p_node_type = StringName()) const;
/* TOOLTIP */
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 5fccde3597..649f5a5f66 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -679,7 +679,7 @@ void LineEdit::_notification(int p_what) {
} break;
#endif
case NOTIFICATION_RESIZED: {
- window_pos = 0;
+ scroll_offset = 0;
set_cursor_position(get_cursor_position());
} break;
@@ -735,7 +735,7 @@ void LineEdit::_notification(int p_what) {
x_ofs = style->get_offset().x;
} break;
case ALIGN_CENTER: {
- if (window_pos != 0) {
+ if (scroll_offset != 0) {
x_ofs = style->get_offset().x;
} else {
x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - (cached_text_width)) / 2);
@@ -747,7 +747,7 @@ void LineEdit::_notification(int p_what) {
}
int ofs_max = width - style->get_margin(MARGIN_RIGHT);
- int char_ofs = window_pos;
+ int char_ofs = scroll_offset;
int y_area = height - style->get_minimum_size().height;
int y_ofs = style->get_offset().y + (y_area - font->get_height()) / 2;
@@ -780,7 +780,7 @@ void LineEdit::_notification(int p_what) {
r_icon->draw(ci, Point2(width - r_icon->get_width() - style->get_margin(MARGIN_RIGHT), height / 2 - r_icon->get_height() / 2), color_icon);
if (align == ALIGN_CENTER) {
- if (window_pos == 0) {
+ if (scroll_offset == 0) {
x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - cached_text_width - r_icon->get_width() - style->get_margin(MARGIN_RIGHT) * 2) / 2);
}
} else {
@@ -1023,7 +1023,7 @@ void LineEdit::undo() {
TextOperation op = undo_stack_pos->get();
text = op.text;
cached_width = op.cached_width;
- window_pos = op.window_pos;
+ scroll_offset = op.scroll_offset;
set_cursor_position(op.cursor_pos);
if (expand_to_text_length) {
@@ -1044,7 +1044,7 @@ void LineEdit::redo() {
TextOperation op = undo_stack_pos->get();
text = op.text;
cached_width = op.cached_width;
- window_pos = op.window_pos;
+ scroll_offset = op.scroll_offset;
set_cursor_position(op.cursor_pos);
if (expand_to_text_length) {
@@ -1071,7 +1071,7 @@ void LineEdit::shift_selection_check_post(bool p_shift) {
void LineEdit::set_cursor_at_pixel_pos(int p_x) {
Ref<Font> font = get_theme_font("font");
- int ofs = window_pos;
+ int ofs = scroll_offset;
Ref<StyleBox> style = get_theme_stylebox("normal");
int pixel_ofs = 0;
Size2 size = get_size();
@@ -1084,7 +1084,7 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) {
pixel_ofs = int(style->get_offset().x);
} break;
case ALIGN_CENTER: {
- if (window_pos != 0) {
+ if (scroll_offset != 0) {
pixel_ofs = int(style->get_offset().x);
} else {
pixel_ofs = int(size.width - (cached_width)) / 2;
@@ -1122,7 +1122,7 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) {
int LineEdit::get_cursor_pixel_pos() {
Ref<Font> font = get_theme_font("font");
- int ofs = window_pos;
+ int ofs = scroll_offset;
Ref<StyleBox> style = get_theme_stylebox("normal");
int pixel_ofs = 0;
Size2 size = get_size();
@@ -1135,7 +1135,7 @@ int LineEdit::get_cursor_pixel_pos() {
pixel_ofs = int(style->get_offset().x);
} break;
case ALIGN_CENTER: {
- if (window_pos != 0) {
+ if (scroll_offset != 0) {
pixel_ofs = int(style->get_offset().x);
} else {
pixel_ofs = int(size.width - (cached_width)) / 2;
@@ -1236,7 +1236,7 @@ void LineEdit::delete_char() {
set_cursor_position(get_cursor_position() - 1);
if (align == ALIGN_CENTER || align == ALIGN_RIGHT) {
- window_pos = CLAMP(window_pos - 1, 0, MAX(text.length() - 1, 0));
+ scroll_offset = CLAMP(scroll_offset - 1, 0, MAX(text.length() - 1, 0));
}
_text_changed();
@@ -1262,12 +1262,12 @@ void LineEdit::delete_text(int p_from_column, int p_to_column) {
if (cursor_pos >= text.length()) {
cursor_pos = text.length();
}
- if (window_pos > cursor_pos) {
- window_pos = cursor_pos;
+ if (scroll_offset > cursor_pos) {
+ scroll_offset = cursor_pos;
}
if (align == ALIGN_CENTER || align == ALIGN_RIGHT) {
- window_pos = CLAMP(window_pos - (p_to_column - p_from_column), 0, MAX(text.length() - 1, 0));
+ scroll_offset = CLAMP(scroll_offset - (p_to_column - p_from_column), 0, MAX(text.length() - 1, 0));
}
if (!text_changed_dirty) {
@@ -1288,7 +1288,7 @@ void LineEdit::set_text(String p_text) {
update();
cursor_pos = 0;
- window_pos = 0;
+ scroll_offset = 0;
}
void LineEdit::clear() {
@@ -1332,16 +1332,16 @@ void LineEdit::set_cursor_position(int p_pos) {
cursor_pos = p_pos;
if (!is_inside_tree()) {
- window_pos = cursor_pos;
+ scroll_offset = cursor_pos;
return;
}
Ref<StyleBox> style = get_theme_stylebox("normal");
Ref<Font> font = get_theme_font("font");
- if (cursor_pos <= window_pos) {
+ if (cursor_pos <= scroll_offset) {
// Adjust window if cursor goes too much to the left.
- set_window_pos(MAX(0, cursor_pos - 1));
+ set_scroll_offset(MAX(0, cursor_pos - 1));
} else {
// Adjust window if cursor goes too much to the right.
int window_width = get_size().width - style->get_minimum_size().width;
@@ -1354,12 +1354,12 @@ void LineEdit::set_cursor_position(int p_pos) {
if (window_width < 0) {
return;
}
- int wp = window_pos;
+ int wp = scroll_offset;
if (font.is_valid()) {
int accum_width = 0;
- for (int i = cursor_pos; i >= window_pos; i--) {
+ for (int i = cursor_pos; i >= scroll_offset; i--) {
if (i >= text.length()) {
// Do not do this, because if the cursor is at the end, its just fine that it takes no space.
// accum_width = font->get_char_size(' ').width;
@@ -1378,8 +1378,8 @@ void LineEdit::set_cursor_position(int p_pos) {
}
}
- if (wp != window_pos) {
- set_window_pos(wp);
+ if (wp != scroll_offset) {
+ set_scroll_offset(wp);
}
}
update();
@@ -1389,13 +1389,17 @@ int LineEdit::get_cursor_position() const {
return cursor_pos;
}
-void LineEdit::set_window_pos(int p_pos) {
- window_pos = p_pos;
- if (window_pos < 0) {
- window_pos = 0;
+void LineEdit::set_scroll_offset(int p_pos) {
+ scroll_offset = p_pos;
+ if (scroll_offset < 0) {
+ scroll_offset = 0;
}
}
+int LineEdit::get_scroll_offset() const {
+ return scroll_offset;
+}
+
void LineEdit::append_at_cursor(String p_text) {
if ((max_length <= 0) || (text.length() + p_text.length() <= max_length)) {
String pre = text.substr(0, cursor_pos);
@@ -1413,7 +1417,7 @@ void LineEdit::clear_internal() {
_clear_undo_stack();
cached_width = 0;
cursor_pos = 0;
- window_pos = 0;
+ scroll_offset = 0;
undo_text = "";
text = "";
update();
@@ -1644,7 +1648,7 @@ void LineEdit::_editor_settings_changed() {
void LineEdit::set_expand_to_text_length(bool p_enabled) {
expand_to_text_length = p_enabled;
minimum_size_changed();
- set_window_pos(0);
+ set_scroll_offset(0);
}
bool LineEdit::get_expand_to_text_length() const {
@@ -1771,7 +1775,7 @@ void LineEdit::_create_undo_state() {
op.text = text;
op.cached_width = cached_width;
op.cursor_pos = cursor_pos;
- op.window_pos = window_pos;
+ op.scroll_offset = scroll_offset;
undo_stack.push_back(op);
}
@@ -1816,6 +1820,7 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_placeholder_alpha"), &LineEdit::get_placeholder_alpha);
ClassDB::bind_method(D_METHOD("set_cursor_position", "position"), &LineEdit::set_cursor_position);
ClassDB::bind_method(D_METHOD("get_cursor_position"), &LineEdit::get_cursor_position);
+ ClassDB::bind_method(D_METHOD("get_scroll_offset"), &LineEdit::get_scroll_offset);
ClassDB::bind_method(D_METHOD("set_expand_to_text_length", "enabled"), &LineEdit::set_expand_to_text_length);
ClassDB::bind_method(D_METHOD("get_expand_to_text_length"), &LineEdit::get_expand_to_text_length);
ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enabled"), &LineEdit::cursor_set_blink_enabled);
@@ -1898,7 +1903,7 @@ LineEdit::LineEdit() {
cached_width = 0;
cached_placeholder_width = 0;
cursor_pos = 0;
- window_pos = 0;
+ scroll_offset = 0;
window_has_focus = true;
max_length = 0;
pass = false;
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index d6cc1f1f11..a5e5b6988f 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -80,7 +80,7 @@ private:
PopupMenu *menu;
int cursor_pos;
- int window_pos;
+ int scroll_offset;
int max_length; // 0 for no maximum.
int cached_width;
@@ -106,7 +106,7 @@ private:
struct TextOperation {
int cursor_pos;
- int window_pos;
+ int scroll_offset;
int cached_width;
String text;
};
@@ -144,7 +144,8 @@ private:
void shift_selection_check_post(bool);
void selection_fill_at_cursor();
- void set_window_pos(int p_pos);
+ void set_scroll_offset(int p_pos);
+ int get_scroll_offset() const;
void set_cursor_at_pixel_pos(int p_x);
int get_cursor_pixel_pos();
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index 49ddd5c3ee..791c78e2b4 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -93,7 +93,7 @@ void Popup::_notification(int p_what) {
}
void Popup::_parent_focused() {
- if (popped_up) {
+ if (popped_up && close_on_parent_focus) {
_close_pressed();
}
}
@@ -112,7 +112,19 @@ void Popup::set_as_minsize() {
set_size(get_contents_minimum_size());
}
+void Popup::set_close_on_parent_focus(bool p_close) {
+ close_on_parent_focus = p_close;
+}
+
+bool Popup::get_close_on_parent_focus() {
+ return close_on_parent_focus;
+}
+
void Popup::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_close_on_parent_focus", "close"), &Popup::set_close_on_parent_focus);
+ ClassDB::bind_method(D_METHOD("get_close_on_parent_focus"), &Popup::get_close_on_parent_focus);
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "close_on_parent_focus"), "set_close_on_parent_focus", "get_close_on_parent_focus");
+
ADD_SIGNAL(MethodInfo("popup_hide"));
}
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index 44577811ff..48e7ea9452 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -40,6 +40,7 @@ class Popup : public Window {
LocalVector<Window *> visible_parents;
bool popped_up = false;
+ bool close_on_parent_focus = true;
void _input_from_window(const Ref<InputEvent> &p_event);
@@ -57,6 +58,10 @@ protected:
public:
void set_as_minsize();
+
+ void set_close_on_parent_focus(bool p_close);
+ bool get_close_on_parent_focus();
+
Popup();
~Popup();
};
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 0a469d8373..7baf32173f 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -173,11 +173,11 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
return -1;
}
-void PopupMenu::_activate_submenu(int over) {
- Node *n = get_node(items[over].submenu);
- ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[over].submenu + ".");
+void PopupMenu::_activate_submenu(int p_over) {
+ Node *n = get_node(items[p_over].submenu);
+ ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[p_over].submenu + ".");
Popup *submenu_popup = Object::cast_to<Popup>(n);
- ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[over].submenu + ".");
+ ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[p_over].submenu + ".");
if (submenu_popup->is_visible()) {
return; //already visible!
}
@@ -190,7 +190,7 @@ void PopupMenu::_activate_submenu(int over) {
float scroll_offset = control->get_position().y;
- Point2 submenu_pos = this_pos + Point2(this_rect.size.width, items[over]._ofs_cache + scroll_offset);
+ Point2 submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset);
Size2 submenu_size = submenu_popup->get_size();
// Fix pos if going outside parent rect
@@ -198,6 +198,7 @@ void PopupMenu::_activate_submenu(int over) {
submenu_pos.x = this_pos.x - submenu_size.width;
}
+ submenu_popup->set_close_on_parent_focus(false);
submenu_popup->set_position(submenu_pos);
submenu_popup->set_as_minsize(); // Shrink the popup size to it's contents.
submenu_popup->popup();
@@ -210,11 +211,11 @@ void PopupMenu::_activate_submenu(int over) {
// Autohide area above the submenu item
submenu_pum->clear_autohide_areas();
- submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
+ submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
// If there is an area below the submenu item, add an autohide area there.
- if (items[over]._ofs_cache + items[over]._height_cache + scroll_offset <= control->get_size().height) {
- int from = items[over]._ofs_cache + items[over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
+ if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) {
+ int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
}
}
@@ -547,6 +548,31 @@ void PopupMenu::_draw_background() {
style->draw(ci2, Rect2(Point2(), margin_container->get_size()));
}
+void PopupMenu::_minimum_lifetime_timeout() {
+ close_allowed = true;
+ // If the mouse still isn't in this popup after timer expires, close.
+ if (!get_visible_rect().has_point(get_mouse_position())) {
+ _close_pressed();
+ }
+}
+
+void PopupMenu::_close_pressed() {
+ // Only apply minimum lifetime to submenus.
+ PopupMenu *parent_pum = Object::cast_to<PopupMenu>(get_parent());
+ if (!parent_pum) {
+ Popup::_close_pressed();
+ return;
+ }
+
+ // If the timer has expired, close. If timer is still running, do nothing.
+ if (close_allowed) {
+ close_allowed = false;
+ Popup::_close_pressed();
+ } else if (minimum_lifetime_timer->is_stopped()) {
+ minimum_lifetime_timer->start();
+ }
+}
+
void PopupMenu::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@@ -566,7 +592,7 @@ void PopupMenu::_notification(int p_what) {
control->update();
} break;
case NOTIFICATION_WM_MOUSE_ENTER: {
- //grab_focus();
+ grab_focus();
} break;
case NOTIFICATION_WM_MOUSE_EXIT: {
if (mouse_over >= 0 && (items[mouse_over].submenu == "" || submenu_over != -1)) {
@@ -1484,6 +1510,12 @@ PopupMenu::PopupMenu() {
submenu_timer->set_one_shot(true);
submenu_timer->connect("timeout", callable_mp(this, &PopupMenu::_submenu_timeout));
add_child(submenu_timer);
+
+ 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);
}
PopupMenu::~PopupMenu() {
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index e8f82ba869..a2e7d7e6cd 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -86,6 +86,9 @@ class PopupMenu : public Popup {
}
};
+ bool close_allowed = false;
+
+ Timer *minimum_lifetime_timer = nullptr;
Timer *submenu_timer;
List<Rect2> autohide_areas;
Vector<Item> items;
@@ -102,7 +105,7 @@ class PopupMenu : public Popup {
void _scroll_to_item(int p_item);
void _gui_input(const Ref<InputEvent> &p_event);
- void _activate_submenu(int over);
+ void _activate_submenu(int p_over);
void _submenu_timeout();
uint64_t popup_time_msec = 0;
@@ -130,6 +133,9 @@ class PopupMenu : public Popup {
void _draw_items();
void _draw_background();
+ void _minimum_lifetime_timeout();
+ void _close_pressed();
+
protected:
friend class MenuButton;
void _notification(int p_what);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index cbe6c6bdb9..77ac3d6702 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1559,7 +1559,19 @@ void TextEdit::_notification(int p_what) {
}
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), true);
+ String text = _base_get_text(0, 0, selection.selecting_line, selection.selecting_column);
+ int cursor_start = text.length();
+ int cursor_end = -1;
+
+ if (selection.active) {
+ String selected_text = _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
+
+ if (selected_text.length() > 0) {
+ cursor_end = cursor_start + selected_text.length();
+ }
+ }
+
+ DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), true, -1, cursor_start, cursor_end);
}
} break;
case NOTIFICATION_FOCUS_EXIT: {
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 36c3f03f70..38baa6c97e 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -2889,7 +2889,6 @@ void Node::_bind_methods() {
ADD_SIGNAL(MethodInfo("tree_exiting"));
ADD_SIGNAL(MethodInfo("tree_exited"));
- ADD_GROUP("Pause", "pause_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "pause_mode", PROPERTY_HINT_ENUM, "Inherit,Stop,Process"), "set_pause_mode", "get_pause_mode");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", 0), "set_name", "get_name");
diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp
index f02e7987a9..600a859894 100644
--- a/scene/resources/audio_stream_sample.cpp
+++ b/scene/resources/audio_stream_sample.cpp
@@ -261,7 +261,8 @@ void AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, in
sign = -1;
}
- float base_rate = AudioServer::get_singleton()->get_mix_rate();
+ float global_rate_scale = AudioServer::get_singleton()->get_global_rate_scale();
+ float base_rate = AudioServer::get_singleton()->get_mix_rate() * global_rate_scale;
float srate = base->mix_rate;
srate *= p_rate_scale;
float fincrement = srate / base_rate;
diff --git a/scene/resources/capsule_shape_3d.cpp b/scene/resources/capsule_shape_3d.cpp
index 9d1355eec6..5da7f682e5 100644
--- a/scene/resources/capsule_shape_3d.cpp
+++ b/scene/resources/capsule_shape_3d.cpp
@@ -106,8 +106,8 @@ void CapsuleShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape3D::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape3D::get_height);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,4096,0.01"), "set_radius", "get_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.01,4096,0.01"), "set_height", "get_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_radius", "get_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_height", "get_height");
}
CapsuleShape3D::CapsuleShape3D() :
diff --git a/scene/resources/cylinder_shape_3d.cpp b/scene/resources/cylinder_shape_3d.cpp
index ad64541247..bb8c27a60d 100644
--- a/scene/resources/cylinder_shape_3d.cpp
+++ b/scene/resources/cylinder_shape_3d.cpp
@@ -99,8 +99,8 @@ void CylinderShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_height", "height"), &CylinderShape3D::set_height);
ClassDB::bind_method(D_METHOD("get_height"), &CylinderShape3D::get_height);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,4096,0.01"), "set_radius", "get_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.01,4096,0.01"), "set_height", "get_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_radius", "get_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,4096,0.001"), "set_height", "get_height");
}
CylinderShape3D::CylinderShape3D() :
diff --git a/scene/resources/ray_shape_3d.cpp b/scene/resources/ray_shape_3d.cpp
index 39df4c22f9..1705fb0f55 100644
--- a/scene/resources/ray_shape_3d.cpp
+++ b/scene/resources/ray_shape_3d.cpp
@@ -81,7 +81,7 @@ void RayShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_slips_on_slope", "active"), &RayShape3D::set_slips_on_slope);
ClassDB::bind_method(D_METHOD("get_slips_on_slope"), &RayShape3D::get_slips_on_slope);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_length", "get_length");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_length", "get_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slips_on_slope"), "set_slips_on_slope", "get_slips_on_slope");
}
diff --git a/scene/resources/sphere_shape_3d.cpp b/scene/resources/sphere_shape_3d.cpp
index fd33387df6..64e0a701b7 100644
--- a/scene/resources/sphere_shape_3d.cpp
+++ b/scene/resources/sphere_shape_3d.cpp
@@ -77,7 +77,7 @@ void SphereShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereShape3D::set_radius);
ClassDB::bind_method(D_METHOD("get_radius"), &SphereShape3D::get_radius);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_radius", "get_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_radius", "get_radius");
}
SphereShape3D::SphereShape3D() :
diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp
index 39ef6eeafa..07de202cc8 100644
--- a/scene/resources/texture.cpp
+++ b/scene/resources/texture.cpp
@@ -498,7 +498,7 @@ Error StreamTexture2D::_load_data(const String &p_path, int &tw, int &th, int &t
ERR_FAIL_COND_V(image.is_null(), ERR_INVALID_PARAMETER);
FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
+ ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path));
uint8_t header[4];
f->get_buffer(header, 4);
@@ -893,7 +893,7 @@ Image::Format StreamTexture3D::get_format() const {
Error StreamTexture3D::_load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps) {
FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
+ ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path));
uint8_t header[4];
f->get_buffer(header, 4);
@@ -2325,7 +2325,7 @@ Error StreamTextureLayered::_load_data(const String &p_path, Vector<Ref<Image>>
ERR_FAIL_COND_V(images.size() != 0, ERR_INVALID_PARAMETER);
FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);
+ ERR_FAIL_COND_V_MSG(!f, ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path));
uint8_t header[4];
f->get_buffer(header, 4);
diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp
index a05abbb4c0..ccff49829e 100644
--- a/scene/resources/theme.cpp
+++ b/scene/resources/theme.cpp
@@ -36,11 +36,11 @@ void Theme::_emit_theme_changed() {
emit_changed();
}
-Vector<String> Theme::_get_icon_list(const String &p_type) const {
+Vector<String> Theme::_get_icon_list(const String &p_node_type) const {
Vector<String> ilret;
List<StringName> il;
- get_icon_list(p_type, &il);
+ get_icon_list(p_node_type, &il);
ilret.resize(il.size());
int i = 0;
@@ -51,11 +51,11 @@ Vector<String> Theme::_get_icon_list(const String &p_type) const {
return ilret;
}
-Vector<String> Theme::_get_stylebox_list(const String &p_type) const {
+Vector<String> Theme::_get_stylebox_list(const String &p_node_type) const {
Vector<String> ilret;
List<StringName> il;
- get_stylebox_list(p_type, &il);
+ get_stylebox_list(p_node_type, &il);
ilret.resize(il.size());
int i = 0;
@@ -81,11 +81,11 @@ Vector<String> Theme::_get_stylebox_types() const {
return ilret;
}
-Vector<String> Theme::_get_font_list(const String &p_type) const {
+Vector<String> Theme::_get_font_list(const String &p_node_type) const {
Vector<String> ilret;
List<StringName> il;
- get_font_list(p_type, &il);
+ get_font_list(p_node_type, &il);
ilret.resize(il.size());
int i = 0;
@@ -96,11 +96,11 @@ Vector<String> Theme::_get_font_list(const String &p_type) const {
return ilret;
}
-Vector<String> Theme::_get_color_list(const String &p_type) const {
+Vector<String> Theme::_get_color_list(const String &p_node_type) const {
Vector<String> ilret;
List<StringName> il;
- get_color_list(p_type, &il);
+ get_color_list(p_node_type, &il);
ilret.resize(il.size());
int i = 0;
@@ -111,11 +111,11 @@ Vector<String> Theme::_get_color_list(const String &p_type) const {
return ilret;
}
-Vector<String> Theme::_get_constant_list(const String &p_type) const {
+Vector<String> Theme::_get_constant_list(const String &p_node_type) const {
Vector<String> ilret;
List<StringName> il;
- get_constant_list(p_type, &il);
+ get_constant_list(p_node_type, &il);
ilret.resize(il.size());
int i = 0;
@@ -126,7 +126,7 @@ Vector<String> Theme::_get_constant_list(const String &p_type) const {
return ilret;
}
-Vector<String> Theme::_get_type_list(const String &p_type) const {
+Vector<String> Theme::_get_type_list(const String &p_node_type) const {
Vector<String> ilret;
List<StringName> il;
@@ -325,19 +325,19 @@ void Theme::set_default_font(const Ref<Font> &p_font) {
default_font = p_font;
}
-void Theme::set_icon(const StringName &p_name, const StringName &p_type, const Ref<Texture2D> &p_icon) {
+void Theme::set_icon(const StringName &p_name, const StringName &p_node_type, const Ref<Texture2D> &p_icon) {
//ERR_FAIL_COND(p_icon.is_null());
- bool new_value = !icon_map.has(p_type) || !icon_map[p_type].has(p_name);
+ bool new_value = !icon_map.has(p_node_type) || !icon_map[p_node_type].has(p_name);
- if (icon_map[p_type].has(p_name) && icon_map[p_type][p_name].is_valid()) {
- icon_map[p_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
+ if (icon_map[p_node_type].has(p_name) && icon_map[p_node_type][p_name].is_valid()) {
+ icon_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
}
- icon_map[p_type][p_name] = p_icon;
+ icon_map[p_node_type][p_name] = p_icon;
if (p_icon.is_valid()) {
- icon_map[p_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED);
+ icon_map[p_node_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED);
}
if (new_value) {
@@ -346,50 +346,50 @@ void Theme::set_icon(const StringName &p_name, const StringName &p_type, const R
}
}
-Ref<Texture2D> Theme::get_icon(const StringName &p_name, const StringName &p_type) const {
- if (icon_map.has(p_type) && icon_map[p_type].has(p_name) && icon_map[p_type][p_name].is_valid()) {
- return icon_map[p_type][p_name];
+Ref<Texture2D> Theme::get_icon(const StringName &p_name, const StringName &p_node_type) const {
+ if (icon_map.has(p_node_type) && icon_map[p_node_type].has(p_name) && icon_map[p_node_type][p_name].is_valid()) {
+ return icon_map[p_node_type][p_name];
} else {
return default_icon;
}
}
-bool Theme::has_icon(const StringName &p_name, const StringName &p_type) const {
- return (icon_map.has(p_type) && icon_map[p_type].has(p_name) && icon_map[p_type][p_name].is_valid());
+bool Theme::has_icon(const StringName &p_name, const StringName &p_node_type) const {
+ return (icon_map.has(p_node_type) && icon_map[p_node_type].has(p_name) && icon_map[p_node_type][p_name].is_valid());
}
-void Theme::clear_icon(const StringName &p_name, const StringName &p_type) {
- ERR_FAIL_COND(!icon_map.has(p_type));
- ERR_FAIL_COND(!icon_map[p_type].has(p_name));
+void Theme::clear_icon(const StringName &p_name, const StringName &p_node_type) {
+ ERR_FAIL_COND(!icon_map.has(p_node_type));
+ ERR_FAIL_COND(!icon_map[p_node_type].has(p_name));
- if (icon_map[p_type][p_name].is_valid()) {
- icon_map[p_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
+ if (icon_map[p_node_type][p_name].is_valid()) {
+ icon_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
}
- icon_map[p_type].erase(p_name);
+ icon_map[p_node_type].erase(p_name);
_change_notify();
emit_changed();
}
-void Theme::get_icon_list(StringName p_type, List<StringName> *p_list) const {
+void Theme::get_icon_list(StringName p_node_type, List<StringName> *p_list) const {
ERR_FAIL_NULL(p_list);
- if (!icon_map.has(p_type)) {
+ if (!icon_map.has(p_node_type)) {
return;
}
const StringName *key = nullptr;
- while ((key = icon_map[p_type].next(key))) {
+ while ((key = icon_map[p_node_type].next(key))) {
p_list->push_back(*key);
}
}
-void Theme::set_shader(const StringName &p_name, const StringName &p_type, const Ref<Shader> &p_shader) {
- bool new_value = !shader_map.has(p_type) || !shader_map[p_type].has(p_name);
+void Theme::set_shader(const StringName &p_name, const StringName &p_node_type, const Ref<Shader> &p_shader) {
+ bool new_value = !shader_map.has(p_node_type) || !shader_map[p_node_type].has(p_name);
- shader_map[p_type][p_name] = p_shader;
+ shader_map[p_node_type][p_name] = p_shader;
if (new_value) {
_change_notify();
@@ -397,54 +397,54 @@ void Theme::set_shader(const StringName &p_name, const StringName &p_type, const
}
}
-Ref<Shader> Theme::get_shader(const StringName &p_name, const StringName &p_type) const {
- if (shader_map.has(p_type) && shader_map[p_type].has(p_name) && shader_map[p_type][p_name].is_valid()) {
- return shader_map[p_type][p_name];
+Ref<Shader> Theme::get_shader(const StringName &p_name, const StringName &p_node_type) const {
+ if (shader_map.has(p_node_type) && shader_map[p_node_type].has(p_name) && shader_map[p_node_type][p_name].is_valid()) {
+ return shader_map[p_node_type][p_name];
} else {
return nullptr;
}
}
-bool Theme::has_shader(const StringName &p_name, const StringName &p_type) const {
- return (shader_map.has(p_type) && shader_map[p_type].has(p_name) && shader_map[p_type][p_name].is_valid());
+bool Theme::has_shader(const StringName &p_name, const StringName &p_node_type) const {
+ return (shader_map.has(p_node_type) && shader_map[p_node_type].has(p_name) && shader_map[p_node_type][p_name].is_valid());
}
-void Theme::clear_shader(const StringName &p_name, const StringName &p_type) {
- ERR_FAIL_COND(!shader_map.has(p_type));
- ERR_FAIL_COND(!shader_map[p_type].has(p_name));
+void Theme::clear_shader(const StringName &p_name, const StringName &p_node_type) {
+ ERR_FAIL_COND(!shader_map.has(p_node_type));
+ ERR_FAIL_COND(!shader_map[p_node_type].has(p_name));
- shader_map[p_type].erase(p_name);
+ shader_map[p_node_type].erase(p_name);
_change_notify();
emit_changed();
}
-void Theme::get_shader_list(const StringName &p_type, List<StringName> *p_list) const {
+void Theme::get_shader_list(const StringName &p_node_type, List<StringName> *p_list) const {
ERR_FAIL_NULL(p_list);
- if (!shader_map.has(p_type)) {
+ if (!shader_map.has(p_node_type)) {
return;
}
const StringName *key = nullptr;
- while ((key = shader_map[p_type].next(key))) {
+ while ((key = shader_map[p_node_type].next(key))) {
p_list->push_back(*key);
}
}
-void Theme::set_stylebox(const StringName &p_name, const StringName &p_type, const Ref<StyleBox> &p_style) {
+void Theme::set_stylebox(const StringName &p_name, const StringName &p_node_type, const Ref<StyleBox> &p_style) {
//ERR_FAIL_COND(p_style.is_null());
- bool new_value = !style_map.has(p_type) || !style_map[p_type].has(p_name);
+ bool new_value = !style_map.has(p_node_type) || !style_map[p_node_type].has(p_name);
- if (style_map[p_type].has(p_name) && style_map[p_type][p_name].is_valid()) {
- style_map[p_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
+ if (style_map[p_node_type].has(p_name) && style_map[p_node_type][p_name].is_valid()) {
+ style_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
}
- style_map[p_type][p_name] = p_style;
+ style_map[p_node_type][p_name] = p_style;
if (p_style.is_valid()) {
- style_map[p_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED);
+ style_map[p_node_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED);
}
if (new_value) {
@@ -453,42 +453,42 @@ void Theme::set_stylebox(const StringName &p_name, const StringName &p_type, con
emit_changed();
}
-Ref<StyleBox> Theme::get_stylebox(const StringName &p_name, const StringName &p_type) const {
- if (style_map.has(p_type) && style_map[p_type].has(p_name) && style_map[p_type][p_name].is_valid()) {
- return style_map[p_type][p_name];
+Ref<StyleBox> Theme::get_stylebox(const StringName &p_name, const StringName &p_node_type) const {
+ if (style_map.has(p_node_type) && style_map[p_node_type].has(p_name) && style_map[p_node_type][p_name].is_valid()) {
+ return style_map[p_node_type][p_name];
} else {
return default_style;
}
}
-bool Theme::has_stylebox(const StringName &p_name, const StringName &p_type) const {
- return (style_map.has(p_type) && style_map[p_type].has(p_name) && style_map[p_type][p_name].is_valid());
+bool Theme::has_stylebox(const StringName &p_name, const StringName &p_node_type) const {
+ return (style_map.has(p_node_type) && style_map[p_node_type].has(p_name) && style_map[p_node_type][p_name].is_valid());
}
-void Theme::clear_stylebox(const StringName &p_name, const StringName &p_type) {
- ERR_FAIL_COND(!style_map.has(p_type));
- ERR_FAIL_COND(!style_map[p_type].has(p_name));
+void Theme::clear_stylebox(const StringName &p_name, const StringName &p_node_type) {
+ ERR_FAIL_COND(!style_map.has(p_node_type));
+ ERR_FAIL_COND(!style_map[p_node_type].has(p_name));
- if (style_map[p_type][p_name].is_valid()) {
- style_map[p_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
+ if (style_map[p_node_type][p_name].is_valid()) {
+ style_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
}
- style_map[p_type].erase(p_name);
+ style_map[p_node_type].erase(p_name);
_change_notify();
emit_changed();
}
-void Theme::get_stylebox_list(StringName p_type, List<StringName> *p_list) const {
+void Theme::get_stylebox_list(StringName p_node_type, List<StringName> *p_list) const {
ERR_FAIL_NULL(p_list);
- if (!style_map.has(p_type)) {
+ if (!style_map.has(p_node_type)) {
return;
}
const StringName *key = nullptr;
- while ((key = style_map[p_type].next(key))) {
+ while ((key = style_map[p_node_type].next(key))) {
p_list->push_back(*key);
}
}
@@ -502,19 +502,19 @@ void Theme::get_stylebox_types(List<StringName> *p_list) const {
}
}
-void Theme::set_font(const StringName &p_name, const StringName &p_type, const Ref<Font> &p_font) {
+void Theme::set_font(const StringName &p_name, const StringName &p_node_type, const Ref<Font> &p_font) {
//ERR_FAIL_COND(p_font.is_null());
- bool new_value = !font_map.has(p_type) || !font_map[p_type].has(p_name);
+ bool new_value = !font_map.has(p_node_type) || !font_map[p_node_type].has(p_name);
- if (font_map[p_type][p_name].is_valid()) {
- font_map[p_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
+ if (font_map[p_node_type][p_name].is_valid()) {
+ font_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
}
- font_map[p_type][p_name] = p_font;
+ font_map[p_node_type][p_name] = p_font;
if (p_font.is_valid()) {
- font_map[p_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED);
+ font_map[p_node_type][p_name]->connect("changed", callable_mp(this, &Theme::_emit_theme_changed), varray(), CONNECT_REFERENCE_COUNTED);
}
if (new_value) {
@@ -523,9 +523,9 @@ void Theme::set_font(const StringName &p_name, const StringName &p_type, const R
}
}
-Ref<Font> Theme::get_font(const StringName &p_name, const StringName &p_type) const {
- if (font_map.has(p_type) && font_map[p_type].has(p_name) && font_map[p_type][p_name].is_valid()) {
- return font_map[p_type][p_name];
+Ref<Font> Theme::get_font(const StringName &p_name, const StringName &p_node_type) const {
+ if (font_map.has(p_node_type) && font_map[p_node_type].has(p_name) && font_map[p_node_type][p_name].is_valid()) {
+ return font_map[p_node_type][p_name];
} else if (default_theme_font.is_valid()) {
return default_theme_font;
} else {
@@ -533,41 +533,41 @@ Ref<Font> Theme::get_font(const StringName &p_name, const StringName &p_type) co
}
}
-bool Theme::has_font(const StringName &p_name, const StringName &p_type) const {
- return (font_map.has(p_type) && font_map[p_type].has(p_name) && font_map[p_type][p_name].is_valid());
+bool Theme::has_font(const StringName &p_name, const StringName &p_node_type) const {
+ return (font_map.has(p_node_type) && font_map[p_node_type].has(p_name) && font_map[p_node_type][p_name].is_valid());
}
-void Theme::clear_font(const StringName &p_name, const StringName &p_type) {
- ERR_FAIL_COND(!font_map.has(p_type));
- ERR_FAIL_COND(!font_map[p_type].has(p_name));
+void Theme::clear_font(const StringName &p_name, const StringName &p_node_type) {
+ ERR_FAIL_COND(!font_map.has(p_node_type));
+ ERR_FAIL_COND(!font_map[p_node_type].has(p_name));
- if (font_map[p_type][p_name].is_valid()) {
- font_map[p_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
+ if (font_map[p_node_type][p_name].is_valid()) {
+ font_map[p_node_type][p_name]->disconnect("changed", callable_mp(this, &Theme::_emit_theme_changed));
}
- font_map[p_type].erase(p_name);
+ font_map[p_node_type].erase(p_name);
_change_notify();
emit_changed();
}
-void Theme::get_font_list(StringName p_type, List<StringName> *p_list) const {
+void Theme::get_font_list(StringName p_node_type, List<StringName> *p_list) const {
ERR_FAIL_NULL(p_list);
- if (!font_map.has(p_type)) {
+ if (!font_map.has(p_node_type)) {
return;
}
const StringName *key = nullptr;
- while ((key = font_map[p_type].next(key))) {
+ while ((key = font_map[p_node_type].next(key))) {
p_list->push_back(*key);
}
}
-void Theme::set_color(const StringName &p_name, const StringName &p_type, const Color &p_color) {
- bool new_value = !color_map.has(p_type) || !color_map[p_type].has(p_name);
+void Theme::set_color(const StringName &p_name, const StringName &p_node_type, const Color &p_color) {
+ bool new_value = !color_map.has(p_node_type) || !color_map[p_node_type].has(p_name);
- color_map[p_type][p_name] = p_color;
+ color_map[p_node_type][p_name] = p_color;
if (new_value) {
_change_notify();
@@ -575,44 +575,44 @@ void Theme::set_color(const StringName &p_name, const StringName &p_type, const
}
}
-Color Theme::get_color(const StringName &p_name, const StringName &p_type) const {
- if (color_map.has(p_type) && color_map[p_type].has(p_name)) {
- return color_map[p_type][p_name];
+Color Theme::get_color(const StringName &p_name, const StringName &p_node_type) const {
+ if (color_map.has(p_node_type) && color_map[p_node_type].has(p_name)) {
+ return color_map[p_node_type][p_name];
} else {
return Color();
}
}
-bool Theme::has_color(const StringName &p_name, const StringName &p_type) const {
- return (color_map.has(p_type) && color_map[p_type].has(p_name));
+bool Theme::has_color(const StringName &p_name, const StringName &p_node_type) const {
+ return (color_map.has(p_node_type) && color_map[p_node_type].has(p_name));
}
-void Theme::clear_color(const StringName &p_name, const StringName &p_type) {
- ERR_FAIL_COND(!color_map.has(p_type));
- ERR_FAIL_COND(!color_map[p_type].has(p_name));
+void Theme::clear_color(const StringName &p_name, const StringName &p_node_type) {
+ ERR_FAIL_COND(!color_map.has(p_node_type));
+ ERR_FAIL_COND(!color_map[p_node_type].has(p_name));
- color_map[p_type].erase(p_name);
+ color_map[p_node_type].erase(p_name);
_change_notify();
emit_changed();
}
-void Theme::get_color_list(StringName p_type, List<StringName> *p_list) const {
+void Theme::get_color_list(StringName p_node_type, List<StringName> *p_list) const {
ERR_FAIL_NULL(p_list);
- if (!color_map.has(p_type)) {
+ if (!color_map.has(p_node_type)) {
return;
}
const StringName *key = nullptr;
- while ((key = color_map[p_type].next(key))) {
+ while ((key = color_map[p_node_type].next(key))) {
p_list->push_back(*key);
}
}
-void Theme::set_constant(const StringName &p_name, const StringName &p_type, int p_constant) {
- bool new_value = !constant_map.has(p_type) || !constant_map[p_type].has(p_name);
- constant_map[p_type][p_name] = p_constant;
+void Theme::set_constant(const StringName &p_name, const StringName &p_node_type, int p_constant) {
+ bool new_value = !constant_map.has(p_node_type) || !constant_map[p_node_type].has(p_name);
+ constant_map[p_node_type][p_name] = p_constant;
if (new_value) {
_change_notify();
@@ -620,37 +620,37 @@ void Theme::set_constant(const StringName &p_name, const StringName &p_type, int
}
}
-int Theme::get_constant(const StringName &p_name, const StringName &p_type) const {
- if (constant_map.has(p_type) && constant_map[p_type].has(p_name)) {
- return constant_map[p_type][p_name];
+int Theme::get_constant(const StringName &p_name, const StringName &p_node_type) const {
+ if (constant_map.has(p_node_type) && constant_map[p_node_type].has(p_name)) {
+ return constant_map[p_node_type][p_name];
} else {
return 0;
}
}
-bool Theme::has_constant(const StringName &p_name, const StringName &p_type) const {
- return (constant_map.has(p_type) && constant_map[p_type].has(p_name));
+bool Theme::has_constant(const StringName &p_name, const StringName &p_node_type) const {
+ return (constant_map.has(p_node_type) && constant_map[p_node_type].has(p_name));
}
-void Theme::clear_constant(const StringName &p_name, const StringName &p_type) {
- ERR_FAIL_COND(!constant_map.has(p_type));
- ERR_FAIL_COND(!constant_map[p_type].has(p_name));
+void Theme::clear_constant(const StringName &p_name, const StringName &p_node_type) {
+ ERR_FAIL_COND(!constant_map.has(p_node_type));
+ ERR_FAIL_COND(!constant_map[p_node_type].has(p_name));
- constant_map[p_type].erase(p_name);
+ constant_map[p_node_type].erase(p_name);
_change_notify();
emit_changed();
}
-void Theme::get_constant_list(StringName p_type, List<StringName> *p_list) const {
+void Theme::get_constant_list(StringName p_node_type, List<StringName> *p_list) const {
ERR_FAIL_NULL(p_list);
- if (!constant_map.has(p_type)) {
+ if (!constant_map.has(p_node_type)) {
return;
}
const StringName *key = nullptr;
- while ((key = constant_map[p_type].next(key))) {
+ while ((key = constant_map[p_node_type].next(key))) {
p_list->push_back(*key);
}
}
@@ -800,43 +800,43 @@ void Theme::get_type_list(List<StringName> *p_list) const {
}
void Theme::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_icon", "name", "type", "texture"), &Theme::set_icon);
- ClassDB::bind_method(D_METHOD("get_icon", "name", "type"), &Theme::get_icon);
- ClassDB::bind_method(D_METHOD("has_icon", "name", "type"), &Theme::has_icon);
- ClassDB::bind_method(D_METHOD("clear_icon", "name", "type"), &Theme::clear_icon);
- ClassDB::bind_method(D_METHOD("get_icon_list", "type"), &Theme::_get_icon_list);
-
- ClassDB::bind_method(D_METHOD("set_stylebox", "name", "type", "texture"), &Theme::set_stylebox);
- ClassDB::bind_method(D_METHOD("get_stylebox", "name", "type"), &Theme::get_stylebox);
- ClassDB::bind_method(D_METHOD("has_stylebox", "name", "type"), &Theme::has_stylebox);
- ClassDB::bind_method(D_METHOD("clear_stylebox", "name", "type"), &Theme::clear_stylebox);
- ClassDB::bind_method(D_METHOD("get_stylebox_list", "type"), &Theme::_get_stylebox_list);
+ ClassDB::bind_method(D_METHOD("set_icon", "name", "node_type", "texture"), &Theme::set_icon);
+ ClassDB::bind_method(D_METHOD("get_icon", "name", "node_type"), &Theme::get_icon);
+ ClassDB::bind_method(D_METHOD("has_icon", "name", "node_type"), &Theme::has_icon);
+ ClassDB::bind_method(D_METHOD("clear_icon", "name", "node_type"), &Theme::clear_icon);
+ ClassDB::bind_method(D_METHOD("get_icon_list", "node_type"), &Theme::_get_icon_list);
+
+ ClassDB::bind_method(D_METHOD("set_stylebox", "name", "node_type", "texture"), &Theme::set_stylebox);
+ ClassDB::bind_method(D_METHOD("get_stylebox", "name", "node_type"), &Theme::get_stylebox);
+ ClassDB::bind_method(D_METHOD("has_stylebox", "name", "node_type"), &Theme::has_stylebox);
+ ClassDB::bind_method(D_METHOD("clear_stylebox", "name", "node_type"), &Theme::clear_stylebox);
+ ClassDB::bind_method(D_METHOD("get_stylebox_list", "node_type"), &Theme::_get_stylebox_list);
ClassDB::bind_method(D_METHOD("get_stylebox_types"), &Theme::_get_stylebox_types);
- ClassDB::bind_method(D_METHOD("set_font", "name", "type", "font"), &Theme::set_font);
- ClassDB::bind_method(D_METHOD("get_font", "name", "type"), &Theme::get_font);
- ClassDB::bind_method(D_METHOD("has_font", "name", "type"), &Theme::has_font);
- ClassDB::bind_method(D_METHOD("clear_font", "name", "type"), &Theme::clear_font);
- ClassDB::bind_method(D_METHOD("get_font_list", "type"), &Theme::_get_font_list);
+ ClassDB::bind_method(D_METHOD("set_font", "name", "node_type", "font"), &Theme::set_font);
+ ClassDB::bind_method(D_METHOD("get_font", "name", "node_type"), &Theme::get_font);
+ ClassDB::bind_method(D_METHOD("has_font", "name", "node_type"), &Theme::has_font);
+ ClassDB::bind_method(D_METHOD("clear_font", "name", "node_type"), &Theme::clear_font);
+ ClassDB::bind_method(D_METHOD("get_font_list", "node_type"), &Theme::_get_font_list);
- ClassDB::bind_method(D_METHOD("set_color", "name", "type", "color"), &Theme::set_color);
- ClassDB::bind_method(D_METHOD("get_color", "name", "type"), &Theme::get_color);
- ClassDB::bind_method(D_METHOD("has_color", "name", "type"), &Theme::has_color);
- ClassDB::bind_method(D_METHOD("clear_color", "name", "type"), &Theme::clear_color);
- ClassDB::bind_method(D_METHOD("get_color_list", "type"), &Theme::_get_color_list);
+ ClassDB::bind_method(D_METHOD("set_color", "name", "node_type", "color"), &Theme::set_color);
+ ClassDB::bind_method(D_METHOD("get_color", "name", "node_type"), &Theme::get_color);
+ ClassDB::bind_method(D_METHOD("has_color", "name", "node_type"), &Theme::has_color);
+ ClassDB::bind_method(D_METHOD("clear_color", "name", "node_type"), &Theme::clear_color);
+ ClassDB::bind_method(D_METHOD("get_color_list", "node_type"), &Theme::_get_color_list);
- ClassDB::bind_method(D_METHOD("set_constant", "name", "type", "constant"), &Theme::set_constant);
- ClassDB::bind_method(D_METHOD("get_constant", "name", "type"), &Theme::get_constant);
- ClassDB::bind_method(D_METHOD("has_constant", "name", "type"), &Theme::has_constant);
- ClassDB::bind_method(D_METHOD("clear_constant", "name", "type"), &Theme::clear_constant);
- ClassDB::bind_method(D_METHOD("get_constant_list", "type"), &Theme::_get_constant_list);
+ ClassDB::bind_method(D_METHOD("set_constant", "name", "node_type", "constant"), &Theme::set_constant);
+ ClassDB::bind_method(D_METHOD("get_constant", "name", "node_type"), &Theme::get_constant);
+ ClassDB::bind_method(D_METHOD("has_constant", "name", "node_type"), &Theme::has_constant);
+ ClassDB::bind_method(D_METHOD("clear_constant", "name", "node_type"), &Theme::clear_constant);
+ ClassDB::bind_method(D_METHOD("get_constant_list", "node_type"), &Theme::_get_constant_list);
ClassDB::bind_method(D_METHOD("clear"), &Theme::clear);
ClassDB::bind_method(D_METHOD("set_default_font", "font"), &Theme::set_default_theme_font);
ClassDB::bind_method(D_METHOD("get_default_font"), &Theme::get_default_theme_font);
- ClassDB::bind_method(D_METHOD("get_type_list", "type"), &Theme::_get_type_list);
+ ClassDB::bind_method(D_METHOD("get_type_list", "node_type"), &Theme::_get_type_list);
ClassDB::bind_method("copy_default_theme", &Theme::copy_default_theme);
ClassDB::bind_method(D_METHOD("copy_theme", "other"), &Theme::copy_theme);
diff --git a/scene/resources/theme.h b/scene/resources/theme.h
index 5f46ce6303..9c17a69e5d 100644
--- a/scene/resources/theme.h
+++ b/scene/resources/theme.h
@@ -51,13 +51,13 @@ class Theme : public Resource {
HashMap<StringName, HashMap<StringName, Color>> color_map;
HashMap<StringName, HashMap<StringName, int>> constant_map;
- Vector<String> _get_icon_list(const String &p_type) const;
- Vector<String> _get_stylebox_list(const String &p_type) const;
+ Vector<String> _get_icon_list(const String &p_node_type) const;
+ Vector<String> _get_stylebox_list(const String &p_node_type) const;
Vector<String> _get_stylebox_types() const;
- Vector<String> _get_font_list(const String &p_type) const;
- Vector<String> _get_color_list(const String &p_type) const;
- Vector<String> _get_constant_list(const String &p_type) const;
- Vector<String> _get_type_list(const String &p_type) const;
+ Vector<String> _get_font_list(const String &p_node_type) const;
+ Vector<String> _get_color_list(const String &p_node_type) const;
+ Vector<String> _get_constant_list(const String &p_node_type) const;
+ Vector<String> _get_type_list(const String &p_node_type) const;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@@ -88,42 +88,42 @@ public:
void set_default_theme_font(const Ref<Font> &p_default_font);
Ref<Font> get_default_theme_font() const;
- void set_icon(const StringName &p_name, const StringName &p_type, const Ref<Texture2D> &p_icon);
- Ref<Texture2D> get_icon(const StringName &p_name, const StringName &p_type) const;
- bool has_icon(const StringName &p_name, const StringName &p_type) const;
- void clear_icon(const StringName &p_name, const StringName &p_type);
- void get_icon_list(StringName p_type, List<StringName> *p_list) const;
-
- void set_shader(const StringName &p_name, const StringName &p_type, const Ref<Shader> &p_shader);
- Ref<Shader> get_shader(const StringName &p_name, const StringName &p_type) const;
- bool has_shader(const StringName &p_name, const StringName &p_type) const;
- void clear_shader(const StringName &p_name, const StringName &p_type);
- void get_shader_list(const StringName &p_type, List<StringName> *p_list) const;
-
- void set_stylebox(const StringName &p_name, const StringName &p_type, const Ref<StyleBox> &p_style);
- Ref<StyleBox> get_stylebox(const StringName &p_name, const StringName &p_type) const;
- bool has_stylebox(const StringName &p_name, const StringName &p_type) const;
- void clear_stylebox(const StringName &p_name, const StringName &p_type);
- void get_stylebox_list(StringName p_type, List<StringName> *p_list) const;
+ void set_icon(const StringName &p_name, const StringName &p_node_type, const Ref<Texture2D> &p_icon);
+ Ref<Texture2D> get_icon(const StringName &p_name, const StringName &p_node_type) const;
+ bool has_icon(const StringName &p_name, const StringName &p_node_type) const;
+ void clear_icon(const StringName &p_name, const StringName &p_node_type);
+ void get_icon_list(StringName p_node_type, List<StringName> *p_list) const;
+
+ void set_shader(const StringName &p_name, const StringName &p_node_type, const Ref<Shader> &p_shader);
+ Ref<Shader> get_shader(const StringName &p_name, const StringName &p_node_type) const;
+ bool has_shader(const StringName &p_name, const StringName &p_node_type) const;
+ void clear_shader(const StringName &p_name, const StringName &p_node_type);
+ void get_shader_list(const StringName &p_node_type, List<StringName> *p_list) const;
+
+ void set_stylebox(const StringName &p_name, const StringName &p_node_type, const Ref<StyleBox> &p_style);
+ Ref<StyleBox> get_stylebox(const StringName &p_name, const StringName &p_node_type) const;
+ bool has_stylebox(const StringName &p_name, const StringName &p_node_type) const;
+ void clear_stylebox(const StringName &p_name, const StringName &p_node_type);
+ void get_stylebox_list(StringName p_node_type, List<StringName> *p_list) const;
void get_stylebox_types(List<StringName> *p_list) const;
- void set_font(const StringName &p_name, const StringName &p_type, const Ref<Font> &p_font);
- Ref<Font> get_font(const StringName &p_name, const StringName &p_type) const;
- bool has_font(const StringName &p_name, const StringName &p_type) const;
- void clear_font(const StringName &p_name, const StringName &p_type);
- void get_font_list(StringName p_type, List<StringName> *p_list) const;
-
- void set_color(const StringName &p_name, const StringName &p_type, const Color &p_color);
- Color get_color(const StringName &p_name, const StringName &p_type) const;
- bool has_color(const StringName &p_name, const StringName &p_type) const;
- void clear_color(const StringName &p_name, const StringName &p_type);
- void get_color_list(StringName p_type, List<StringName> *p_list) const;
-
- void set_constant(const StringName &p_name, const StringName &p_type, int p_constant);
- int get_constant(const StringName &p_name, const StringName &p_type) const;
- bool has_constant(const StringName &p_name, const StringName &p_type) const;
- void clear_constant(const StringName &p_name, const StringName &p_type);
- void get_constant_list(StringName p_type, List<StringName> *p_list) const;
+ void set_font(const StringName &p_name, const StringName &p_node_type, const Ref<Font> &p_font);
+ Ref<Font> get_font(const StringName &p_name, const StringName &p_node_type) const;
+ bool has_font(const StringName &p_name, const StringName &p_node_type) const;
+ void clear_font(const StringName &p_name, const StringName &p_node_type);
+ void get_font_list(StringName p_node_type, List<StringName> *p_list) const;
+
+ void set_color(const StringName &p_name, const StringName &p_node_type, const Color &p_color);
+ Color get_color(const StringName &p_name, const StringName &p_node_type) const;
+ bool has_color(const StringName &p_name, const StringName &p_node_type) const;
+ void clear_color(const StringName &p_name, const StringName &p_node_type);
+ void get_color_list(StringName p_node_type, List<StringName> *p_list) const;
+
+ void set_constant(const StringName &p_name, const StringName &p_node_type, int p_constant);
+ int get_constant(const StringName &p_name, const StringName &p_node_type) const;
+ bool has_constant(const StringName &p_name, const StringName &p_node_type) const;
+ void clear_constant(const StringName &p_name, const StringName &p_node_type);
+ void get_constant_list(StringName p_node_type, List<StringName> *p_list) const;
void get_type_list(List<StringName> *p_list) const;
diff --git a/servers/physics_2d/physics_server_2d_sw.cpp b/servers/physics_2d/physics_server_2d_sw.cpp
index 9d00d01759..755804fe36 100644
--- a/servers/physics_2d/physics_server_2d_sw.cpp
+++ b/servers/physics_2d/physics_server_2d_sw.cpp
@@ -1230,8 +1230,6 @@ void PhysicsServer2DSW::step(real_t p_step) {
_update_shapes();
- doing_sync = false;
-
last_step = p_step;
PhysicsDirectBodyState2DSW::singleton->step = p_step;
island_count = 0;
diff --git a/servers/physics_3d/physics_server_3d_sw.cpp b/servers/physics_3d/physics_server_3d_sw.cpp
index 143cc9ebbd..07a7498fec 100644
--- a/servers/physics_3d/physics_server_3d_sw.cpp
+++ b/servers/physics_3d/physics_server_3d_sw.cpp
@@ -174,7 +174,7 @@ real_t PhysicsServer3DSW::space_get_param(RID p_space, SpaceParameter p_param) c
PhysicsDirectSpaceState3D *PhysicsServer3DSW::space_get_direct_state(RID p_space) {
Space3DSW *space = space_owner.getornull(p_space);
ERR_FAIL_COND_V(!space, nullptr);
- ERR_FAIL_COND_V_MSG(!doing_sync || space->is_locked(), nullptr, "Space state is inaccessible right now, wait for iteration or physics process notification.");
+ ERR_FAIL_COND_V_MSG(space->is_locked(), nullptr, "Space state is inaccessible right now, wait for iteration or physics process notification.");
return space->get_direct_state();
}
@@ -888,7 +888,7 @@ int PhysicsServer3DSW::body_test_ray_separation(RID p_body, const Transform &p_t
PhysicsDirectBodyState3D *PhysicsServer3DSW::body_get_direct_state(RID p_body) {
Body3DSW *body = body_owner.getornull(p_body);
ERR_FAIL_COND_V(!body, nullptr);
- ERR_FAIL_COND_V_MSG(!doing_sync || body->get_space()->is_locked(), nullptr, "Body state is inaccessible right now, wait for iteration or physics process notification.");
+ 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;
@@ -1287,7 +1287,6 @@ void PhysicsServer3DSW::set_active(bool p_active) {
};
void PhysicsServer3DSW::init() {
- doing_sync = true;
last_step = 0.001;
iterations = 8; // 8?
stepper = memnew(Step3DSW);
@@ -1303,8 +1302,6 @@ void PhysicsServer3DSW::step(real_t p_step) {
_update_shapes();
- doing_sync = false;
-
last_step = p_step;
PhysicsDirectBodyState3DSW::singleton->step = p_step;
@@ -1327,8 +1324,6 @@ void PhysicsServer3DSW::flush_queries() {
return;
}
- doing_sync = true;
-
flushing_queries = true;
uint64_t time_beg = OS::get_singleton()->get_ticks_usec();
diff --git a/servers/physics_3d/physics_server_3d_sw.h b/servers/physics_3d/physics_server_3d_sw.h
index a45ee7c2c8..f96a8863c3 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;
- bool doing_sync;
real_t last_step;
int island_count;
@@ -365,7 +364,6 @@ public:
virtual void set_active(bool p_active) override;
virtual void init() override;
virtual void step(real_t p_step) override;
- virtual void sync() override {}
virtual void flush_queries() override;
virtual void finish() override;
diff --git a/servers/physics_server_3d.h b/servers/physics_server_3d.h
index 90ef2bb8dd..73fc4b649d 100644
--- a/servers/physics_server_3d.h
+++ b/servers/physics_server_3d.h
@@ -749,7 +749,6 @@ public:
virtual void set_active(bool p_active) = 0;
virtual void init() = 0;
virtual void step(float p_step) = 0;
- virtual void sync() = 0;
virtual void flush_queries() = 0;
virtual void finish() = 0;
diff --git a/servers/rendering/rendering_server_canvas.cpp b/servers/rendering/rendering_server_canvas.cpp
index 4480b79f75..5a12be5659 100644
--- a/servers/rendering/rendering_server_canvas.cpp
+++ b/servers/rendering/rendering_server_canvas.cpp
@@ -115,7 +115,7 @@ void RenderingServerCanvas::_cull_canvas_item(Item *p_canvas_item, const Transfo
Rect2 rect = ci->get_rect();
Transform2D xform = ci->xform;
if (snapping_2d_transforms_to_pixel) {
- xform.elements[2].floor();
+ xform.elements[2] = xform.elements[2].floor();
}
xform = p_transform * xform;
diff --git a/tests/test_config_file.h b/tests/test_config_file.h
new file mode 100644
index 0000000000..6d163a095e
--- /dev/null
+++ b/tests/test_config_file.h
@@ -0,0 +1,157 @@
+/*************************************************************************/
+/* test_config_file.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 TEST_CONFIG_FILE_H
+#define TEST_CONFIG_FILE_H
+
+#include "core/io/config_file.h"
+
+#include "tests/test_macros.h"
+
+namespace TestConfigFile {
+
+TEST_CASE("[ConfigFile] Parsing well-formatted files") {
+ ConfigFile config_file;
+ // Formatting is intentionally hand-edited to see how human-friendly the parser is.
+ const Error error = config_file.parse(R"(
+[player]
+
+name = "Unnamed Player"
+tagline="Waiting
+for
+Godot"
+
+color =Color( 0, 0.5,1, 1) ; Inline comment
+position= Vector2(
+ 3,
+ 4
+)
+
+[graphics]
+
+antialiasing = true
+
+; Testing comments and case-sensitivity...
+antiAliasing = false
+)");
+
+ CHECK_MESSAGE(error == OK, "The configuration file should parse successfully.");
+ CHECK_MESSAGE(
+ String(config_file.get_value("player", "name")) == "Unnamed Player",
+ "Reading `player/name` should return the expected value.");
+ CHECK_MESSAGE(
+ String(config_file.get_value("player", "tagline")) == "Waiting\nfor\nGodot",
+ "Reading `player/tagline` should return the expected value.");
+ CHECK_MESSAGE(
+ Color(config_file.get_value("player", "color")).is_equal_approx(Color(0, 0.5, 1)),
+ "Reading `player/color` should return the expected value.");
+ CHECK_MESSAGE(
+ Vector2(config_file.get_value("player", "position")).is_equal_approx(Vector2(3, 4)),
+ "Reading `player/position` should return the expected value.");
+ CHECK_MESSAGE(
+ bool(config_file.get_value("graphics", "antialiasing")),
+ "Reading `graphics/antialiasing` should return `true`.");
+ CHECK_MESSAGE(
+ bool(config_file.get_value("graphics", "antiAliasing")) == false,
+ "Reading `graphics/antiAliasing` should return `false`.");
+
+ // An empty ConfigFile is valid.
+ const Error error_empty = config_file.parse("");
+ CHECK_MESSAGE(error_empty == OK,
+ "An empty configuration file should parse successfully.");
+}
+
+TEST_CASE("[ConfigFile] Parsing malformatted file") {
+ ConfigFile config_file;
+ ERR_PRINT_OFF;
+ const Error error = config_file.parse(R"(
+[player]
+
+name = "Unnamed Player"" ; Extraneous closing quote.
+tagline = "Waiting\nfor\nGodot"
+
+color = Color(0, 0.5, 1) ; Missing 4th parameter.
+position = Vector2(
+ 3,,
+ 4
+) ; Extraneous comma.
+
+[graphics]
+
+antialiasing = true
+antialiasing = false ; Duplicate key.
+)");
+ ERR_PRINT_ON;
+
+ CHECK_MESSAGE(error == ERR_PARSE_ERROR,
+ "The configuration file shouldn't parse successfully.");
+}
+
+TEST_CASE("[ConfigFile] Saving file") {
+ ConfigFile config_file;
+ config_file.set_value("player", "name", "Unnamed Player");
+ config_file.set_value("player", "tagline", "Waiting\nfor\nGodot");
+ config_file.set_value("player", "color", Color(0, 0.5, 1));
+ config_file.set_value("player", "position", Vector2(3, 4));
+ config_file.set_value("graphics", "antialiasing", true);
+ config_file.set_value("graphics", "antiAliasing", false);
+
+#ifdef WINDOWS_ENABLED
+ const String config_path = OS::get_singleton()->get_environment("TEMP").plus_file("config.ini");
+#else
+ const String config_path = "/tmp/config.ini";
+#endif
+
+ config_file.save(config_path);
+
+ // Expected contents of the saved ConfigFile.
+ const String contents = R"([player]
+
+name="Unnamed Player"
+tagline="Waiting
+for
+Godot"
+color=Color( 0, 0.5, 1, 1 )
+position=Vector2( 3, 4 )
+
+[graphics]
+
+antialiasing=true
+antiAliasing=false
+)";
+
+ FileAccessRef file = FileAccess::open(config_path, FileAccess::READ);
+ CHECK_MESSAGE(file->get_as_utf8_string() == contents,
+ "The saved configuration file should match the expected format.");
+}
+
+} // namespace TestConfigFile
+
+#endif // TEST_CONFIG_FILE_H
diff --git a/tests/test_curve.h b/tests/test_curve.h
new file mode 100644
index 0000000000..55f2d9a367
--- /dev/null
+++ b/tests/test_curve.h
@@ -0,0 +1,222 @@
+/*************************************************************************/
+/* test_curve.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 TEST_CURVE_H
+#define TEST_CURVE_H
+
+#include "scene/resources/curve.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestCurve {
+
+TEST_CASE("[Curve] Default curve") {
+ const Ref<Curve> curve = memnew(Curve);
+
+ CHECK_MESSAGE(
+ curve->get_point_count() == 0,
+ "Default curve should contain the expected number of points.");
+ CHECK_MESSAGE(
+ Math::is_zero_approx(curve->interpolate(0)),
+ "Default curve should return the expected value at offset 0.0.");
+ CHECK_MESSAGE(
+ Math::is_zero_approx(curve->interpolate(0.5)),
+ "Default curve should return the expected value at offset 0.5.");
+ CHECK_MESSAGE(
+ Math::is_zero_approx(curve->interpolate(1)),
+ "Default curve should return the expected value at offset 1.0.");
+}
+
+TEST_CASE("[Curve] Custom curve with free tangents") {
+ Ref<Curve> curve = memnew(Curve);
+ // "Sawtooth" curve with an open ending towards the 1.0 offset.
+ curve->add_point(Vector2(0, 0));
+ curve->add_point(Vector2(0.25, 1));
+ curve->add_point(Vector2(0.5, 0));
+ curve->add_point(Vector2(0.75, 1));
+
+ CHECK_MESSAGE(
+ Math::is_zero_approx(curve->get_point_left_tangent(0)),
+ "get_point_left_tangent() should return the expected value for point index 0.");
+ CHECK_MESSAGE(
+ Math::is_zero_approx(curve->get_point_right_tangent(0)),
+ "get_point_right_tangent() should return the expected value for point index 0.");
+ CHECK_MESSAGE(
+ curve->get_point_left_mode(0) == Curve::TangentMode::TANGENT_FREE,
+ "get_point_left_mode() should return the expected value for point index 0.");
+ CHECK_MESSAGE(
+ curve->get_point_right_mode(0) == Curve::TangentMode::TANGENT_FREE,
+ "get_point_right_mode() should return the expected value for point index 0.");
+
+ CHECK_MESSAGE(
+ curve->get_point_count() == 4,
+ "Custom free curve should contain the expected number of points.");
+
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(-0.1), 0),
+ "Custom free curve should return the expected value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.1), 0.352),
+ "Custom free curve should return the expected value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.4), 0.352),
+ "Custom free curve should return the expected value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.7), 0.896),
+ "Custom free curve should return the expected value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(1), 1),
+ "Custom free curve should return the expected value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(2), 1),
+ "Custom free curve should return the expected value at offset 0.1.");
+
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(-0.1), 0),
+ "Custom free curve should return the expected baked value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.1), 0.352),
+ "Custom free curve should return the expected baked value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.4), 0.352),
+ "Custom free curve should return the expected baked value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.7), 0.896),
+ "Custom free curve should return the expected baked value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(1), 1),
+ "Custom free curve should return the expected baked value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(2), 1),
+ "Custom free curve should return the expected baked value at offset 0.1.");
+
+ curve->remove_point(1);
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.1), 0),
+ "Custom free curve should return the expected value at offset 0.1 after removing point at index 1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.1), 0),
+ "Custom free curve should return the expected baked value at offset 0.1 after removing point at index 1.");
+
+ curve->clear_points();
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.6), 0),
+ "Custom free curve should return the expected value at offset 0.6 after clearing all points.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.6), 0),
+ "Custom free curve should return the expected baked value at offset 0.6 after clearing all points.");
+}
+
+TEST_CASE("[Curve] Custom curve with linear tangents") {
+ Ref<Curve> curve = memnew(Curve);
+ // "Sawtooth" curve with an open ending towards the 1.0 offset.
+ curve->add_point(Vector2(0, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
+ curve->add_point(Vector2(0.25, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
+ curve->add_point(Vector2(0.5, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
+ curve->add_point(Vector2(0.75, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR);
+
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->get_point_left_tangent(3), 4),
+ "get_point_left_tangent() should return the expected value for point index 3.");
+ CHECK_MESSAGE(
+ Math::is_zero_approx(curve->get_point_right_tangent(3)),
+ "get_point_right_tangent() should return the expected value for point index 3.");
+ CHECK_MESSAGE(
+ curve->get_point_left_mode(3) == Curve::TangentMode::TANGENT_LINEAR,
+ "get_point_left_mode() should return the expected value for point index 3.");
+ CHECK_MESSAGE(
+ curve->get_point_right_mode(3) == Curve::TangentMode::TANGENT_LINEAR,
+ "get_point_right_mode() should return the expected value for point index 3.");
+
+ ERR_PRINT_OFF;
+ CHECK_MESSAGE(
+ Math::is_zero_approx(curve->get_point_right_tangent(300)),
+ "get_point_right_tangent() should return the expected value for invalid point index 300.");
+ CHECK_MESSAGE(
+ curve->get_point_left_mode(-12345) == Curve::TangentMode::TANGENT_FREE,
+ "get_point_left_mode() should return the expected value for invalid point index -12345.");
+ ERR_PRINT_ON;
+
+ CHECK_MESSAGE(
+ curve->get_point_count() == 4,
+ "Custom linear curve should contain the expected number of points.");
+
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(-0.1), 0),
+ "Custom linear curve should return the expected value at offset -0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.1), 0.4),
+ "Custom linear curve should return the expected value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.4), 0.4),
+ "Custom linear curve should return the expected value at offset 0.4.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.7), 0.8),
+ "Custom linear curve should return the expected value at offset 0.7.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(1), 1),
+ "Custom linear curve should return the expected value at offset 1.0.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(2), 1),
+ "Custom linear curve should return the expected value at offset 2.0.");
+
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(-0.1), 0),
+ "Custom linear curve should return the expected baked value at offset -0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.1), 0.4),
+ "Custom linear curve should return the expected baked value at offset 0.1.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.4), 0.4),
+ "Custom linear curve should return the expected baked value at offset 0.4.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.7), 0.8),
+ "Custom linear curve should return the expected baked value at offset 0.7.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(1), 1),
+ "Custom linear curve should return the expected baked value at offset 1.0.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(2), 1),
+ "Custom linear curve should return the expected baked value at offset 2.0.");
+
+ ERR_PRINT_OFF;
+ curve->remove_point(10);
+ ERR_PRINT_ON;
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate(0.7), 0.8),
+ "Custom free curve should return the expected value at offset 0.7 after removing point at invalid index 10.");
+ CHECK_MESSAGE(
+ Math::is_equal_approx(curve->interpolate_baked(0.7), 0.8),
+ "Custom free curve should return the expected baked value at offset 0.7 after removing point at invalid index 10.");
+}
+
+} // namespace TestCurve
+
+#endif // TEST_CURVE_H
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index b4ddf0f1c1..cd7f1d3eac 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -37,14 +37,18 @@
#include "test_class_db.h"
#include "test_color.h"
#include "test_command_queue.h"
+#include "test_config_file.h"
+#include "test_curve.h"
#include "test_expression.h"
#include "test_gradient.h"
#include "test_gui.h"
#include "test_list.h"
#include "test_math.h"
#include "test_method_bind.h"
+#include "test_node_path.h"
#include "test_oa_hash_map.h"
#include "test_ordered_hash_map.h"
+#include "test_pck_packer.h"
#include "test_physics_2d.h"
#include "test_physics_3d.h"
#include "test_render.h"
diff --git a/tests/test_node_path.h b/tests/test_node_path.h
new file mode 100644
index 0000000000..fdfff8d4c7
--- /dev/null
+++ b/tests/test_node_path.h
@@ -0,0 +1,173 @@
+/*************************************************************************/
+/* test_node_path.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 TEST_NODE_PATH_H
+#define TEST_NODE_PATH_H
+
+#include "core/string/node_path.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestNodePath {
+
+TEST_CASE("[NodePath] Relative path") {
+ const NodePath node_path_relative = NodePath("Path2D/PathFollow2D/Sprite2D:position:x");
+
+ CHECK_MESSAGE(
+ node_path_relative.get_as_property_path() == NodePath(":Path2D/PathFollow2D/Sprite2D:position:x"),
+ "The returned property path should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_relative.get_concatenated_subnames() == "position:x",
+ "The returned concatenated subnames should match the expected value.");
+
+ CHECK_MESSAGE(
+ node_path_relative.get_name(0) == "Path2D",
+ "The returned name at index 0 should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_relative.get_name(1) == "PathFollow2D",
+ "The returned name at index 1 should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_relative.get_name(2) == "Sprite2D",
+ "The returned name at index 2 should match the expected value.");
+ ERR_PRINT_OFF;
+ CHECK_MESSAGE(
+ node_path_relative.get_name(3) == "",
+ "The returned name at invalid index 3 should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_relative.get_name(-1) == "",
+ "The returned name at invalid index -1 should match the expected value.");
+ ERR_PRINT_ON;
+
+ CHECK_MESSAGE(
+ node_path_relative.get_name_count() == 3,
+ "The returned number of names should match the expected value.");
+
+ CHECK_MESSAGE(
+ node_path_relative.get_subname(0) == "position",
+ "The returned subname at index 0 should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_relative.get_subname(1) == "x",
+ "The returned subname at index 1 should match the expected value.");
+ ERR_PRINT_OFF;
+ CHECK_MESSAGE(
+ node_path_relative.get_subname(2) == "",
+ "The returned subname at invalid index 2 should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_relative.get_subname(-1) == "",
+ "The returned subname at invalid index -1 should match the expected value.");
+ ERR_PRINT_ON;
+
+ CHECK_MESSAGE(
+ node_path_relative.get_subname_count() == 2,
+ "The returned number of subnames should match the expected value.");
+
+ CHECK_MESSAGE(
+ !node_path_relative.is_absolute(),
+ "The node path should be considered relative.");
+
+ CHECK_MESSAGE(
+ !node_path_relative.is_empty(),
+ "The node path shouldn't be considered empty.");
+}
+
+TEST_CASE("[NodePath] Absolute path") {
+ const NodePath node_path_aboslute = NodePath("/root/Sprite2D");
+
+ CHECK_MESSAGE(
+ node_path_aboslute.get_as_property_path() == NodePath(":root/Sprite2D"),
+ "The returned property path should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_aboslute.get_concatenated_subnames() == "",
+ "The returned concatenated subnames should match the expected value.");
+
+ CHECK_MESSAGE(
+ node_path_aboslute.get_name(0) == "root",
+ "The returned name at index 0 should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_aboslute.get_name(1) == "Sprite2D",
+ "The returned name at index 1 should match the expected value.");
+ ERR_PRINT_OFF;
+ CHECK_MESSAGE(
+ node_path_aboslute.get_name(2) == "",
+ "The returned name at invalid index 2 should match the expected value.");
+ CHECK_MESSAGE(
+ node_path_aboslute.get_name(-1) == "",
+ "The returned name at invalid index -1 should match the expected value.");
+ ERR_PRINT_ON;
+
+ CHECK_MESSAGE(
+ node_path_aboslute.get_name_count() == 2,
+ "The returned number of names should match the expected value.");
+
+ CHECK_MESSAGE(
+ node_path_aboslute.get_subname_count() == 0,
+ "The returned number of subnames should match the expected value.");
+
+ CHECK_MESSAGE(
+ node_path_aboslute.is_absolute(),
+ "The node path should be considered absolute.");
+
+ CHECK_MESSAGE(
+ !node_path_aboslute.is_empty(),
+ "The node path shouldn't be considered empty.");
+}
+
+TEST_CASE("[NodePath] Empty path") {
+ const NodePath node_path_empty = NodePath();
+
+ CHECK_MESSAGE(
+ node_path_empty.get_as_property_path() == NodePath(),
+ "The returned property path should match the expected value.");
+ ERR_PRINT_OFF;
+ CHECK_MESSAGE(
+ node_path_empty.get_concatenated_subnames() == "",
+ "The returned concatenated subnames should match the expected value.");
+ ERR_PRINT_ON;
+
+ CHECK_MESSAGE(
+ node_path_empty.get_name_count() == 0,
+ "The returned number of names should match the expected value.");
+
+ CHECK_MESSAGE(
+ node_path_empty.get_subname_count() == 0,
+ "The returned number of subnames should match the expected value.");
+
+ CHECK_MESSAGE(
+ !node_path_empty.is_absolute(),
+ "The node path shouldn't be considered absolute.");
+
+ CHECK_MESSAGE(
+ node_path_empty.is_empty(),
+ "The node path should be considered empty.");
+}
+
+} // namespace TestNodePath
+
+#endif // TEST_NODE_PATH_H
diff --git a/tests/test_pck_packer.h b/tests/test_pck_packer.h
new file mode 100644
index 0000000000..17904d8d8a
--- /dev/null
+++ b/tests/test_pck_packer.h
@@ -0,0 +1,115 @@
+/*************************************************************************/
+/* test_pck_packer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 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 TEST_PCK_PACKER_H
+#define TEST_PCK_PACKER_H
+
+#include "core/io/file_access_pack.h"
+#include "core/io/pck_packer.h"
+#include "core/os/os.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestPCKPacker {
+
+// Dummy 64-character encryption key (since it's required).
+constexpr const char *ENCRYPTION_KEY = "0000000000000000000000000000000000000000000000000000000000000000";
+
+TEST_CASE("[PCKPacker] Pack an empty PCK file") {
+ PCKPacker pck_packer;
+ const String output_pck_path = OS::get_singleton()->get_cache_path().plus_file("output_empty.pck");
+ CHECK_MESSAGE(
+ pck_packer.pck_start(
+ output_pck_path,
+ 32,
+ ENCRYPTION_KEY) == OK,
+ "Starting a PCK file should return an OK error code.");
+
+ CHECK_MESSAGE(
+ pck_packer.flush() == OK,
+ "Flushing the PCK should return an OK error code.");
+
+ Error err;
+ FileAccessRef f = FileAccess::open(output_pck_path, FileAccess::READ, &err);
+ CHECK_MESSAGE(
+ err == OK,
+ "The generated empty PCK file should be opened successfully.");
+ CHECK_MESSAGE(
+ f->get_len() >= 100,
+ "The generated empty PCK file shouldn't be too small (it should have the PCK header).");
+ CHECK_MESSAGE(
+ f->get_len() <= 500,
+ "The generated empty PCK file shouldn't be too large.");
+}
+
+TEST_CASE("[PCKPacker] Pack a PCK file with some files and directories") {
+ PCKPacker pck_packer;
+ const String output_pck_path = OS::get_singleton()->get_cache_path().plus_file("output_with_files.pck");
+ CHECK_MESSAGE(
+ pck_packer.pck_start(
+ output_pck_path,
+ 32,
+ ENCRYPTION_KEY) == OK,
+ "Starting a PCK file should return an OK error code.");
+
+ const String base_dir = OS::get_singleton()->get_executable_path().get_base_dir();
+
+ CHECK_MESSAGE(
+ pck_packer.add_file("version.py", base_dir.plus_file("../version.py"), "version.py") == OK,
+ "Adding a file to the PCK should return an OK error code.");
+ CHECK_MESSAGE(
+ pck_packer.add_file("some/directories with spaces/to/create/icon.png", base_dir.plus_file("../icon.png")) == OK,
+ "Adding a file to a new subdirectory in the PCK should return an OK error code.");
+ CHECK_MESSAGE(
+ pck_packer.add_file("some/directories with spaces/to/create/icon.svg", base_dir.plus_file("../icon.svg")) == OK,
+ "Adding a file to an existing subdirectory in the PCK should return an OK error code.");
+ CHECK_MESSAGE(
+ pck_packer.add_file("some/directories with spaces/to/create/icon.png", base_dir.plus_file("../logo.png")) == OK,
+ "Overriding a non-flushed file to an existing subdirectory in the PCK should return an OK error code.");
+ CHECK_MESSAGE(
+ pck_packer.flush() == OK,
+ "Flushing the PCK should return an OK error code.");
+
+ Error err;
+ FileAccessRef f = FileAccess::open(output_pck_path, FileAccess::READ, &err);
+ CHECK_MESSAGE(
+ err == OK,
+ "The generated non-empty PCK file should be opened successfully.");
+ CHECK_MESSAGE(
+ f->get_len() >= 25000,
+ "The generated non-empty PCK file should be large enough to actually hold the contents specified above.");
+ CHECK_MESSAGE(
+ f->get_len() <= 35000,
+ "The generated non-empty PCK file shouldn't be too large.");
+}
+
+} // namespace TestPCKPacker
+
+#endif // TEST_PCK_PACKER_H