summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md11
-rw-r--r--SConstruct68
-rw-r--r--core/oa_hash_map.h42
-rw-r--r--core/project_settings.cpp67
-rw-r--r--editor/SCsub26
-rw-r--r--editor/animation_track_editor.cpp4
-rw-r--r--editor/editor_feature_profile.cpp36
-rw-r--r--editor/editor_feature_profile.h3
-rw-r--r--editor/editor_settings.cpp37
-rw-r--r--editor/icons/SCsub9
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp196
-rw-r--r--editor/plugins/node_3d_editor_plugin.h1
-rw-r--r--main/main.cpp6
-rw-r--r--main/tests/test_math.cpp2
-rw-r--r--main/tests/test_oa_hash_map.cpp83
-rw-r--r--methods.py69
-rw-r--r--modules/SCsub13
-rw-r--r--modules/gdscript/gdscript_editor.cpp6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs2
-rw-r--r--platform/android/export/export.cpp29
-rw-r--r--platform/android/java/app/build.gradle5
-rw-r--r--platform/android/plugin/godot_plugin_config.h16
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp5
-rw-r--r--platform/javascript/audio_driver_javascript.cpp4
-rw-r--r--scene/debugger/scene_debugger.cpp9
-rw-r--r--scene/gui/color_picker.cpp2
26 files changed, 560 insertions, 191 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 91528465c0..7e8c5fd740 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -6,13 +6,24 @@ labels: ''
assignees: ''
---
+<!-- Please search existing issues for potential duplicates before filing yours:
+https://github.com/godotengine/godot/issues?q=is%3Aissue
+-->
**Godot version:**
+<!-- Specify commit hash if using non-official build. -->
+
**OS/device including version:**
+<!-- Specify GPU model, drivers, and the backend (GLES2, GLES3, Vulkan) if graphics-related. -->
+
**Issue description:**
+<!-- What happened, and what was expected. -->
+
**Steps to reproduce:**
+
**Minimal reproduction project:**
+<!-- A small Godot project which reproduces the issue. Drag and drop a zip archive to upload it. -->
diff --git a/SConstruct b/SConstruct
index f74940b059..515cad57d0 100644
--- a/SConstruct
+++ b/SConstruct
@@ -50,8 +50,6 @@ for x in sorted(glob.glob("platform/*")):
sys.path.remove(tmppath)
sys.modules.pop("detect")
-module_list = methods.detect_modules()
-
methods.save_active_platforms(active_platforms, active_platform_ids)
custom_tools = ["default"]
@@ -123,6 +121,7 @@ opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise e
opts.Add(BoolVariable("deprecated", "Enable deprecated features", True))
opts.Add(BoolVariable("minizip", "Enable ZIP archive support using minizip", True))
opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver", False))
+opts.Add("custom_modules", "A list of comma-separated directory paths containing custom modules to build.", "")
# Advanced options
opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
@@ -181,18 +180,41 @@ for k in platform_opts.keys():
for o in opt_list:
opts.Add(o)
-for x in module_list:
- module_enabled = True
- tmppath = "./modules/" + x
- sys.path.insert(0, tmppath)
+# Detect modules.
+modules_detected = {}
+module_search_paths = ["modules"] # Built-in path.
+
+if ARGUMENTS.get("custom_modules"):
+ paths = ARGUMENTS.get("custom_modules").split(",")
+ for p in paths:
+ try:
+ module_search_paths.append(methods.convert_custom_modules_path(p))
+ except ValueError as e:
+ print(e)
+ sys.exit(255)
+
+for path in module_search_paths:
+ # Note: custom modules can override built-in ones.
+ modules_detected.update(methods.detect_modules(path))
+ include_path = os.path.dirname(path)
+ if include_path:
+ env_base.Prepend(CPPPATH=[include_path])
+
+# Add module options.
+for name, path in modules_detected.items():
+ enabled = True
+ sys.path.insert(0, path)
import config
- enabled_attr = getattr(config, "is_enabled", None)
- if callable(enabled_attr) and not config.is_enabled():
- module_enabled = False
- sys.path.remove(tmppath)
+ try:
+ enabled = config.is_enabled()
+ except AttributeError:
+ pass
+ sys.path.remove(path)
sys.modules.pop("config")
- opts.Add(BoolVariable("module_" + x + "_enabled", "Enable module '%s'" % (x,), module_enabled))
+ opts.Add(BoolVariable("module_" + name + "_enabled", "Enable module '%s'" % (name,), enabled))
+
+methods.write_modules(modules_detected)
opts.Update(env_base) # update environment
Help(opts.GenerateHelpText(env_base)) # generate help
@@ -501,41 +523,41 @@ if selected_platform in platform_list:
sys.path.remove(tmppath)
sys.modules.pop("detect")
- env.module_list = []
+ modules_enabled = {}
env.module_icons_paths = []
env.doc_class_path = {}
- for x in sorted(module_list):
- if not env["module_" + x + "_enabled"]:
+ for name, path in sorted(modules_detected.items()):
+ if not env["module_" + name + "_enabled"]:
continue
- tmppath = "./modules/" + x
- sys.path.insert(0, tmppath)
- env.current_module = x
+ sys.path.insert(0, path)
+ env.current_module = name
import config
if config.can_build(env, selected_platform):
config.configure(env)
- env.module_list.append(x)
-
# Get doc classes paths (if present)
try:
doc_classes = config.get_doc_classes()
doc_path = config.get_doc_path()
for c in doc_classes:
- env.doc_class_path[c] = "modules/" + x + "/" + doc_path
+ env.doc_class_path[c] = path + "/" + doc_path
except:
pass
# Get icon paths (if present)
try:
icons_path = config.get_icons_path()
- env.module_icons_paths.append("modules/" + x + "/" + icons_path)
+ env.module_icons_paths.append(path + "/" + icons_path)
except:
# Default path for module icons
- env.module_icons_paths.append("modules/" + x + "/" + "icons")
+ env.module_icons_paths.append(path + "/" + "icons")
+ modules_enabled[name] = path
- sys.path.remove(tmppath)
+ sys.path.remove(path)
sys.modules.pop("config")
+ env.module_list = modules_enabled
+
methods.update_version(env.module_version_string)
env["PROGSUFFIX"] = suffix + env.module_version_string + env["PROGSUFFIX"]
diff --git a/core/oa_hash_map.h b/core/oa_hash_map.h
index c595e445d5..775e17fdb5 100644
--- a/core/oa_hash_map.h
+++ b/core/oa_hash_map.h
@@ -48,17 +48,19 @@
*
* Only used keys and values are constructed. For free positions there's space
* in the arrays for each, but that memory is kept uninitialized.
+ *
+ * The assignment operator copy the pairs from one map to the other.
*/
template <class TKey, class TValue,
class Hasher = HashMapHasherDefault,
class Comparator = HashMapComparatorDefault<TKey>>
class OAHashMap {
private:
- TValue *values;
- TKey *keys;
- uint32_t *hashes;
+ TValue *values = nullptr;
+ TKey *keys = nullptr;
+ uint32_t *hashes = nullptr;
- uint32_t capacity;
+ uint32_t capacity = 0;
uint32_t num_elements = 0;
@@ -142,7 +144,9 @@ private:
void _resize_and_rehash(uint32_t p_new_capacity) {
uint32_t old_capacity = capacity;
- capacity = p_new_capacity;
+
+ // Capacity can't be 0.
+ capacity = MAX(1, p_new_capacity);
TKey *old_keys = keys;
TValue *old_values = values;
@@ -157,6 +161,11 @@ private:
hashes[i] = 0;
}
+ if (old_capacity == 0) {
+ // Nothing to do.
+ return;
+ }
+
for (uint32_t i = 0; i < old_capacity; i++) {
if (old_hashes[i] == EMPTY_HASH) {
continue;
@@ -341,17 +350,32 @@ public:
return it;
}
- OAHashMap(const OAHashMap &) = delete; // Delete the copy constructor so we don't get unexpected copies and dangling pointers.
- OAHashMap &operator=(const OAHashMap &) = delete; // Same for assignment operator.
+ OAHashMap(const OAHashMap &p_other) {
+ (*this) = p_other;
+ }
+
+ OAHashMap &operator=(const OAHashMap &p_other) {
+ if (capacity != 0) {
+ clear();
+ }
+
+ _resize_and_rehash(p_other.capacity);
+
+ for (Iterator it = p_other.iter(); it.valid; it = p_other.next_iter(it)) {
+ set(*it.key, *it.value);
+ }
+ return *this;
+ }
OAHashMap(uint32_t p_initial_capacity = 64) {
- capacity = p_initial_capacity;
+ // Capacity can't be 0.
+ capacity = MAX(1, p_initial_capacity);
keys = static_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
values = static_cast<TValue *>(Memory::alloc_static(sizeof(TValue) * capacity));
hashes = static_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
- for (uint32_t i = 0; i < p_initial_capacity; i++) {
+ for (uint32_t i = 0; i < capacity; i++) {
hashes[i] = EMPTY_HASH;
}
}
diff --git a/core/project_settings.cpp b/core/project_settings.cpp
index 83d94ad607..5247f6da40 100644
--- a/core/project_settings.cpp
+++ b/core/project_settings.cpp
@@ -295,10 +295,16 @@ void ProjectSettings::_convert_to_last_version(int p_from_version) {
* using the following merit order:
* - If using NetworkClient, try to lookup project file or fail.
* - If --main-pack was passed by the user (`p_main_pack`), load it or fail.
- * - Search for .pck file matching binary name. There are two possibilities:
- * o exec_path.get_basename() + '.pck' (e.g. 'win_game.exe' -> 'win_game.pck')
- * o exec_path + '.pck' (e.g. 'linux_game' -> 'linux_game.pck')
- * For each tentative, if the file exists, load it or fail.
+ * - Search for project PCKs automatically. For each step we try loading a potential
+ * PCK, and if it doesn't work, we proceed to the next step. If any step succeeds,
+ * we try loading the project settings, and abort if it fails. Steps:
+ * o Bundled PCK in the executable.
+ * o [macOS only] PCK with same basename as the binary in the .app resource dir.
+ * o PCK with same basename as the binary in the binary's directory. We handle both
+ * changing the extension to '.pck' (e.g. 'win_game.exe' -> 'win_game.pck') and
+ * appending '.pck' to the binary name (e.g. 'linux_game' -> 'linux_game.pck').
+ * o PCK with the same basename as the binary in the current working directory.
+ * Same as above for the two possible PCK file names.
* - On relevant platforms (Android/iOS), lookup project file in OS resource path.
* If found, load it or fail.
* - Lookup project file in passed `p_path` (--path passed by the user), i.e. we
@@ -339,65 +345,68 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
String exec_path = OS::get_singleton()->get_executable_path();
if (exec_path != "") {
- // Attempt with exec_name.pck
- // (This is the usual case when distributing a Godot game.)
+ // We do several tests sequentially until one succeeds to find a PCK,
+ // and if so we attempt loading it at the end.
- // Based on the OS, it can be the exec path + '.pck' (Linux w/o extension, macOS in .app bundle)
- // or the exec path's basename + '.pck' (Windows).
- // We need to test both possibilities as extensions for Linux binaries are optional
- // (so both 'mygame.bin' and 'mygame' should be able to find 'mygame.pck').
+ // Attempt with PCK bundled into executable.
+ bool found = _load_resource_pack(exec_path);
+ // Attempt with exec_name.pck.
+ // (This is the usual case when distributing a Godot game.)
String exec_dir = exec_path.get_base_dir();
String exec_filename = exec_path.get_file();
String exec_basename = exec_filename.get_basename();
- // Attempt with PCK bundled into executable
- bool found = _load_resource_pack(exec_path);
+ // Based on the OS, it can be the exec path + '.pck' (Linux w/o extension, macOS in .app bundle)
+ // or the exec path's basename + '.pck' (Windows).
+ // We need to test both possibilities as extensions for Linux binaries are optional
+ // (so both 'mygame.bin' and 'mygame' should be able to find 'mygame.pck').
#ifdef OSX_ENABLED
if (!found) {
- // Attempt to load PCK from macOS .app bundle resources
+ // Attempt to load PCK from macOS .app bundle resources.
found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().plus_file(exec_basename + ".pck"));
}
#endif
if (!found) {
- // Try to load data pack at the location of the executable
- // As mentioned above, we have two potential names to attempt
+ // Try to load data pack at the location of the executable.
+ // As mentioned above, we have two potential names to attempt.
found = _load_resource_pack(exec_dir.plus_file(exec_basename + ".pck")) || _load_resource_pack(exec_dir.plus_file(exec_filename + ".pck"));
+ }
- if (!found) {
- // If we couldn't find them next to the executable, we attempt
- // the current working directory. Same story, two tests.
- found = _load_resource_pack(exec_basename + ".pck") || _load_resource_pack(exec_filename + ".pck");
- }
+ if (!found) {
+ // If we couldn't find them next to the executable, we attempt
+ // the current working directory. Same story, two tests.
+ found = _load_resource_pack(exec_basename + ".pck") || _load_resource_pack(exec_filename + ".pck");
}
- // If we opened our package, try and load our project
+ // If we opened our package, try and load our project.
if (found) {
Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary");
if (err == OK) {
- // Load override from location of executable
- // Optional, we don't mind if it fails
+ // Load override from location of the executable.
+ // Optional, we don't mind if it fails.
_load_settings_text(exec_path.get_base_dir().plus_file("override.cfg"));
}
return err;
}
}
- // Try to use the filesystem for files, according to OS. (only Android -when reading from pck- and iOS use this)
+ // Try to use the filesystem for files, according to OS.
+ // (Only Android -when reading from pck- and iOS use this.)
if (OS::get_singleton()->get_resource_dir() != "") {
// OS will call ProjectSettings->get_resource_path which will be empty if not overridden!
// If the OS would rather use a specific location, then it will not be empty.
resource_path = OS::get_singleton()->get_resource_dir().replace("\\", "/");
if (resource_path != "" && resource_path[resource_path.length() - 1] == '/') {
- resource_path = resource_path.substr(0, resource_path.length() - 1); // chop end
+ resource_path = resource_path.substr(0, resource_path.length() - 1); // Chop end.
}
Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary");
if (err == OK) {
- // Optional, we don't mind if it fails
+ // Optional, we don't mind if it fails.
_load_settings_text("res://override.cfg");
}
return err;
@@ -418,7 +427,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
while (true) {
err = _load_settings_text_or_binary(current_dir.plus_file("project.godot"), current_dir.plus_file("project.binary"));
if (err == OK) {
- // Optional, we don't mind if it fails
+ // Optional, we don't mind if it fails.
_load_settings_text(current_dir.plus_file("override.cfg"));
candidate = current_dir;
found = true;
@@ -438,7 +447,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
}
resource_path = candidate;
- resource_path = resource_path.replace("\\", "/"); // windows path to unix path just in case
+ resource_path = resource_path.replace("\\", "/"); // Windows path to Unix path just in case.
memdelete(d);
if (!found) {
@@ -446,7 +455,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
}
if (resource_path.length() && resource_path[resource_path.length() - 1] == '/') {
- resource_path = resource_path.substr(0, resource_path.length() - 1); // chop end
+ resource_path = resource_path.substr(0, resource_path.length() - 1); // Chop end.
}
return OK;
diff --git a/editor/SCsub b/editor/SCsub
index 13ae85bbf0..651dd5fffd 100644
--- a/editor/SCsub
+++ b/editor/SCsub
@@ -6,6 +6,7 @@ env.editor_sources = []
import os
import os.path
+import glob
from platform_methods import run_in_subprocess
import editor_builders
@@ -40,20 +41,21 @@ if env["tools"]:
f.write(reg_exporters_inc)
f.write(reg_exporters)
- # API documentation
+ # Core API documentation.
docs = []
- doc_dirs = ["doc/classes"]
+ docs += Glob("#doc/classes/*.xml")
- for p in env.doc_class_path.values():
- if p not in doc_dirs:
- doc_dirs.append(p)
+ # Module API documentation.
+ module_dirs = []
+ for d in env.doc_class_path.values():
+ if d not in module_dirs:
+ module_dirs.append(d)
- for d in doc_dirs:
- try:
- for f in os.listdir(os.path.join(env.Dir("#").abspath, d)):
- docs.append("#" + os.path.join(d, f))
- except OSError:
- pass
+ for d in module_dirs:
+ if not os.path.isabs(d):
+ docs += Glob("#" + d + "/*.xml") # Built-in.
+ else:
+ docs += Glob(d + "/*.xml") # Custom.
_make_doc_data_class_path(os.path.join(env.Dir("#").abspath, "editor"))
@@ -61,8 +63,6 @@ if env["tools"]:
env.Depends("#editor/doc_data_compressed.gen.h", docs)
env.CommandNoCache("#editor/doc_data_compressed.gen.h", docs, run_in_subprocess(editor_builders.make_doc_header))
- import glob
-
path = env.Dir(".").abspath
# Editor translations
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 2a8e0d856e..75e7542abb 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -4882,12 +4882,12 @@ void AnimationTrackEditor::_box_selection_draw() {
void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
- if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_DOWN) {
+ if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_UP) {
timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05);
scroll->accept_event();
}
- if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_UP) {
+ if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_DOWN) {
timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05);
scroll->accept_event();
}
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index d3749477cc..2a410c03e7 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "editor_feature_profile.h"
+
#include "core/io/json.h"
#include "core/os/dir_access.h"
#include "editor/editor_settings.h"
@@ -353,7 +354,7 @@ void EditorFeatureProfileManager::_update_profile_list(const String &p_select_pr
}
if (name == current_profile) {
- name += " (current)";
+ name += " " + TTR("(current)");
}
profile_list->add_item(name);
int index = profile_list->get_item_count() - 1;
@@ -363,12 +364,15 @@ void EditorFeatureProfileManager::_update_profile_list(const String &p_select_pr
}
}
+ class_list_vbc->set_visible(selected_profile != String());
+ property_list_vbc->set_visible(selected_profile != String());
+ no_profile_selected_help->set_visible(selected_profile == String());
profile_actions[PROFILE_CLEAR]->set_disabled(current_profile == String());
profile_actions[PROFILE_ERASE]->set_disabled(selected_profile == String());
profile_actions[PROFILE_EXPORT]->set_disabled(selected_profile == String());
profile_actions[PROFILE_SET]->set_disabled(selected_profile == String());
- current_profile_name->set_text(current_profile);
+ current_profile_name->set_text(current_profile != String() ? current_profile : TTR("(none)"));
_update_selected_profile();
}
@@ -451,6 +455,10 @@ void EditorFeatureProfileManager::_create_new_profile() {
new_profile->save_to_file(file);
_update_profile_list(name);
+ // The newly created profile is the first one, make it the current profile automatically.
+ if (profile_list->get_item_count() == 1) {
+ _profile_action(PROFILE_SET);
+ }
}
void EditorFeatureProfileManager::_profile_selected(int p_what) {
@@ -730,6 +738,10 @@ void EditorFeatureProfileManager::_import_profiles(const Vector<String> &p_paths
}
_update_profile_list();
+ // The newly imported profile is the first one, make it the current profile automatically.
+ if (profile_list->get_item_count() == 1) {
+ _profile_action(PROFILE_SET);
+ }
}
void EditorFeatureProfileManager::_export_profile(const String &p_path) {
@@ -779,6 +791,7 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() {
HBoxContainer *name_hbc = memnew(HBoxContainer);
current_profile_name = memnew(LineEdit);
name_hbc->add_child(current_profile_name);
+ current_profile_name->set_text(TTR("(none)"));
current_profile_name->set_editable(false);
current_profile_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
profile_actions[PROFILE_CLEAR] = memnew(Button(TTR("Unset")));
@@ -827,7 +840,7 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() {
h_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
main_vbc->add_child(h_split);
- VBoxContainer *class_list_vbc = memnew(VBoxContainer);
+ class_list_vbc = memnew(VBoxContainer);
h_split->add_child(class_list_vbc);
class_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -837,17 +850,30 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() {
class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
class_list->connect("cell_selected", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_selected));
class_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_edited), varray(), CONNECT_DEFERRED);
+ // It will be displayed once the user creates or chooses a profile.
+ class_list_vbc->hide();
- VBoxContainer *property_list_vbc = memnew(VBoxContainer);
+ property_list_vbc = memnew(VBoxContainer);
h_split->add_child(property_list_vbc);
property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
property_list = memnew(Tree);
- property_list_vbc->add_margin_child(TTR("Class Options"), property_list, true);
+ property_list_vbc->add_margin_child(TTR("Class Options:"), property_list, true);
property_list->set_hide_root(true);
property_list->set_hide_folding(true);
property_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
property_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_property_item_edited), varray(), CONNECT_DEFERRED);
+ // It will be displayed once the user creates or chooses a profile.
+ property_list_vbc->hide();
+
+ no_profile_selected_help = memnew(Label(TTR("Create or import a profile to edit available classes and properties.")));
+ // Add some spacing above the help label.
+ Ref<StyleBoxEmpty> sb = memnew(StyleBoxEmpty);
+ sb->set_default_margin(MARGIN_TOP, 20 * EDSCALE);
+ no_profile_selected_help->add_theme_style_override("normal", sb);
+ no_profile_selected_help->set_align(Label::ALIGN_CENTER);
+ no_profile_selected_help->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ h_split->add_child(no_profile_selected_help);
new_profile_dialog = memnew(ConfirmationDialog);
new_profile_dialog->set_title(TTR("New profile name:"));
diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h
index 4036ec7ec6..38413e35a2 100644
--- a/editor/editor_feature_profile.h
+++ b/editor/editor_feature_profile.h
@@ -120,8 +120,11 @@ class EditorFeatureProfileManager : public AcceptDialog {
HSplitContainer *h_split;
+ VBoxContainer *class_list_vbc;
Tree *class_list;
+ VBoxContainer *property_list_vbc;
Tree *property_list;
+ Label *no_profile_selected_help;
EditorFileDialog *import_profiles;
EditorFileDialog *export_profile;
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index a16605ab44..5f293f1fb3 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -503,17 +503,38 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("editors/grid_map/pick_distance", 5000.0);
// 3D
- _initial_set("editors/3d/primary_grid_color", Color(0.56, 0.56, 0.56));
- hints["editors/3d/primary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/primary_grid_color", PROPERTY_HINT_COLOR_NO_ALPHA, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
+ _initial_set("editors/3d/primary_grid_color", Color(0.56, 0.56, 0.56, 0.5));
+ hints["editors/3d/primary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/primary_grid_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT);
- _initial_set("editors/3d/secondary_grid_color", Color(0.38, 0.38, 0.38));
- hints["editors/3d/secondary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/secondary_grid_color", PROPERTY_HINT_COLOR_NO_ALPHA, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
-
- _initial_set("editors/3d/grid_size", 50);
- hints["editors/3d/grid_size"] = PropertyInfo(Variant::INT, "editors/3d/grid_size", PROPERTY_HINT_RANGE, "1,500,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
+ _initial_set("editors/3d/secondary_grid_color", Color(0.38, 0.38, 0.38, 0.5));
+ hints["editors/3d/secondary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/secondary_grid_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT);
+ // If a line is a multiple of this, it uses the primary grid color.
_initial_set("editors/3d/primary_grid_steps", 10);
- hints["editors/3d/primary_grid_steps"] = PropertyInfo(Variant::INT, "editors/3d/primary_grid_steps", PROPERTY_HINT_RANGE, "1,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
+ hints["editors/3d/primary_grid_steps"] = PropertyInfo(Variant::INT, "editors/3d/primary_grid_steps", PROPERTY_HINT_RANGE, "1,100,1", PROPERTY_USAGE_DEFAULT);
+
+ // At 1000, the grid mostly looks like it has no edge.
+ _initial_set("editors/3d/grid_size", 200);
+ hints["editors/3d/grid_size"] = PropertyInfo(Variant::INT, "editors/3d/grid_size", PROPERTY_HINT_RANGE, "1,2000,1", PROPERTY_USAGE_DEFAULT);
+
+ // Default largest grid size is 100m, 10^2 (primary grid lines are 1km apart when primary_grid_steps is 10).
+ _initial_set("editors/3d/grid_division_level_max", 2);
+ // Higher values produce graphical artifacts when far away unless View Z-Far
+ // is increased significantly more than it really should need to be.
+ hints["editors/3d/grid_division_level_max"] = PropertyInfo(Variant::INT, "editors/3d/grid_division_level_max", PROPERTY_HINT_RANGE, "-1,3,1", PROPERTY_USAGE_DEFAULT);
+
+ // Default smallest grid size is 1cm, 10^-2.
+ _initial_set("editors/3d/grid_division_level_min", -2);
+ // Lower values produce graphical artifacts regardless of view clipping planes, so limit to -2 as a lower bound.
+ hints["editors/3d/grid_division_level_min"] = PropertyInfo(Variant::INT, "editors/3d/grid_division_level_min", PROPERTY_HINT_RANGE, "-2,2,1", PROPERTY_USAGE_DEFAULT);
+
+ // -0.2 seems like a sensible default. -1.0 gives Blender-like behavior, 0.5 gives huge grids.
+ _initial_set("editors/3d/grid_division_level_bias", -0.2);
+ hints["editors/3d/grid_division_level_bias"] = PropertyInfo(Variant::FLOAT, "editors/3d/grid_division_level_bias", PROPERTY_HINT_RANGE, "-1.0,0.5,0.1", PROPERTY_USAGE_DEFAULT);
+
+ _initial_set("editors/3d/grid_xz_plane", true);
+ _initial_set("editors/3d/grid_xy_plane", false);
+ _initial_set("editors/3d/grid_yz_plane", false);
_initial_set("editors/3d/default_fov", 70.0);
_initial_set("editors/3d/default_z_near", 0.05);
diff --git a/editor/icons/SCsub b/editor/icons/SCsub
index f0d51999f0..e143276259 100644
--- a/editor/icons/SCsub
+++ b/editor/icons/SCsub
@@ -2,6 +2,8 @@
Import("env")
+import os
+
from platform_methods import run_in_subprocess
import editor_icons_builders
@@ -15,7 +17,10 @@ env["BUILDERS"]["MakeEditorIconsBuilder"] = make_editor_icons_builder
icon_sources = Glob("*.svg")
# Module icons
-for module_icons in env.module_icons_paths:
- icon_sources += Glob("#" + module_icons + "/*.svg")
+for path in env.module_icons_paths:
+ if not os.path.isabs(path):
+ icon_sources += Glob("#" + path + "/*.svg") # Built-in.
+ else:
+ icon_sources += Glob(path + "/*.svg") # Custom.
env.Alias("editor_icons", [env.MakeEditorIconsBuilder("#editor/editor_icons.gen.h", icon_sources)])
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 3c12022854..8466ee86e5 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -328,17 +328,13 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
//-------
// Apply camera transform
- float tolerance = 0.001;
+ real_t tolerance = 0.001;
bool equal = true;
- if (Math::abs(old_camera_cursor.x_rot - camera_cursor.x_rot) > tolerance || Math::abs(old_camera_cursor.y_rot - camera_cursor.y_rot) > tolerance) {
+ if (!Math::is_equal_approx(old_camera_cursor.x_rot, camera_cursor.x_rot, tolerance) || !Math::is_equal_approx(old_camera_cursor.y_rot, camera_cursor.y_rot, tolerance)) {
equal = false;
- }
-
- if (equal && old_camera_cursor.pos.distance_squared_to(camera_cursor.pos) > tolerance * tolerance) {
+ } else if (!old_camera_cursor.pos.is_equal_approx(camera_cursor.pos)) {
equal = false;
- }
-
- if (equal && Math::abs(old_camera_cursor.distance - camera_cursor.distance) > tolerance) {
+ } else if (!Math::is_equal_approx(old_camera_cursor.distance, camera_cursor.distance, tolerance)) {
equal = false;
}
@@ -356,6 +352,7 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
update_transform_gizmo_view();
rotation_control->update();
}
+ spatial_editor->update_grid();
}
Transform Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const {
@@ -4929,8 +4926,10 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
for (int i = 0; i < 3; ++i) {
if (grid_enable[i]) {
- RenderingServer::get_singleton()->instance_set_visible(grid_instance[i], grid_enabled);
grid_visible[i] = grid_enabled;
+ if (grid_instance[i].is_valid()) {
+ RenderingServer::get_singleton()->instance_set_visible(grid_instance[i], grid_enabled);
+ }
}
}
@@ -5054,6 +5053,7 @@ void Node3DEditor::_init_indicators() {
indicator_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+ indicator_mat->set_transparency(StandardMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS);
Vector<Color> origin_colors;
Vector<Vector3> origin_points;
@@ -5082,12 +5082,27 @@ void Node3DEditor::_init_indicators() {
origin_colors.push_back(origin_color);
origin_colors.push_back(origin_color);
- origin_points.push_back(axis * 4096);
- origin_points.push_back(axis * -4096);
- }
-
- grid_enable[1] = true;
- grid_visible[1] = true;
+ origin_colors.push_back(origin_color);
+ origin_colors.push_back(origin_color);
+ origin_colors.push_back(origin_color);
+ origin_colors.push_back(origin_color);
+ // To both allow having a large origin size and avoid jitter
+ // at small scales, we should segment the line into pieces.
+ // 3 pieces seems to do the trick, and let's use powers of 2.
+ origin_points.push_back(axis * 1048576);
+ origin_points.push_back(axis * 1024);
+ origin_points.push_back(axis * 1024);
+ origin_points.push_back(axis * -1024);
+ origin_points.push_back(axis * -1024);
+ origin_points.push_back(axis * -1048576);
+ }
+
+ grid_enable[0] = EditorSettings::get_singleton()->get("editors/3d/grid_xy_plane");
+ grid_enable[1] = EditorSettings::get_singleton()->get("editors/3d/grid_yz_plane");
+ grid_enable[2] = EditorSettings::get_singleton()->get("editors/3d/grid_xz_plane");
+ grid_visible[0] = grid_enable[0];
+ grid_visible[1] = grid_enable[1];
+ grid_visible[2] = grid_enable[2];
_init_grid();
@@ -5418,6 +5433,15 @@ void Node3DEditor::_update_gizmos_menu_theme() {
}
void Node3DEditor::_init_grid() {
+ if (!grid_enabled) {
+ return;
+ }
+ Camera3D *camera = get_editor_viewport(0)->camera;
+ Vector3 camera_position = camera->get_translation();
+ if (camera_position == Vector3()) {
+ return; // Camera3D is invalid, don't draw the grid.
+ }
+
Vector<Color> grid_colors[3];
Vector<Vector3> grid_points[3];
@@ -5426,52 +5450,111 @@ void Node3DEditor::_init_grid() {
int grid_size = EditorSettings::get_singleton()->get("editors/3d/grid_size");
int primary_grid_steps = EditorSettings::get_singleton()->get("editors/3d/primary_grid_steps");
- for (int i = 0; i < 3; i++) {
- Vector3 axis;
- axis[i] = 1;
- Vector3 axis_n1;
- axis_n1[(i + 1) % 3] = 1;
- Vector3 axis_n2;
- axis_n2[(i + 2) % 3] = 1;
-
- for (int j = -grid_size; j <= grid_size; j++) {
- Vector3 p1 = axis_n1 * j + axis_n2 * -grid_size;
- Vector3 p1_dest = p1 * (-axis_n2 + axis_n1);
- Vector3 p2 = axis_n2 * j + axis_n1 * -grid_size;
- Vector3 p2_dest = p2 * (-axis_n1 + axis_n2);
-
- Color line_color = secondary_grid_color;
- if (origin_enabled && j == 0) {
- // Don't draw the center lines of the grid if the origin is enabled
- // The origin would overlap the grid lines in this case, causing flickering
- continue;
- } else if (j % primary_grid_steps == 0) {
- line_color = primary_grid_color;
+ // Which grid planes are enabled? Which should we generate?
+ grid_enable[0] = grid_visible[0] = EditorSettings::get_singleton()->get("editors/3d/grid_xy_plane");
+ grid_enable[1] = grid_visible[1] = EditorSettings::get_singleton()->get("editors/3d/grid_yz_plane");
+ grid_enable[2] = grid_visible[2] = EditorSettings::get_singleton()->get("editors/3d/grid_xz_plane");
+
+ // Offsets division_level for bigger or smaller grids.
+ // Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids.
+ real_t division_level_bias = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_bias");
+ // Default largest grid size is 100m, 10^2 (default value is 2).
+ int division_level_max = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_max");
+ // Default smallest grid size is 1cm, 10^-2 (default value is -2).
+ int division_level_min = EditorSettings::get_singleton()->get("editors/3d/grid_division_level_min");
+ ERR_FAIL_COND_MSG(division_level_max < division_level_min, "The 3D grid's maximum division level cannot be lower than its minimum division level.");
+
+ if (primary_grid_steps != 10) { // Log10 of 10 is 1.
+ // Change of base rule, divide by ln(10).
+ real_t div = Math::log((real_t)primary_grid_steps) / (real_t)2.302585092994045901094;
+ // Trucation (towards zero) is intentional.
+ division_level_max = (int)(division_level_max / div);
+ division_level_min = (int)(division_level_min / div);
+ }
+
+ for (int a = 0; a < 3; a++) {
+ if (!grid_enable[a]) {
+ continue; // If this grid plane is disabled, skip generation.
+ }
+ int b = (a + 1) % 3;
+ int c = (a + 2) % 3;
+
+ real_t division_level = Math::log(Math::abs(camera_position[c])) / Math::log((double)primary_grid_steps) + division_level_bias;
+ division_level = CLAMP(division_level, division_level_min, division_level_max);
+ real_t division_level_floored = Math::floor(division_level);
+ real_t division_level_decimals = division_level - division_level_floored;
+
+ real_t small_step_size = Math::pow(primary_grid_steps, division_level_floored);
+ real_t large_step_size = small_step_size * primary_grid_steps;
+ real_t center_a = large_step_size * (int)(camera_position[a] / large_step_size);
+ real_t center_b = large_step_size * (int)(camera_position[b] / large_step_size);
+
+ real_t bgn_a = center_a - grid_size * small_step_size;
+ real_t end_a = center_a + grid_size * small_step_size;
+ real_t bgn_b = center_b - grid_size * small_step_size;
+ real_t end_b = center_b + grid_size * small_step_size;
+
+ // In each iteration of this loop, draw one line in each direction (so two lines per loop, in each if statement).
+ for (int i = -grid_size; i <= grid_size; i++) {
+ Color line_color;
+ // Is this a primary line? Set the appropriate color.
+ if (i % primary_grid_steps == 0) {
+ line_color = primary_grid_color.lerp(secondary_grid_color, division_level_decimals);
+ } else {
+ line_color = secondary_grid_color;
+ line_color.a = line_color.a * (1 - division_level_decimals);
+ }
+ // Makes lines farther from the center fade out.
+ // Due to limitations of lines, any that come near the camera have full opacity always.
+ // This should eventually be replaced by some kind of "distance fade" system, outside of this function.
+ // But the effect is still somewhat convincing...
+ line_color.a *= 1 - (1 - division_level_decimals * 0.9) * (Math::abs(i / (float)grid_size));
+
+ real_t position_a = center_a + i * small_step_size;
+ real_t position_b = center_b + i * small_step_size;
+
+ // Don't draw lines over the origin if it's enabled.
+ if (!(origin_enabled && Math::is_zero_approx(position_a))) {
+ Vector3 line_bgn = Vector3();
+ Vector3 line_end = Vector3();
+ line_bgn[a] = position_a;
+ line_end[a] = position_a;
+ line_bgn[b] = bgn_b;
+ line_end[b] = end_b;
+ grid_points[c].push_back(line_bgn);
+ grid_points[c].push_back(line_end);
+ grid_colors[c].push_back(line_color);
+ grid_colors[c].push_back(line_color);
}
- grid_points[i].push_back(p1);
- grid_points[i].push_back(p1_dest);
- grid_colors[i].push_back(line_color);
- grid_colors[i].push_back(line_color);
-
- grid_points[i].push_back(p2);
- grid_points[i].push_back(p2_dest);
- grid_colors[i].push_back(line_color);
- grid_colors[i].push_back(line_color);
+ if (!(origin_enabled && Math::is_zero_approx(position_b))) {
+ Vector3 line_bgn = Vector3();
+ Vector3 line_end = Vector3();
+ line_bgn[b] = position_b;
+ line_end[b] = position_b;
+ line_bgn[a] = bgn_a;
+ line_end[a] = end_a;
+ grid_points[c].push_back(line_bgn);
+ grid_points[c].push_back(line_end);
+ grid_colors[c].push_back(line_color);
+ grid_colors[c].push_back(line_color);
+ }
}
- grid[i] = RenderingServer::get_singleton()->mesh_create();
+ // Create a mesh from the pushed vector points and colors.
+ grid[c] = RenderingServer::get_singleton()->mesh_create();
Array d;
d.resize(RS::ARRAY_MAX);
- d[RenderingServer::ARRAY_VERTEX] = grid_points[i];
- d[RenderingServer::ARRAY_COLOR] = grid_colors[i];
- RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], RenderingServer::PRIMITIVE_LINES, d);
- RenderingServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid());
- grid_instance[i] = RenderingServer::get_singleton()->instance_create2(grid[i], get_tree()->get_root()->get_world_3d()->get_scenario());
+ d[RenderingServer::ARRAY_VERTEX] = grid_points[c];
+ d[RenderingServer::ARRAY_COLOR] = grid_colors[c];
+ RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[c], RenderingServer::PRIMITIVE_LINES, d);
+ RenderingServer::get_singleton()->mesh_surface_set_material(grid[c], 0, indicator_mat->get_rid());
+ grid_instance[c] = RenderingServer::get_singleton()->instance_create2(grid[c], get_tree()->get_root()->get_world_3d()->get_scenario());
- RenderingServer::get_singleton()->instance_set_visible(grid_instance[i], grid_visible[i]);
- RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[i], RS::SHADOW_CASTING_SETTING_OFF);
- RS::get_singleton()->instance_set_layer_mask(grid_instance[i], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);
+ // Yes, the end of this line is supposed to be a.
+ RenderingServer::get_singleton()->instance_set_visible(grid_instance[c], grid_visible[a]);
+ RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);
}
}
@@ -5489,6 +5572,11 @@ void Node3DEditor::_finish_grid() {
}
}
+void Node3DEditor::update_grid() {
+ _finish_grid();
+ _init_grid();
+}
+
bool Node3DEditor::is_any_freelook_active() const {
for (unsigned int i = 0; i < VIEWPORTS_COUNT; ++i) {
if (viewports[i]->is_freelook_active()) {
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 3d92e7e7e1..32b087c372 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -766,6 +766,7 @@ public:
Ref<ArrayMesh> get_scale_gizmo(int idx) const { return scale_gizmo[idx]; }
Ref<ArrayMesh> get_scale_plane_gizmo(int idx) const { return scale_plane_gizmo[idx]; }
+ void update_grid();
void update_transform_gizmo();
void update_all_gizmos(Node *p_node = nullptr);
void snap_selected_nodes_to_floor();
diff --git a/main/main.cpp b/main/main.cpp
index f05032e68c..94dd895a26 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1645,7 +1645,11 @@ bool Main::start() {
print_line("Loading docs...");
for (int i = 0; i < _doc_data_class_path_count; i++) {
- String path = doc_tool.plus_file(_doc_data_class_paths[i].path);
+ // Custom modules are always located by absolute path.
+ String path = _doc_data_class_paths[i].path;
+ if (path.is_rel_path()) {
+ path = doc_tool.plus_file(path);
+ }
String name = _doc_data_class_paths[i].name;
doc_data_classes[name] = path;
if (!checked_paths.has(path)) {
diff --git a/main/tests/test_math.cpp b/main/tests/test_math.cpp
index 11aa164709..9e159798bb 100644
--- a/main/tests/test_math.cpp
+++ b/main/tests/test_math.cpp
@@ -482,7 +482,7 @@ MainLoop *test() {
float gb = (rgbe >> 9) & 0x1ff;
float bb = (rgbe >> 18) & 0x1ff;
float eb = (rgbe >> 27);
- float mb = Math::pow(2, eb - 15.0 - 9.0);
+ float mb = Math::pow(2.0, eb - 15.0 - 9.0);
float rd = rb * mb;
float gd = gb * mb;
float bd = bb * mb;
diff --git a/main/tests/test_oa_hash_map.cpp b/main/tests/test_oa_hash_map.cpp
index 719817baf4..9182f66b61 100644
--- a/main/tests/test_oa_hash_map.cpp
+++ b/main/tests/test_oa_hash_map.cpp
@@ -210,6 +210,89 @@ MainLoop *test() {
}
}
+ // Test map with 0 capacity.
+ {
+ OAHashMap<int, String> original_map(0);
+ original_map.set(1, "1");
+ OS::get_singleton()->print("OAHashMap 0 capacity initialization passed.\n");
+ }
+
+ // Test copy constructor.
+ {
+ OAHashMap<int, String> original_map;
+ original_map.set(1, "1");
+ original_map.set(2, "2");
+ original_map.set(3, "3");
+ original_map.set(4, "4");
+ original_map.set(5, "5");
+
+ OAHashMap<int, String> map_copy(original_map);
+
+ bool pass = true;
+ for (
+ OAHashMap<int, String>::Iterator it = original_map.iter();
+ it.valid;
+ it = original_map.next_iter(it)) {
+ if (map_copy.lookup_ptr(*it.key) == nullptr) {
+ pass = false;
+ }
+ if (*it.value != *map_copy.lookup_ptr(*it.key)) {
+ pass = false;
+ }
+ }
+ if (pass) {
+ OS::get_singleton()->print("OAHashMap copy constructor test passed.\n");
+ } else {
+ OS::get_singleton()->print("OAHashMap copy constructor test FAILED.\n");
+ }
+
+ map_copy.set(1, "Random String");
+ if (*map_copy.lookup_ptr(1) == *original_map.lookup_ptr(1)) {
+ OS::get_singleton()->print("OAHashMap copy constructor, atomic copy test FAILED.\n");
+ } else {
+ OS::get_singleton()->print("OAHashMap copy constructor, atomic copy test passed.\n");
+ }
+ }
+
+ // Test assign operator.
+ {
+ OAHashMap<int, String> original_map;
+ original_map.set(1, "1");
+ original_map.set(2, "2");
+ original_map.set(3, "3");
+ original_map.set(4, "4");
+ original_map.set(5, "5");
+
+ OAHashMap<int, String> map_copy(100000);
+ map_copy.set(1, "Just a string.");
+ map_copy = original_map;
+
+ bool pass = true;
+ for (
+ OAHashMap<int, String>::Iterator it = map_copy.iter();
+ it.valid;
+ it = map_copy.next_iter(it)) {
+ if (original_map.lookup_ptr(*it.key) == nullptr) {
+ pass = false;
+ }
+ if (*it.value != *original_map.lookup_ptr(*it.key)) {
+ pass = false;
+ }
+ }
+ if (pass) {
+ OS::get_singleton()->print("OAHashMap assign operation test passed.\n");
+ } else {
+ OS::get_singleton()->print("OAHashMap assign operation test FAILED.\n");
+ }
+
+ map_copy.set(1, "Random String");
+ if (*map_copy.lookup_ptr(1) == *original_map.lookup_ptr(1)) {
+ OS::get_singleton()->print("OAHashMap assign operation atomic copy test FAILED.\n");
+ } else {
+ OS::get_singleton()->print("OAHashMap assign operation atomic copy test passed.\n");
+ }
+ }
+
return nullptr;
}
diff --git a/methods.py b/methods.py
index 805ae256c3..46b58a13cd 100644
--- a/methods.py
+++ b/methods.py
@@ -137,37 +137,47 @@ def parse_cg_file(fname, uniforms, sizes, conditionals):
fs.close()
-def detect_modules():
+def detect_modules(at_path):
+ module_list = {} # name : path
- module_list = []
+ modules_glob = os.path.join(at_path, "*")
+ files = glob.glob(modules_glob)
+ files.sort() # so register_module_types does not change that often, and also plugins are registered in alphabetic order
+
+ for x in files:
+ if not is_module(x):
+ continue
+ name = os.path.basename(x)
+ path = x.replace("\\", "/") # win32
+ module_list[name] = path
+
+ return module_list
+
+
+def is_module(path):
+ return os.path.isdir(path) and os.path.exists(path + "/config.py")
+
+
+def write_modules(module_list):
includes_cpp = ""
+ preregister_cpp = ""
register_cpp = ""
unregister_cpp = ""
- preregister_cpp = ""
- files = glob.glob("modules/*")
- files.sort() # so register_module_types does not change that often, and also plugins are registered in alphabetic order
- for x in files:
- if not os.path.isdir(x):
- continue
- if not os.path.exists(x + "/config.py"):
- continue
- x = x.replace("modules/", "") # rest of world
- x = x.replace("modules\\", "") # win32
- module_list.append(x)
+ for name, path in module_list.items():
try:
- with open("modules/" + x + "/register_types.h"):
- includes_cpp += '#include "modules/' + x + '/register_types.h"\n'
- register_cpp += "#ifdef MODULE_" + x.upper() + "_ENABLED\n"
- register_cpp += "\tregister_" + x + "_types();\n"
- register_cpp += "#endif\n"
- preregister_cpp += "#ifdef MODULE_" + x.upper() + "_ENABLED\n"
- preregister_cpp += "#ifdef MODULE_" + x.upper() + "_HAS_PREREGISTER\n"
- preregister_cpp += "\tpreregister_" + x + "_types();\n"
+ with open(os.path.join(path, "register_types.h")):
+ includes_cpp += '#include "' + path + '/register_types.h"\n'
+ preregister_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n"
+ preregister_cpp += "#ifdef MODULE_" + name.upper() + "_HAS_PREREGISTER\n"
+ preregister_cpp += "\tpreregister_" + name + "_types();\n"
preregister_cpp += "#endif\n"
preregister_cpp += "#endif\n"
- unregister_cpp += "#ifdef MODULE_" + x.upper() + "_ENABLED\n"
- unregister_cpp += "\tunregister_" + x + "_types();\n"
+ register_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n"
+ register_cpp += "\tregister_" + name + "_types();\n"
+ register_cpp += "#endif\n"
+ unregister_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n"
+ unregister_cpp += "\tunregister_" + name + "_types();\n"
unregister_cpp += "#endif\n"
except IOError:
pass
@@ -202,7 +212,18 @@ void unregister_module_types() {
with open("modules/register_module_types.gen.cpp", "w") as f:
f.write(modules_cpp)
- return module_list
+
+def convert_custom_modules_path(path):
+ if not path:
+ return path
+ err_msg = "Build option 'custom_modules' must %s"
+ if not os.path.isdir(path):
+ raise ValueError(err_msg % "point to an existing directory.")
+ if os.path.realpath(path) == os.path.realpath("modules"):
+ raise ValueError(err_msg % "be a directory other than built-in `modules` directory.")
+ if is_module(path):
+ raise ValueError(err_msg % "point to a directory with modules, not a single module.")
+ return os.path.realpath(os.path.expanduser(path))
def disable_module(self):
diff --git a/modules/SCsub b/modules/SCsub
index fb46c5f877..9155a53eaf 100644
--- a/modules/SCsub
+++ b/modules/SCsub
@@ -3,6 +3,7 @@
Import("env")
import modules_builders
+import os
env_modules = env.Clone()
@@ -13,16 +14,20 @@ env.CommandNoCache("modules_enabled.gen.h", Value(env.module_list), modules_buil
vs_sources = []
# libmodule_<name>.a for each active module.
-for module in env.module_list:
+for name, path in env.module_list.items():
env.modules_sources = []
- SConscript(module + "/SCsub")
+
+ if not os.path.isabs(path):
+ SConscript(name + "/SCsub") # Built-in.
+ else:
+ SConscript(path + "/SCsub") # Custom.
# Some modules are not linked automatically but can be enabled optionally
# on iOS, so we handle those specially.
- if env["platform"] == "iphone" and module in ["arkit", "camera"]:
+ if env["platform"] == "iphone" and name in ["arkit", "camera"]:
continue
- lib = env_modules.add_library("module_%s" % module, env.modules_sources)
+ lib = env_modules.add_library("module_%s" % name, env.modules_sources)
env.Prepend(LIBS=[lib])
if env["vsproj"]:
vs_sources += env.modules_sources
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 7433c4a5bc..50d8289fd1 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2082,7 +2082,11 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
if (!p_only_functions) {
List<PropertyInfo> members;
- tmp.get_property_list(&members);
+ if (p_base.value.get_type() != Variant::NIL) {
+ p_base.value.get_property_list(&members);
+ } else {
+ tmp.get_property_list(&members);
+ }
for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
if (String(E->get().name).find("/") == -1) {
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index eb7696685f..c874025be0 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -421,7 +421,7 @@ namespace GodotTools
aboutLabel.Text =
"C# support in Godot Engine is in late alpha stage and, while already usable, " +
"it is not meant for use in production.\n\n" +
- "Projects can be exported to Linux, macOS, Windows and Android, but not yet to iOS, HTML5 or UWP. " +
+ "Projects can be exported to Linux, macOS, Windows, Android, iOS and HTML5, but not yet to UWP. " +
"Bugs and usability issues will be addressed gradually over future releases, " +
"potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" +
"If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" +
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index dfaaf68b69..1bd198ccc0 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -254,6 +254,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
};
Vector<PluginConfig> plugins;
+ String last_plugin_names;
+ uint64_t last_custom_build_time = 0;
volatile bool plugins_changed;
Mutex plugins_lock;
Vector<Device> devices;
@@ -1831,6 +1833,29 @@ public:
return list;
}
+ inline bool is_clean_build_required(Vector<PluginConfig> enabled_plugins) {
+ String plugin_names = get_plugins_names(enabled_plugins);
+ bool first_build = last_custom_build_time == 0;
+ bool have_plugins_changed = false;
+
+ if (!first_build) {
+ have_plugins_changed = plugin_names != last_plugin_names;
+ if (!have_plugins_changed) {
+ for (int i = 0; i < enabled_plugins.size(); i++) {
+ if (enabled_plugins.get(i).last_updated > last_custom_build_time) {
+ have_plugins_changed = true;
+ break;
+ }
+ }
+ }
+ }
+
+ last_custom_build_time = OS::get_singleton()->get_unix_time();
+ last_plugin_names = plugin_names;
+
+ return have_plugins_changed || first_build;
+ }
+
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
@@ -1877,8 +1902,12 @@ public:
String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
String remote_plugins_binaries = get_plugins_binaries(BINARY_TYPE_REMOTE, enabled_plugins);
String custom_maven_repos = get_plugins_custom_maven_repos(enabled_plugins);
+ bool clean_build_required = is_clean_build_required(enabled_plugins);
List<String> cmdline;
+ if (clean_build_required) {
+ cmdline.push_back("clean");
+ }
cmdline.push_back("build");
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index ea341b37b1..19202d2310 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -75,6 +75,11 @@ android {
}
defaultConfig {
+ // The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects.
+ aaptOptions {
+ ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
+ }
+
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
minSdkVersion versions.minSdk
diff --git a/platform/android/plugin/godot_plugin_config.h b/platform/android/plugin/godot_plugin_config.h
index 2151451bc8..5bc0fc3a58 100644
--- a/platform/android/plugin/godot_plugin_config.h
+++ b/platform/android/plugin/godot_plugin_config.h
@@ -70,6 +70,8 @@ The `dependencies` section and fields are optional and defined as follow:
struct PluginConfig {
// Set to true when the config file is properly loaded.
bool valid_config = false;
+ // Unix timestamp of last change to this plugin.
+ uint64_t last_updated = 0;
// Required config section
String name;
@@ -87,6 +89,7 @@ struct PluginConfig {
*/
static const PluginConfig GODOT_PAYMENT = {
/*.valid_config =*/true,
+ /*.last_updated =*/0,
/*.name =*/"GodotPayment",
/*.binary_type =*/"local",
/*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar",
@@ -150,6 +153,18 @@ static inline bool is_plugin_config_valid(PluginConfig plugin_config) {
return valid_name && valid_binary && valid_binary_type && valid_local_dependencies;
}
+static inline uint64_t get_plugin_modification_time(const PluginConfig &plugin_config, const String &config_path) {
+ uint64_t last_updated = FileAccess::get_modified_time(config_path);
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(plugin_config.binary));
+
+ for (int i = 0; i < plugin_config.local_dependencies.size(); i++) {
+ String binary = plugin_config.local_dependencies.get(i);
+ last_updated = MAX(last_updated, FileAccess::get_modified_time(binary));
+ }
+
+ return last_updated;
+}
+
static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const String &path) {
PluginConfig plugin_config = {};
@@ -177,6 +192,7 @@ static inline PluginConfig load_plugin_config(Ref<ConfigFile> config_file, const
}
plugin_config.valid_config = is_plugin_config_valid(plugin_config);
+ plugin_config.last_updated = get_plugin_modification_time(plugin_config, path);
}
}
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
index 557743fa73..053a4c078e 100644
--- a/platform/android/plugin/godot_plugin_jni.cpp
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -114,12 +114,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS
String signal_name = jstring_to_string(j_signal_name, env);
int count = env->GetArrayLength(j_signal_params);
+ Variant variant_params[count];
const Variant *args[count];
for (int i = 0; i < count; i++) {
jobject j_param = env->GetObjectArrayElement(j_signal_params, i);
- Variant variant = _jobject_to_variant(env, j_param);
- args[i] = &variant;
+ variant_params[i] = _jobject_to_variant(env, j_param);
+ args[i] = &variant_params[i];
env->DeleteLocalRef(j_param);
};
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp
index b52bd4ce60..b8914414e6 100644
--- a/platform/javascript/audio_driver_javascript.cpp
+++ b/platform/javascript/audio_driver_javascript.cpp
@@ -70,14 +70,14 @@ Error AudioDriverJavaScript::init() {
/* clang-format off */
_driver_id = EM_ASM_INT({
const MIX_RATE = $0;
- const LATENCY = $1;
+ const LATENCY = $1 / 1000;
return Module.IDHandler.add({
'context': new (window.AudioContext || window.webkitAudioContext)({ sampleRate: MIX_RATE, latencyHint: LATENCY}),
'input': null,
'stream': null,
'script': null
});
- });
+ }, mix_rate, latency);
/* clang-format on */
int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 0222585948..f57c8e58db 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -361,15 +361,6 @@ void SceneDebuggerObject::serialize(Array &r_arr, int p_max_size) {
RES res = var;
- if (var.get_type() == Variant::OBJECT && var.is_ref()) {
- REF r = var;
- if (r.is_valid()) {
- res = *r;
- } else {
- res = RES();
- }
- }
-
Array prop;
prop.push_back(pi.name);
prop.push_back(pi.type);
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 88710289c7..84170a65d1 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -66,7 +66,7 @@ void ColorPicker::_notification(int p_what) {
} break;
case NOTIFICATION_PARENTED: {
for (int i = 0; i < 4; i++) {
- set_margin((Margin)i, get_theme_constant("margin"));
+ set_margin((Margin)i, get_margin((Margin)i) + get_theme_constant("margin"));
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {