diff options
Diffstat (limited to 'core')
104 files changed, 3556 insertions, 1107 deletions
diff --git a/core/SCsub b/core/SCsub index af83b49fea..c508ecc37e 100644 --- a/core/SCsub +++ b/core/SCsub @@ -2,6 +2,8 @@ Import('env') +import methods + env.core_sources = [] @@ -18,9 +20,8 @@ gd_cpp = '#include "project_settings.h"\n' gd_cpp += gd_inc gd_cpp += "void ProjectSettings::register_global_defaults() {\n" + gd_call + "\n}\n" -f = open("global_defaults.gen.cpp", "w") -f.write(gd_cpp) -f.close() +with open("global_defaults.gen.cpp", "w") as f: + f.write(gd_cpp) # Generate AES256 script encryption key @@ -47,9 +48,8 @@ if ("SCRIPT_AES256_ENCRYPTION_KEY" in os.environ): txt = "0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0" print("Invalid AES256 encryption key, not 64 bits hex: " + e) -f = open("script_encryption_key.gen.cpp", "w") -f.write("#include \"project_settings.h\"\nuint8_t script_encryption_key[32]={" + txt + "};\n") -f.close() +with open("script_encryption_key.gen.cpp", "w") as f: + f.write("#include \"project_settings.h\"\nuint8_t script_encryption_key[32]={" + txt + "};\n") # Add required thirdparty code. Header paths are hardcoded, we don't need to append @@ -68,7 +68,6 @@ thirdparty_sources = [ "md5.cpp", "pcg.cpp", "triangulator.cpp", - "clipper.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env.add_source_files(env.core_sources, thirdparty_sources) @@ -94,8 +93,19 @@ env.add_source_files(env.core_sources, "*.cpp") # Make binders import make_binders -env.Command(['method_bind.gen.inc', 'method_bind_ext.gen.inc'], 'make_binders.py', make_binders.run) +env.CommandNoCache(['method_bind.gen.inc', 'method_bind_ext.gen.inc'], 'make_binders.py', make_binders.run) + +# Authors +env.Depends('#core/authors.gen.h', "../AUTHORS.md") +env.CommandNoCache('#core/authors.gen.h', "../AUTHORS.md", methods.make_authors_header) + +# Donors +env.Depends('#core/donors.gen.h', "../DONORS.md") +env.CommandNoCache('#core/donors.gen.h', "../DONORS.md", methods.make_donors_header) +# License +env.Depends('#core/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"]) +env.CommandNoCache('#core/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"], methods.make_license_header) # Chain load SCsubs SConscript('os/SCsub') diff --git a/core/array.cpp b/core/array.cpp index 0ddac1662c..9e3250fd47 100644 --- a/core/array.cpp +++ b/core/array.cpp @@ -35,8 +35,8 @@ #include "variant.h" #include "vector.h" -struct ArrayPrivate { - +class ArrayPrivate { +public: SafeRefCount refcount; Vector<Variant> array; }; @@ -211,13 +211,13 @@ const Variant &Array::get(int p_idx) const { return operator[](p_idx); } -Array Array::duplicate() const { +Array Array::duplicate(bool p_deep) const { Array new_arr; int element_count = size(); new_arr.resize(element_count); for (int i = 0; i < element_count; i++) { - new_arr[i] = get(i); + new_arr[i] = p_deep ? get(i).duplicate(p_deep) : get(i); } return new_arr; diff --git a/core/array.h b/core/array.h index 684a8e265d..e549a886e6 100644 --- a/core/array.h +++ b/core/array.h @@ -88,7 +88,7 @@ public: Variant pop_back(); Variant pop_front(); - Array duplicate() const; + Array duplicate(bool p_deep = false) const; Array(const Array &p_from); Array(); diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index b57b24ee7d..7a14e85f20 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -205,6 +205,22 @@ String _OS::get_clipboard() const { return OS::get_singleton()->get_clipboard(); } +int _OS::get_video_driver_count() const { + return OS::get_singleton()->get_video_driver_count(); +} + +String _OS::get_video_driver_name(int p_driver) const { + return OS::get_singleton()->get_video_driver_name(p_driver); +} + +int _OS::get_audio_driver_count() const { + return OS::get_singleton()->get_audio_driver_count(); +} + +String _OS::get_audio_driver_name(int p_driver) const { + return OS::get_singleton()->get_audio_driver_name(p_driver); +} + void _OS::set_video_mode(const Size2 &p_size, bool p_fullscreen, bool p_resizeable, int p_screen) { OS::VideoMode vm; @@ -272,6 +288,10 @@ void _OS::set_window_size(const Size2 &p_size) { OS::get_singleton()->set_window_size(p_size); } +Rect2 _OS::get_window_safe_area() const { + return OS::get_singleton()->get_window_safe_area(); +} + void _OS::set_window_fullscreen(bool p_enabled) { OS::get_singleton()->set_window_fullscreen(p_enabled); } @@ -316,10 +336,23 @@ void _OS::set_borderless_window(bool p_borderless) { OS::get_singleton()->set_borderless_window(p_borderless); } +bool _OS::get_window_per_pixel_transparency_enabled() const { + return OS::get_singleton()->get_window_per_pixel_transparency_enabled(); +} + +void _OS::set_window_per_pixel_transparency_enabled(bool p_enabled) { + OS::get_singleton()->set_window_per_pixel_transparency_enabled(p_enabled); +} + bool _OS::get_borderless_window() const { return OS::get_singleton()->get_borderless_window(); } +void _OS::set_ime_active(const bool p_active) { + + return OS::get_singleton()->set_ime_active(p_active); +} + void _OS::set_ime_position(const Point2 &p_pos) { return OS::get_singleton()->set_ime_position(p_pos); @@ -371,7 +404,7 @@ Error _OS::shell_open(String p_uri) { int _OS::execute(const String &p_path, const Vector<String> &p_arguments, bool p_blocking, Array p_output) { - OS::ProcessID pid; + OS::ProcessID pid = -2; List<String> args; for (int i = 0; i < p_arguments.size(); i++) args.push_back(p_arguments[i]); @@ -384,6 +417,7 @@ int _OS::execute(const String &p_path, const Vector<String> &p_arguments, bool p else return pid; } + Error _OS::kill(int p_pid) { return OS::get_singleton()->kill(p_pid); @@ -619,8 +653,8 @@ uint64_t _OS::get_unix_time_from_datetime(Dictionary datetime) const { unsigned int second = ((datetime.has(SECOND_KEY)) ? static_cast<unsigned int>(datetime[SECOND_KEY]) : 0); unsigned int minute = ((datetime.has(MINUTE_KEY)) ? static_cast<unsigned int>(datetime[MINUTE_KEY]) : 0); unsigned int hour = ((datetime.has(HOUR_KEY)) ? static_cast<unsigned int>(datetime[HOUR_KEY]) : 0); - unsigned int day = ((datetime.has(DAY_KEY)) ? static_cast<unsigned int>(datetime[DAY_KEY]) : 0); - unsigned int month = ((datetime.has(MONTH_KEY)) ? static_cast<unsigned int>(datetime[MONTH_KEY]) - 1 : 0); + unsigned int day = ((datetime.has(DAY_KEY)) ? static_cast<unsigned int>(datetime[DAY_KEY]) : 1); + unsigned int month = ((datetime.has(MONTH_KEY)) ? static_cast<unsigned int>(datetime[MONTH_KEY]) : 1); unsigned int year = ((datetime.has(YEAR_KEY)) ? static_cast<unsigned int>(datetime[YEAR_KEY]) : 0); /// How many days come before each month (0-12) @@ -640,15 +674,15 @@ uint64_t _OS::get_unix_time_from_datetime(Dictionary datetime) const { ERR_EXPLAIN("Invalid hour value of: " + itos(hour)); ERR_FAIL_COND_V(hour > 23, 0); - ERR_EXPLAIN("Invalid month value of: " + itos(month + 1)); - ERR_FAIL_COND_V(month + 1 > 12, 0); + ERR_EXPLAIN("Invalid month value of: " + itos(month)); + ERR_FAIL_COND_V(month > 12 || month == 0, 0); // Do this check after month is tested as valid - ERR_EXPLAIN("Invalid day value of: " + itos(day) + " which is larger than " + itos(MONTH_DAYS_TABLE[LEAPYEAR(year)][month])); - ERR_FAIL_COND_V(day > MONTH_DAYS_TABLE[LEAPYEAR(year)][month], 0); + ERR_EXPLAIN("Invalid day value of: " + itos(day) + " which is larger than " + itos(MONTH_DAYS_TABLE[LEAPYEAR(year)][month - 1]) + " or 0"); + ERR_FAIL_COND_V(day > MONTH_DAYS_TABLE[LEAPYEAR(year)][month - 1] || day == 0, 0); // Calculate all the seconds from months past in this year - uint64_t SECONDS_FROM_MONTHS_PAST_THIS_YEAR = DAYS_PAST_THIS_YEAR_TABLE[LEAPYEAR(year)][month] * SECONDS_PER_DAY; + uint64_t SECONDS_FROM_MONTHS_PAST_THIS_YEAR = DAYS_PAST_THIS_YEAR_TABLE[LEAPYEAR(year)][month - 1] * SECONDS_PER_DAY; uint64_t SECONDS_FROM_YEARS_PAST = 0; for (unsigned int iyear = EPOCH_YR; iyear < year; iyear++) { @@ -765,6 +799,11 @@ uint32_t _OS::get_ticks_msec() const { return OS::get_singleton()->get_ticks_msec(); } +uint64_t _OS::get_ticks_usec() const { + + return OS::get_singleton()->get_ticks_usec(); +} + uint32_t _OS::get_splash_tick_msec() const { return OS::get_singleton()->get_splash_tick_msec(); @@ -1015,6 +1054,11 @@ void _OS::_bind_methods() { //ClassDB::bind_method(D_METHOD("is_video_mode_resizable","screen"),&_OS::is_video_mode_resizable,DEFVAL(0)); //ClassDB::bind_method(D_METHOD("get_fullscreen_mode_list","screen"),&_OS::get_fullscreen_mode_list,DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_video_driver_count"), &_OS::get_video_driver_count); + ClassDB::bind_method(D_METHOD("get_video_driver_name", "driver"), &_OS::get_video_driver_name); + ClassDB::bind_method(D_METHOD("get_audio_driver_count"), &_OS::get_audio_driver_count); + ClassDB::bind_method(D_METHOD("get_audio_driver_name", "driver"), &_OS::get_audio_driver_name); + ClassDB::bind_method(D_METHOD("get_screen_count"), &_OS::get_screen_count); ClassDB::bind_method(D_METHOD("get_current_screen"), &_OS::get_current_screen); ClassDB::bind_method(D_METHOD("set_current_screen", "screen"), &_OS::set_current_screen); @@ -1025,6 +1069,7 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("set_window_position", "position"), &_OS::set_window_position); ClassDB::bind_method(D_METHOD("get_window_size"), &_OS::get_window_size); ClassDB::bind_method(D_METHOD("set_window_size", "size"), &_OS::set_window_size); + ClassDB::bind_method(D_METHOD("get_window_safe_area"), &_OS::get_window_safe_area); ClassDB::bind_method(D_METHOD("set_window_fullscreen", "enabled"), &_OS::set_window_fullscreen); ClassDB::bind_method(D_METHOD("is_window_fullscreen"), &_OS::is_window_fullscreen); ClassDB::bind_method(D_METHOD("set_window_resizable", "enabled"), &_OS::set_window_resizable); @@ -1042,6 +1087,9 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("set_borderless_window", "borderless"), &_OS::set_borderless_window); ClassDB::bind_method(D_METHOD("get_borderless_window"), &_OS::get_borderless_window); + ClassDB::bind_method(D_METHOD("get_window_per_pixel_transparency_enabled"), &_OS::get_window_per_pixel_transparency_enabled); + ClassDB::bind_method(D_METHOD("set_window_per_pixel_transparency_enabled", "enabled"), &_OS::set_window_per_pixel_transparency_enabled); + ClassDB::bind_method(D_METHOD("set_ime_position", "position"), &_OS::set_ime_position); ClassDB::bind_method(D_METHOD("set_screen_orientation", "orientation"), &_OS::set_screen_orientation); @@ -1088,6 +1136,7 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("delay_usec", "usec"), &_OS::delay_usec); ClassDB::bind_method(D_METHOD("delay_msec", "msec"), &_OS::delay_msec); ClassDB::bind_method(D_METHOD("get_ticks_msec"), &_OS::get_ticks_msec); + ClassDB::bind_method(D_METHOD("get_ticks_usec"), &_OS::get_ticks_usec); ClassDB::bind_method(D_METHOD("get_splash_tick_msec"), &_OS::get_splash_tick_msec); ClassDB::bind_method(D_METHOD("get_locale"), &_OS::get_locale); ClassDB::bind_method(D_METHOD("get_latin_keyboard_variant"), &_OS::get_latin_keyboard_variant); @@ -1159,6 +1208,7 @@ void _OS::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "screen_orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait,Reverse Landscape,Reverse Portrait,Sensor Landscape,Sensor Portrait,Sensor"), "set_screen_orientation", "get_screen_orientation"); ADD_GROUP("Window", "window_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_borderless"), "set_borderless_window", "get_borderless_window"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_per_pixel_transparency_enabled"), "set_window_per_pixel_transparency_enabled", "get_window_per_pixel_transparency_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_fullscreen"), "set_window_fullscreen", "is_window_fullscreen"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_maximized"), "set_window_maximized", "is_window_maximized"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "window_minimized"), "set_window_minimized", "is_window_minimized"); @@ -1255,6 +1305,16 @@ Variant _Geometry::segment_intersects_segment_2d(const Vector2 &p_from_a, const }; }; +Variant _Geometry::line_intersects_line_2d(const Vector2 &p_from_a, const Vector2 &p_dir_a, const Vector2 &p_from_b, const Vector2 &p_dir_b) { + + Vector2 result; + if (Geometry::line_intersects_line_2d(p_from_a, p_dir_a, p_from_b, p_dir_b, result)) { + return result; + } else { + return Variant(); + } +} + PoolVector<Vector2> _Geometry::get_closest_points_between_segments_2d(const Vector2 &p1, const Vector2 &q1, const Vector2 &p2, const Vector2 &q2) { Vector2 r1, r2; @@ -1410,6 +1470,7 @@ void _Geometry::_bind_methods() { ClassDB::bind_method(D_METHOD("build_capsule_planes", "radius", "height", "sides", "lats", "axis"), &_Geometry::build_capsule_planes, DEFVAL(Vector3::AXIS_Z)); ClassDB::bind_method(D_METHOD("segment_intersects_circle", "segment_from", "segment_to", "circle_position", "circle_radius"), &_Geometry::segment_intersects_circle); ClassDB::bind_method(D_METHOD("segment_intersects_segment_2d", "from_a", "to_a", "from_b", "to_b"), &_Geometry::segment_intersects_segment_2d); + ClassDB::bind_method(D_METHOD("line_intersects_line_2d", "from_a", "dir_a", "from_b", "dir_b"), &_Geometry::line_intersects_line_2d); ClassDB::bind_method(D_METHOD("get_closest_points_between_segments_2d", "p1", "q1", "p2", "q2"), &_Geometry::get_closest_points_between_segments_2d); ClassDB::bind_method(D_METHOD("get_closest_points_between_segments", "p1", "p2", "q1", "q2"), &_Geometry::get_closest_points_between_segments); @@ -1514,6 +1575,17 @@ bool _File::is_open() const { return f != NULL; } +String _File::get_path() const { + + ERR_FAIL_COND_V(!f, ""); + return f->get_path(); +} + +String _File::get_path_absolute() const { + + ERR_FAIL_COND_V(!f, ""); + return f->get_path_absolute(); +} void _File::seek(int64_t p_position) { @@ -1803,6 +1875,8 @@ void _File::_bind_methods() { ClassDB::bind_method(D_METHOD("open", "path", "flags"), &_File::open); ClassDB::bind_method(D_METHOD("close"), &_File::close); + ClassDB::bind_method(D_METHOD("get_path"), &_File::get_path); + ClassDB::bind_method(D_METHOD("get_path_absolute"), &_File::get_path_absolute); ClassDB::bind_method(D_METHOD("is_open"), &_File::is_open); ClassDB::bind_method(D_METHOD("seek", "position"), &_File::seek); ClassDB::bind_method(D_METHOD("seek_end", "position"), &_File::seek_end, DEFVAL(0)); @@ -2374,7 +2448,7 @@ _Thread::_Thread() { _Thread::~_Thread() { if (active) { - ERR_EXPLAIN("Reference to a Thread object object was lost while the thread is still running.."); + ERR_EXPLAIN("Reference to a Thread object object was lost while the thread is still running..."); } ERR_FAIL_COND(active == true); } @@ -2602,6 +2676,14 @@ int _Engine::get_iterations_per_second() const { return Engine::get_singleton()->get_iterations_per_second(); } +void _Engine::set_physics_jitter_fix(float p_threshold) { + Engine::get_singleton()->set_physics_jitter_fix(p_threshold); +} + +float _Engine::get_physics_jitter_fix() const { + return Engine::get_singleton()->get_physics_jitter_fix(); +} + void _Engine::set_target_fps(int p_fps) { Engine::get_singleton()->set_target_fps(p_fps); } @@ -2640,6 +2722,26 @@ Dictionary _Engine::get_version_info() const { return Engine::get_singleton()->get_version_info(); } +Dictionary _Engine::get_author_info() const { + return Engine::get_singleton()->get_author_info(); +} + +Array _Engine::get_copyright_info() const { + return Engine::get_singleton()->get_copyright_info(); +} + +Dictionary _Engine::get_donor_info() const { + return Engine::get_singleton()->get_donor_info(); +} + +Dictionary _Engine::get_license_info() const { + return Engine::get_singleton()->get_license_info(); +} + +String _Engine::get_license_text() const { + return Engine::get_singleton()->get_license_text(); +} + bool _Engine::is_in_physics_frame() const { return Engine::get_singleton()->is_in_physics_frame(); } @@ -2668,6 +2770,8 @@ void _Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_iterations_per_second", "iterations_per_second"), &_Engine::set_iterations_per_second); ClassDB::bind_method(D_METHOD("get_iterations_per_second"), &_Engine::get_iterations_per_second); + ClassDB::bind_method(D_METHOD("set_physics_jitter_fix", "physics_jitter_fix"), &_Engine::set_physics_jitter_fix); + ClassDB::bind_method(D_METHOD("get_physics_jitter_fix"), &_Engine::get_physics_jitter_fix); ClassDB::bind_method(D_METHOD("set_target_fps", "target_fps"), &_Engine::set_target_fps); ClassDB::bind_method(D_METHOD("get_target_fps"), &_Engine::get_target_fps); @@ -2680,6 +2784,11 @@ void _Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("get_main_loop"), &_Engine::get_main_loop); ClassDB::bind_method(D_METHOD("get_version_info"), &_Engine::get_version_info); + ClassDB::bind_method(D_METHOD("get_author_info"), &_Engine::get_author_info); + ClassDB::bind_method(D_METHOD("get_copyright_info"), &_Engine::get_copyright_info); + ClassDB::bind_method(D_METHOD("get_donor_info"), &_Engine::get_donor_info); + ClassDB::bind_method(D_METHOD("get_license_info"), &_Engine::get_license_info); + ClassDB::bind_method(D_METHOD("get_license_text"), &_Engine::get_license_text); ClassDB::bind_method(D_METHOD("is_in_physics_frame"), &_Engine::is_in_physics_frame); @@ -2693,6 +2802,7 @@ void _Engine::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "iterations_per_second"), "set_iterations_per_second", "get_iterations_per_second"); ADD_PROPERTY(PropertyInfo(Variant::INT, "target_fps"), "set_target_fps", "get_target_fps"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "time_scale"), "set_time_scale", "get_time_scale"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "physics_jitter_fix"), "set_physics_jitter_fix", "get_physics_jitter_fix"); } _Engine *_Engine::singleton = NULL; diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index 734b57937a..48b7b74005 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -146,6 +146,12 @@ public: bool is_video_mode_resizable(int p_screen = 0) const; Array get_fullscreen_mode_list(int p_screen = 0) const; + virtual int get_video_driver_count() const; + virtual String get_video_driver_name(int p_driver) const; + + virtual int get_audio_driver_count() const; + virtual String get_audio_driver_name(int p_driver) const; + virtual int get_screen_count() const; virtual int get_current_screen() const; virtual void set_current_screen(int p_screen); @@ -156,6 +162,7 @@ public: virtual void set_window_position(const Point2 &p_position); virtual Size2 get_window_size() const; virtual Size2 get_real_window_size() const; + virtual Rect2 get_window_safe_area() const; virtual void set_window_size(const Size2 &p_size); virtual void set_window_fullscreen(bool p_enabled); virtual bool is_window_fullscreen() const; @@ -173,6 +180,10 @@ public: virtual void set_borderless_window(bool p_borderless); virtual bool get_borderless_window() const; + virtual bool get_window_per_pixel_transparency_enabled() const; + virtual void set_window_per_pixel_transparency_enabled(bool p_enabled); + + virtual void set_ime_active(const bool p_active); virtual void set_ime_position(const Point2 &p_pos); Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); @@ -266,6 +277,7 @@ public: void delay_usec(uint32_t p_usec) const; void delay_msec(uint32_t p_msec) const; uint32_t get_ticks_msec() const; + uint64_t get_ticks_usec() const; uint32_t get_splash_tick_msec() const; bool can_use_threads() const; @@ -351,6 +363,7 @@ public: PoolVector<Plane> build_cylinder_planes(float p_radius, float p_height, int p_sides, Vector3::Axis p_axis = Vector3::AXIS_Z); PoolVector<Plane> build_capsule_planes(float p_radius, float p_height, int p_sides, int p_lats, Vector3::Axis p_axis = Vector3::AXIS_Z); Variant segment_intersects_segment_2d(const Vector2 &p_from_a, const Vector2 &p_to_a, const Vector2 &p_from_b, const Vector2 &p_to_b); + Variant line_intersects_line_2d(const Vector2 &p_from_a, const Vector2 &p_dir_a, const Vector2 &p_from_b, const Vector2 &p_dir_b); PoolVector<Vector2> get_closest_points_between_segments_2d(const Vector2 &p1, const Vector2 &q1, const Vector2 &p2, const Vector2 &q2); PoolVector<Vector3> get_closest_points_between_segments(const Vector3 &p1, const Vector3 &p2, const Vector3 &q1, const Vector3 &q2); Vector2 get_closest_point_to_segment_2d(const Vector2 &p_point, const Vector2 &p_a, const Vector2 &p_b); @@ -409,6 +422,9 @@ public: void close(); ///< close a file bool is_open() const; ///< true when file is open + String get_path() const; /// returns the path for the current open file + String get_path_absolute() const; /// returns the absolute path for the current open file + void seek(int64_t p_position); ///< seek to a given position void seek_end(int64_t p_position = 0); ///< seek from the end of file int64_t get_position() const; ///< get position in the file @@ -659,6 +675,9 @@ public: void set_iterations_per_second(int p_ips); int get_iterations_per_second() const; + void set_physics_jitter_fix(float p_threshold); + float get_physics_jitter_fix() const; + void set_target_fps(int p_fps); int get_target_fps() const; @@ -672,6 +691,11 @@ public: MainLoop *get_main_loop() const; Dictionary get_version_info() const; + Dictionary get_author_info() const; + Array get_copyright_info() const; + Dictionary get_donor_info() const; + Dictionary get_license_info() const; + String get_license_text() const; bool is_in_physics_frame() const; diff --git a/core/class_db.cpp b/core/class_db.cpp index afcc8de0f2..f97eaf6099 100644 --- a/core/class_db.cpp +++ b/core/class_db.cpp @@ -33,18 +33,9 @@ #include "os/mutex.h" #include "version.h" -#ifdef NO_THREADS - -#define OBJTYPE_RLOCK -#define OBJTYPE_WLOCK - -#else - #define OBJTYPE_RLOCK RWLockRead _rw_lockr_(lock); #define OBJTYPE_WLOCK RWLockWrite _rw_lockw_(lock); -#endif - #ifdef DEBUG_METHODS_ENABLED MethodDefinition D_METHOD(const char *p_name) { @@ -257,17 +248,19 @@ void ClassDB::set_current_api(APIType p_api) { current_api = p_api; } -HashMap<StringName, ClassDB::ClassInfo, StringNameHasher> ClassDB::classes; -HashMap<StringName, StringName, StringNameHasher> ClassDB::resource_base_extensions; -HashMap<StringName, StringName, StringNameHasher> ClassDB::compat_classes; +HashMap<StringName, ClassDB::ClassInfo> ClassDB::classes; +HashMap<StringName, StringName> ClassDB::resource_base_extensions; +HashMap<StringName, StringName> ClassDB::compat_classes; ClassDB::ClassInfo::ClassInfo() { + api = API_NONE; creation_func = NULL; inherits_ptr = NULL; disabled = false; exposed = false; } + ClassDB::ClassInfo::~ClassInfo() { } @@ -347,7 +340,7 @@ uint64_t ClassDB::get_api_hash(APIType p_api) { OBJTYPE_RLOCK; #ifdef DEBUG_METHODS_ENABLED - uint64_t hash = hash_djb2_one_64(HashMapHasherDefault::hash(VERSION_FULL_NAME)); + uint64_t hash = hash_djb2_one_64(HashMapHasherDefault::hash(VERSION_FULL_CONFIG)); List<StringName> names; @@ -364,7 +357,7 @@ uint64_t ClassDB::get_api_hash(APIType p_api) { ClassInfo *t = classes.getptr(E->get()); ERR_FAIL_COND_V(!t, 0); - if (t->api != p_api) + if (t->api != p_api || !t->exposed) continue; hash = hash_djb2_one_64(t->name.hash(), hash); hash = hash_djb2_one_64(t->inherits.hash(), hash); @@ -660,7 +653,6 @@ void ClassDB::bind_integer_constant(const StringName &p_class, const StringName } type->constant_map[p_name] = p_constant; -#ifdef DEBUG_METHODS_ENABLED String enum_name = p_enum; if (enum_name != String()) { @@ -679,6 +671,7 @@ void ClassDB::bind_integer_constant(const StringName &p_class, const StringName } } +#ifdef DEBUG_METHODS_ENABLED type->constant_order.push_back(p_name); #endif } @@ -734,7 +727,6 @@ int ClassDB::get_integer_constant(const StringName &p_class, const StringName &p return 0; } -#ifdef DEBUG_METHODS_ENABLED StringName ClassDB::get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) { OBJTYPE_RLOCK; @@ -803,7 +795,6 @@ void ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_ type = type->inherits_ptr; } } -#endif void ClassDB::add_signal(StringName p_class, const MethodInfo &p_signal) { @@ -895,15 +886,9 @@ void ClassDB::add_property_group(StringName p_class, const String &p_name, const void ClassDB::add_property(StringName p_class, const PropertyInfo &p_pinfo, const StringName &p_setter, const StringName &p_getter, int p_index) { -#ifndef NO_THREADS lock->read_lock(); -#endif - ClassInfo *type = classes.getptr(p_class); - -#ifndef NO_THREADS lock->read_unlock(); -#endif ERR_FAIL_COND(!type); @@ -1380,10 +1365,7 @@ RWLock *ClassDB::lock = NULL; void ClassDB::init() { -#ifndef NO_THREADS - lock = RWLock::create(); -#endif } void ClassDB::cleanup() { @@ -1406,10 +1388,7 @@ void ClassDB::cleanup() { resource_base_extensions.clear(); compat_classes.clear(); -#ifndef NO_THREADS - memdelete(lock); -#endif } // diff --git a/core/class_db.h b/core/class_db.h index d74317239b..f1d1879236 100644 --- a/core/class_db.h +++ b/core/class_db.h @@ -114,19 +114,19 @@ public: APIType api; ClassInfo *inherits_ptr; - HashMap<StringName, MethodBind *, StringNameHasher> method_map; - HashMap<StringName, int, StringNameHasher> constant_map; - HashMap<StringName, MethodInfo, StringNameHasher> signal_map; + HashMap<StringName, MethodBind *> method_map; + HashMap<StringName, int> constant_map; + HashMap<StringName, List<StringName> > enum_map; + HashMap<StringName, MethodInfo> signal_map; List<PropertyInfo> property_list; #ifdef DEBUG_METHODS_ENABLED - HashMap<StringName, List<StringName> > enum_map; List<StringName> constant_order; List<StringName> method_order; Set<StringName> methods_in_properties; List<MethodInfo> virtual_methods; StringName category; #endif - HashMap<StringName, PropertySetGet, StringNameHasher> property_setget; + HashMap<StringName, PropertySetGet> property_setget; StringName inherits; StringName name; @@ -143,9 +143,9 @@ public: } static RWLock *lock; - static HashMap<StringName, ClassInfo, StringNameHasher> classes; - static HashMap<StringName, StringName, StringNameHasher> resource_base_extensions; - static HashMap<StringName, StringName, StringNameHasher> compat_classes; + static HashMap<StringName, ClassInfo> classes; + static HashMap<StringName, StringName> resource_base_extensions; + static HashMap<StringName, StringName> compat_classes; #ifdef DEBUG_METHODS_ENABLED static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount); @@ -344,11 +344,9 @@ public: static void get_integer_constant_list(const StringName &p_class, List<String> *p_constants, bool p_no_inheritance = false); static int get_integer_constant(const StringName &p_class, const StringName &p_name, bool *p_success = NULL); -#ifdef DEBUG_METHODS_ENABLED static StringName get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false); static void get_enum_list(const StringName &p_class, List<StringName> *p_enums, bool p_no_inheritance = false); static void get_enum_constants(const StringName &p_class, const StringName &p_enum, List<StringName> *p_constants, bool p_no_inheritance = false); -#endif static StringName get_category(const StringName &p_node); diff --git a/core/color.cpp b/core/color.cpp index 27d8e9b891..88e57ec6e2 100644 --- a/core/color.cpp +++ b/core/color.cpp @@ -37,38 +37,38 @@ uint32_t Color::to_argb32() const { - uint32_t c = (uint8_t)(a * 255); + uint32_t c = (uint8_t)Math::round(a * 255); c <<= 8; - c |= (uint8_t)(r * 255); + c |= (uint8_t)Math::round(r * 255); c <<= 8; - c |= (uint8_t)(g * 255); + c |= (uint8_t)Math::round(g * 255); c <<= 8; - c |= (uint8_t)(b * 255); + c |= (uint8_t)Math::round(b * 255); return c; } uint32_t Color::to_abgr32() const { - uint32_t c = (uint8_t)(a * 255); + uint32_t c = (uint8_t)Math::round(a * 255); c <<= 8; - c |= (uint8_t)(b * 255); + c |= (uint8_t)Math::round(b * 255); c <<= 8; - c |= (uint8_t)(g * 255); + c |= (uint8_t)Math::round(g * 255); c <<= 8; - c |= (uint8_t)(r * 255); + c |= (uint8_t)Math::round(r * 255); return c; } uint32_t Color::to_rgba32() const { - uint32_t c = (uint8_t)(r * 255); + uint32_t c = (uint8_t)Math::round(r * 255); c <<= 8; - c |= (uint8_t)(g * 255); + c |= (uint8_t)Math::round(g * 255); c <<= 8; - c |= (uint8_t)(b * 255); + c |= (uint8_t)Math::round(b * 255); c <<= 8; - c |= (uint8_t)(a * 255); + c |= (uint8_t)Math::round(a * 255); return c; } @@ -368,7 +368,7 @@ Color Color::named(const String &p_name) { String _to_hex(float p_val) { - int v = p_val * 255; + int v = Math::round(p_val * 255); v = CLAMP(v, 0, 255); String ret; @@ -402,6 +402,10 @@ String Color::to_html(bool p_alpha) const { Color Color::from_hsv(float p_h, float p_s, float p_v, float p_a) { + p_h = Math::fmod(p_h * 360.0f, 360.0f); + if (p_h < 0.0) + p_h += 360.0f; + const float h_ = p_h / 60.0f; const float c = p_v * p_s; const float x = c * (1.0f - Math::abs(Math::fmod(h_, 2.0f) - 1.0f)); diff --git a/core/color_names.inc b/core/color_names.inc index b05684acc6..3ae42648d0 100644 --- a/core/color_names.inc +++ b/core/color_names.inc @@ -3,7 +3,7 @@ static Map<String, Color> _named_colors; static void _populate_named_colors() { - if(!_named_colors.empty()) return; + if (!_named_colors.empty()) return; _named_colors.insert("aliceblue", Color(0.94, 0.97, 1.00)); _named_colors.insert("antiquewhite", Color(0.98, 0.92, 0.84)); _named_colors.insert("aqua", Color(0.00, 1.00, 1.00)); diff --git a/core/command_queue_mt.cpp b/core/command_queue_mt.cpp index 6bb3135757..a39c920dfa 100644 --- a/core/command_queue_mt.cpp +++ b/core/command_queue_mt.cpp @@ -105,6 +105,7 @@ CommandQueueMT::CommandQueueMT(bool p_sync) { read_ptr = 0; write_ptr = 0; + dealloc_ptr = 0; mutex = Mutex::create(); for (int i = 0; i < SYNC_SEMAPHORES; i++) { diff --git a/core/command_queue_mt.h b/core/command_queue_mt.h index 3942b961d3..7978eaa7bf 100644 --- a/core/command_queue_mt.h +++ b/core/command_queue_mt.h @@ -54,9 +54,13 @@ #define _COMMA_10 , #define _COMMA_11 , #define _COMMA_12 , +#define _COMMA_13 , // 1-based comma separated list of ITEMs #define COMMA_SEP_LIST(ITEM, LENGTH) _COMMA_SEP_LIST_##LENGTH(ITEM) +#define _COMMA_SEP_LIST_13(ITEM) \ + _COMMA_SEP_LIST_12(ITEM) \ + , ITEM(13) #define _COMMA_SEP_LIST_12(ITEM) \ _COMMA_SEP_LIST_11(ITEM) \ , ITEM(12) @@ -97,6 +101,9 @@ // 1-based semicolon separated list of ITEMs #define SEMIC_SEP_LIST(ITEM, LENGTH) _SEMIC_SEP_LIST_##LENGTH(ITEM) +#define _SEMIC_SEP_LIST_13(ITEM) \ + _SEMIC_SEP_LIST_12(ITEM); \ + ITEM(13) #define _SEMIC_SEP_LIST_12(ITEM) \ _SEMIC_SEP_LIST_11(ITEM); \ ITEM(12) @@ -137,6 +144,9 @@ // 1-based space separated list of ITEMs #define SPACE_SEP_LIST(ITEM, LENGTH) _SPACE_SEP_LIST_##LENGTH(ITEM) +#define _SPACE_SEP_LIST_13(ITEM) \ + _SPACE_SEP_LIST_12(ITEM) \ + ITEM(13) #define _SPACE_SEP_LIST_12(ITEM) \ _SPACE_SEP_LIST_11(ITEM) \ ITEM(12) @@ -262,7 +272,7 @@ ss->sem->wait(); \ } -#define MAX_CMD_PARAMS 12 +#define MAX_CMD_PARAMS 13 class CommandQueueMT { @@ -290,15 +300,15 @@ class CommandQueueMT { }; DECL_CMD(0) - SPACE_SEP_LIST(DECL_CMD, 12) + SPACE_SEP_LIST(DECL_CMD, 13) /* comands that return */ DECL_CMD_RET(0) - SPACE_SEP_LIST(DECL_CMD_RET, 12) + SPACE_SEP_LIST(DECL_CMD_RET, 13) /* commands that don't return but sync */ DECL_CMD_SYNC(0) - SPACE_SEP_LIST(DECL_CMD_SYNC, 12) + SPACE_SEP_LIST(DECL_CMD_SYNC, 13) /***** BASE *******/ @@ -432,15 +442,15 @@ class CommandQueueMT { public: /* NORMAL PUSH COMMANDS */ DECL_PUSH(0) - SPACE_SEP_LIST(DECL_PUSH, 12) + SPACE_SEP_LIST(DECL_PUSH, 13) /* PUSH AND RET COMMANDS */ DECL_PUSH_AND_RET(0) - SPACE_SEP_LIST(DECL_PUSH_AND_RET, 12) + SPACE_SEP_LIST(DECL_PUSH_AND_RET, 13) /* PUSH AND RET SYNC COMMANDS*/ DECL_PUSH_AND_SYNC(0) - SPACE_SEP_LIST(DECL_PUSH_AND_SYNC, 12) + SPACE_SEP_LIST(DECL_PUSH_AND_SYNC, 13) void wait_and_flush_one() { ERR_FAIL_COND(!sync); diff --git a/core/dictionary.cpp b/core/dictionary.cpp index e3f4aa5f28..d68411a572 100644 --- a/core/dictionary.cpp +++ b/core/dictionary.cpp @@ -50,6 +50,32 @@ void Dictionary::get_key_list(List<Variant> *p_keys) const { } } +Variant Dictionary::get_key_at_index(int p_index) const { + + int index = 0; + for (OrderedHashMap<Variant, Variant, VariantHasher, VariantComparator>::Element E = _p->variant_map.front(); E; E = E.next()) { + if (index == p_index) { + return E.key(); + } + index++; + } + + return Variant(); +} + +Variant Dictionary::get_value_at_index(int p_index) const { + + int index = 0; + for (OrderedHashMap<Variant, Variant, VariantHasher, VariantComparator>::Element E = _p->variant_map.front(); E; E = E.next()) { + if (index == p_index) { + return E.value(); + } + index++; + } + + return Variant(); +} + Variant &Dictionary::operator[](const Variant &p_key) { return _p->variant_map[p_key]; @@ -211,7 +237,7 @@ const Variant *Dictionary::next(const Variant *p_key) const { return NULL; } -Dictionary Dictionary::duplicate() const { +Dictionary Dictionary::duplicate(bool p_deep) const { Dictionary n; @@ -219,7 +245,7 @@ Dictionary Dictionary::duplicate() const { get_key_list(&keys); for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - n[E->get()] = operator[](E->get()); + n[E->get()] = p_deep ? operator[](E->get()).duplicate(p_deep) : operator[](E->get()); } return n; diff --git a/core/dictionary.h b/core/dictionary.h index f001f2d5e1..84a5cafe1d 100644 --- a/core/dictionary.h +++ b/core/dictionary.h @@ -47,6 +47,8 @@ class Dictionary { public: void get_key_list(List<Variant> *p_keys) const; + Variant get_key_at_index(int p_index) const; + Variant get_value_at_index(int p_index) const; Variant &operator[](const Variant &p_key); const Variant &operator[](const Variant &p_key) const; @@ -75,7 +77,7 @@ public: Array keys() const; Array values() const; - Dictionary duplicate() const; + Dictionary duplicate(bool p_deep = false) const; Dictionary(const Dictionary &p_from); Dictionary(); diff --git a/core/engine.cpp b/core/engine.cpp index af9052110f..7c8024b946 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -30,6 +30,9 @@ #include "engine.h" +#include "authors.gen.h" +#include "donors.gen.h" +#include "license.gen.h" #include "version.h" #include "version_hash.gen.h" @@ -42,6 +45,16 @@ int Engine::get_iterations_per_second() const { return ips; } +void Engine::set_physics_jitter_fix(float p_threshold) { + if (p_threshold < 0) + p_threshold = 0; + physics_jitter_fix = p_threshold; +} + +float Engine::get_physics_jitter_fix() const { + return physics_jitter_fix; +} + void Engine::set_target_fps(int p_fps) { _target_fps = p_fps > 0 ? p_fps : 0; } @@ -101,6 +114,78 @@ Dictionary Engine::get_version_info() const { return dict; } +static Array array_from_info(const char *const *info_list) { + Array arr; + for (int i = 0; info_list[i] != NULL; i++) { + arr.push_back(info_list[i]); + } + return arr; +} + +static Array array_from_info_count(const char *const *info_list, int info_count) { + Array arr; + for (int i = 0; i < info_count; i++) { + arr.push_back(info_list[i]); + } + return arr; +} + +Dictionary Engine::get_author_info() const { + Dictionary dict; + + dict["lead_developers"] = array_from_info(AUTHORS_LEAD_DEVELOPERS); + dict["project_managers"] = array_from_info(AUTHORS_PROJECT_MANAGERS); + dict["founders"] = array_from_info(AUTHORS_FOUNDERS); + dict["developers"] = array_from_info(AUTHORS_DEVELOPERS); + + return dict; +} + +Array Engine::get_copyright_info() const { + Array components; + for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) { + const ComponentCopyright &cp_info = COPYRIGHT_INFO[component_index]; + Dictionary component_dict; + component_dict["name"] = cp_info.name; + Array parts; + for (int i = 0; i < cp_info.part_count; i++) { + const ComponentCopyrightPart &cp_part = cp_info.parts[i]; + Dictionary part_dict; + part_dict["files"] = array_from_info_count(cp_part.files, cp_part.file_count); + part_dict["copyright"] = array_from_info_count(cp_part.copyright_statements, cp_part.copyright_count); + part_dict["license"] = cp_part.license; + parts.push_back(part_dict); + } + component_dict["parts"] = parts; + + components.push_back(component_dict); + } + return components; +} + +Dictionary Engine::get_donor_info() const { + Dictionary donors; + donors["platinum_sponsors"] = array_from_info(DONORS_SPONSOR_PLAT); + donors["gold_sponsors"] = array_from_info(DONORS_SPONSOR_GOLD); + donors["mini_sponsors"] = array_from_info(DONORS_SPONSOR_MINI); + donors["gold_donors"] = array_from_info(DONORS_GOLD); + donors["silver_donors"] = array_from_info(DONORS_SILVER); + donors["bronze_donors"] = array_from_info(DONORS_BRONZE); + return donors; +} + +Dictionary Engine::get_license_info() const { + Dictionary licenses; + for (int i = 0; i < LICENSE_COUNT; i++) { + licenses[LICENSE_NAMES[i]] = LICENSE_BODIES[i]; + } + return licenses; +} + +String Engine::get_license_text() const { + return String(GODOT_LICENSE_TEXT); +} + void Engine::add_singleton(const Singleton &p_singleton) { singletons.push_back(p_singleton); @@ -137,6 +222,7 @@ Engine::Engine() { singleton = this; frames_drawn = 0; ips = 60; + physics_jitter_fix = 0.5; _frame_delay = 0; _fps = 1; _target_fps = 0; diff --git a/core/engine.h b/core/engine.h index 54b30ab81f..031ba29cd6 100644 --- a/core/engine.h +++ b/core/engine.h @@ -57,6 +57,7 @@ private: float _frame_step; int ips; + float physics_jitter_fix; float _fps; int _target_fps; float _time_scale; @@ -79,6 +80,9 @@ public: virtual void set_iterations_per_second(int p_ips); virtual int get_iterations_per_second() const; + void set_physics_jitter_fix(float p_threshold); + float get_physics_jitter_fix() const; + virtual void set_target_fps(int p_fps); virtual float get_target_fps() const; @@ -114,6 +118,11 @@ public: #endif Dictionary get_version_info() const; + Dictionary get_author_info() const; + Array get_copyright_info() const; + Dictionary get_donor_info() const; + Dictionary get_license_info() const; + String get_license_text() const; Engine(); }; diff --git a/core/error_macros.h b/core/error_macros.h index b8d0c7e0c3..3587e01d54 100644 --- a/core/error_macros.h +++ b/core/error_macros.h @@ -311,4 +311,14 @@ extern bool _err_error_exists; _err_error_exists = false; \ } +#define WARN_DEPRECATED \ + { \ + static bool warning_shown = false; \ + if (!warning_shown) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future", ERR_HANDLER_WARNING); \ + _err_error_exists = false; \ + warning_shown = true; \ + } \ + } + #endif diff --git a/core/hashfuncs.h b/core/hashfuncs.h index ae99fa39c8..735e679d1e 100644 --- a/core/hashfuncs.h +++ b/core/hashfuncs.h @@ -33,6 +33,8 @@ #include "math_defs.h" #include "math_funcs.h" +#include "node_path.h" +#include "string_db.h" #include "typedefs.h" #include "ustring.h" @@ -131,6 +133,7 @@ static inline uint64_t make_uint64_t(T p_in) { } struct HashMapHasherDefault { + static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); } static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); } static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); } @@ -145,6 +148,10 @@ struct HashMapHasherDefault { static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return p_int; } static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return (uint32_t)p_int; } static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return (uint32_t)p_wchar; } + + static _FORCE_INLINE_ uint32_t hash(const StringName &p_string_name) { return p_string_name.hash(); } + static _FORCE_INLINE_ uint32_t hash(const NodePath &p_path) { return p_path.hash(); } + //static _FORCE_INLINE_ uint32_t hash(const void* p_ptr) { return uint32_t(uint64_t(p_ptr))*(0x9e3779b1L); } }; diff --git a/core/image.cpp b/core/image.cpp index 07e705265d..b0bed80a6f 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -366,6 +366,8 @@ int Image::get_mipmap_count() const { template <uint32_t read_bytes, bool read_alpha, uint32_t write_bytes, bool write_alpha, bool read_gray, bool write_gray> static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p_dst) { + uint32_t max_bytes = MAX(read_bytes, write_bytes); + for (int y = 0; y < p_height; y++) { for (int x = 0; x < p_width; x++) { @@ -379,7 +381,8 @@ static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p rgba[1] = rofs[0]; rgba[2] = rofs[0]; } else { - for (uint32_t i = 0; i < MAX(read_bytes, write_bytes); i++) { + + for (uint32_t i = 0; i < max_bytes; i++) { rgba[i] = (i < read_bytes) ? rofs[i] : 0; } @@ -937,7 +940,7 @@ bool Image::_can_modify(Format p_format) const { return p_format <= FORMAT_RGBE9995; } -template <int CC> +template <int CC, bool renormalize> static void _generate_po2_mipmap(const uint8_t *p_src, uint8_t *p_dst, uint32_t p_width, uint32_t p_height) { //fast power of 2 mipmap generation @@ -963,6 +966,19 @@ static void _generate_po2_mipmap(const uint8_t *p_src, uint8_t *p_dst, uint32_t dst_ptr[j] = val >> 2; } + if (renormalize) { + Vector3 n(dst_ptr[0] / 255.0, dst_ptr[1] / 255.0, dst_ptr[2] / 255.0); + n *= 2.0; + n -= Vector3(1, 1, 1); + n.normalize(); + n += Vector3(1, 1, 1); + n *= 0.5; + n *= 255; + dst_ptr[0] = CLAMP(int(n.x), 0, 255); + dst_ptr[1] = CLAMP(int(n.y), 0, 255); + dst_ptr[2] = CLAMP(int(n.z), 0, 255); + } + dst_ptr += CC; rup_ptr += CC * 2; rdown_ptr += CC * 2; @@ -1045,11 +1061,11 @@ void Image::shrink_x2() { switch (format) { case FORMAT_L8: - case FORMAT_R8: _generate_po2_mipmap<1>(r.ptr(), w.ptr(), width, height); break; - case FORMAT_LA8: _generate_po2_mipmap<2>(r.ptr(), w.ptr(), width, height); break; - case FORMAT_RG8: _generate_po2_mipmap<2>(r.ptr(), w.ptr(), width, height); break; - case FORMAT_RGB8: _generate_po2_mipmap<3>(r.ptr(), w.ptr(), width, height); break; - case FORMAT_RGBA8: _generate_po2_mipmap<4>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_R8: _generate_po2_mipmap<1, false>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_LA8: _generate_po2_mipmap<2, false>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_RG8: _generate_po2_mipmap<2, false>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_RGB8: _generate_po2_mipmap<3, false>(r.ptr(), w.ptr(), width, height); break; + case FORMAT_RGBA8: _generate_po2_mipmap<4, false>(r.ptr(), w.ptr(), width, height); break; default: {} } } @@ -1060,7 +1076,37 @@ void Image::shrink_x2() { } } -Error Image::generate_mipmaps() { +void Image::normalize() { + + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { + clear_mipmaps(); + } + + lock(); + + for (int y = 0; y < height; y++) { + + for (int x = 0; x < width; x++) { + + Color c = get_pixel(x, y); + Vector3 v(c.r * 2.0 - 1.0, c.g * 2.0 - 1.0, c.b * 2.0 - 1.0); + v.normalize(); + c.r = v.x * 0.5 + 0.5; + c.g = v.y * 0.5 + 0.5; + c.b = v.z * 0.5 + 0.5; + set_pixel(x, y, c); + } + } + + unlock(); + + if (used_mipmaps) { + generate_mipmaps(true); + } +} + +Error Image::generate_mipmaps(bool p_renormalize) { if (!_can_modify(format)) { ERR_EXPLAIN("Cannot generate mipmaps in indexed, compressed or custom image formats."); @@ -1077,61 +1123,40 @@ Error Image::generate_mipmaps() { PoolVector<uint8_t>::Write wp = data.write(); - if (next_power_of_2(width) == uint32_t(width) && next_power_of_2(height) == uint32_t(height)) { - //use fast code for powers of 2 - int prev_ofs = 0; - int prev_h = height; - int prev_w = width; + int prev_ofs = 0; + int prev_h = height; + int prev_w = width; - for (int i = 1; i < mmcount; i++) { + for (int i = 1; i < mmcount; i++) { - int ofs, w, h; - _get_mipmap_offset_and_size(i, ofs, w, h); + int ofs, w, h; + _get_mipmap_offset_and_size(i, ofs, w, h); - switch (format) { + switch (format) { - case FORMAT_L8: - case FORMAT_R8: _generate_po2_mipmap<1>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; - case FORMAT_LA8: - case FORMAT_RG8: _generate_po2_mipmap<2>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; - case FORMAT_RGB8: _generate_po2_mipmap<3>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; - case FORMAT_RGBA8: _generate_po2_mipmap<4>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; - default: {} - } + case FORMAT_L8: + case FORMAT_R8: _generate_po2_mipmap<1, false>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; + case FORMAT_LA8: + case FORMAT_RG8: _generate_po2_mipmap<2, false>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); break; + case FORMAT_RGB8: + if (p_renormalize) + _generate_po2_mipmap<3, true>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + else + _generate_po2_mipmap<3, false>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); - prev_ofs = ofs; - prev_w = w; - prev_h = h; + break; + case FORMAT_RGBA8: + if (p_renormalize) + _generate_po2_mipmap<4, true>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + else + _generate_po2_mipmap<4, false>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + break; + default: {} } - } else { - //use slow code.. - - //use bilinear filtered code for non powers of 2 - int prev_ofs = 0; - int prev_h = height; - int prev_w = width; - - for (int i = 1; i < mmcount; i++) { - - int ofs, w, h; - _get_mipmap_offset_and_size(i, ofs, w, h); - - switch (format) { - - case FORMAT_L8: - case FORMAT_R8: _scale_bilinear<1>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h, w, h); break; - case FORMAT_LA8: - case FORMAT_RG8: _scale_bilinear<2>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h, w, h); break; - case FORMAT_RGB8: _scale_bilinear<3>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h, w, h); break; - case FORMAT_RGBA8: _scale_bilinear<4>(&wp[prev_ofs], &wp[ofs], prev_w, prev_h, w, h); break; - default: {} - } - - prev_ofs = ofs; - prev_w = w; - prev_h = h; - } + prev_ofs = ofs; + prev_w = w; + prev_h = h; } mipmaps = true; @@ -1166,6 +1191,9 @@ PoolVector<uint8_t> Image::get_data() const { void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { + ERR_FAIL_INDEX(p_width - 1, MAX_WIDTH); + ERR_FAIL_INDEX(p_height - 1, MAX_HEIGHT); + int mm = 0; int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); data.resize(size); @@ -1630,6 +1658,12 @@ void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Po ERR_FAIL_COND(format != p_src->format); Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); + + if (p_dest.x < 0) + clipped_src_rect.position.x = ABS(p_dest.x); + if (p_dest.y < 0) + clipped_src_rect.position.y = ABS(p_dest.y); + if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) return; @@ -1678,6 +1712,12 @@ void Image::blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, co ERR_FAIL_COND(format != p_src->format); Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); + + if (p_dest.x < 0) + clipped_src_rect.position.x = ABS(p_dest.x); + if (p_dest.y < 0) + clipped_src_rect.position.y = ABS(p_dest.y); + if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) return; @@ -1729,6 +1769,12 @@ void Image::blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const P ERR_FAIL_COND(format != p_src->format); Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); + + if (p_dest.x < 0) + clipped_src_rect.position.x = ABS(p_dest.x); + if (p_dest.y < 0) + clipped_src_rect.position.y = ABS(p_dest.y); + if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) return; @@ -1777,6 +1823,12 @@ void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, c ERR_FAIL_COND(format != p_src->format); Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); + + if (p_dest.x < 0) + clipped_src_rect.position.x = ABS(p_dest.x); + if (p_dest.y < 0) + clipped_src_rect.position.y = ABS(p_dest.y); + if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) return; @@ -1912,6 +1964,10 @@ void Image::unlock() { write_lock = PoolVector<uint8_t>::Write(); } +Color Image::get_pixelv(const Point2 &p_src) const { + return get_pixel(p_src.x, p_src.y); +} + Color Image::get_pixel(int p_x, int p_y) const { uint8_t *ptr = write_lock.ptr(); @@ -2058,6 +2114,10 @@ Color Image::get_pixel(int p_x, int p_y) const { return Color(); } +void Image::set_pixelv(const Point2 &p_dst, const Color &p_color) { + return set_pixel(p_dst.x, p_dst.y, p_color); +} + void Image::set_pixel(int p_x, int p_y, const Color &p_color) { uint8_t *ptr = write_lock.ptr(); @@ -2249,7 +2309,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("crop", "width", "height"), &Image::crop); ClassDB::bind_method(D_METHOD("flip_x"), &Image::flip_x); ClassDB::bind_method(D_METHOD("flip_y"), &Image::flip_y); - ClassDB::bind_method(D_METHOD("generate_mipmaps"), &Image::generate_mipmaps); + ClassDB::bind_method(D_METHOD("generate_mipmaps", "renormalize"), &Image::generate_mipmaps, DEFVAL(false)); ClassDB::bind_method(D_METHOD("clear_mipmaps"), &Image::clear_mipmaps); ClassDB::bind_method(D_METHOD("create", "width", "height", "use_mipmaps", "format"), &Image::_create_empty); @@ -2271,6 +2331,8 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha); ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear); ClassDB::bind_method(D_METHOD("normalmap_to_xy"), &Image::normalmap_to_xy); + ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb); + ClassDB::bind_method(D_METHOD("bumpmap_to_normalmap", "bump_scale"), &Image::bumpmap_to_normalmap, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("blit_rect", "src", "src_rect", "dst"), &Image::blit_rect); ClassDB::bind_method(D_METHOD("blit_rect_mask", "src", "mask", "src_rect", "dst"), &Image::blit_rect_mask); @@ -2288,8 +2350,10 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("lock"), &Image::lock); ClassDB::bind_method(D_METHOD("unlock"), &Image::unlock); - ClassDB::bind_method(D_METHOD("set_pixel", "x", "y", "color"), &Image::set_pixel); + ClassDB::bind_method(D_METHOD("get_pixelv", "src"), &Image::get_pixelv); ClassDB::bind_method(D_METHOD("get_pixel", "x", "y"), &Image::get_pixel); + ClassDB::bind_method(D_METHOD("set_pixelv", "dst", "color"), &Image::set_pixelv); + ClassDB::bind_method(D_METHOD("set_pixel", "x", "y", "color"), &Image::set_pixel); ClassDB::bind_method(D_METHOD("load_png_from_buffer", "buffer"), &Image::load_png_from_buffer); ClassDB::bind_method(D_METHOD("load_jpg_from_buffer", "buffer"), &Image::load_jpg_from_buffer); @@ -2379,6 +2443,78 @@ void Image::normalmap_to_xy() { convert(Image::FORMAT_LA8); } +Ref<Image> Image::rgbe_to_srgb() { + + if (data.size() == 0) + return Ref<Image>(); + + ERR_FAIL_COND_V(format != FORMAT_RGBE9995, Ref<Image>()); + + Ref<Image> new_image; + new_image.instance(); + new_image->create(width, height, 0, Image::FORMAT_RGB8); + + lock(); + + new_image->lock(); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + new_image->set_pixel(col, row, get_pixel(col, row).to_srgb()); + } + } + + unlock(); + new_image->unlock(); + + if (has_mipmaps()) { + new_image->generate_mipmaps(); + } + + return new_image; +} + +void Image::bumpmap_to_normalmap(float bump_scale) { + ERR_FAIL_COND(!_can_modify(format)); + convert(Image::FORMAT_RF); + + PoolVector<uint8_t> result_image; //rgba output + result_image.resize(width * height * 4); + + { + PoolVector<uint8_t>::Read rp = data.read(); + PoolVector<uint8_t>::Write wp = result_image.write(); + + unsigned char *write_ptr = wp.ptr(); + float *read_ptr = (float *)rp.ptr(); + + for (int ty = 0; ty < height; ty++) { + int py = ty + 1; + if (py >= height) py -= height; + + for (int tx = 0; tx < width; tx++) { + int px = tx + 1; + if (px >= width) px -= width; + float here = read_ptr[ty * width + tx]; + float to_right = read_ptr[ty * width + px]; + float above = read_ptr[py * width + tx]; + Vector3 up = Vector3(0, 1, (here - above) * bump_scale); + Vector3 across = Vector3(1, 0, (to_right - here) * bump_scale); + + Vector3 normal = across.cross(up); + normal.normalize(); + + write_ptr[((ty * width + tx) << 2) + 0] = (127.5 + normal.x * 127.5); + write_ptr[((ty * width + tx) << 2) + 1] = (127.5 + normal.y * 127.5); + write_ptr[((ty * width + tx) << 2) + 2] = (127.5 + normal.z * 127.5); + write_ptr[((ty * width + tx) << 2) + 3] = 255; + } + } + } + format = FORMAT_RGBA8; + data = result_image; +} + void Image::srgb_to_linear() { if (data.size() == 0) diff --git a/core/image.h b/core/image.h index e962787ae9..43516e2c0b 100644 --- a/core/image.h +++ b/core/image.h @@ -217,9 +217,10 @@ public: /** * Generate a mipmap to an image (creates an image 1/4 the size, with averaging of 4->1) */ - Error generate_mipmaps(); + Error generate_mipmaps(bool p_renormalize = false); void clear_mipmaps(); + void normalize(); //for normal maps /** * Create a new image of a given size and format. Current image will be lost @@ -284,6 +285,8 @@ public: void premultiply_alpha(); void srgb_to_linear(); void normalmap_to_xy(); + Ref<Image> rgbe_to_srgb(); + void bumpmap_to_normalmap(float bump_scale = 1.0); void blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest); void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest); @@ -320,7 +323,9 @@ public: DetectChannels get_detected_channels(); + Color get_pixelv(const Point2 &p_src) const; Color get_pixel(int p_x, int p_y) const; + void set_pixelv(const Point2 &p_dest, const Color &p_color); void set_pixel(int p_x, int p_y, const Color &p_color); void copy_internals_from(const Ref<Image> &p_image) { diff --git a/core/input_map.cpp b/core/input_map.cpp index a9ea1d9545..d33f40cbcf 100644 --- a/core/input_map.cpp +++ b/core/input_map.cpp @@ -35,27 +35,32 @@ InputMap *InputMap::singleton = NULL; +int InputMap::ALL_DEVICES = -1; + void InputMap::_bind_methods() { ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action); ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions); - ClassDB::bind_method(D_METHOD("add_action", "action"), &InputMap::add_action); + ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.5f)); ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action); + ClassDB::bind_method(D_METHOD("action_set_deadzone", "deadzone"), &InputMap::action_set_deadzone); ClassDB::bind_method(D_METHOD("action_add_event", "action", "event"), &InputMap::action_add_event); ClassDB::bind_method(D_METHOD("action_has_event", "action", "event"), &InputMap::action_has_event); ClassDB::bind_method(D_METHOD("action_erase_event", "action", "event"), &InputMap::action_erase_event); + ClassDB::bind_method(D_METHOD("action_erase_events", "action"), &InputMap::action_erase_events); ClassDB::bind_method(D_METHOD("get_action_list", "action"), &InputMap::_get_action_list); ClassDB::bind_method(D_METHOD("event_is_action", "event", "action"), &InputMap::event_is_action); ClassDB::bind_method(D_METHOD("load_from_globals"), &InputMap::load_from_globals); } -void InputMap::add_action(const StringName &p_action) { +void InputMap::add_action(const StringName &p_action, float p_deadzone) { ERR_FAIL_COND(input_map.has(p_action)); input_map[p_action] = Action(); static int last_id = 1; input_map[p_action].id = last_id; + input_map[p_action].deadzone = p_deadzone; last_id++; } @@ -94,19 +99,21 @@ List<StringName> InputMap::get_actions() const { return actions; } -List<Ref<InputEvent> >::Element *InputMap::_find_event(List<Ref<InputEvent> > &p_list, const Ref<InputEvent> &p_event, bool p_action_test) const { +List<Ref<InputEvent> >::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength) const { - for (List<Ref<InputEvent> >::Element *E = p_list.front(); E; E = E->next()) { + for (List<Ref<InputEvent> >::Element *E = p_action.inputs.front(); E; E = E->next()) { const Ref<InputEvent> e = E->get(); //if (e.type != Ref<InputEvent>::KEY && e.device != p_event.device) -- unsure about the KEY comparison, why is this here? // continue; - if (e->get_device() != p_event->get_device()) - continue; - if (e->action_match(p_event)) - return E; + 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)) { + return E; + } + } } return NULL; @@ -117,11 +124,18 @@ bool InputMap::has_action(const StringName &p_action) const { return input_map.has(p_action); } +void InputMap::action_set_deadzone(const StringName &p_action, float p_deadzone) { + + ERR_FAIL_COND(!input_map.has(p_action)); + + input_map[p_action].deadzone = p_deadzone; +} + void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); ERR_FAIL_COND(!input_map.has(p_action)); - if (_find_event(input_map[p_action].inputs, p_event)) + if (_find_event(input_map[p_action], p_event)) return; //already gots input_map[p_action].inputs.push_back(p_event); @@ -130,18 +144,25 @@ void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent bool InputMap::action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event) { ERR_FAIL_COND_V(!input_map.has(p_action), false); - return (_find_event(input_map[p_action].inputs, p_event) != NULL); + return (_find_event(input_map[p_action], p_event) != NULL); } void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event) { ERR_FAIL_COND(!input_map.has(p_action)); - List<Ref<InputEvent> >::Element *E = _find_event(input_map[p_action].inputs, p_event); + List<Ref<InputEvent> >::Element *E = _find_event(input_map[p_action], p_event); if (E) input_map[p_action].inputs.erase(E); } +void InputMap::action_erase_events(const StringName &p_action) { + + ERR_FAIL_COND(!input_map.has(p_action)); + + input_map[p_action].inputs.clear(); +} + Array InputMap::_get_action_list(const StringName &p_action) { Array ret; @@ -166,19 +187,33 @@ const List<Ref<InputEvent> > *InputMap::get_action_list(const StringName &p_acti } bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action) const { + 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 { Map<StringName, Action>::Element *E = input_map.find(p_action); if (!E) { ERR_EXPLAIN("Request for nonexistent InputMap action: " + String(p_action)); ERR_FAIL_COND_V(!E, false); } - Ref<InputEventAction> iea = p_event; - if (iea.is_valid()) { - return iea->get_action() == p_action; + Ref<InputEventAction> input_event_action = p_event; + if (input_event_action.is_valid()) { + return input_event_action->get_action() == p_action; } - return _find_event(E->get().inputs, p_event, true) != NULL; + bool pressed; + float strength; + List<Ref<InputEvent> >::Element *event = _find_event(E->get(), p_event, &pressed, &strength); + if (event != NULL) { + if (p_pressed != NULL) + *p_pressed = pressed; + if (p_strength != NULL) + *p_strength = strength; + return true; + } else { + return false; + } } const Map<StringName, InputMap::Action> &InputMap::get_action_map() const { @@ -200,16 +235,16 @@ void InputMap::load_from_globals() { String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length()); - add_action(name); - - Array va = ProjectSettings::get_singleton()->get(pi.name); + Dictionary action = ProjectSettings::get_singleton()->get(pi.name); + float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.5f; + Array events = action["events"]; - for (int i = 0; i < va.size(); i++) { - - Ref<InputEvent> ie = va[i]; - if (ie.is_null()) + add_action(name, deadzone); + for (int i = 0; i < events.size(); i++) { + Ref<InputEvent> event = events[i]; + if (event.is_null()) continue; - action_add_event(name, ie); + action_add_event(name, event); } } } @@ -282,6 +317,16 @@ void InputMap::load_default() { key->set_scancode(KEY_PAGEDOWN); action_add_event("ui_page_down", key); + add_action("ui_home"); + key.instance(); + key->set_scancode(KEY_HOME); + action_add_event("ui_home", key); + + add_action("ui_end"); + key.instance(); + key->set_scancode(KEY_END); + action_add_event("ui_end", key); + //set("display/window/handheld/orientation", "landscape"); } diff --git a/core/input_map.h b/core/input_map.h index 84d90f6f2a..bdec75c65b 100644 --- a/core/input_map.h +++ b/core/input_map.h @@ -39,8 +39,14 @@ class InputMap : public Object { GDCLASS(InputMap, Object); public: + /** + * A special value used to signify that a given Action can be triggered by any device + */ + static int ALL_DEVICES; + struct Action { int id; + float deadzone; List<Ref<InputEvent> > inputs; }; @@ -49,7 +55,7 @@ private: mutable Map<StringName, Action> input_map; - List<Ref<InputEvent> >::Element *_find_event(List<Ref<InputEvent> > &p_list, const Ref<InputEvent> &p_event, bool p_action_test = false) const; + List<Ref<InputEvent> >::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool *p_pressed = NULL, float *p_strength = NULL) const; Array _get_action_list(const StringName &p_action); Array _get_actions(); @@ -62,15 +68,18 @@ public: bool has_action(const StringName &p_action) const; List<StringName> get_actions() const; - void add_action(const StringName &p_action); + void add_action(const StringName &p_action, float p_deadzone = 0.5); void erase_action(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); void action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event); + void action_erase_events(const StringName &p_action); const List<Ref<InputEvent> > *get_action_list(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 = NULL, float *p_strength = NULL) const; const Map<StringName, Action> &get_action_map() const; void load_from_globals(); diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 1a16d0f61c..efb4c7a073 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -88,7 +88,11 @@ void PackedData::add_path(const String &pkg_path, const String &path, uint64_t o } } } - cd->files.insert(path.get_file()); + String filename = path.get_file(); + // Don't add as a file if the path points to a directoryy + if (!filename.empty()) { + cd->files.insert(filename); + } } } diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index a9eb9466b7..8d85e78226 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -248,6 +248,7 @@ void HTTPClient::close() { body_size = 0; body_left = 0; chunk_left = 0; + read_until_eof = false; response_num = 0; } @@ -298,7 +299,7 @@ Error HTTPClient::poll() { case StreamPeerTCP::STATUS_CONNECTED: { if (ssl) { Ref<StreamPeerSSL> ssl = StreamPeerSSL::create(); - Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, ssl_verify_host ? conn_host : String()); + Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); if (err != OK) { close(); status = STATUS_SSL_HANDSHAKE_ERROR; @@ -352,10 +353,17 @@ Error HTTPClient::poll() { chunked = false; body_left = 0; chunk_left = 0; + read_until_eof = false; response_str.clear(); response_headers.clear(); response_num = RESPONSE_OK; + // Per the HTTP 1.1 spec, keep-alive is the default, but in practice + // it's safe to assume it only if the explicit header is found, allowing + // to handle body-up-to-EOF responses on naive servers; that's what Curl + // and browsers do + bool keep_alive = false; + for (int i = 0; i < responses.size(); i++) { String header = responses[i].strip_edges(); @@ -365,13 +373,14 @@ Error HTTPClient::poll() { if (s.begins_with("content-length:")) { body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int(); body_left = body_size; - } - if (s.begins_with("transfer-encoding:")) { + } else if (s.begins_with("transfer-encoding:")) { String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges(); if (encoding == "chunked") { chunked = true; } + } else if (s.begins_with("connection: keep-alive")) { + keep_alive = true; } if (i == 0 && responses[i].begins_with("HTTP")) { @@ -384,11 +393,16 @@ Error HTTPClient::poll() { } } - if (body_size == 0 && !chunked) { + if (body_size || chunked) { - status = STATUS_CONNECTED; // Ready for new requests - } else { status = STATUS_BODY; + } else if (!keep_alive) { + + read_until_eof = true; + status = STATUS_BODY; + } else { + + status = STATUS_CONNECTED; } return OK; } @@ -515,34 +529,53 @@ PoolByteArray HTTPClient::read_response_body_chunk() { } else { - int to_read = MIN(body_left, read_chunk_size); + int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size; PoolByteArray ret; ret.resize(to_read); int _offset = 0; - while (to_read > 0) { + while (read_until_eof || to_read > 0) { int rec = 0; { PoolByteArray::Write w = ret.write(); err = _get_http_data(w.ptr() + _offset, to_read, rec); } - if (rec > 0) { - body_left -= rec; - to_read -= rec; - _offset += rec; - } else { + if (rec < 0) { if (to_read > 0) // Ended up reading less ret.resize(_offset); break; + } else { + _offset += rec; + if (!read_until_eof) { + body_left -= rec; + to_read -= rec; + } else { + if (rec < to_read) { + ret.resize(_offset); + err = ERR_FILE_EOF; + break; + } + ret.resize(_offset + to_read); + } } } - if (body_left == 0) { - status = STATUS_CONNECTED; + if (!read_until_eof) { + if (body_left == 0) { + status = STATUS_CONNECTED; + } + return ret; + } else { + if (err == ERR_FILE_EOF) { + err = OK; // EOF is expected here + close(); + return ret; + } } - return ret; } if (err != OK) { + close(); + if (err == ERR_FILE_EOF) { status = STATUS_DISCONNECTED; // Server disconnected @@ -602,6 +635,7 @@ HTTPClient::HTTPClient() { body_size = 0; chunked = false; body_left = 0; + read_until_eof = false; chunk_left = 0; response_num = 0; ssl = false; @@ -618,7 +652,27 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { String query = ""; Array keys = p_dict.keys(); for (int i = 0; i < keys.size(); ++i) { - query += "&" + String(keys[i]).http_escape() + "=" + String(p_dict[keys[i]]).http_escape(); + String encoded_key = String(keys[i]).http_escape(); + Variant value = p_dict[keys[i]]; + switch (value.get_type()) { + case Variant::ARRAY: { + // Repeat the key with every values + Array values = value; + for (int j = 0; j < values.size(); ++j) { + query += "&" + encoded_key + "=" + String(values[j]).http_escape(); + } + break; + } + case Variant::NIL: { + // Add the key with no value + query += "&" + encoded_key; + break; + } + default: { + // Add the key-value pair + query += "&" + encoded_key + "=" + String(value).http_escape(); + } + } } query.erase(0, 1); return query; diff --git a/core/io/http_client.h b/core/io/http_client.h index 839012e701..38ec82ce8c 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -173,6 +173,7 @@ private: int chunk_left; int body_size; int body_left; + bool read_until_eof; Ref<StreamPeerTCP> tcp_connection; Ref<StreamPeer> connection; diff --git a/core/io/image_loader.cpp b/core/io/image_loader.cpp index 999c9a8ca2..8ebd9d6cd9 100644 --- a/core/io/image_loader.cpp +++ b/core/io/image_loader.cpp @@ -37,7 +37,7 @@ bool ImageFormatLoader::recognize(const String &p_extension) const { get_recognized_extensions(&extensions); for (List<String>::Element *E = extensions.front(); E; E = E->next()) { - if (E->get().nocasecmp_to(p_extension.get_extension()) == 0) + if (E->get().nocasecmp_to(p_extension) == 0) return true; } diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 983b829d8d..786bec461b 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -112,7 +112,7 @@ void RotatedFileLogger::clear_old_backups() { int max_backups = max_files - 1; // -1 for the current file String basename = base_path.get_file().get_basename(); - String extension = "." + base_path.get_extension(); + String extension = base_path.get_extension(); DirAccess *da = DirAccess::open(base_path.get_base_dir()); if (!da) { @@ -123,7 +123,7 @@ void RotatedFileLogger::clear_old_backups() { String f = da->get_next(); Set<String> backups; while (f != String()) { - if (!da->current_is_dir() && f.begins_with(basename) && f.ends_with(extension) && f != base_path.get_file()) { + if (!da->current_is_dir() && f.begins_with(basename) && f.get_extension() == extension && f != base_path.get_file()) { backups.insert(f); } f = da->get_next(); @@ -152,7 +152,10 @@ void RotatedFileLogger::rotate_file() { OS::Time time = OS::get_singleton()->get_time(); sprintf(timestamp, "-%04d-%02d-%02d-%02d-%02d-%02d", date.year, date.month, date.day, time.hour, time.min, time.sec); - String backup_name = base_path.get_basename() + timestamp + "." + base_path.get_extension(); + String backup_name = base_path.get_basename() + timestamp; + if (base_path.get_extension() != String()) { + backup_name += "." + base_path.get_extension(); + } DirAccess *da = DirAccess::open(base_path.get_base_dir()); if (da) { diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index 9e21287780..0a3a6c1ba1 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -813,7 +813,7 @@ static void _encode_string(const String &p_string, uint8_t *&buf, int &r_len) { while (r_len % 4) { r_len++; //pad if (buf) { - buf++; + *(buf++) = 0; } } } @@ -1211,7 +1211,9 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo r_len += len; if (buf) buf += len; - encode_variant(d[E->get()], buf, len, p_object_as_id); + Variant *v = d.getptr(E->get()); + ERR_FAIL_COND_V(!v, ERR_BUG); + encode_variant(*v, buf, len, p_object_as_id); ERR_FAIL_COND_V(len % 4, ERR_BUG); r_len += len; if (buf) diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp new file mode 100644 index 0000000000..846c89510e --- /dev/null +++ b/core/io/multiplayer_api.cpp @@ -0,0 +1,817 @@ +/*************************************************************************/ +/* multiplayer_api.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/io/multiplayer_api.h" +#include "core/io/marshalls.h" +#include "scene/main/node.h" + +_FORCE_INLINE_ bool _should_call_local(MultiplayerAPI::RPCMode mode, bool is_master, bool &r_skip_rpc) { + + switch (mode) { + + case MultiplayerAPI::RPC_MODE_DISABLED: { + //do nothing + } break; + case MultiplayerAPI::RPC_MODE_REMOTE: { + //do nothing also, no need to call local + } break; + case MultiplayerAPI::RPC_MODE_REMOTESYNC: + case MultiplayerAPI::RPC_MODE_MASTERSYNC: + case MultiplayerAPI::RPC_MODE_SLAVESYNC: + case MultiplayerAPI::RPC_MODE_SYNC: { + //call it, sync always results in call + return true; + } break; + case MultiplayerAPI::RPC_MODE_MASTER: { + if (is_master) + r_skip_rpc = true; //no other master so.. + return is_master; + } break; + case MultiplayerAPI::RPC_MODE_SLAVE: { + return !is_master; + } break; + } + return false; +} + +_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) { + switch (mode) { + + case MultiplayerAPI::RPC_MODE_DISABLED: { + return false; + } break; + case MultiplayerAPI::RPC_MODE_REMOTE: { + return true; + } break; + case MultiplayerAPI::RPC_MODE_REMOTESYNC: + case MultiplayerAPI::RPC_MODE_SYNC: { + return true; + } break; + case MultiplayerAPI::RPC_MODE_MASTERSYNC: + case MultiplayerAPI::RPC_MODE_MASTER: { + return p_node->is_network_master(); + } break; + case MultiplayerAPI::RPC_MODE_SLAVESYNC: + case MultiplayerAPI::RPC_MODE_SLAVE: { + return !p_node->is_network_master() && p_remote_id == p_node->get_network_master(); + } break; + } + + return false; +} + +void MultiplayerAPI::poll() { + + if (!network_peer.is_valid() || network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED) + return; + + network_peer->poll(); + + if (!network_peer.is_valid()) //it's possible that polling might have resulted in a disconnection, so check here + return; + + while (network_peer->get_available_packet_count()) { + + int sender = network_peer->get_packet_peer(); + const uint8_t *packet; + int len; + + Error err = network_peer->get_packet(&packet, len); + if (err != OK) { + ERR_PRINT("Error getting packet!"); + } + + rpc_sender_id = sender; + _process_packet(sender, packet, len); + rpc_sender_id = 0; + + if (!network_peer.is_valid()) { + break; //it's also possible that a packet or RPC caused a disconnection, so also check here + } + } +} + +void MultiplayerAPI::clear() { + connected_peers.clear(); + path_get_cache.clear(); + path_send_cache.clear(); + last_send_cache_id = 1; +} + +void MultiplayerAPI::set_root_node(Node *p_node) { + root_node = p_node; +} + +void MultiplayerAPI::set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_peer) { + + if (network_peer.is_valid()) { + network_peer->disconnect("peer_connected", this, "_add_peer"); + network_peer->disconnect("peer_disconnected", this, "_del_peer"); + network_peer->disconnect("connection_succeeded", this, "_connected_to_server"); + network_peer->disconnect("connection_failed", this, "_connection_failed"); + network_peer->disconnect("server_disconnected", this, "_server_disconnected"); + clear(); + } + + network_peer = p_peer; + + ERR_EXPLAIN("Supplied NetworkedNetworkPeer must be connecting or connected."); + ERR_FAIL_COND(p_peer.is_valid() && p_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED); + + if (network_peer.is_valid()) { + network_peer->connect("peer_connected", this, "_add_peer"); + network_peer->connect("peer_disconnected", this, "_del_peer"); + network_peer->connect("connection_succeeded", this, "_connected_to_server"); + network_peer->connect("connection_failed", this, "_connection_failed"); + network_peer->connect("server_disconnected", this, "_server_disconnected"); + } +} + +Ref<NetworkedMultiplayerPeer> MultiplayerAPI::get_network_peer() const { + return network_peer; +} + +void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { + + ERR_FAIL_COND(root_node == NULL); + ERR_FAIL_COND(p_packet_len < 1); + + uint8_t packet_type = p_packet[0]; + + switch (packet_type) { + + case NETWORK_COMMAND_SIMPLIFY_PATH: { + + _process_simplify_path(p_from, p_packet, p_packet_len); + } break; + + case NETWORK_COMMAND_CONFIRM_PATH: { + + _process_confirm_path(p_from, p_packet, p_packet_len); + } break; + + case NETWORK_COMMAND_REMOTE_CALL: + case NETWORK_COMMAND_REMOTE_SET: { + + ERR_FAIL_COND(p_packet_len < 6); + + Node *node = _process_get_node(p_from, p_packet, p_packet_len); + + ERR_FAIL_COND(node == NULL); + + //detect cstring end + int len_end = 5; + for (; len_end < p_packet_len; len_end++) { + if (p_packet[len_end] == 0) { + break; + } + } + + ERR_FAIL_COND(len_end >= p_packet_len); + + StringName name = String::utf8((const char *)&p_packet[5]); + + if (packet_type == NETWORK_COMMAND_REMOTE_CALL) { + + _process_rpc(node, name, p_from, p_packet, p_packet_len, len_end + 1); + + } else { + + _process_rset(node, name, p_from, p_packet, p_packet_len, len_end + 1); + } + + } break; + + case NETWORK_COMMAND_RAW: { + + _process_raw(p_from, p_packet, p_packet_len); + } break; + } +} + +Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, int p_packet_len) { + + uint32_t target = decode_uint32(&p_packet[1]); + Node *node = NULL; + + if (target & 0x80000000) { + //use full path (not cached yet) + + int ofs = target & 0x7FFFFFFF; + ERR_FAIL_COND_V(ofs >= p_packet_len, NULL); + + String paths; + paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); + + NodePath np = paths; + + node = root_node->get_node(np); + + if (!node) + ERR_PRINTS("Failed to get path from RPC: " + String(np)); + } else { + //use cached path + int id = target; + + Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); + ERR_FAIL_COND_V(!E, NULL); + + Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(id); + ERR_FAIL_COND_V(!F, NULL); + + PathGetCache::NodeInfo *ni = &F->get(); + //do proper caching later + + node = root_node->get_node(ni->path); + if (!node) + ERR_PRINTS("Failed to get cached path from RPC: " + String(ni->path)); + } + return node; +} + +void MultiplayerAPI::_process_rpc(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { + + ERR_FAIL_COND(p_offset >= p_packet_len); + + // Check that remote can call the RPC on this node + RPCMode rpc_mode = RPC_MODE_DISABLED; + const Map<StringName, RPCMode>::Element *E = p_node->get_node_rpc_mode(p_name); + if (E) { + rpc_mode = E->get(); + } else if (p_node->get_script_instance()) { + rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_name); + } + ERR_FAIL_COND(!_can_call_mode(p_node, rpc_mode, p_from)); + + int argc = p_packet[p_offset]; + Vector<Variant> args; + Vector<const Variant *> argp; + args.resize(argc); + argp.resize(argc); + + p_offset++; + + for (int i = 0; i < argc; i++) { + + ERR_FAIL_COND(p_offset >= p_packet_len); + int vlen; + Error err = decode_variant(args[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen); + ERR_FAIL_COND(err != OK); + //args[i]=p_packet[3+i]; + argp[i] = &args[i]; + p_offset += vlen; + } + + Variant::CallError ce; + + p_node->call(p_name, (const Variant **)argp.ptr(), argc, ce); + if (ce.error != Variant::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_name, (const Variant **)argp.ptr(), argc, ce); + error = "RPC - " + error; + ERR_PRINTS(error); + } +} + +void MultiplayerAPI::_process_rset(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { + + ERR_FAIL_COND(p_offset >= p_packet_len); + + // Check that remote can call the RSET on this node + RPCMode rset_mode = RPC_MODE_DISABLED; + const Map<StringName, RPCMode>::Element *E = p_node->get_node_rset_mode(p_name); + if (E) { + rset_mode = E->get(); + } else if (p_node->get_script_instance()) { + rset_mode = p_node->get_script_instance()->get_rset_mode(p_name); + } + ERR_FAIL_COND(!_can_call_mode(p_node, rset_mode, p_from)); + + Variant value; + decode_variant(value, &p_packet[p_offset], p_packet_len - p_offset); + + bool valid; + + p_node->set(p_name, value, &valid); + if (!valid) { + String error = "Error setting remote property '" + String(p_name) + "', not found in object of type " + p_node->get_class(); + ERR_PRINTS(error); + } +} + +void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + + ERR_FAIL_COND(p_packet_len < 5); + int id = decode_uint32(&p_packet[1]); + + String paths; + paths.parse_utf8((const char *)&p_packet[5], p_packet_len - 5); + + NodePath path = paths; + + if (!path_get_cache.has(p_from)) { + path_get_cache[p_from] = PathGetCache(); + } + + PathGetCache::NodeInfo ni; + ni.path = path; + ni.instance = 0; + + path_get_cache[p_from].nodes[id] = ni; + + //send ack + + //encode path + CharString pname = String(path).utf8(); + int len = encode_cstring(pname.get_data(), NULL); + + Vector<uint8_t> packet; + + packet.resize(1 + len); + packet[0] = NETWORK_COMMAND_CONFIRM_PATH; + encode_cstring(pname.get_data(), &packet[1]); + + network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); + network_peer->set_target_peer(p_from); + network_peer->put_packet(packet.ptr(), packet.size()); +} + +void MultiplayerAPI::_process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { + + ERR_FAIL_COND(p_packet_len < 2); + + String paths; + paths.parse_utf8((const char *)&p_packet[1], p_packet_len - 1); + + NodePath path = paths; + + PathSentCache *psc = path_send_cache.getptr(path); + ERR_FAIL_COND(!psc); + + Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); + ERR_FAIL_COND(!E); + E->get() = true; +} + +bool MultiplayerAPI::_send_confirm_path(NodePath p_path, PathSentCache *psc, int p_target) { + bool has_all_peers = true; + List<int> peers_to_add; //if one is missing, take note to add it + + for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { + + if (p_target < 0 && E->get() == -p_target) + continue; //continue, excluded + + if (p_target > 0 && E->get() != p_target) + continue; //continue, not for this peer + + Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); + + if (!F || F->get() == false) { + //path was not cached, or was cached but is unconfirmed + if (!F) { + //not cached at all, take note + peers_to_add.push_back(E->get()); + } + + has_all_peers = false; + } + } + + //those that need to be added, send a message for this + + for (List<int>::Element *E = peers_to_add.front(); E; E = E->next()) { + + //encode function name + CharString pname = String(p_path).utf8(); + int len = encode_cstring(pname.get_data(), NULL); + + Vector<uint8_t> packet; + + packet.resize(1 + 4 + len); + packet[0] = NETWORK_COMMAND_SIMPLIFY_PATH; + encode_uint32(psc->id, &packet[1]); + encode_cstring(pname.get_data(), &packet[5]); + + network_peer->set_target_peer(E->get()); //to all of you + network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); + network_peer->put_packet(packet.ptr(), packet.size()); + + psc->confirmed_peers.insert(E->get(), false); //insert into confirmed, but as false since it was not confirmed + } + + return has_all_peers; +} + +void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount) { + + if (network_peer.is_null()) { + ERR_EXPLAIN("Attempt to remote call/set when networking is not active in SceneTree."); + ERR_FAIL(); + } + + if (network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_CONNECTING) { + ERR_EXPLAIN("Attempt to remote call/set when networking is not connected yet in SceneTree."); + ERR_FAIL(); + } + + if (network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED) { + ERR_EXPLAIN("Attempt to remote call/set when networking is disconnected."); + ERR_FAIL(); + } + + if (p_argcount > 255) { + ERR_EXPLAIN("Too many arguments >255."); + ERR_FAIL(); + } + + if (p_to != 0 && !connected_peers.has(ABS(p_to))) { + if (p_to == network_peer->get_unique_id()) { + ERR_EXPLAIN("Attempt to remote call/set yourself! unique ID: " + itos(network_peer->get_unique_id())); + } else { + ERR_EXPLAIN("Attempt to remote call unexisting ID: " + itos(p_to)); + } + + ERR_FAIL(); + } + + NodePath from_path = (root_node->get_path()).rel_path_to(p_from->get_path()); + ERR_FAIL_COND(from_path.is_empty()); + + //see if the path is cached + PathSentCache *psc = path_send_cache.getptr(from_path); + if (!psc) { + //path is not cached, create + path_send_cache[from_path] = PathSentCache(); + psc = path_send_cache.getptr(from_path); + psc->id = last_send_cache_id++; + } + + //create base packet, lots of hardcode because it must be tight + + int ofs = 0; + +#define MAKE_ROOM(m_amount) \ + if (packet_cache.size() < m_amount) packet_cache.resize(m_amount); + + //encode type + MAKE_ROOM(1); + packet_cache[0] = p_set ? NETWORK_COMMAND_REMOTE_SET : NETWORK_COMMAND_REMOTE_CALL; + ofs += 1; + + //encode ID + MAKE_ROOM(ofs + 4); + encode_uint32(psc->id, &(packet_cache[ofs])); + ofs += 4; + + //encode function name + CharString name = String(p_name).utf8(); + int len = encode_cstring(name.get_data(), NULL); + MAKE_ROOM(ofs + len); + encode_cstring(name.get_data(), &(packet_cache[ofs])); + ofs += len; + + if (p_set) { + //set argument + Error err = encode_variant(*p_arg[0], NULL, len); + ERR_FAIL_COND(err != OK); + MAKE_ROOM(ofs + len); + encode_variant(*p_arg[0], &(packet_cache[ofs]), len); + ofs += len; + + } else { + //call arguments + MAKE_ROOM(ofs + 1); + packet_cache[ofs] = p_argcount; + ofs += 1; + for (int i = 0; i < p_argcount; i++) { + Error err = encode_variant(*p_arg[i], NULL, len); + ERR_FAIL_COND(err != OK); + MAKE_ROOM(ofs + len); + encode_variant(*p_arg[i], &(packet_cache[ofs]), len); + ofs += len; + } + } + + //see if all peers have cached path (is so, call can be fast) + bool has_all_peers = _send_confirm_path(from_path, psc, p_to); + + //take chance and set transfer mode, since all send methods will use it + network_peer->set_transfer_mode(p_unreliable ? NetworkedMultiplayerPeer::TRANSFER_MODE_UNRELIABLE : NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); + + if (has_all_peers) { + + //they all have verified paths, so send fast + network_peer->set_target_peer(p_to); //to all of you + network_peer->put_packet(packet_cache.ptr(), ofs); //a message with love + } else { + //not all verified path, so send one by one + + //apend path at the end, since we will need it for some packets + CharString pname = String(from_path).utf8(); + int path_len = encode_cstring(pname.get_data(), NULL); + MAKE_ROOM(ofs + path_len); + encode_cstring(pname.get_data(), &(packet_cache[ofs])); + + for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { + + if (p_to < 0 && E->get() == -p_to) + continue; //continue, excluded + + if (p_to > 0 && E->get() != p_to) + continue; //continue, not for this peer + + Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); + ERR_CONTINUE(!F); //should never happen + + network_peer->set_target_peer(E->get()); //to this one specifically + + if (F->get() == true) { + //this one confirmed path, so use id + encode_uint32(psc->id, &(packet_cache[1])); + network_peer->put_packet(packet_cache.ptr(), ofs); + } else { + //this one did not confirm path yet, so use entire path (sorry!) + encode_uint32(0x80000000 | ofs, &(packet_cache[1])); //offset to path and flag + network_peer->put_packet(packet_cache.ptr(), ofs + path_len); + } + } + } +} + +void MultiplayerAPI::_add_peer(int p_id) { + connected_peers.insert(p_id); + path_get_cache.insert(p_id, PathGetCache()); + emit_signal("network_peer_connected", p_id); +} + +void MultiplayerAPI::_del_peer(int p_id) { + connected_peers.erase(p_id); + path_get_cache.erase(p_id); //I no longer need your cache, sorry + emit_signal("network_peer_disconnected", p_id); +} + +void MultiplayerAPI::_connected_to_server() { + + emit_signal("connected_to_server"); +} + +void MultiplayerAPI::_connection_failed() { + + emit_signal("connection_failed"); +} + +void MultiplayerAPI::_server_disconnected() { + + emit_signal("server_disconnected"); +} + +void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount) { + + ERR_FAIL_COND(!p_node->is_inside_tree()); + ERR_FAIL_COND(!network_peer.is_valid()); + + int node_id = network_peer->get_unique_id(); + bool skip_rpc = false; + bool call_local_native = false; + bool call_local_script = false; + bool is_master = p_node->is_network_master(); + + if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { + //check that send mode can use local call + + const Map<StringName, RPCMode>::Element *E = p_node->get_node_rpc_mode(p_method); + if (E) { + call_local_native = _should_call_local(E->get(), is_master, skip_rpc); + } + + if (call_local_native) { + // done below + } else if (p_node->get_script_instance()) { + //attempt with script + RPCMode rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_method); + call_local_script = _should_call_local(rpc_mode, is_master, skip_rpc); + } + } + + if (!skip_rpc) { + _send_rpc(p_node, p_peer_id, p_unreliable, false, p_method, p_arg, p_argcount); + } + + if (call_local_native) { + Variant::CallError ce; + p_node->call(p_method, p_arg, p_argcount, ce); + if (ce.error != Variant::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in local call: - " + error; + ERR_PRINTS(error); + return; + } + } + + if (call_local_script) { + Variant::CallError ce; + ce.error = Variant::CallError::CALL_OK; + p_node->get_script_instance()->call(p_method, p_arg, p_argcount, ce); + if (ce.error != Variant::CallError::CALL_OK) { + String error = Variant::get_call_error_text(p_node, p_method, p_arg, p_argcount, ce); + error = "rpc() aborted in script local call: - " + error; + ERR_PRINTS(error); + return; + } + } +} + +void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_property, const Variant &p_value) { + + ERR_FAIL_COND(!p_node->is_inside_tree()); + ERR_FAIL_COND(!network_peer.is_valid()); + + int node_id = network_peer->get_unique_id(); + bool is_master = p_node->is_network_master(); + bool skip_rset = false; + + if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { + //check that send mode can use local call + + bool set_local = false; + + const Map<StringName, RPCMode>::Element *E = p_node->get_node_rset_mode(p_property); + if (E) { + + set_local = _should_call_local(E->get(), is_master, skip_rset); + } + + if (set_local) { + bool valid; + p_node->set(p_property, p_value, &valid); + + if (!valid) { + String error = "rset() aborted in local set, property not found: - " + String(p_property); + ERR_PRINTS(error); + return; + } + } else if (p_node->get_script_instance()) { + //attempt with script + RPCMode rpc_mode = p_node->get_script_instance()->get_rset_mode(p_property); + + set_local = _should_call_local(rpc_mode, is_master, skip_rset); + + if (set_local) { + + bool valid = p_node->get_script_instance()->set(p_property, p_value); + + if (!valid) { + String error = "rset() aborted in local script set, property not found: - " + String(p_property); + ERR_PRINTS(error); + return; + } + } + } + } + + if (skip_rset) + return; + + const Variant *vptr = &p_value; + + _send_rpc(p_node, p_peer_id, p_unreliable, true, p_property, &vptr, 1); +} + +Error MultiplayerAPI::send_bytes(PoolVector<uint8_t> p_data, int p_to) { + + ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA); + ERR_FAIL_COND_V(!network_peer.is_valid(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(network_peer->get_connection_status() != NetworkedMultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED); + + MAKE_ROOM(p_data.size() + 1); + PoolVector<uint8_t>::Read r = p_data.read(); + packet_cache[0] = NETWORK_COMMAND_RAW; + memcpy(&packet_cache[1], &r[0], p_data.size()); + network_peer->set_target_peer(p_to); + return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1); +} + +void MultiplayerAPI::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) { + + ERR_FAIL_COND(p_packet_len < 2); + + PoolVector<uint8_t> out; + int len = p_packet_len - 1; + out.resize(len); + { + PoolVector<uint8_t>::Write w = out.write(); + memcpy(&w[0], &p_packet[1], len); + } + emit_signal("network_peer_packet", p_from, out); +} + +int MultiplayerAPI::get_network_unique_id() const { + + ERR_FAIL_COND_V(!network_peer.is_valid(), 0); + return network_peer->get_unique_id(); +} + +bool MultiplayerAPI::is_network_server() const { + + ERR_FAIL_COND_V(!network_peer.is_valid(), false); + return network_peer->is_server(); +} + +void MultiplayerAPI::set_refuse_new_network_connections(bool p_refuse) { + + ERR_FAIL_COND(!network_peer.is_valid()); + network_peer->set_refuse_new_connections(p_refuse); +} + +bool MultiplayerAPI::is_refusing_new_network_connections() const { + + ERR_FAIL_COND_V(!network_peer.is_valid(), false); + return network_peer->is_refusing_new_connections(); +} + +Vector<int> MultiplayerAPI::get_network_connected_peers() const { + + ERR_FAIL_COND_V(!network_peer.is_valid(), Vector<int>()); + + Vector<int> ret; + for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { + ret.push_back(E->get()); + } + + return ret; +} + +void MultiplayerAPI::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); + ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id"), &MultiplayerAPI::send_bytes, DEFVAL(NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST)); + ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer); + ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer); + ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id); + ClassDB::bind_method(D_METHOD("is_network_server"), &MultiplayerAPI::is_network_server); + ClassDB::bind_method(D_METHOD("get_rpc_sender_id"), &MultiplayerAPI::get_rpc_sender_id); + ClassDB::bind_method(D_METHOD("_add_peer", "id"), &MultiplayerAPI::_add_peer); + ClassDB::bind_method(D_METHOD("_del_peer", "id"), &MultiplayerAPI::_del_peer); + ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &MultiplayerAPI::set_network_peer); + ClassDB::bind_method(D_METHOD("poll"), &MultiplayerAPI::poll); + ClassDB::bind_method(D_METHOD("clear"), &MultiplayerAPI::clear); + + ClassDB::bind_method(D_METHOD("_connected_to_server"), &MultiplayerAPI::_connected_to_server); + ClassDB::bind_method(D_METHOD("_connection_failed"), &MultiplayerAPI::_connection_failed); + ClassDB::bind_method(D_METHOD("_server_disconnected"), &MultiplayerAPI::_server_disconnected); + ClassDB::bind_method(D_METHOD("get_network_connected_peers"), &MultiplayerAPI::get_network_connected_peers); + ClassDB::bind_method(D_METHOD("set_refuse_new_network_connections", "refuse"), &MultiplayerAPI::set_refuse_new_network_connections); + ClassDB::bind_method(D_METHOD("is_refusing_new_network_connections"), &MultiplayerAPI::is_refusing_new_network_connections); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "NetworkedMultiplayerPeer", 0), "set_network_peer", "get_network_peer"); + + ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("network_peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::POOL_BYTE_ARRAY, "packet"))); + ADD_SIGNAL(MethodInfo("connected_to_server")); + ADD_SIGNAL(MethodInfo("connection_failed")); + ADD_SIGNAL(MethodInfo("server_disconnected")); + + BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); + BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); + BIND_ENUM_CONSTANT(RPC_MODE_SYNC); + BIND_ENUM_CONSTANT(RPC_MODE_MASTER); + BIND_ENUM_CONSTANT(RPC_MODE_SLAVE); + BIND_ENUM_CONSTANT(RPC_MODE_REMOTESYNC); + BIND_ENUM_CONSTANT(RPC_MODE_MASTERSYNC); + BIND_ENUM_CONSTANT(RPC_MODE_SLAVESYNC); +} + +MultiplayerAPI::MultiplayerAPI() { + clear(); +} + +MultiplayerAPI::~MultiplayerAPI() { + clear(); +} diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h new file mode 100644 index 0000000000..ef56c4c7f2 --- /dev/null +++ b/core/io/multiplayer_api.h @@ -0,0 +1,134 @@ +/*************************************************************************/ +/* multiplayer_api.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 MULTIPLAYER_PROTOCOL_H +#define MULTIPLAYER_PROTOCOL_H + +#include "core/io/networked_multiplayer_peer.h" +#include "core/reference.h" + +class MultiplayerAPI : public Reference { + + GDCLASS(MultiplayerAPI, Reference); + +private: + //path sent caches + struct PathSentCache { + Map<int, bool> confirmed_peers; + int id; + }; + + //path get caches + struct PathGetCache { + struct NodeInfo { + NodePath path; + ObjectID instance; + }; + + Map<int, NodeInfo> nodes; + }; + + Ref<NetworkedMultiplayerPeer> network_peer; + int rpc_sender_id; + Set<int> connected_peers; + HashMap<NodePath, PathSentCache> path_send_cache; + Map<int, PathGetCache> path_get_cache; + int last_send_cache_id; + Vector<uint8_t> packet_cache; + Node *root_node; + +protected: + static void _bind_methods(); + + void _process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); + void _process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len); + void _process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len); + Node *_process_get_node(int p_from, const uint8_t *p_packet, int p_packet_len); + void _process_rpc(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); + void _process_rset(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); + void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len); + + void _send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount); + bool _send_confirm_path(NodePath p_path, PathSentCache *psc, int p_from); + +public: + enum NetworkCommands { + NETWORK_COMMAND_REMOTE_CALL, + NETWORK_COMMAND_REMOTE_SET, + NETWORK_COMMAND_SIMPLIFY_PATH, + NETWORK_COMMAND_CONFIRM_PATH, + NETWORK_COMMAND_RAW, + }; + + enum RPCMode { + + RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) + RPC_MODE_REMOTE, // Using rpc() on it will call method / set property in all remote peers + RPC_MODE_SYNC, // Using rpc() on it will call method / set property in all remote peers and locally + RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote + RPC_MODE_SLAVE, // Using rpc() on it will call method for all slaves + RPC_MODE_REMOTESYNC, // Same as RPC_MODE_SYNC, compatibility + RPC_MODE_MASTERSYNC, // Using rpc() on it will call method / set property in the master peer and locally + RPC_MODE_SLAVESYNC, // Using rpc() on it will call method / set property in all slave peers and locally + }; + + void poll(); + void clear(); + void set_root_node(Node *p_node); + void set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_peer); + Ref<NetworkedMultiplayerPeer> get_network_peer() const; + Error send_bytes(PoolVector<uint8_t> p_data, int p_to = NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST); + + // Called by Node.rpc + void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); + // Called by Node.rset + void rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_property, const Variant &p_value); + + void _add_peer(int p_id); + void _del_peer(int p_id); + void _connected_to_server(); + void _connection_failed(); + void _server_disconnected(); + + bool has_network_peer() const { return network_peer.is_valid(); } + Vector<int> get_network_connected_peers() const; + int get_rpc_sender_id() const { return rpc_sender_id; } + int get_network_unique_id() const; + bool is_network_server() const; + void set_refuse_new_network_connections(bool p_refuse); + bool is_refusing_new_network_connections() const; + + MultiplayerAPI(); + ~MultiplayerAPI(); +}; + +VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); + +#endif // MULTIPLAYER_PROTOCOL_H diff --git a/core/io/packet_peer.cpp b/core/io/packet_peer.cpp index bd851ebb6d..b777a9f960 100644 --- a/core/io/packet_peer.cpp +++ b/core/io/packet_peer.cpp @@ -172,6 +172,7 @@ Error PacketPeerStream::_poll_buffer() const { ERR_FAIL_COND_V(peer.is_null(), ERR_UNCONFIGURED); int read = 0; + ERR_FAIL_COND_V(input_buffer.size() < ring_buffer.space_left(), ERR_UNAVAILABLE); Error err = peer->get_partial_data(&input_buffer[0], ring_buffer.space_left(), read); if (err) return err; @@ -223,6 +224,7 @@ Error PacketPeerStream::get_packet(const uint8_t **r_buffer, int &r_buffer_size) uint32_t len = decode_uint32(lbuf); ERR_FAIL_COND_V(remaining < (int)len, ERR_UNAVAILABLE); + ERR_FAIL_COND_V(input_buffer.size() < len, ERR_UNAVAILABLE); ring_buffer.read(lbuf, 4); //get rid of first 4 bytes ring_buffer.read(&input_buffer[0], len); // read packet diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index 596060221e..b6377662de 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -29,8 +29,8 @@ /*************************************************************************/ #include "pck_packer.h" - #include "core/os/file_access.h" +#include "version.h" static uint64_t _align(uint64_t p_n, int p_alignment) { @@ -70,9 +70,9 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment) { alignment = p_alignment; file->store_32(0x43504447); // MAGIC - file->store_32(0); // # version - file->store_32(0); // # major - file->store_32(0); // # minor + file->store_32(1); // # version + file->store_32(VERSION_MAJOR); // # major + file->store_32(VERSION_MINOR); // # minor file->store_32(0); // # revision for (int i = 0; i < 16; i++) { diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 5dfe067902..0c626c197b 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -1162,9 +1162,11 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons ERR_FAIL_V(ERR_FILE_UNRECOGNIZED); } - fw->store_32(VERSION_MAJOR); //current version - fw->store_32(VERSION_MINOR); - fw->store_32(FORMAT_VERSION); + // Since we're not actually converting the file contents, leave the version + // numbers in the file untouched. + fw->store_32(ver_major); + fw->store_32(ver_minor); + fw->store_32(ver_format); save_ustring(fw, get_ustring(f)); //type diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp index 609dd7e93c..3dcd94880a 100644 --- a/core/io/resource_saver.cpp +++ b/core/io/resource_saver.cpp @@ -56,7 +56,7 @@ Error ResourceSaver::save(const String &p_path, const RES &p_resource, uint32_t for (List<String>::Element *E = extensions.front(); E; E = E->next()) { - if (E->get().nocasecmp_to(extension.get_extension()) == 0) + if (E->get().nocasecmp_to(extension) == 0) recognized = true; } diff --git a/core/io/stream_peer_ssl.cpp b/core/io/stream_peer_ssl.cpp index 07a01ff99f..012ba78c6d 100644 --- a/core/io/stream_peer_ssl.cpp +++ b/core/io/stream_peer_ssl.cpp @@ -29,6 +29,8 @@ /*************************************************************************/ #include "stream_peer_ssl.h" +#include "os/file_access.h" +#include "project_settings.h" StreamPeerSSL *(*StreamPeerSSL::_create)() = NULL; @@ -50,6 +52,35 @@ bool StreamPeerSSL::is_available() { return available; } +PoolByteArray StreamPeerSSL::get_project_cert_array() { + + PoolByteArray out; + String certs_path = GLOBAL_DEF("network/ssl/certificates", ""); + ProjectSettings::get_singleton()->set_custom_property_info("network/ssl/certificates", PropertyInfo(Variant::STRING, "network/ssl/certificates", PROPERTY_HINT_FILE, "*.crt")); + + if (certs_path != "") { + + FileAccess *f = FileAccess::open(certs_path, FileAccess::READ); + if (f) { + int flen = f->get_len(); + out.resize(flen + 1); + { + PoolByteArray::Write w = out.write(); + f->get_buffer(w.ptr(), flen); + w[flen] = 0; //end f string + } + + memdelete(f); + +#ifdef DEBUG_ENABLED + print_line("Loaded certs from '" + certs_path); +#endif + } + } + + return out; +} + void StreamPeerSSL::_bind_methods() { ClassDB::bind_method(D_METHOD("poll"), &StreamPeerSSL::poll); diff --git a/core/io/stream_peer_ssl.h b/core/io/stream_peer_ssl.h index f903438c28..77301a7c87 100644 --- a/core/io/stream_peer_ssl.h +++ b/core/io/stream_peer_ssl.h @@ -66,6 +66,7 @@ public: static StreamPeerSSL *create(); + static PoolByteArray get_project_cert_array(); static void load_certs_from_memory(const PoolByteArray &p_memory); static bool is_available(); diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp index e01e2a84c5..16d5e3c282 100644 --- a/core/io/translation_loader_po.cpp +++ b/core/io/translation_loader_po.cpp @@ -175,7 +175,7 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error, const S String prop = c.substr(0, p).strip_edges(); String value = c.substr(p + 1, c.length()).strip_edges(); - if (prop == "X-Language") { + if (prop == "X-Language" || prop == "Language") { translation->set_locale(value); } } diff --git a/core/make_binders.py b/core/make_binders.py index 1e581f8ce3..6a7602cd5d 100644 --- a/core/make_binders.py +++ b/core/make_binders.py @@ -265,10 +265,8 @@ def run(target, source, env): else: text += t - f = open(target[0].path, "w") - f.write(text) - f.close() + with open(target[0].path, "w") as f: + f.write(text) - f = open(target[1].path, "w") - f.write(text_ext) - f.close() + with open(target[1].path, "w") as f: + f.write(text_ext) diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 6908d7831d..021391da83 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -96,11 +96,11 @@ void AStar::remove_point(int p_id) { Point *p = points[p_id]; - for (int i = 0; i < p->neighbours.size(); i++) { + for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { - Segment s(p_id, p->neighbours[i]->id); + Segment s(p_id, E->get()->id); segments.erase(s); - p->neighbours[i]->neighbours.erase(p); + E->get()->neighbours.erase(p); } memdelete(p); @@ -115,10 +115,10 @@ void AStar::connect_points(int p_id, int p_with_id, bool bidirectional) { Point *a = points[p_id]; Point *b = points[p_with_id]; - a->neighbours.push_back(b); + a->neighbours.insert(b); if (bidirectional) - b->neighbours.push_back(a); + b->neighbours.insert(a); Segment s(p_id, p_with_id); if (s.from == p_id) { @@ -168,8 +168,8 @@ PoolVector<int> AStar::get_point_connections(int p_id) { Point *p = points[p_id]; - for (int i = 0; i < p->neighbours.size(); i++) { - point_list.push_back(p->neighbours[i]->id); + for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { + point_list.push_back(E->get()->id); } return point_list; @@ -242,9 +242,9 @@ bool AStar::_solve(Point *begin_point, Point *end_point) { bool found_route = false; - for (int i = 0; i < begin_point->neighbours.size(); i++) { + for (Set<Point *>::Element *E = begin_point->neighbours.front(); E; E = E->next()) { - Point *n = begin_point->neighbours[i]; + Point *n = E->get(); n->prev_point = begin_point; n->distance = _compute_cost(begin_point->id, n->id) * n->weight_scale; n->last_pass = pass; @@ -283,12 +283,10 @@ bool AStar::_solve(Point *begin_point, Point *end_point) { } Point *p = least_cost_point->self(); - // Open the neighbours for search - int es = p->neighbours.size(); - for (int i = 0; i < es; i++) { + for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { - Point *e = p->neighbours[i]; + Point *e = E->get(); real_t distance = _compute_cost(p->id, e->id) * e->weight_scale + p->distance; diff --git a/core/math/a_star.h b/core/math/a_star.h index f89e17c7bb..8c1b5f64cb 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -54,7 +54,7 @@ class AStar : public Reference { real_t weight_scale; uint64_t last_pass; - Vector<Point *> neighbours; + Set<Point *> neighbours; // Used for pathfinding Point *prev_point; diff --git a/core/math/aabb.h b/core/math/aabb.h index 39b8f403e7..cdb8eb48a3 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -76,6 +76,7 @@ public: _FORCE_INLINE_ bool smits_intersect_ray(const Vector3 &p_from, const Vector3 &p_dir, real_t t0, real_t t1) const; _FORCE_INLINE_ bool intersects_convex_shape(const Plane *p_planes, int p_plane_count) const; + _FORCE_INLINE_ bool inside_convex_shape(const Plane *p_planes, int p_plane_count) const; bool intersects_plane(const Plane &p_plane) const; _FORCE_INLINE_ bool has_point(const Vector3 &p_point) const; @@ -207,6 +208,25 @@ bool AABB::intersects_convex_shape(const Plane *p_planes, int p_plane_count) con return true; } +bool AABB::inside_convex_shape(const Plane *p_planes, int p_plane_count) const { + + Vector3 half_extents = size * 0.5; + Vector3 ofs = position + half_extents; + + for (int i = 0; i < p_plane_count; i++) { + const Plane &p = p_planes[i]; + Vector3 point( + (p.normal.x < 0) ? -half_extents.x : half_extents.x, + (p.normal.y < 0) ? -half_extents.y : half_extents.y, + (p.normal.z < 0) ? -half_extents.z : half_extents.z); + point += ofs; + if (p.is_point_over(point)) + return false; + } + + return true; +} + bool AABB::has_point(const Vector3 &p_point) const { if (p_point.x < position.x) diff --git a/core/math/delaunay.cpp b/core/math/delaunay.cpp new file mode 100644 index 0000000000..8cae92b7c0 --- /dev/null +++ b/core/math/delaunay.cpp @@ -0,0 +1 @@ +#include "delaunay.h" diff --git a/core/math/delaunay.h b/core/math/delaunay.h new file mode 100644 index 0000000000..09aebc773f --- /dev/null +++ b/core/math/delaunay.h @@ -0,0 +1,145 @@ +#ifndef DELAUNAY_H +#define DELAUNAY_H + +#include "math_2d.h" + +class Delaunay2D { +public: + struct Triangle { + + int points[3]; + bool bad; + Triangle() { bad = false; } + Triangle(int p_a, int p_b, int p_c) { + points[0] = p_a; + points[1] = p_b; + points[2] = p_c; + bad = false; + } + }; + + struct Edge { + int edge[2]; + bool bad; + Edge() { bad = false; } + Edge(int p_a, int p_b) { + bad = false; + edge[0] = p_a; + edge[1] = p_b; + } + }; + + static bool circum_circle_contains(const Vector<Vector2> &p_vertices, const Triangle &p_triangle, int p_vertex) { + + Vector2 p1 = p_vertices[p_triangle.points[0]]; + Vector2 p2 = p_vertices[p_triangle.points[1]]; + Vector2 p3 = p_vertices[p_triangle.points[2]]; + + real_t ab = p1.x * p1.x + p1.y * p1.y; + real_t cd = p2.x * p2.x + p2.y * p2.y; + real_t ef = p3.x * p3.x + p3.y * p3.y; + + Vector2 circum( + (ab * (p3.y - p2.y) + cd * (p1.y - p3.y) + ef * (p2.y - p1.y)) / (p1.x * (p3.y - p2.y) + p2.x * (p1.y - p3.y) + p3.x * (p2.y - p1.y)), + (ab * (p3.x - p2.x) + cd * (p1.x - p3.x) + ef * (p2.x - p1.x)) / (p1.y * (p3.x - p2.x) + p2.y * (p1.x - p3.x) + p3.y * (p2.x - p1.x))); + + circum *= 0.5; + float r = p1.distance_squared_to(circum); + float d = p_vertices[p_vertex].distance_squared_to(circum); + return d <= r; + } + + static bool edge_compare(const Vector<Vector2> &p_vertices, const Edge &p_a, const Edge &p_b) { + if (p_vertices[p_a.edge[0]].distance_to(p_vertices[p_b.edge[0]]) < CMP_EPSILON && p_vertices[p_a.edge[1]].distance_to(p_vertices[p_b.edge[1]]) < CMP_EPSILON) { + return true; + } + + if (p_vertices[p_a.edge[0]].distance_to(p_vertices[p_b.edge[1]]) < CMP_EPSILON && p_vertices[p_a.edge[1]].distance_to(p_vertices[p_b.edge[0]]) < CMP_EPSILON) { + return true; + } + + return false; + } + + static Vector<Triangle> triangulate(const Vector<Vector2> &p_points) { + + Vector<Vector2> points = p_points; + Vector<Triangle> triangles; + + Rect2 rect; + for (int i = 0; i < p_points.size(); i++) { + if (i == 0) { + rect.position = p_points[i]; + } else { + rect.expand_to(p_points[i]); + } + } + + float delta_max = MAX(rect.size.width, rect.size.height); + Vector2 center = rect.position + rect.size * 0.5; + + points.push_back(Vector2(center.x - 20 * delta_max, center.y - delta_max)); + points.push_back(Vector2(center.x, center.y + 20 * delta_max)); + points.push_back(Vector2(center.x + 20 * delta_max, center.y - delta_max)); + + triangles.push_back(Triangle(p_points.size() + 0, p_points.size() + 1, p_points.size() + 2)); + + for (int i = 0; i < p_points.size(); i++) { + //std::cout << "Traitement du point " << *p << std::endl; + //std::cout << "_triangles contains " << _triangles.size() << " elements" << std::endl; + + Vector<Edge> polygon; + + for (int j = 0; j < triangles.size(); j++) { + if (circum_circle_contains(points, triangles[j], i)) { + triangles[j].bad = true; + polygon.push_back(Edge(triangles[j].points[0], triangles[j].points[1])); + polygon.push_back(Edge(triangles[j].points[1], triangles[j].points[2])); + polygon.push_back(Edge(triangles[j].points[2], triangles[j].points[0])); + } + } + + for (int j = 0; j < triangles.size(); j++) { + if (triangles[j].bad) { + triangles.remove(j); + j--; + } + } + + for (int j = 0; j < polygon.size(); j++) { + for (int k = j + 1; k < polygon.size(); k++) { + if (edge_compare(points, polygon[j], polygon[k])) { + polygon[j].bad = true; + polygon[k].bad = true; + } + } + } + + for (int j = 0; j < polygon.size(); j++) { + + if (polygon[j].bad) { + continue; + } + triangles.push_back(Triangle(polygon[j].edge[0], polygon[j].edge[1], i)); + } + } + + for (int i = 0; i < triangles.size(); i++) { + bool invalid = false; + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] >= p_points.size()) { + invalid = true; + break; + } + } + if (invalid) { + triangles.remove(i); + i--; + } + } + + return triangles; + } +}; + +#endif // DELAUNAY_H diff --git a/core/math/geometry.h b/core/math/geometry.h index ca4363e129..be998aef0b 100644 --- a/core/math/geometry.h +++ b/core/math/geometry.h @@ -502,16 +502,15 @@ public: } static bool is_point_in_triangle(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) { - int as_x = s.x - a.x; - int as_y = s.y - a.y; + Vector2 an = a - s; + Vector2 bn = b - s; + Vector2 cn = c - s; - bool s_ab = (b.x - a.x) * as_y - (b.y - a.y) * as_x > 0; + bool orientation = an.cross(bn) > 0; - if (((c.x - a.x) * as_y - (c.y - a.y) * as_x > 0) == s_ab) return false; + if ((bn.cross(cn) > 0) != orientation) return false; - if (((c.x - b.x) * (s.y - b.y) - (c.y - b.y) * (s.x - b.x) > 0) != s_ab) return false; - - return true; + return (cn.cross(an) > 0) == orientation; } static bool is_point_in_polygon(const Vector2 &p_point, const Vector<Vector2> &p_polygon); @@ -530,6 +529,21 @@ public: return p_segment[0] + n * d; // inside } + static bool line_intersects_line_2d(const Vector2 &p_from_a, const Vector2 &p_dir_a, const Vector2 &p_from_b, const Vector2 &p_dir_b, Vector2 &r_result) { + + // see http://paulbourke.net/geometry/pointlineplane/ + + const real_t denom = p_dir_b.y * p_dir_a.x - p_dir_b.x * p_dir_a.y; + if (Math::abs(denom) < CMP_EPSILON) { // parallel? + return false; + } + + const Vector2 v = p_from_a - p_from_b; + const real_t t = (p_dir_b.x * v.y - p_dir_b.y * v.x) / denom; + r_result = p_from_a + t * p_dir_a; + return true; + } + static bool segment_intersects_segment_2d(const Vector2 &p_from_a, const Vector2 &p_to_a, const Vector2 &p_from_b, const Vector2 &p_to_b, Vector2 *r_result) { Vector2 B = p_to_a - p_from_a; diff --git a/core/math/math_2d.cpp b/core/math/math_2d.cpp index d2e4101999..a053ffbd93 100644 --- a/core/math/math_2d.cpp +++ b/core/math/math_2d.cpp @@ -98,14 +98,19 @@ real_t Vector2::cross(const Vector2 &p_other) const { return x * p_other.y - y * p_other.x; } -Vector2 Vector2::cross(real_t p_other) const { +Vector2 Vector2::floor() const { - return Vector2(p_other * y, -p_other * x); + return Vector2(Math::floor(x), Math::floor(y)); } -Vector2 Vector2::floor() const { +Vector2 Vector2::ceil() const { - return Vector2(Math::floor(x), Math::floor(y)); + return Vector2(Math::ceil(x), Math::ceil(y)); +} + +Vector2 Vector2::round() const { + + return Vector2(Math::round(x), Math::round(y)); } Vector2 Vector2::rotated(real_t p_by) const { diff --git a/core/math/math_2d.h b/core/math/math_2d.h index 8928349a44..25c39e5d7a 100644 --- a/core/math/math_2d.h +++ b/core/math/math_2d.h @@ -104,7 +104,6 @@ struct Vector2 { real_t dot(const Vector2 &p_other) const; real_t cross(const Vector2 &p_other) const; - Vector2 cross(real_t p_other) const; Vector2 project(const Vector2 &p_vec) const; Vector2 plane_project(real_t p_d, const Vector2 &p_vec) const; @@ -113,6 +112,7 @@ struct Vector2 { _FORCE_INLINE_ static Vector2 linear_interpolate(const Vector2 &p_a, const Vector2 &p_b, real_t p_t); _FORCE_INLINE_ Vector2 linear_interpolate(const Vector2 &p_b, real_t p_t) const; + _FORCE_INLINE_ Vector2 slerp(const Vector2 &p_b, real_t p_t) const; Vector2 cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, real_t p_t) const; Vector2 slide(const Vector2 &p_normal) const; @@ -163,6 +163,8 @@ struct Vector2 { } Vector2 floor() const; + Vector2 ceil() const; + Vector2 round() const; Vector2 snapped(const Vector2 &p_by) const; real_t aspect() const { return width / height; } @@ -262,6 +264,14 @@ Vector2 Vector2::linear_interpolate(const Vector2 &p_b, real_t p_t) const { return res; } +Vector2 Vector2::slerp(const Vector2 &p_b, real_t p_t) const { +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Vector2()); +#endif + real_t theta = angle_to(p_b); + return rotated(theta * p_t); +} + Vector2 Vector2::linear_interpolate(const Vector2 &p_a, const Vector2 &p_b, real_t p_t) { Vector2 res = p_a; @@ -304,7 +314,7 @@ struct Rect2 { inline real_t distance_to(const Vector2 &p_point) const { - real_t dist; + real_t dist = 0.0; bool inside = true; if (p_point.x < position.x) { diff --git a/core/math/math_funcs.cpp b/core/math/math_funcs.cpp index f060a8e4ab..5c8512d8bd 100644 --- a/core/math/math_funcs.cpp +++ b/core/math/math_funcs.cpp @@ -177,18 +177,3 @@ float Math::random(float from, float to) { float ret = (float)r / (float)RANDOM_MAX; return (ret) * (to - from) + from; } - -int Math::wrapi(int value, int min, int max) { - --max; - int rng = max - min + 1; - value = ((value - min) % rng); - if (value < 0) - return max + 1 + value; - else - return min + value; -} - -float Math::wrapf(float value, float min, float max) { - float rng = max - min; - return min + (value - min) - (rng * floor((value - min) / rng)); -} diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index e15abc6b50..20001bb9a6 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -209,8 +209,18 @@ public: static _ALWAYS_INLINE_ double round(double p_val) { return (p_val >= 0) ? Math::floor(p_val + 0.5) : -Math::floor(-p_val + 0.5); } static _ALWAYS_INLINE_ float round(float p_val) { return (p_val >= 0) ? Math::floor(p_val + 0.5) : -Math::floor(-p_val + 0.5); } - static int wrapi(int value, int min, int max); - static float wrapf(float value, float min, float max); + static _ALWAYS_INLINE_ int wrapi(int value, int min, int max) { + int rng = max - min; + return min + ((((value - min) % rng) + rng) % rng); + } + static _ALWAYS_INLINE_ double wrapf(double value, double min, double max) { + double rng = max - min; + return value - (rng * Math::floor((value - min) / rng)); + } + static _ALWAYS_INLINE_ float wrapf(float value, float min, float max) { + float rng = max - min; + return value - (rng * Math::floor((value - min) / rng)); + } // double only, as these functions are mainly used by the editor and not performance-critical, static double ease(double p_x, double p_c); diff --git a/core/math/matrix3.cpp b/core/math/matrix3.cpp index 189b1ef9b3..2371f49561 100644 --- a/core/math/matrix3.cpp +++ b/core/math/matrix3.cpp @@ -254,7 +254,7 @@ void Basis::set_scale(const Vector3 &p_scale) { set_axis(2, get_axis(2).normalized() * p_scale.z); } -Vector3 Basis::get_scale() const { +Vector3 Basis::get_scale_abs() const { return Vector3( Vector3(elements[0][0], elements[1][0], elements[2][0]).length(), @@ -262,7 +262,13 @@ Vector3 Basis::get_scale() const { Vector3(elements[0][2], elements[1][2], elements[2][2]).length()); } -Vector3 Basis::get_signed_scale() const { +Vector3 Basis::get_scale_local() const { + real_t det_sign = determinant() > 0 ? 1 : -1; + return det_sign * Vector3(elements[0].length(), elements[1].length(), elements[2].length()); +} + +// get_scale works with get_rotation, use get_scale_abs if you need to enforce positive signature. +Vector3 Basis::get_scale() const { // FIXME: We are assuming M = R.S (R is rotation and S is scaling), and use polar decomposition to extract R and S. // A polar decomposition is M = O.P, where O is an orthogonal matrix (meaning rotation and reflection) and // P is a positive semi-definite matrix (meaning it contains absolute values of scaling along its diagonal). @@ -342,8 +348,15 @@ void Basis::rotate(const Vector3 &p_euler) { *this = rotated(p_euler); } -// TODO: rename this to get_rotation_euler -Vector3 Basis::get_rotation() const { +Basis Basis::rotated(const Quat &p_quat) const { + return Basis(p_quat) * (*this); +} + +void Basis::rotate(const Quat &p_quat) { + *this = rotated(p_quat); +} + +Vector3 Basis::get_rotation_euler() const { // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). // See the comment in get_scale() for further information. @@ -357,6 +370,20 @@ Vector3 Basis::get_rotation() const { return m.get_euler(); } +Quat Basis::get_rotation_quat() const { + // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, + // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). + // See the comment in get_scale() for further information. + Basis m = orthonormalized(); + real_t det = m.determinant(); + if (det < 0) { + // Ensure that the determinant is 1, such that result is a proper rotation matrix which can be represented by Euler angles. + m.scale(Vector3(-1, -1, -1)); + } + + return m.get_quat(); +} + void Basis::get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const { // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). @@ -371,6 +398,22 @@ void Basis::get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const { m.get_axis_angle(p_axis, p_angle); } +void Basis::get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) const { + // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, + // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). + // See the comment in get_scale() for further information. + Basis m = transposed(); + m.orthonormalize(); + real_t det = m.determinant(); + if (det < 0) { + // Ensure that the determinant is 1, such that result is a proper rotation matrix which can be represented by Euler angles. + m.scale(Vector3(-1, -1, -1)); + } + + m.get_axis_angle(p_axis, p_angle); + p_angle = -p_angle; +} + // get_euler_xyz returns a vector containing the Euler angles in the format // (a1,a2,a3), where a3 is the angle of the first rotation, and a1 is the last // (following the convention they are commonly defined in the literature). @@ -561,10 +604,9 @@ Basis::operator String() const { } Quat Basis::get_quat() const { - //commenting this check because precision issues cause it to fail when it shouldn't - //#ifdef MATH_CHECKS - //ERR_FAIL_COND_V(is_rotation() == false, Quat()); - //#endif +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_rotation() == false, Quat()); +#endif real_t trace = elements[0][0] + elements[1][1] + elements[2][2]; real_t temp[4]; @@ -767,3 +809,45 @@ void Basis::set_axis_angle(const Vector3 &p_axis, real_t p_phi) { elements[2][1] = p_axis.y * p_axis.z * (1.0 - cosine) + p_axis.x * sine; elements[2][2] = axis_sq.z + cosine * (1.0 - axis_sq.z); } + +void Basis::set_axis_angle_scale(const Vector3 &p_axis, real_t p_phi, const Vector3 &p_scale) { + set_diagonal(p_scale); + rotate(p_axis, p_phi); +} + +void Basis::set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale) { + set_diagonal(p_scale); + rotate(p_euler); +} + +void Basis::set_quat_scale(const Quat &p_quat, const Vector3 &p_scale) { + set_diagonal(p_scale); + rotate(p_quat); +} + +void Basis::set_diagonal(const Vector3 p_diag) { + elements[0][0] = p_diag.x; + elements[0][1] = 0; + elements[0][2] = 0; + + elements[1][0] = 0; + elements[1][1] = p_diag.y; + elements[1][2] = 0; + + elements[2][0] = 0; + elements[2][1] = 0; + elements[2][2] = p_diag.z; +} + +Basis Basis::slerp(const Basis &target, const real_t &t) const { +// TODO: implement this directly without using quaternions to make it more efficient +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_rotation() == false, Basis()); + ERR_FAIL_COND_V(target.is_rotation() == false, Basis()); +#endif + + Quat from(*this); + Quat to(target); + + return Basis(from.slerp(to, t)); +} diff --git a/core/math/matrix3.h b/core/math/matrix3.h index c426435729..cd1b51baa6 100644 --- a/core/math/matrix3.h +++ b/core/math/matrix3.h @@ -81,8 +81,14 @@ public: void rotate(const Vector3 &p_euler); Basis rotated(const Vector3 &p_euler) const; - Vector3 get_rotation() const; + void rotate(const Quat &p_quat); + Basis rotated(const Quat &p_quat) const; + + Vector3 get_rotation_euler() const; void get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const; + void get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) const; + Quat get_rotation_quat() const; + Vector3 get_rotation() const { return get_rotation_euler(); }; Vector3 rotref_posscale_decomposition(Basis &rotref) const; @@ -108,7 +114,12 @@ public: void set_scale(const Vector3 &p_scale); Vector3 get_scale() const; - Vector3 get_signed_scale() const; + Vector3 get_scale_abs() const; + Vector3 get_scale_local() const; + + void set_axis_angle_scale(const Vector3 &p_axis, real_t p_phi, const Vector3 &p_scale); + void set_euler_scale(const Vector3 &p_euler, const Vector3 &p_scale); + void set_quat_scale(const Quat &p_quat, const Vector3 &p_scale); // transposed dot products _FORCE_INLINE_ real_t tdotx(const Vector3 &v) const { @@ -140,10 +151,14 @@ public: int get_orthogonal_index() const; void set_orthogonal_index(int p_index); + void set_diagonal(const Vector3 p_diag); + bool is_orthogonal() const; bool is_diagonal() const; bool is_rotation() const; + Basis slerp(const Basis &target, const real_t &t) const; + operator String() const; /* create / set */ @@ -219,6 +234,8 @@ public: Basis(const Quat &p_quat) { set_quat(p_quat); }; Basis(const Vector3 &p_euler) { set_euler(p_euler); } Basis(const Vector3 &p_axis, real_t p_phi) { set_axis_angle(p_axis, p_phi); } + Basis(const Vector3 &p_axis, real_t p_phi, const Vector3 &p_scale) { set_axis_angle_scale(p_axis, p_phi, p_scale); } + Basis(const Quat &p_quat, const Vector3 &p_scale) { set_quat_scale(p_quat, p_scale); } _FORCE_INLINE_ Basis(const Vector3 &row0, const Vector3 &row1, const Vector3 &row2) { elements[0] = row0; diff --git a/core/math/quat.cpp b/core/math/quat.cpp index 9aa8b537d2..67c9048a41 100644 --- a/core/math/quat.cpp +++ b/core/math/quat.cpp @@ -89,7 +89,7 @@ void Quat::set_euler_yxz(const Vector3 &p_euler) { set(sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3, sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3, - -sin_a1 * sin_a2 * cos_a3 + cos_a1 * sin_a2 * sin_a3, + -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3, sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3); } @@ -98,6 +98,9 @@ void Quat::set_euler_yxz(const Vector3 &p_euler) { // and similar for other axes. // This implementation uses YXZ convention (Z is the first rotation). Vector3 Quat::get_euler_yxz() const { +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Vector3(0, 0, 0)); +#endif Basis m(*this); return m.get_euler_yxz(); } @@ -135,11 +138,17 @@ bool Quat::is_normalized() const { } Quat Quat::inverse() const { +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Quat()); +#endif return Quat(-x, -y, -z, w); } Quat Quat::slerp(const Quat &q, const real_t &t) const { - +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Quat()); + ERR_FAIL_COND_V(q.is_normalized() == false, Quat()); +#endif Quat to1; real_t omega, cosom, sinom, scale0, scale1; @@ -183,7 +192,10 @@ Quat Quat::slerp(const Quat &q, const real_t &t) const { } Quat Quat::slerpni(const Quat &q, const real_t &t) const { - +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Quat()); + ERR_FAIL_COND_V(q.is_normalized() == false, Quat()); +#endif const Quat &from = *this; real_t dot = from.dot(q); @@ -202,7 +214,10 @@ Quat Quat::slerpni(const Quat &q, const real_t &t) const { } Quat Quat::cubic_slerp(const Quat &q, const Quat &prep, const Quat &postq, const real_t &t) const { - +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Quat()); + ERR_FAIL_COND_V(q.is_normalized() == false, Quat()); +#endif //the only way to do slerp :| real_t t2 = (1.0 - t) * t * 2; Quat sp = this->slerp(q, t); @@ -215,7 +230,10 @@ Quat::operator String() const { return String::num(x) + ", " + String::num(y) + ", " + String::num(z) + ", " + String::num(w); } -Quat::Quat(const Vector3 &axis, const real_t &angle) { +void Quat::set_axis_angle(const Vector3 &axis, const real_t &angle) { +#ifdef MATH_CHECKS + ERR_FAIL_COND(axis.is_normalized() == false); +#endif real_t d = axis.length(); if (d == 0) set(0, 0, 0, 0); diff --git a/core/math/quat.h b/core/math/quat.h index ebc924504b..6dc8d66f60 100644 --- a/core/math/quat.h +++ b/core/math/quat.h @@ -64,11 +64,13 @@ public: Quat slerpni(const Quat &q, const real_t &t) const; Quat cubic_slerp(const Quat &q, const Quat &prep, const Quat &postq, const real_t &t) const; + void set_axis_angle(const Vector3 &axis, const real_t &angle); _FORCE_INLINE_ void get_axis_angle(Vector3 &r_axis, real_t &r_angle) const { r_angle = 2 * Math::acos(w); - r_axis.x = x / Math::sqrt(1 - w * w); - r_axis.y = y / Math::sqrt(1 - w * w); - r_axis.z = z / Math::sqrt(1 - w * w); + real_t r = ((real_t)1) / Math::sqrt(1 - w * w); + r_axis.x = x * r; + r_axis.y = y * r; + r_axis.z = z * r; } void operator*=(const Quat &q); @@ -82,10 +84,12 @@ public: } _FORCE_INLINE_ Vector3 xform(const Vector3 &v) const { - - Quat q = *this * v; - q *= this->inverse(); - return Vector3(q.x, q.y, q.z); +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, v); +#endif + Vector3 u(x, y, z); + Vector3 uv = u.cross(v); + return v + ((uv * w) + u.cross(uv)) * ((real_t)2); } _FORCE_INLINE_ void operator+=(const Quat &q); @@ -115,7 +119,15 @@ public: z = p_z; w = p_w; } - Quat(const Vector3 &axis, const real_t &angle); + Quat(const Vector3 &axis, const real_t &angle) { set_axis_angle(axis, angle); } + + Quat(const Vector3 &euler) { set_euler(euler); } + Quat(const Quat &q) { + x = q.x; + y = q.y; + z = q.z; + w = q.w; + } Quat(const Vector3 &v0, const Vector3 &v1) // shortest arc { diff --git a/core/math/quick_hull.cpp b/core/math/quick_hull.cpp index 102e454e02..fc90417413 100644 --- a/core/math/quick_hull.cpp +++ b/core/math/quick_hull.cpp @@ -74,7 +74,7 @@ Error QuickHull::build(const Vector<Vector3> &p_points, Geometry::MeshData &r_me int longest_axis = aabb.get_longest_axis_index(); //first two vertices are the most distant - int simplex[4]; + int simplex[4] = { 0 }; { real_t max = 0, min = 0; diff --git a/core/math/transform.cpp b/core/math/transform.cpp index f727d00e30..d1e190f4b9 100644 --- a/core/math/transform.cpp +++ b/core/math/transform.cpp @@ -119,12 +119,12 @@ Transform Transform::interpolate_with(const Transform &p_transform, real_t p_c) /* not sure if very "efficient" but good enough? */ - Vector3 src_scale = basis.get_signed_scale(); - Quat src_rot = basis.orthonormalized(); + Vector3 src_scale = basis.get_scale(); + Quat src_rot = basis.get_rotation_quat(); Vector3 src_loc = origin; - Vector3 dst_scale = p_transform.basis.get_signed_scale(); - Quat dst_rot = p_transform.basis; + Vector3 dst_scale = p_transform.basis.get_scale(); + Quat dst_rot = p_transform.basis.get_rotation_quat(); Vector3 dst_loc = p_transform.origin; Transform dst; //this could be made faster by using a single function in Basis.. diff --git a/core/math/triangle_mesh.cpp b/core/math/triangle_mesh.cpp index edd4ad3441..5475f733c3 100644 --- a/core/math/triangle_mesh.cpp +++ b/core/math/triangle_mesh.cpp @@ -88,6 +88,26 @@ int TriangleMesh::_create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, in return index; } +void TriangleMesh::get_indices(PoolVector<int> *r_triangles_indices) const { + + if (!valid) + return; + + const int triangles_num = triangles.size(); + + // Parse vertices indices + PoolVector<Triangle>::Read triangles_read = triangles.read(); + + r_triangles_indices->resize(triangles_num * 3); + PoolVector<int>::Write r_indices_write = r_triangles_indices->write(); + + for (int i = 0; i < triangles_num; ++i) { + r_indices_write[3 * i + 0] = triangles_read[i].indices[0]; + r_indices_write[3 * i + 1] = triangles_read[i].indices[1]; + r_indices_write[3 * i + 2] = triangles_read[i].indices[2]; + } +} + void TriangleMesh::create(const PoolVector<Vector3> &p_faces) { valid = false; @@ -490,6 +510,222 @@ bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, V return inters; } +bool TriangleMesh::intersect_convex_shape(const Plane *p_planes, int p_plane_count) const { + uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth); + + //p_fully_inside = true; + + enum { + TEST_AABB_BIT = 0, + VISIT_LEFT_BIT = 1, + VISIT_RIGHT_BIT = 2, + VISIT_DONE_BIT = 3, + VISITED_BIT_SHIFT = 29, + NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1, + VISITED_BIT_MASK = ~NODE_IDX_MASK, + + }; + + int level = 0; + + PoolVector<Triangle>::Read trianglesr = triangles.read(); + PoolVector<Vector3>::Read verticesr = vertices.read(); + PoolVector<BVH>::Read bvhr = bvh.read(); + + const Triangle *triangleptr = trianglesr.ptr(); + const Vector3 *vertexptr = verticesr.ptr(); + int pos = bvh.size() - 1; + const BVH *bvhptr = bvhr.ptr(); + + stack[0] = pos; + while (true) { + + uint32_t node = stack[level] & NODE_IDX_MASK; + const BVH &b = bvhptr[node]; + bool done = false; + + switch (stack[level] >> VISITED_BIT_SHIFT) { + case TEST_AABB_BIT: { + + bool valid = b.aabb.intersects_convex_shape(p_planes, p_plane_count); + if (!valid) { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + if (b.face_index >= 0) { + + const Triangle &s = triangleptr[b.face_index]; + + for (int j = 0; j < 3; ++j) { + const Vector3 &point = vertexptr[s.indices[j]]; + const Vector3 &next_point = vertexptr[s.indices[(j + 1) % 3]]; + Vector3 res; + bool over = true; + for (int i = 0; i < p_plane_count; i++) { + const Plane &p = p_planes[i]; + + if (p.intersects_segment(point, next_point, &res)) { + bool inisde = true; + for (int k = 0; k < p_plane_count; k++) { + if (k == i) continue; + const Plane &pp = p_planes[k]; + if (pp.is_point_over(res)) { + inisde = false; + break; + } + } + if (inisde) return true; + } + + if (p.is_point_over(point)) { + over = false; + break; + } + } + if (over) return true; + } + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node; + } + } + continue; + } + case VISIT_LEFT_BIT: { + + stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.left | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_RIGHT_BIT: { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.right | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_DONE_BIT: { + + if (level == 0) { + done = true; + break; + } else + level--; + continue; + } + } + + if (done) + break; + } + + return false; +} + +bool TriangleMesh::inside_convex_shape(const Plane *p_planes, int p_plane_count, Vector3 p_scale) const { + uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth); + + enum { + TEST_AABB_BIT = 0, + VISIT_LEFT_BIT = 1, + VISIT_RIGHT_BIT = 2, + VISIT_DONE_BIT = 3, + VISITED_BIT_SHIFT = 29, + NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1, + VISITED_BIT_MASK = ~NODE_IDX_MASK, + + }; + + int level = 0; + + PoolVector<Triangle>::Read trianglesr = triangles.read(); + PoolVector<Vector3>::Read verticesr = vertices.read(); + PoolVector<BVH>::Read bvhr = bvh.read(); + + Transform scale(Basis().scaled(p_scale)); + + const Triangle *triangleptr = trianglesr.ptr(); + const Vector3 *vertexptr = verticesr.ptr(); + int pos = bvh.size() - 1; + const BVH *bvhptr = bvhr.ptr(); + + stack[0] = pos; + while (true) { + + uint32_t node = stack[level] & NODE_IDX_MASK; + const BVH &b = bvhptr[node]; + bool done = false; + + switch (stack[level] >> VISITED_BIT_SHIFT) { + case TEST_AABB_BIT: { + + bool intersects = scale.xform(b.aabb).intersects_convex_shape(p_planes, p_plane_count); + if (!intersects) return false; + + bool inside = scale.xform(b.aabb).inside_convex_shape(p_planes, p_plane_count); + if (inside) { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + if (b.face_index >= 0) { + const Triangle &s = triangleptr[b.face_index]; + for (int j = 0; j < 3; ++j) { + Vector3 point = scale.xform(vertexptr[s.indices[j]]); + for (int i = 0; i < p_plane_count; i++) { + const Plane &p = p_planes[i]; + if (p.is_point_over(point)) return false; + } + } + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node; + } + } + continue; + } + case VISIT_LEFT_BIT: { + + stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.left | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_RIGHT_BIT: { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.right | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_DONE_BIT: { + + if (level == 0) { + done = true; + break; + } else + level--; + continue; + } + } + + if (done) + break; + } + + return true; +} + bool TriangleMesh::is_valid() const { return valid; diff --git a/core/math/triangle_mesh.h b/core/math/triangle_mesh.h index 9f145f2afb..bf793fc50f 100644 --- a/core/math/triangle_mesh.h +++ b/core/math/triangle_mesh.h @@ -89,9 +89,15 @@ public: bool is_valid() const; bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal) const; bool intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, Vector3 &r_point, Vector3 &r_normal) const; + bool intersect_convex_shape(const Plane *p_planes, int p_plane_count) const; + bool inside_convex_shape(const Plane *p_planes, int p_plane_count, Vector3 p_scale = Vector3(1, 1, 1)) const; Vector3 get_area_normal(const AABB &p_aabb) const; PoolVector<Face3> get_faces() const; + PoolVector<Triangle> get_triangles() const { return triangles; } + PoolVector<Vector3> get_vertices() const { return vertices; } + void get_indices(PoolVector<int> *p_triangles_indices) const; + void create(const PoolVector<Vector3> &p_faces); TriangleMesh(); }; diff --git a/core/math/triangulate.cpp b/core/math/triangulate.cpp index 5bae74ac7e..563ba7268f 100644 --- a/core/math/triangulate.cpp +++ b/core/math/triangulate.cpp @@ -51,7 +51,8 @@ real_t Triangulate::get_area(const Vector<Vector2> &contour) { bool Triangulate::is_inside_triangle(real_t Ax, real_t Ay, real_t Bx, real_t By, real_t Cx, real_t Cy, - real_t Px, real_t Py) + real_t Px, real_t Py, + bool include_edges) { real_t ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; @@ -74,10 +75,14 @@ bool Triangulate::is_inside_triangle(real_t Ax, real_t Ay, cCROSSap = cx * apy - cy * apx; bCROSScp = bx * cpy - by * cpx; - return ((aCROSSbp > 0.0) && (bCROSScp > 0.0) && (cCROSSap > 0.0)); + if (include_edges) { + return ((aCROSSbp > 0.0) && (bCROSScp > 0.0) && (cCROSSap > 0.0)); + } else { + return ((aCROSSbp >= 0.0) && (bCROSScp >= 0.0) && (cCROSSap >= 0.0)); + } }; -bool Triangulate::snip(const Vector<Vector2> &p_contour, int u, int v, int w, int n, const Vector<int> &V) { +bool Triangulate::snip(const Vector<Vector2> &p_contour, int u, int v, int w, int n, const Vector<int> &V, bool relaxed) { int p; real_t Ax, Ay, Bx, By, Cx, Cy, Px, Py; const Vector2 *contour = &p_contour[0]; @@ -91,13 +96,20 @@ bool Triangulate::snip(const Vector<Vector2> &p_contour, int u, int v, int w, in Cx = contour[V[w]].x; Cy = contour[V[w]].y; - if (CMP_EPSILON > (((Bx - Ax) * (Cy - Ay)) - ((By - Ay) * (Cx - Ax)))) return false; + // It can happen that the triangulation ends up with three aligned vertices to deal with. + // In this scenario, making the check below strict may reject the possibility of + // forming a last triangle with these aligned vertices, preventing the triangulatiom + // from completing. + // To avoid that we allow zero-area triangles if all else failed. + float threshold = relaxed ? -CMP_EPSILON : CMP_EPSILON; + + if (threshold > (((Bx - Ax) * (Cy - Ay)) - ((By - Ay) * (Cx - Ax)))) return false; for (p = 0; p < n; p++) { if ((p == u) || (p == v) || (p == w)) continue; Px = contour[V[p]].x; Py = contour[V[p]].y; - if (is_inside_triangle(Ax, Ay, Bx, By, Cx, Cy, Px, Py)) return false; + if (is_inside_triangle(Ax, Ay, Bx, By, Cx, Cy, Px, Py, relaxed)) return false; } return true; @@ -121,6 +133,8 @@ bool Triangulate::triangulate(const Vector<Vector2> &contour, Vector<int> &resul for (int v = 0; v < n; v++) V[v] = (n - 1) - v; + bool relaxed = false; + int nv = n; /* remove nv-2 Vertices, creating 1 triangle every time */ @@ -129,8 +143,20 @@ bool Triangulate::triangulate(const Vector<Vector2> &contour, Vector<int> &resul for (int v = nv - 1; nv > 2;) { /* if we loop, it is probably a non-simple polygon */ if (0 >= (count--)) { - //** Triangulate: ERROR - probable bad polygon! - return false; + if (relaxed) { + //** Triangulate: ERROR - probable bad polygon! + return false; + } else { + // There may be aligned vertices that the strict + // checks prevent from triangulating. In this situation + // we are better off adding flat triangles than + // failing, so we relax the checks and try one last + // round. + // Only relaxing the constraints as a last resort avoids + // degenerate triangles when they aren't necessary. + count = 2 * nv; + relaxed = true; + } } /* three consecutive vertices in current polygon, <u,v,w> */ @@ -141,7 +167,7 @@ bool Triangulate::triangulate(const Vector<Vector2> &contour, Vector<int> &resul int w = v + 1; if (nv <= w) w = 0; /* next */ - if (snip(contour, u, v, w, nv, V)) { + if (snip(contour, u, v, w, nv, V, relaxed)) { int a, b, c, s, t; /* true names of the vertices */ diff --git a/core/math/triangulate.h b/core/math/triangulate.h index e336dc5756..b1a583d0c5 100644 --- a/core/math/triangulate.h +++ b/core/math/triangulate.h @@ -51,10 +51,11 @@ public: static bool is_inside_triangle(real_t Ax, real_t Ay, real_t Bx, real_t By, real_t Cx, real_t Cy, - real_t Px, real_t Py); + real_t Px, real_t Py, + bool include_edges); private: - static bool snip(const Vector<Vector2> &p_contour, int u, int v, int w, int n, const Vector<int> &V); + static bool snip(const Vector<Vector2> &p_contour, int u, int v, int w, int n, const Vector<int> &V, bool relaxed); }; #endif diff --git a/core/math/vector3.h b/core/math/vector3.h index 10ec4f5641..433adf09ee 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -91,6 +91,7 @@ struct Vector3 { /* Static Methods between 2 vector3s */ _FORCE_INLINE_ Vector3 linear_interpolate(const Vector3 &p_b, real_t p_t) const; + _FORCE_INLINE_ Vector3 slerp(const Vector3 &p_b, real_t p_t) const; Vector3 cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, real_t p_t) const; Vector3 cubic_interpolaten(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, real_t p_t) const; @@ -103,6 +104,7 @@ struct Vector3 { _FORCE_INLINE_ Vector3 floor() const; _FORCE_INLINE_ Vector3 sign() const; _FORCE_INLINE_ Vector3 ceil() const; + _FORCE_INLINE_ Vector3 round() const; _FORCE_INLINE_ real_t distance_to(const Vector3 &p_b) const; _FORCE_INLINE_ real_t distance_squared_to(const Vector3 &p_b) const; @@ -204,6 +206,11 @@ Vector3 Vector3::ceil() const { return Vector3(Math::ceil(x), Math::ceil(y), Math::ceil(z)); } +Vector3 Vector3::round() const { + + return Vector3(Math::round(x), Math::round(y), Math::round(z)); +} + Vector3 Vector3::linear_interpolate(const Vector3 &p_b, real_t p_t) const { return Vector3( @@ -212,6 +219,15 @@ Vector3 Vector3::linear_interpolate(const Vector3 &p_b, real_t p_t) const { z + (p_t * (p_b.z - z))); } +Vector3 Vector3::slerp(const Vector3 &p_b, real_t p_t) const { +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Vector3()); +#endif + + real_t theta = angle_to(p_b); + return rotated(cross(p_b), theta * p_t); +} + real_t Vector3::distance_to(const Vector3 &p_b) const { return (p_b - *this).length(); diff --git a/core/method_bind.h b/core/method_bind.h index e02d64c935..41b500c401 100644 --- a/core/method_bind.h +++ b/core/method_bind.h @@ -128,10 +128,36 @@ struct VariantCaster<const T &> { // Object enum casts must go here VARIANT_ENUM_CAST(Object::ConnectFlags); +template <typename T> +struct VariantObjectClassChecker { + static _FORCE_INLINE_ bool check(const Variant &p_variant) { + return true; + } +}; + +template <> +struct VariantObjectClassChecker<Node *> { + static _FORCE_INLINE_ bool check(const Variant &p_variant) { + Object *obj = p_variant; + Node *node = p_variant; + return node || !obj; + } +}; + +template <> +struct VariantObjectClassChecker<Control *> { + static _FORCE_INLINE_ bool check(const Variant &p_variant) { + Object *obj = p_variant; + Control *control = p_variant; + return control || !obj; + } +}; + #define CHECK_ARG(m_arg) \ if ((m_arg - 1) < p_arg_count) { \ Variant::Type argtype = get_argument_type(m_arg - 1); \ - if (!Variant::can_convert_strict(p_args[m_arg - 1]->get_type(), argtype)) { \ + if (!Variant::can_convert_strict(p_args[m_arg - 1]->get_type(), argtype) || \ + !VariantObjectClassChecker<P##m_arg>::check(*p_args[m_arg - 1])) { \ r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; \ r_error.argument = m_arg - 1; \ r_error.expected = argtype; \ diff --git a/core/method_ptrcall.h b/core/method_ptrcall.h index 2007c3def5..677e8e1fb2 100644 --- a/core/method_ptrcall.h +++ b/core/method_ptrcall.h @@ -214,6 +214,50 @@ struct PtrToArg<const T *> { } \ } +#define MAKE_VECARG_ALT(m_type, m_type_alt) \ + template <> \ + struct PtrToArg<Vector<m_type_alt> > { \ + _FORCE_INLINE_ static Vector<m_type_alt> convert(const void *p_ptr) { \ + const PoolVector<m_type> *dvs = reinterpret_cast<const PoolVector<m_type> *>(p_ptr); \ + Vector<m_type_alt> ret; \ + int len = dvs->size(); \ + ret.resize(len); \ + { \ + PoolVector<m_type>::Read r = dvs->read(); \ + for (int i = 0; i < len; i++) { \ + ret[i] = r[i]; \ + } \ + } \ + return ret; \ + } \ + _FORCE_INLINE_ static void encode(Vector<m_type_alt> p_vec, void *p_ptr) { \ + PoolVector<m_type> *dv = reinterpret_cast<PoolVector<m_type> *>(p_ptr); \ + int len = p_vec.size(); \ + dv->resize(len); \ + { \ + PoolVector<m_type>::Write w = dv->write(); \ + for (int i = 0; i < len; i++) { \ + w[i] = p_vec[i]; \ + } \ + } \ + } \ + }; \ + template <> \ + struct PtrToArg<const Vector<m_type_alt> &> { \ + _FORCE_INLINE_ static Vector<m_type_alt> convert(const void *p_ptr) { \ + const PoolVector<m_type> *dvs = reinterpret_cast<const PoolVector<m_type> *>(p_ptr); \ + Vector<m_type_alt> ret; \ + int len = dvs->size(); \ + ret.resize(len); \ + { \ + PoolVector<m_type>::Read r = dvs->read(); \ + for (int i = 0; i < len; i++) { \ + ret[i] = r[i]; \ + } \ + } \ + return ret; \ + } \ + } MAKE_VECARG(String); MAKE_VECARG(uint8_t); MAKE_VECARG(int); @@ -221,6 +265,7 @@ MAKE_VECARG(float); MAKE_VECARG(Vector2); MAKE_VECARG(Vector3); MAKE_VECARG(Color); +MAKE_VECARG_ALT(String, StringName); //for stuff that gets converted to Array vectors #define MAKE_VECARR(m_type) \ diff --git a/core/node_path.cpp b/core/node_path.cpp index cd7ad77534..487d5ee8c6 100644 --- a/core/node_path.cpp +++ b/core/node_path.cpp @@ -32,10 +32,7 @@ #include "print_string.h" -uint32_t NodePath::hash() const { - - if (!data) - return 0; +void NodePath::_update_hash_cache() const { uint32_t h = data->absolute ? 1 : 0; int pc = data->path.size(); @@ -49,13 +46,15 @@ uint32_t NodePath::hash() const { h = h ^ ssn[i].hash(); } - return h; + data->hash_cache_valid = true; + data->hash_cache = h; } void NodePath::prepend_period() { if (data->path.size() && data->path[0].operator String() != ".") { data->path.insert(0, "."); + data->hash_cache_valid = false; } } @@ -114,21 +113,33 @@ bool NodePath::operator==(const NodePath &p_path) const { if (data->absolute != p_path.data->absolute) return false; - if (data->path.size() != p_path.data->path.size()) + int path_size = data->path.size(); + + if (path_size != p_path.data->path.size()) { return false; + } + + int subpath_size = data->subpath.size(); - if (data->subpath.size() != p_path.data->subpath.size()) + if (subpath_size != p_path.data->subpath.size()) { return false; + } - for (int i = 0; i < data->path.size(); i++) { + const StringName *l_path_ptr = data->path.ptr(); + const StringName *r_path_ptr = p_path.data->path.ptr(); + + for (int i = 0; i < path_size; i++) { - if (data->path[i] != p_path.data->path[i]) + if (l_path_ptr[i] != r_path_ptr[i]) return false; } - for (int i = 0; i < data->subpath.size(); i++) { + const StringName *l_subpath_ptr = data->subpath.ptr(); + const StringName *r_subpath_ptr = p_path.data->subpath.ptr(); + + for (int i = 0; i < subpath_size; i++) { - if (data->subpath[i] != p_path.data->subpath[i]) + if (l_subpath_ptr[i] != r_subpath_ptr[i]) return false; } @@ -264,8 +275,9 @@ NodePath NodePath::get_as_property_path() const { Vector<StringName> new_path = data->subpath; String initial_subname = data->path[0]; + for (size_t i = 1; i < data->path.size(); i++) { - initial_subname += i == 0 ? data->path[i].operator String() : "/" + data->path[i]; + initial_subname += "/" + data->path[i]; } new_path.insert(0, initial_subname); @@ -285,6 +297,7 @@ NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) { data->absolute = p_absolute; data->path = p_path; data->has_slashes = true; + data->hash_cache_valid = false; } NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute) { @@ -300,6 +313,7 @@ NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p data->path = p_path; data->subpath = p_subpath; data->has_slashes = true; + data->hash_cache_valid = false; } void NodePath::simplify() { @@ -323,6 +337,7 @@ void NodePath::simplify() { } } } + data->hash_cache_valid = false; } NodePath NodePath::simplified() const { @@ -395,6 +410,7 @@ NodePath::NodePath(const String &p_path) { data->absolute = absolute ? true : false; data->has_slashes = has_slashes; data->subpath = subpath; + data->hash_cache_valid = false; if (slices == 0) return; diff --git a/core/node_path.h b/core/node_path.h index 288f39721f..71235029af 100644 --- a/core/node_path.h +++ b/core/node_path.h @@ -47,11 +47,15 @@ class NodePath { StringName concatenated_subpath; bool absolute; bool has_slashes; + mutable bool hash_cache_valid; + mutable uint32_t hash_cache; }; - Data *data; + mutable Data *data; void unref(); + void _update_hash_cache() const; + public: _FORCE_INLINE_ StringName get_sname() const { @@ -78,7 +82,14 @@ public: NodePath get_parent() const; - uint32_t hash() const; + _FORCE_INLINE_ uint32_t hash() const { + if (!data) + return 0; + if (!data->hash_cache_valid) { + _update_hash_cache(); + } + return data->hash_cache; + } operator String() const; bool is_empty() const; diff --git a/core/oa_hash_map.h b/core/oa_hash_map.h index 280aea6a14..0b3b40f30c 100644 --- a/core/oa_hash_map.h +++ b/core/oa_hash_map.h @@ -36,176 +36,181 @@ #include "os/copymem.h" #include "os/memory.h" -// uncomment this to disable initial local storage. -#define OA_HASH_MAP_INITIAL_LOCAL_STORAGE - /** - * This class implements a hash map datastructure that uses open addressing with - * local probing. - * - * It can give huge performance improvements over a chained HashMap because of - * the increased data locality. - * - * Because of that locality property it's important to not use "large" value - * types as the "TData" type. If TData values are too big it can cause more - * cache misses then chaining. If larger values are needed then storing those - * in a separate array and using pointers or indices to reference them is the - * better solution. - * - * This hash map also implements real-time incremental rehashing. + * A HashMap implementation that uses open addressing with robinhood hashing. + * Robinhood hashing swaps out entries that have a smaller probing distance + * than the to-be-inserted entry, that evens out the average probing distance + * and enables faster lookups. * + * The entries are stored inplace, so huge keys or values might fill cache lines + * a lot faster. */ -template <class TKey, class TData, - uint16_t INITIAL_NUM_ELEMENTS = 64, +template <class TKey, class TValue, class Hasher = HashMapHasherDefault, class Comparator = HashMapComparatorDefault<TKey> > class OAHashMap { private: -#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE - TData local_data[INITIAL_NUM_ELEMENTS]; - TKey local_keys[INITIAL_NUM_ELEMENTS]; - uint32_t local_hashes[INITIAL_NUM_ELEMENTS]; - uint8_t local_flags[INITIAL_NUM_ELEMENTS / 4 + (INITIAL_NUM_ELEMENTS % 4 != 0 ? 1 : 0)]; -#endif + TValue *values; + TKey *keys; + uint32_t *hashes; - struct { - TData *data; - TKey *keys; - uint32_t *hashes; + uint32_t capacity; - // This is actually an array of bits, 4 bit pairs per octet. - // | ba ba ba ba | ba ba ba ba | .... - // - // if a is set it means that there is an element present. - // if b is set it means that an element was deleted. This is needed for - // the local probing to work without relocating any succeeding and - // colliding entries. - uint8_t *flags; + uint32_t num_elements; - uint32_t capacity; - } table, old_table; + static const uint32_t EMPTY_HASH = 0; + static const uint32_t DELETED_HASH_BIT = 1 << 31; - bool is_rehashing; - uint32_t rehash_position; - uint32_t rehash_amount; + _FORCE_INLINE_ uint32_t _hash(const TKey &p_key) { + uint32_t hash = Hasher::hash(p_key); - uint32_t elements; + if (hash == EMPTY_HASH) { + hash = EMPTY_HASH + 1; + } else if (hash & DELETED_HASH_BIT) { + hash &= ~DELETED_HASH_BIT; + } - /* Methods */ + return hash; + } - // returns true if the value already existed, false if it's a new entry - bool _raw_set_with_hash(uint32_t p_hash, const TKey &p_key, const TData &p_data) { - for (int i = 0; i < table.capacity; i++) { + _FORCE_INLINE_ uint32_t _get_probe_length(uint32_t p_pos, uint32_t p_hash) { + p_hash = p_hash & ~DELETED_HASH_BIT; // we don't care if it was deleted or not - int pos = (p_hash + i) % table.capacity; + uint32_t original_pos = p_hash % capacity; - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; + return p_pos - original_pos; + } - bool is_filled_flag = table.flags[flags_pos] & (1 << (2 * flags_pos_offset)); - bool is_deleted_flag = table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1)); + _FORCE_INLINE_ void _construct(uint32_t p_pos, uint32_t p_hash, const TKey &p_key, const TValue &p_value) { + memnew_placement(&keys[p_pos], TKey(p_key)); + memnew_placement(&values[p_pos], TValue(p_value)); + hashes[p_pos] = p_hash; - if (is_filled_flag) { - if (table.hashes[pos] == p_hash && Comparator::compare(table.keys[pos], p_key)) { - table.data[pos] = p_data; - return true; - } - continue; + num_elements++; + } + + bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) { + uint32_t hash = _hash(p_key); + uint32_t pos = hash % capacity; + uint32_t distance = 0; + + while (42) { + if (hashes[pos] == EMPTY_HASH) { + return false; } - table.keys[pos] = p_key; - table.data[pos] = p_data; - table.hashes[pos] = p_hash; + if (distance > _get_probe_length(pos, hashes[pos])) { + return false; + } - table.flags[flags_pos] |= (1 << (2 * flags_pos_offset)); - table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset + 1)); + if (hashes[pos] == hash && Comparator::compare(keys[pos], p_key)) { + r_pos = pos; + return true; + } - return false; + pos = (pos + 1) % capacity; + distance++; } - return false; } -public: - _FORCE_INLINE_ uint32_t get_capacity() const { return table.capacity; } - _FORCE_INLINE_ uint32_t get_num_elements() const { return elements; } + void _insert_with_hash(uint32_t p_hash, const TKey &p_key, const TValue &p_value) { - void set(const TKey &p_key, const TData &p_data) { + uint32_t hash = p_hash; + uint32_t distance = 0; + uint32_t pos = hash % capacity; - uint32_t hash = Hasher::hash(p_key); + TKey key = p_key; + TValue value = p_value; - // We don't progress the rehashing if the table just got resized - // to keep the cost of this function low. - if (is_rehashing) { + while (42) { + if (hashes[pos] == EMPTY_HASH) { + _construct(pos, hash, p_key, p_value); - // rehash progress + return; + } - for (int i = 0; i <= rehash_amount && rehash_position < old_table.capacity; rehash_position++) { + // not an empty slot, let's check the probing length of the existing one + uint32_t existing_probe_len = _get_probe_length(pos, hashes[pos]); + if (existing_probe_len < distance) { - int flags_pos = rehash_position / 4; - int flags_pos_offset = rehash_position % 4; + if (hashes[pos] & DELETED_HASH_BIT) { + // we found a place where we can fit in! + _construct(pos, hash, p_key, p_value); - bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0; + return; + } - if (is_filled_flag) { - _raw_set_with_hash(old_table.hashes[rehash_position], old_table.keys[rehash_position], old_table.data[rehash_position]); + SWAP(hash, hashes[pos]); + SWAP(key, keys[pos]); + SWAP(value, values[pos]); + distance = existing_probe_len; + } - old_table.keys[rehash_position].~TKey(); - old_table.data[rehash_position].~TData(); + pos = (pos + 1) % capacity; + distance++; + } + } + void _resize_and_rehash() { - memnew_placement(&old_table.keys[rehash_position], TKey); - memnew_placement(&old_table.data[rehash_position], TData); + TKey *old_keys = keys; + TValue *old_values = values; + uint32_t *old_hashes = hashes; - old_table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset)); - old_table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1)); - } - } + uint32_t old_capacity = capacity; - if (rehash_position >= old_table.capacity) { + capacity = old_capacity * 2; + num_elements = 0; - // wohooo, we can get rid of the old table. - is_rehashing = false; + keys = memnew_arr(TKey, capacity); + values = memnew_arr(TValue, capacity); + hashes = memnew_arr(uint32_t, capacity); -#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE - if (old_table.data == local_data) { - // Everything is local, so no cleanup :P - } else -#endif - { - memdelete_arr(old_table.data); - memdelete_arr(old_table.keys); - memdelete_arr(old_table.hashes); - memdelete_arr(old_table.flags); - } - } + for (int i = 0; i < capacity; i++) { + hashes[i] = 0; } - // Table is almost full, resize and start rehashing process. - if (elements >= table.capacity * 0.7) { + for (uint32_t i = 0; i < old_capacity; i++) { + if (old_hashes[i] == EMPTY_HASH) { + continue; + } + if (old_hashes[i] & DELETED_HASH_BIT) { + continue; + } - old_table.capacity = table.capacity; - old_table.data = table.data; - old_table.flags = table.flags; - old_table.hashes = table.hashes; - old_table.keys = table.keys; + _insert_with_hash(old_hashes[i], old_keys[i], old_values[i]); + } - table.capacity = old_table.capacity * 2; + memdelete_arr(old_keys); + memdelete_arr(old_values); + memdelete_arr(old_hashes); + } - table.data = memnew_arr(TData, table.capacity); - table.flags = memnew_arr(uint8_t, table.capacity / 4 + (table.capacity % 4 != 0 ? 1 : 0)); - table.hashes = memnew_arr(uint32_t, table.capacity); - table.keys = memnew_arr(TKey, table.capacity); +public: + _FORCE_INLINE_ uint32_t get_capacity() const { return capacity; } + _FORCE_INLINE_ uint32_t get_num_elements() const { return num_elements; } - zeromem(table.flags, table.capacity / 4 + (table.capacity % 4 != 0 ? 1 : 0)); + void insert(const TKey &p_key, const TValue &p_value) { - is_rehashing = true; - rehash_position = 0; - rehash_amount = (elements * 2) / (table.capacity * 0.7 - old_table.capacity); + if ((float)num_elements / (float)capacity > 0.9) { + _resize_and_rehash(); } - if (!_raw_set_with_hash(hash, p_key, p_data)) - elements++; + uint32_t hash = _hash(p_key); + + _insert_with_hash(hash, p_key, p_value); + } + + void set(const TKey &p_key, const TValue &p_data) { + uint32_t pos = 0; + bool exists = _lookup_pos(p_key, pos); + + if (exists) { + values[pos].~TValue(); + memnew_placement(&values[pos], TValue(p_data)); + } else { + insert(p_key, p_data); + } } /** @@ -214,380 +219,108 @@ public: * if r_data is not NULL then the value will be written to the object * it points to. */ - bool lookup(const TKey &p_key, TData *r_data) { - - uint32_t hash = Hasher::hash(p_key); - - bool check_old_table = is_rehashing; - bool check_new_table = true; - - // search for the key and return the value associated with it - // - // if we're rehashing we need to check both the old and the - // current table. If we find a value in the old table we still - // need to continue searching in the new table as it might have - // been added after - - TData *value = NULL; - - for (int i = 0; i < table.capacity; i++) { - - if (!check_new_table && !check_old_table) { - - break; - } - - // if we're rehashing check the old table - if (check_old_table && i < old_table.capacity) { - - int pos = (hash + i) % old_table.capacity; - - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; - - bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0; - - if (is_filled_flag) { - // found our entry? - if (old_table.hashes[pos] == hash && Comparator::compare(old_table.keys[pos], p_key)) { - value = &old_table.data[pos]; - check_old_table = false; - } - } else if (!is_deleted_flag) { - - // we hit an empty field here, we don't - // need to further check this old table - // because we know it's not in here. + bool lookup(const TKey &p_key, TValue &r_data) { + uint32_t pos = 0; + bool exists = _lookup_pos(p_key, pos); - check_old_table = false; - } - } - - if (check_new_table) { - - int pos = (hash + i) % table.capacity; - - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; - - bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - bool is_deleted_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0; - - if (is_filled_flag) { - // found our entry? - if (table.hashes[pos] == hash && Comparator::compare(table.keys[pos], p_key)) { - if (r_data != NULL) - *r_data = table.data[pos]; - return true; - } - continue; - } else if (is_deleted_flag) { - continue; - } else if (value != NULL) { - - // We found a value in the old table - if (r_data != NULL) - *r_data = *value; - return true; - } else { - check_new_table = false; - } - } - } - - if (value != NULL) { - if (r_data != NULL) - *r_data = *value; + if (exists) { + r_data.~TValue(); + memnew_placement(&r_data, TValue(values[pos])); return true; } + return false; } _FORCE_INLINE_ bool has(const TKey &p_key) { - return lookup(p_key, NULL); + uint32_t _pos = 0; + return _lookup_pos(p_key, _pos); } void remove(const TKey &p_key) { - uint32_t hash = Hasher::hash(p_key); - - bool check_old_table = is_rehashing; - bool check_new_table = true; - - for (int i = 0; i < table.capacity; i++) { - - if (!check_new_table && !check_old_table) { - return; - } - - // if we're rehashing check the old table - if (check_old_table && i < old_table.capacity) { - - int pos = (hash + i) % old_table.capacity; - - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; - - bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - bool is_deleted_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0; - - if (is_filled_flag) { - // found our entry? - if (old_table.hashes[pos] == hash && Comparator::compare(old_table.keys[pos], p_key)) { - old_table.keys[pos].~TKey(); - old_table.data[pos].~TData(); + uint32_t pos = 0; + bool exists = _lookup_pos(p_key, pos); - memnew_placement(&old_table.keys[pos], TKey); - memnew_placement(&old_table.data[pos], TData); - - old_table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset)); - old_table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1)); - - elements--; - return; - } - } else if (!is_deleted_flag) { - - // we hit an empty field here, we don't - // need to further check this old table - // because we know it's not in here. - - check_old_table = false; - } - } - - if (check_new_table) { - - int pos = (hash + i) % table.capacity; - - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; - - bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - bool is_deleted_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset + 1))) > 0; - - if (is_filled_flag) { - // found our entry? - if (table.hashes[pos] == hash && Comparator::compare(table.keys[pos], p_key)) { - table.keys[pos].~TKey(); - table.data[pos].~TData(); - - memnew_placement(&table.keys[pos], TKey); - memnew_placement(&table.data[pos], TData); - - table.flags[flags_pos] &= ~(1 << (2 * flags_pos_offset)); - table.flags[flags_pos] |= (1 << (2 * flags_pos_offset + 1)); - - // don't return here, this value might still be in the old table - // if it was already relocated. - - elements--; - return; - } - continue; - } else if (is_deleted_flag) { - continue; - } else { - check_new_table = false; - } - } + if (!exists) { + return; } + + hashes[pos] |= DELETED_HASH_BIT; + values[pos].~TValue(); + keys[pos].~TKey(); + num_elements--; } struct Iterator { bool valid; - uint32_t hash; - const TKey *key; - const TData *data; + const TValue *value; private: + uint32_t pos; friend class OAHashMap; - bool was_from_old_table; }; Iterator iter() const { Iterator it; - it.valid = false; - it.was_from_old_table = false; - - bool check_old_table = is_rehashing; - - for (int i = 0; i < table.capacity; i++) { - - // if we're rehashing check the old table first - if (check_old_table && i < old_table.capacity) { - - int pos = i; - - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; - - bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - - if (is_filled_flag) { - it.valid = true; - it.hash = old_table.hashes[pos]; - it.data = &old_table.data[pos]; - it.key = &old_table.keys[pos]; - - it.was_from_old_table = true; - - return it; - } - } - - { - - int pos = i; - - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; - - bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - - if (is_filled_flag) { - it.valid = true; - it.hash = table.hashes[pos]; - it.data = &table.data[pos]; - it.key = &table.keys[pos]; - - return it; - } - } - } + it.valid = true; + it.pos = 0; - return it; + return next_iter(it); } Iterator next_iter(const Iterator &p_iter) const { + if (!p_iter.valid) { return p_iter; } Iterator it; - it.valid = false; - it.was_from_old_table = false; - - bool check_old_table = is_rehashing; - - // we use this to skip the first check or not - bool was_from_old_table = p_iter.was_from_old_table; - - int prev_index = (p_iter.data - (p_iter.was_from_old_table ? old_table.data : table.data)); - - if (!was_from_old_table) { - prev_index++; - } + it.pos = p_iter.pos; + it.key = NULL; + it.value = NULL; - for (int i = prev_index; i < table.capacity; i++) { + for (uint32_t i = it.pos; i < capacity; i++) { + it.pos = i + 1; - // if we're rehashing check the old table first - if (check_old_table && i < old_table.capacity && !was_from_old_table) { - - int pos = i; - - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; - - bool is_filled_flag = (old_table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - - if (is_filled_flag) { - it.valid = true; - it.hash = old_table.hashes[pos]; - it.data = &old_table.data[pos]; - it.key = &old_table.keys[pos]; - - it.was_from_old_table = true; - - return it; - } + if (hashes[i] == EMPTY_HASH) { + continue; } - - was_from_old_table = false; - - { - int pos = i; - - int flags_pos = pos / 4; - int flags_pos_offset = pos % 4; - - bool is_filled_flag = (table.flags[flags_pos] & (1 << (2 * flags_pos_offset))) > 0; - - if (is_filled_flag) { - it.valid = true; - it.hash = table.hashes[pos]; - it.data = &table.data[pos]; - it.key = &table.keys[pos]; - - return it; - } + if (hashes[i] & DELETED_HASH_BIT) { + continue; } + + it.valid = true; + it.key = &keys[i]; + it.value = &values[i]; + return it; } return it; } - OAHashMap(uint32_t p_initial_capacity = INITIAL_NUM_ELEMENTS) { + OAHashMap(uint32_t p_initial_capacity = 64) { -#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE + capacity = p_initial_capacity; + num_elements = 0; - if (p_initial_capacity <= INITIAL_NUM_ELEMENTS) { - table.data = local_data; - table.keys = local_keys; - table.hashes = local_hashes; - table.flags = local_flags; + keys = memnew_arr(TKey, p_initial_capacity); + values = memnew_arr(TValue, p_initial_capacity); + hashes = memnew_arr(uint32_t, p_initial_capacity); - zeromem(table.flags, INITIAL_NUM_ELEMENTS / 4 + (INITIAL_NUM_ELEMENTS % 4 != 0 ? 1 : 0)); - - table.capacity = INITIAL_NUM_ELEMENTS; - elements = 0; - } else -#endif - { - table.data = memnew_arr(TData, p_initial_capacity); - table.keys = memnew_arr(TKey, p_initial_capacity); - table.hashes = memnew_arr(uint32_t, p_initial_capacity); - table.flags = memnew_arr(uint8_t, p_initial_capacity / 4 + (p_initial_capacity % 4 != 0 ? 1 : 0)); - - zeromem(table.flags, p_initial_capacity / 4 + (p_initial_capacity % 4 != 0 ? 1 : 0)); - - table.capacity = p_initial_capacity; - elements = 0; + for (int i = 0; i < p_initial_capacity; i++) { + hashes[i] = 0; } - - is_rehashing = false; - rehash_position = 0; } ~OAHashMap() { -#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE - if (table.capacity <= INITIAL_NUM_ELEMENTS) { - return; // Everything is local, so no cleanup :P - } -#endif - if (is_rehashing) { - -#ifdef OA_HASH_MAP_INITIAL_LOCAL_STORAGE - if (old_table.data == local_data) { - // Everything is local, so no cleanup :P - } else -#endif - { - memdelete_arr(old_table.data); - memdelete_arr(old_table.keys); - memdelete_arr(old_table.hashes); - memdelete_arr(old_table.flags); - } - } - memdelete_arr(table.data); - memdelete_arr(table.keys); - memdelete_arr(table.hashes); - memdelete_arr(table.flags); + memdelete_arr(keys); + memdelete_arr(values); + memdelete(hashes); } }; diff --git a/core/object.cpp b/core/object.cpp index aaa37e6cf2..1d2aeb7ba5 100644 --- a/core/object.cpp +++ b/core/object.cpp @@ -1677,6 +1677,7 @@ void Object::_bind_methods() { #ifdef TOOLS_ENABLED MethodInfo miget("_get", PropertyInfo(Variant::STRING, "property")); miget.return_val.name = "Variant"; + miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; BIND_VMETHOD(miget); MethodInfo plget("_get_property_list"); @@ -1919,9 +1920,7 @@ ObjectID ObjectDB::add_instance(Object *p_object) { rw_lock->write_lock(); instances[++instance_counter] = p_object; -#ifdef DEBUG_ENABLED instance_checks[p_object] = instance_counter; -#endif rw_lock->write_unlock(); return instance_counter; @@ -1932,9 +1931,7 @@ void ObjectDB::remove_instance(Object *p_object) { rw_lock->write_lock(); instances.erase(p_object->get_instance_id()); -#ifdef DEBUG_ENABLED instance_checks.erase(p_object); -#endif rw_lock->write_unlock(); } diff --git a/core/object.h b/core/object.h index 8306b5a356..8dc3426d1d 100644 --- a/core/object.h +++ b/core/object.h @@ -31,6 +31,7 @@ #ifndef OBJECT_H #define OBJECT_H +#include "hash_map.h" #include "list.h" #include "map.h" #include "os/rw_lock.h" @@ -55,7 +56,7 @@ enum PropertyHint { PROPERTY_HINT_RANGE, ///< hint_text = "min,max,step,slider; //slider is optional" PROPERTY_HINT_EXP_RANGE, ///< hint_text = "min,max,step", exponential edit PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" - PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) + PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) use "attenuation" hint string to revert (flip h), "full" to also include in/out. (ie: "attenuation,inout") PROPERTY_HINT_LENGTH, ///< hint_text= "length" (as integer) PROPERTY_HINT_SPRITE_FRAME, PROPERTY_HINT_KEY_ACCEL, ///< hint_text= "length" (as integer) @@ -85,6 +86,7 @@ enum PropertyHint { PROPERTY_HINT_PROPERTY_OF_INSTANCE, ///< a property of an instance PROPERTY_HINT_PROPERTY_OF_SCRIPT, ///< a property of a script & base PROPERTY_HINT_OBJECT_TOO_BIG, ///< object is too big to send + PROPERTY_HINT_NODE_PATH_VALID_TYPES, PROPERTY_HINT_MAX, // When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit }; @@ -450,7 +452,7 @@ private: Signal() { lock = 0; } }; - HashMap<StringName, Signal, StringNameHasher> signal_map; + HashMap<StringName, Signal> signal_map; List<Connection> connections; #ifdef DEBUG_ENABLED SafeRefCount _lock_index; @@ -762,15 +764,10 @@ public: static void debug_objects(DebugFunc p_func); static int get_object_count(); -#ifdef DEBUG_ENABLED _FORCE_INLINE_ static bool instance_validate(Object *p_ptr) { return instance_checks.has(p_ptr); } -#else - _FORCE_INLINE_ static bool instance_validate(Object *p_ptr) { return true; } - -#endif }; //needed by macros diff --git a/core/os/dir_access.cpp b/core/os/dir_access.cpp index 1941b82602..330a9153ef 100644 --- a/core/os/dir_access.cpp +++ b/core/os/dir_access.cpp @@ -301,8 +301,8 @@ Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) { FileAccess *fsrc = FileAccess::open(p_from, FileAccess::READ, &err); if (err) { - - ERR_FAIL_COND_V(err, err); + ERR_PRINTS("Failed to open " + p_from); + return err; } FileAccess *fdst = FileAccess::open(p_to, FileAccess::WRITE, &err); @@ -310,7 +310,8 @@ Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) { fsrc->close(); memdelete(fsrc); - ERR_FAIL_COND_V(err, err); + ERR_PRINTS("Failed to open " + p_to); + return err; } fsrc->seek_end(0); diff --git a/core/os/file_access.h b/core/os/file_access.h index 5d10c1a9aa..c4635fdfbb 100644 --- a/core/os/file_access.h +++ b/core/os/file_access.h @@ -89,6 +89,9 @@ public: virtual void close() = 0; ///< close a file virtual bool is_open() const = 0; ///< true when file is open + virtual String get_path() const { return ""; } /// returns the path for the current open file + virtual String get_path_absolute() const { return ""; } /// returns the absolute path for the current open file + virtual void seek(size_t p_position) = 0; ///< seek to a given position virtual void seek_end(int64_t p_position = 0) = 0; ///< seek from the end of file virtual size_t get_position() const = 0; ///< get position in the file diff --git a/core/os/input.cpp b/core/os/input.cpp index 3089ab2ce3..a5b0f91e63 100644 --- a/core/os/input.cpp +++ b/core/os/input.cpp @@ -57,6 +57,7 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("is_action_pressed", "action"), &Input::is_action_pressed); 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("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); @@ -85,6 +86,7 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("warp_mouse_position", "to"), &Input::warp_mouse_position); ClassDB::bind_method(D_METHOD("action_press", "action"), &Input::action_press); ClassDB::bind_method(D_METHOD("action_release", "action"), &Input::action_release); + ClassDB::bind_method(D_METHOD("set_default_cursor_shape", "shape"), &Input::set_default_cursor_shape, DEFVAL(CURSOR_ARROW)); ClassDB::bind_method(D_METHOD("set_custom_mouse_cursor", "image", "shape", "hotspot"), &Input::set_custom_mouse_cursor, DEFVAL(CURSOR_ARROW), DEFVAL(Vector2())); ClassDB::bind_method(D_METHOD("parse_input_event", "event"), &Input::parse_input_event); @@ -111,14 +113,14 @@ void Input::_bind_methods() { BIND_ENUM_CONSTANT(CURSOR_HSPLIT); BIND_ENUM_CONSTANT(CURSOR_HELP); - ADD_SIGNAL(MethodInfo("joy_connection_changed", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "connected"))); + ADD_SIGNAL(MethodInfo("joy_connection_changed", PropertyInfo(Variant::INT, "device"), PropertyInfo(Variant::BOOL, "connected"))); } void Input::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { #ifdef TOOLS_ENABLED 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")) { + 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")) { List<PropertyInfo> pinfo; ProjectSettings::get_singleton()->get_property_list(&pinfo); diff --git a/core/os/input.h b/core/os/input.h index 9c7595ff7f..001871c5dc 100644 --- a/core/os/input.h +++ b/core/os/input.h @@ -85,6 +85,7 @@ public: virtual bool is_action_pressed(const StringName &p_action) const = 0; virtual bool is_action_just_pressed(const StringName &p_action) const = 0; virtual bool is_action_just_released(const StringName &p_action) const = 0; + virtual float get_action_strength(const StringName &p_action) const = 0; virtual float get_joy_axis(int p_device, int p_axis) const = 0; virtual String get_joy_name(int p_idx) = 0; @@ -117,8 +118,11 @@ public: void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const; - virtual bool is_emulating_touchscreen() const = 0; + virtual bool is_emulating_touch_from_mouse() const = 0; + virtual bool is_emulating_mouse_from_touch() const = 0; + virtual CursorShape get_default_cursor_shape() = 0; + virtual void set_default_cursor_shape(CursorShape p_shape) = 0; virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) = 0; virtual void set_mouse_in_window(bool p_in_window) = 0; diff --git a/core/os/input_event.cpp b/core/os/input_event.cpp index 12060f31df..4ebb821a2f 100644 --- a/core/os/input_event.cpp +++ b/core/os/input_event.cpp @@ -41,11 +41,6 @@ int InputEvent::get_device() const { return device; } -bool InputEvent::is_pressed() const { - - return false; -} - bool InputEvent::is_action(const StringName &p_action) const { return InputMap::get_singleton()->event_is_action(Ref<InputEvent>((InputEvent *)this), p_action); @@ -53,11 +48,29 @@ bool InputEvent::is_action(const StringName &p_action) const { bool InputEvent::is_action_pressed(const StringName &p_action) const { - return (is_pressed() && !is_echo() && is_action(p_action)); + bool pressed; + bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, &pressed); + return valid && pressed && !is_echo(); } + bool InputEvent::is_action_released(const StringName &p_action) const { - return (!is_pressed() && is_action(p_action)); + bool pressed; + bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>((InputEvent *)this), p_action, &pressed); + return valid && !pressed; +} + +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); + return valid ? strength : 0.0f; +} + +bool InputEvent::is_pressed() const { + + return false; } bool InputEvent::is_echo() const { @@ -75,7 +88,7 @@ String InputEvent::as_text() const { return String(); } -bool InputEvent::action_match(const Ref<InputEvent> &p_event) const { +bool InputEvent::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const { return false; } @@ -95,15 +108,16 @@ void InputEvent::_bind_methods() { ClassDB::bind_method(D_METHOD("set_device", "device"), &InputEvent::set_device); ClassDB::bind_method(D_METHOD("get_device"), &InputEvent::get_device); - ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed); ClassDB::bind_method(D_METHOD("is_action", "action"), &InputEvent::is_action); ClassDB::bind_method(D_METHOD("is_action_pressed", "action"), &InputEvent::is_action_pressed); ClassDB::bind_method(D_METHOD("is_action_released", "action"), &InputEvent::is_action_released); + ClassDB::bind_method(D_METHOD("get_action_strength", "action"), &InputEvent::get_action_strength); + + ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed); ClassDB::bind_method(D_METHOD("is_echo"), &InputEvent::is_echo); ClassDB::bind_method(D_METHOD("as_text"), &InputEvent::as_text); - ClassDB::bind_method(D_METHOD("action_match", "event"), &InputEvent::action_match); ClassDB::bind_method(D_METHOD("shortcut_match", "event"), &InputEvent::shortcut_match); ClassDB::bind_method(D_METHOD("is_action_type"), &InputEvent::is_action_type); @@ -281,7 +295,7 @@ String InputEventKey::as_text() const { return kc; } -bool InputEventKey::action_match(const Ref<InputEvent> &p_event) const { +bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const { Ref<InputEventKey> key = p_event; if (key.is_null()) @@ -290,7 +304,14 @@ bool InputEventKey::action_match(const Ref<InputEvent> &p_event) const { uint32_t code = get_scancode_with_modifiers(); uint32_t event_code = key->get_scancode_with_modifiers(); - return get_scancode() == key->get_scancode() && (!key->is_pressed() || (code & event_code) == code); + bool match = get_scancode() == key->get_scancode() && (!key->is_pressed() || (code & event_code) == code); + if (match) { + if (p_pressed != NULL) + *p_pressed = key->is_pressed(); + if (p_strength != NULL) + *p_strength = (*p_pressed) ? 1.0f : 0.0f; + } + return match; } bool InputEventKey::shortcut_match(const Ref<InputEvent> &p_event) const { @@ -446,13 +467,21 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co return mb; } -bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event) const { +bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const { Ref<InputEventMouseButton> mb = p_event; if (mb.is_null()) return false; - return mb->button_index == button_index; + bool match = mb->button_index == button_index; + if (match) { + if (p_pressed != NULL) + *p_pressed = mb->is_pressed(); + if (p_strength != NULL) + *p_strength = (*p_pressed) ? 1.0f : 0.0f; + } + + return match; } String InputEventMouseButton::as_text() const { @@ -610,6 +639,7 @@ void InputEventJoypadMotion::set_axis_value(float p_value) { axis_value = p_value; } + float InputEventJoypadMotion::get_axis_value() const { return axis_value; @@ -617,16 +647,25 @@ float InputEventJoypadMotion::get_axis_value() const { bool InputEventJoypadMotion::is_pressed() const { - return Math::abs(axis_value) > 0.5f; + return Math::abs(axis_value) >= 0.5f; } -bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event) const { +bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const { Ref<InputEventJoypadMotion> jm = p_event; if (jm.is_null()) return false; - return (axis == jm->axis && ((axis_value < 0) == (jm->axis_value < 0) || jm->axis_value == 0)); + bool match = (axis == jm->axis); // Matches even if not in the same direction, but returns a "not pressed" event. + if (match) { + 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; + if (p_pressed != NULL) + *p_pressed = pressed; + if (p_strength != NULL) + *p_strength = pressed ? CLAMP(Math::inverse_lerp(p_deadzone, 1.0f, Math::abs(jm->get_axis_value())), 0.0f, 1.0f) : 0.0f; + } + return match; } String InputEventJoypadMotion::as_text() const { @@ -681,13 +720,21 @@ float InputEventJoypadButton::get_pressure() const { return pressure; } -bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event) const { +bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const { Ref<InputEventJoypadButton> jb = p_event; if (jb.is_null()) return false; - return button_index == jb->button_index; + bool match = button_index == jb->button_index; + if (match) { + if (p_pressed != NULL) + *p_pressed = jb->is_pressed(); + if (p_strength != NULL) + *p_strength = (*p_pressed) ? 1.0f : 0.0f; + } + + return match; } String InputEventJoypadButton::as_text() const { @@ -962,6 +1009,11 @@ Ref<InputEvent> InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform, return ev; } +String InputEventMagnifyGesture::as_text() const { + + return "InputEventMagnifyGesture : factor=" + rtos(get_factor()) + ", position=(" + String(get_position()) + ")"; +} + void InputEventMagnifyGesture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_factor", "factor"), &InputEventMagnifyGesture::set_factor); @@ -999,6 +1051,11 @@ Ref<InputEvent> InputEventPanGesture::xformed_by(const Transform2D &p_xform, con return ev; } +String InputEventPanGesture::as_text() const { + + return "InputEventPanGesture : delta=(" + String(get_delta()) + "), position=(" + String(get_position()) + ")"; +} + void InputEventPanGesture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_delta", "delta"), &InputEventPanGesture::set_delta); diff --git a/core/os/input_event.h b/core/os/input_event.h index ad754d0d1f..037649ed60 100644 --- a/core/os/input_event.h +++ b/core/os/input_event.h @@ -154,16 +154,21 @@ public: void set_device(int p_device); int get_device() const; + bool is_action(const StringName &p_action) const; + bool is_action_pressed(const StringName &p_action) const; + bool is_action_released(const StringName &p_action) const; + float get_action_strength(const StringName &p_action) const; + + // To be removed someday, since they do not make sense for all events virtual bool is_pressed() const; - virtual bool is_action(const StringName &p_action) const; - virtual bool is_action_pressed(const StringName &p_action) const; - virtual bool is_action_released(const StringName &p_action) const; virtual bool is_echo() const; + // ...-. + virtual String as_text() const; 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) const; + virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const; virtual bool shortcut_match(const Ref<InputEvent> &p_event) const; virtual bool is_action_type() const; @@ -244,7 +249,7 @@ public: uint32_t get_scancode_with_modifiers() const; - virtual bool action_match(const Ref<InputEvent> &p_event) const; + virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const; virtual bool shortcut_match(const Ref<InputEvent> &p_event) const; virtual bool is_action_type() const { return true; } @@ -305,7 +310,7 @@ public: bool is_doubleclick() const; 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) const; + virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const; virtual bool is_action_type() const { return true; } virtual String as_text() const; @@ -352,7 +357,8 @@ public: float get_axis_value() const; virtual bool is_pressed() const; - virtual bool action_match(const Ref<InputEvent> &p_event) const; + + virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const; virtual bool is_action_type() const { return true; } virtual String as_text() const; @@ -379,7 +385,7 @@ public: void set_pressure(float p_pressure); float get_pressure() const; - virtual bool action_match(const Ref<InputEvent> &p_event) const; + virtual bool action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float p_deadzone) const; virtual bool is_action_type() const { return true; } virtual String as_text() const; @@ -494,6 +500,7 @@ public: real_t get_factor() const; virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const; + virtual String as_text() const; InputEventMagnifyGesture(); }; @@ -511,6 +518,7 @@ public: Vector2 get_delta() const; virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const; + virtual String as_text() const; InputEventPanGesture(); }; diff --git a/core/os/keyboard.cpp b/core/os/keyboard.cpp index fa53cc85c8..9dfc91e308 100644 --- a/core/os/keyboard.cpp +++ b/core/os/keyboard.cpp @@ -461,99 +461,6 @@ const char *find_keycode_name(int p_keycode) { return ""; } -struct _KeyCodeReplace { - int from; - int to; -}; - -static const _KeyCodeReplace _keycode_replace_qwertz[] = { - { KEY_Y, KEY_Z }, - { KEY_Z, KEY_Y }, - { 0, 0 } -}; - -static const _KeyCodeReplace _keycode_replace_azerty[] = { - { KEY_W, KEY_Z }, - { KEY_Z, KEY_W }, - { KEY_A, KEY_Q }, - { KEY_Q, KEY_A }, - { KEY_SEMICOLON, KEY_M }, - { KEY_M, KEY_SEMICOLON }, - { 0, 0 } -}; - -static const _KeyCodeReplace _keycode_replace_qzerty[] = { - { KEY_W, KEY_Z }, - { KEY_Z, KEY_W }, - { KEY_SEMICOLON, KEY_M }, - { KEY_M, KEY_SEMICOLON }, - { 0, 0 } -}; - -static const _KeyCodeReplace _keycode_replace_dvorak[] = { - { KEY_UNDERSCORE, KEY_BRACELEFT }, - { KEY_EQUAL, KEY_BRACERIGHT }, - { KEY_Q, KEY_APOSTROPHE }, - { KEY_W, KEY_COMMA }, - { KEY_E, KEY_PERIOD }, - { KEY_R, KEY_P }, - { KEY_T, KEY_Y }, - { KEY_Y, KEY_F }, - { KEY_U, KEY_G }, - { KEY_I, KEY_C }, - { KEY_O, KEY_R }, - { KEY_P, KEY_L }, - { KEY_BRACELEFT, KEY_SLASH }, - { KEY_BRACERIGHT, KEY_EQUAL }, - { KEY_A, KEY_A }, - { KEY_S, KEY_O }, - { KEY_D, KEY_E }, - { KEY_F, KEY_U }, - { KEY_G, KEY_I }, - { KEY_H, KEY_D }, - { KEY_J, KEY_H }, - { KEY_K, KEY_T }, - { KEY_L, KEY_N }, - { KEY_SEMICOLON, KEY_S }, - { KEY_APOSTROPHE, KEY_UNDERSCORE }, - { KEY_Z, KEY_SEMICOLON }, - { KEY_X, KEY_Q }, - { KEY_C, KEY_J }, - { KEY_V, KEY_K }, - { KEY_B, KEY_X }, - { KEY_N, KEY_B }, - { KEY_M, KEY_M }, - { KEY_COMMA, KEY_W }, - { KEY_PERIOD, KEY_V }, - { KEY_SLASH, KEY_Z }, - { 0, 0 } -}; - -static const _KeyCodeReplace _keycode_replace_neo[] = { - { 0, 0 } -}; - -static const _KeyCodeReplace _keycode_replace_colemak[] = { - { KEY_E, KEY_F }, - { KEY_R, KEY_P }, - { KEY_T, KEY_G }, - { KEY_Y, KEY_J }, - { KEY_U, KEY_L }, - { KEY_I, KEY_U }, - { KEY_O, KEY_Y }, - { KEY_P, KEY_SEMICOLON }, - { KEY_S, KEY_R }, - { KEY_D, KEY_S }, - { KEY_F, KEY_T }, - { KEY_G, KEY_D }, - { KEY_J, KEY_N }, - { KEY_K, KEY_E }, - { KEY_L, KEY_I }, - { KEY_SEMICOLON, KEY_O }, - { KEY_N, KEY_K }, - { 0, 0 } -}; - int keycode_get_count() { const _KeyCodeText *kct = &_keycodes[0]; @@ -574,31 +481,3 @@ int keycode_get_value_by_index(int p_index) { const char *keycode_get_name_by_index(int p_index) { return _keycodes[p_index].text; } - -int latin_keyboard_keycode_convert(int p_keycode) { - - const _KeyCodeReplace *kcr = NULL; - switch (OS::get_singleton()->get_latin_keyboard_variant()) { - - case OS::LATIN_KEYBOARD_QWERTY: return p_keycode; break; - case OS::LATIN_KEYBOARD_QWERTZ: kcr = _keycode_replace_qwertz; break; - case OS::LATIN_KEYBOARD_AZERTY: kcr = _keycode_replace_azerty; break; - case OS::LATIN_KEYBOARD_QZERTY: kcr = _keycode_replace_qzerty; break; - case OS::LATIN_KEYBOARD_DVORAK: kcr = _keycode_replace_dvorak; break; - case OS::LATIN_KEYBOARD_NEO: kcr = _keycode_replace_neo; break; - case OS::LATIN_KEYBOARD_COLEMAK: kcr = _keycode_replace_colemak; break; - default: return p_keycode; - } - - if (!kcr) { - return p_keycode; - } - - while (kcr->from) { - if (kcr->from == p_keycode) - return kcr->to; - kcr++; - } - - return p_keycode; -} diff --git a/core/os/keyboard.h b/core/os/keyboard.h index 4c253fa4ce..a0e6f8b2ef 100644 --- a/core/os/keyboard.h +++ b/core/os/keyboard.h @@ -331,6 +331,5 @@ const char *find_keycode_name(int p_keycode); int keycode_get_count(); int keycode_get_value_by_index(int p_index); const char *keycode_get_name_by_index(int p_index); -int latin_keyboard_keycode_convert(int p_keycode); #endif diff --git a/core/os/os.cpp b/core/os/os.cpp index 422acf95dc..5eed10e30c 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -34,6 +34,7 @@ #include "input.h" #include "os/file_access.h" #include "project_settings.h" +#include "servers/audio_server.h" #include "version_generated.gen.h" #include <stdarg.h> @@ -410,7 +411,7 @@ Error OS::set_cwd(const String &p_cwd) { bool OS::has_touchscreen_ui_hint() const { //return false; - return Input::get_singleton() && Input::get_singleton()->is_emulating_touchscreen(); + return Input::get_singleton() && Input::get_singleton()->is_emulating_touch_from_mouse(); } int OS::get_free_static_memory() const { @@ -627,6 +628,34 @@ void OS::center_window() { set_window_position(Vector2(x, y)); } +int OS::get_video_driver_count() const { + + return 2; +} + +const char *OS::get_video_driver_name(int p_driver) const { + + switch (p_driver) { + case VIDEO_DRIVER_GLES2: + return "GLES2"; + case VIDEO_DRIVER_GLES3: + default: + return "GLES3"; + } +} + +int OS::get_audio_driver_count() const { + + return AudioDriverManager::get_driver_count(); +} + +const char *OS::get_audio_driver_name(int p_driver) const { + + AudioDriver *driver = AudioDriverManager::get_driver(p_driver); + ERR_FAIL_COND_V(!driver, ""); + return AudioDriverManager::get_driver(p_driver)->get_name(); +} + OS::OS() { void *volatile stack_bottom; @@ -643,6 +672,7 @@ OS::OS() { _render_thread_mode = RENDER_THREAD_SAFE; _allow_hidpi = false; + _allow_layered = false; _stack_bottom = (void *)(&stack_bottom); _logger = NULL; diff --git a/core/os/os.h b/core/os/os.h index 38e55fa3b7..adf01a90e7 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -44,6 +44,12 @@ @author Juan Linietsky <reduzio@gmail.com> */ +enum VideoDriver { + VIDEO_DRIVER_GLES3, + VIDEO_DRIVER_GLES2, + VIDEO_DRIVER_MAX, +}; + class OS { static OS *singleton; @@ -59,6 +65,7 @@ class OS { int _exit_code; int _orientation; bool _allow_hidpi; + bool _allow_layered; bool _use_vsync; char *last_error; @@ -96,6 +103,8 @@ public: bool maximized; bool always_on_top; bool use_vsync; + bool layered_splash; + bool layered; float get_aspect() const { return (float)width / (float)height; } VideoMode(int p_width = 1024, int p_height = 600, bool p_fullscreen = false, bool p_resizable = true, bool p_borderless_window = false, bool p_maximized = false, bool p_always_on_top = false, bool p_use_vsync = false) { width = p_width; @@ -106,6 +115,8 @@ public: maximized = p_maximized; always_on_top = p_always_on_top; use_vsync = p_use_vsync; + layered = false; + layered_splash = false; } }; @@ -115,12 +126,6 @@ protected: RenderThreadMode _render_thread_mode; // functions used by main to initialize/deintialize the OS - virtual int get_video_driver_count() const = 0; - virtual const char *get_video_driver_name(int p_driver) const = 0; - - virtual int get_audio_driver_count() const = 0; - virtual const char *get_audio_driver_name(int p_driver) const = 0; - void add_logger(Logger *p_logger); virtual void initialize_core() = 0; @@ -175,6 +180,12 @@ public: virtual VideoMode get_video_mode(int p_screen = 0) const = 0; virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const = 0; + virtual int get_video_driver_count() const; + virtual const char *get_video_driver_name(int p_driver) const; + + virtual int get_audio_driver_count() const; + virtual const char *get_audio_driver_name(int p_driver) const; + virtual int get_screen_count() const { return 1; } virtual int get_current_screen() const { return 0; } virtual void set_current_screen(int p_screen) {} @@ -199,9 +210,29 @@ public: virtual void request_attention() {} virtual void center_window(); + // Returns window area free of hardware controls and other obstacles. + // The application should use this to determine where to place UI elements. + // + // Keep in mind the area returned is in window coordinates rather than + // viewport coordinates - you should perform the conversion on your own. + // + // The maximum size of the area is Rect2(0, 0, window_size.width, window_size.height). + virtual Rect2 get_window_safe_area() const { + Size2 window_size = get_window_size(); + return Rect2(0, 0, window_size.width, window_size.height); + } + virtual void set_borderless_window(bool p_borderless) {} virtual bool get_borderless_window() { return 0; } + virtual bool get_window_per_pixel_transparency_enabled() const { return false; } + virtual void set_window_per_pixel_transparency_enabled(bool p_enabled) {} + + virtual uint8_t *get_layered_buffer_data() { return NULL; } + virtual Size2 get_layered_buffer_size() { return Size2(0, 0); } + virtual void swap_layered_buffer() {} + + virtual void set_ime_active(const bool p_active) {} virtual void set_ime_position(const Point2 &p_pos) {} virtual void set_ime_intermediate_text_callback(ImeCallback p_callback, void *p_inp) {} @@ -302,6 +333,7 @@ public: virtual void disable_crash_handler() {} virtual bool is_disable_crash_handler() const { return false; } + virtual void initialize_debugging() {} enum CursorShape { CURSOR_ARROW, @@ -462,6 +494,7 @@ public: virtual void force_process_input(){}; bool has_feature(const String &p_feature); + bool is_layered_allowed() const { return _allow_layered; } bool is_hidpi_allowed() const { return _allow_hidpi; } OS(); virtual ~OS(); diff --git a/core/os/thread_dummy.cpp b/core/os/thread_dummy.cpp index fa0bb3dafd..b6371235c4 100644 --- a/core/os/thread_dummy.cpp +++ b/core/os/thread_dummy.cpp @@ -55,3 +55,11 @@ Semaphore *SemaphoreDummy::create() { void SemaphoreDummy::make_default() { Semaphore::create_func = &SemaphoreDummy::create; }; + +RWLock *RWLockDummy::create() { + return memnew(RWLockDummy); +}; + +void RWLockDummy::make_default() { + RWLock::create_func = &RWLockDummy::create; +}; diff --git a/core/os/thread_dummy.h b/core/os/thread_dummy.h index b67b52a726..74957b95fe 100644 --- a/core/os/thread_dummy.h +++ b/core/os/thread_dummy.h @@ -32,6 +32,7 @@ #define THREAD_DUMMY_H #include "mutex.h" +#include "rw_lock.h" #include "semaphore.h" #include "thread.h" @@ -69,4 +70,20 @@ public: static void make_default(); }; +class RWLockDummy : public RWLock { + + static RWLock *create(); + +public: + virtual void read_lock() {} + virtual void read_unlock() {} + virtual Error read_try_lock() { return OK; } + + virtual void write_lock() {} + virtual void write_unlock() {} + virtual Error write_try_lock() { return OK; } + + static void make_default(); +}; + #endif diff --git a/core/pool_allocator.cpp b/core/pool_allocator.cpp index 017586b92a..8952314212 100644 --- a/core/pool_allocator.cpp +++ b/core/pool_allocator.cpp @@ -359,7 +359,7 @@ Error PoolAllocator::resize(ID p_mem, int p_new_size) { //p_new_size = align(p_new_size) int _free = free_mem; // - static_area_size; - if ((_free + aligned(e->len)) - alloc_size < 0) { + if (uint32_t(_free + aligned(e->len)) < alloc_size) { mt_unlock(); ERR_FAIL_V(ERR_OUT_OF_MEMORY); }; diff --git a/core/project_settings.cpp b/core/project_settings.cpp index 427fa77e62..a7bfc8895b 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -42,7 +42,7 @@ #include "variant_parser.h" #include <zlib.h> -#define FORMAT_VERSION 3 +#define FORMAT_VERSION 4 ProjectSettings *ProjectSettings::singleton = NULL; @@ -137,7 +137,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { else { if (p_name == CoreStringNames::get_singleton()->_custom_features) { - Vector<String> custom_feature_array = p_value; + Vector<String> custom_feature_array = String(p_value).split(","); for (int i = 0; i < custom_feature_array.size(); i++) { custom_features.insert(custom_feature_array[i]); @@ -262,6 +262,23 @@ bool ProjectSettings::_load_resource_pack(const String &p_pack) { return true; } +void ProjectSettings::_convert_to_last_version() { + if (!has_setting("config_version") || (int)get_setting("config_version") <= 3) { + + // Converts the actions from array to dictionary (array of events to dictionary with deadzone + events) + for (Map<StringName, ProjectSettings::VariantContainer>::Element *E = props.front(); E; E = E->next()) { + Variant value = E->get().variant; + if (String(E->key()).begins_with("input/") && value.get_type() == Variant::ARRAY) { + Array array = value; + Dictionary action; + action["deadzone"] = Variant(0.5f); + action["events"] = array; + E->get().variant = action; + } + } + } +} + Error ProjectSettings::setup(const String &p_path, const String &p_main_pack, bool p_upwards) { //If looking for files in network, just use network! @@ -390,6 +407,8 @@ Error ProjectSettings::setup(const String &p_path, const String &p_main_pack, bo if (resource_path.length() && resource_path[resource_path.length() - 1] == '/') resource_path = resource_path.substr(0, resource_path.length() - 1); // chop end + _convert_to_last_version(); + return OK; } @@ -692,7 +711,10 @@ Error ProjectSettings::_save_settings_text(const String &p_file, const Map<Strin String vstr; VariantWriter::write_to_string(value, vstr); - file->store_string(F->get() + "=" + vstr + "\n"); + if (F->get().find(" ") != -1) + file->store_string(F->get().quote() + "=" + vstr + "\n"); + else + file->store_string(F->get() + "=" + vstr + "\n"); } } @@ -794,12 +816,11 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default) { Variant ret; - if (ProjectSettings::get_singleton()->has_setting(p_var)) { - ret = ProjectSettings::get_singleton()->get(p_var); - } else { + if (!ProjectSettings::get_singleton()->has_setting(p_var)) { ProjectSettings::get_singleton()->set(p_var, p_default); - ret = p_default; } + ret = ProjectSettings::get_singleton()->get(p_var); + ProjectSettings::get_singleton()->set_initial_value(p_var, p_default); ProjectSettings::get_singleton()->set_builtin_order(p_var); return ret; @@ -1027,6 +1048,20 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("input/ui_page_down", va); input_presets.push_back("input/ui_page_down"); + va = Array(); + key.instance(); + key->set_scancode(KEY_HOME); + va.push_back(key); + GLOBAL_DEF("input/ui_home", va); + input_presets.push_back("input/ui_home"); + + va = Array(); + key.instance(); + key->set_scancode(KEY_END); + va.push_back(key); + GLOBAL_DEF("input/ui_end", va); + input_presets.push_back("input/ui_end"); + //GLOBAL_DEF("display/window/handheld/orientation", "landscape"); custom_prop_info["display/window/handheld/orientation"] = PropertyInfo(Variant::STRING, "display/window/handheld/orientation", PROPERTY_HINT_ENUM, "landscape,portrait,reverse_landscape,reverse_portrait,sensor_landscape,sensor_portrait,sensor"); diff --git a/core/project_settings.h b/core/project_settings.h index 9b51bc3ac3..b01e7855aa 100644 --- a/core/project_settings.h +++ b/core/project_settings.h @@ -102,6 +102,8 @@ protected: Error _save_custom_bnd(const String &p_file); + void _convert_to_last_version(); + bool _load_resource_pack(const String &p_pack); void _add_property_info_bind(const Dictionary &p_info); diff --git a/core/reference.h b/core/reference.h index a0bdb62258..0d6b1ced6e 100644 --- a/core/reference.h +++ b/core/reference.h @@ -63,7 +63,7 @@ public: template <class T> class Ref { - T *reference = NULL; + T *reference; void ref(const Ref &p_from) { @@ -213,10 +213,9 @@ public: Ref(T *p_reference) { + reference = NULL; if (p_reference) ref_pointer(p_reference); - else - reference = NULL; } Ref(const Variant &p_variant) { diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 006459c5f6..2a611ccf6a 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -42,6 +42,7 @@ #include "io/config_file.h" #include "io/http_client.h" #include "io/marshalls.h" +#include "io/multiplayer_api.h" #include "io/networked_multiplayer_peer.h" #include "io/packet_peer.h" #include "io/packet_peer_udp.h" @@ -145,6 +146,7 @@ void register_core_types() { ClassDB::register_virtual_class<PacketPeer>(); ClassDB::register_class<PacketPeerStream>(); ClassDB::register_virtual_class<NetworkedMultiplayerPeer>(); + ClassDB::register_class<MultiplayerAPI>(); ClassDB::register_class<MainLoop>(); //ClassDB::register_type<OptimizedSaver>(); ClassDB::register_class<Translation>(); diff --git a/core/resource.cpp b/core/resource.cpp index 2eeed50d9d..87ff4d3c2a 100644 --- a/core/resource.cpp +++ b/core/resource.cpp @@ -187,7 +187,6 @@ Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, Map<Ref<Res void Resource::configure_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource> > &remap_cache) { - print_line("configure for local: " + get_class()); List<PropertyInfo> plist; get_property_list(&plist); @@ -227,14 +226,19 @@ Ref<Resource> Resource::duplicate(bool p_subresources) const { if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) continue; Variant p = get(E->get().name); - if (p.get_type() == Variant::OBJECT && p_subresources) { + + if ((p.get_type() == Variant::DICTIONARY || p.get_type() == Variant::ARRAY)) { + p = p.duplicate(p_subresources); //does not make a long of sense but should work? + } else if (p.get_type() == Variant::OBJECT && (p_subresources || (E->get().usage & PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE))) { RES sr = p; - if (sr.is_valid()) - p = sr->duplicate(true); - } + if (sr.is_valid()) { + r->set(E->get().name, sr->duplicate(p_subresources)); + } + } else { - r->set(E->get().name, p); + r->set(E->get().name, p); + } } return Ref<Resource>(r); @@ -288,7 +292,7 @@ uint32_t Resource::hash_edited_version() const { for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { - if (E->get().type == Variant::OBJECT && E->get().hint == PROPERTY_HINT_RESOURCE_TYPE) { + if (E->get().usage & PROPERTY_USAGE_STORAGE && E->get().type == Variant::OBJECT && E->get().hint == PROPERTY_HINT_RESOURCE_TYPE) { RES res = get(E->get().name); if (res.is_valid()) { hash = hash_djb2_one_32(res->hash_edited_version(), hash); diff --git a/core/script_debugger_local.cpp b/core/script_debugger_local.cpp index c0e115e300..55d7270473 100644 --- a/core/script_debugger_local.cpp +++ b/core/script_debugger_local.cpp @@ -29,12 +29,23 @@ /*************************************************************************/ #include "script_debugger_local.h" +#include "scene/main/scene_tree.h" #include "os/os.h" void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue) { - print_line("Debugger Break, Reason: '" + p_script->debug_get_error() + "'"); + if (!target_function.empty()) { + String current_function = p_script->debug_get_stack_level_function(0); + if (current_function != target_function) { + set_depth(0); + set_lines_left(1); + return; + } + target_function = ""; + } + + print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'"); print_line("*Frame " + itos(0) + " - " + p_script->debug_get_stack_level_source(0) + ":" + itos(p_script->debug_get_stack_level_line(0)) + " in function '" + p_script->debug_get_stack_level_function(0) + "'"); print_line("Enter \"help\" for assistance."); int current_frame = 0; @@ -44,8 +55,11 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue) { OS::get_singleton()->print("debug> "); String line = OS::get_singleton()->get_stdin_string().strip_edges(); + // Cache options + String variable_prefix = options["variable_prefix"]; + if (line == "") { - print_line("Debugger Break, Reason: '" + p_script->debug_get_error() + "'"); + print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'"); print_line("*Frame " + itos(current_frame) + " - " + p_script->debug_get_stack_level_source(current_frame) + ":" + itos(p_script->debug_get_stack_level_line(current_frame)) + " in function '" + p_script->debug_get_stack_level_function(current_frame) + "'"); print_line("Enter \"help\" for assistance."); } else if (line == "c" || line == "continue") @@ -72,38 +86,56 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue) { } } + } else if (line.begins_with("set")) { + + if (line.get_slice_count(" ") == 1) { + + for (Map<String, String>::Element *E = options.front(); E; E = E->next()) { + print_line("\t" + E->key() + "=" + E->value()); + } + + } else { + String key_value = line.get_slicec(' ', 1); + int value_pos = key_value.find("="); + + if (value_pos < 0) { + print_line("Error: Invalid set format. Use: set key=value"); + } else { + + String key = key_value.left(value_pos); + + if (!options.has(key)) { + print_line("Error: Unknown option " + key); + } else { + + // Allow explicit tab character + String value = key_value.right(value_pos + 1).replace("\\t", "\t"); + + options[key] = value; + } + } + } + } else if (line == "lv" || line == "locals") { List<String> locals; List<Variant> values; p_script->debug_get_stack_level_locals(current_frame, &locals, &values); - List<Variant>::Element *V = values.front(); - for (List<String>::Element *E = locals.front(); E; E = E->next()) { - print_line(E->get() + ": " + String(V->get())); - V = V->next(); - } + print_variables(locals, values, variable_prefix); } else if (line == "gv" || line == "globals") { - List<String> locals; + List<String> globals; List<Variant> values; - p_script->debug_get_globals(&locals, &values); - List<Variant>::Element *V = values.front(); - for (List<String>::Element *E = locals.front(); E; E = E->next()) { - print_line(E->get() + ": " + String(V->get())); - V = V->next(); - } + p_script->debug_get_globals(&globals, &values); + print_variables(globals, values, variable_prefix); } else if (line == "mv" || line == "members") { - List<String> locals; + List<String> members; List<Variant> values; - p_script->debug_get_stack_level_members(current_frame, &locals, &values); - List<Variant>::Element *V = values.front(); - for (List<String>::Element *E = locals.front(); E; E = E->next()) { - print_line(E->get() + ": " + String(V->get())); - V = V->next(); - } + p_script->debug_get_stack_level_members(current_frame, &members, &values); + print_variables(members, values, variable_prefix); } else if (line.begins_with("p") || line.begins_with("print")) { @@ -121,65 +153,149 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue) { set_depth(-1); set_lines_left(1); break; - } else if (line.begins_with("n") || line.begins_with("next")) { + } else if (line == "n" || line == "next") { set_depth(0); set_lines_left(1); break; + } else if (line == "fin" || line == "finish") { + + String current_function = p_script->debug_get_stack_level_function(0); + + for (int i = 0; i < total_frames; i++) { + target_function = p_script->debug_get_stack_level_function(i); + if (target_function != current_function) { + set_depth(0); + set_lines_left(1); + return; + } + } + + print_line("Error: Reached last frame."); + target_function = ""; + } else if (line.begins_with("br") || line.begins_with("break")) { if (line.get_slice_count(" ") <= 1) { - //show breakpoints + + const Map<int, Set<StringName> > &breakpoints = get_breakpoints(); + if (breakpoints.size() == 0) { + print_line("No Breakpoints."); + continue; + } + + print_line("Breakpoint(s): " + itos(breakpoints.size())); + for (Map<int, Set<StringName> >::Element *E = breakpoints.front(); E; E = E->next()) { + print_line("\t" + String(E->value().front()->get()) + ":" + itos(E->key())); + } + } else { - String bppos = line.get_slicec(' ', 1); - String source = bppos.get_slicec(':', 0).strip_edges(); - int line = bppos.get_slicec(':', 1).strip_edges().to_int(); + Pair<String, int> breakpoint = to_breakpoint(line); + + String source = breakpoint.first; + int linenr = breakpoint.second; - source = breakpoint_find_source(source); + if (source.empty()) + continue; - insert_breakpoint(line, source); + insert_breakpoint(linenr, source); - print_line("BreakPoint at " + source + ":" + itos(line)); + print_line("Added breakpoint at " + source + ":" + itos(linenr)); } + } else if (line == "q" || line == "quit") { + + // Do not stop again on quit + clear_breakpoints(); + ScriptDebugger::get_singleton()->set_depth(-1); + ScriptDebugger::get_singleton()->set_lines_left(-1); + + SceneTree::get_singleton()->quit(); + break; } else if (line.begins_with("delete")) { if (line.get_slice_count(" ") <= 1) { clear_breakpoints(); } else { - String bppos = line.get_slicec(' ', 1); - String source = bppos.get_slicec(':', 0).strip_edges(); - int line = bppos.get_slicec(':', 1).strip_edges().to_int(); + Pair<String, int> breakpoint = to_breakpoint(line); + + String source = breakpoint.first; + int linenr = breakpoint.second; - source = breakpoint_find_source(source); + if (source.empty()) + continue; - remove_breakpoint(line, source); + remove_breakpoint(linenr, source); - print_line("Removed BreakPoint at " + source + ":" + itos(line)); + print_line("Removed breakpoint at " + source + ":" + itos(linenr)); } } else if (line == "h" || line == "help") { print_line("Built-In Debugger command list:\n"); - print_line("\tc,continue :\t\t Continue execution."); - print_line("\tbt,backtrace :\t\t Show stack trace (frames)."); + print_line("\tc,continue\t\t Continue execution."); + print_line("\tbt,backtrace\t\t Show stack trace (frames)."); print_line("\tfr,frame <frame>:\t Change current frame."); - print_line("\tlv,locals :\t\t Show local variables for current frame."); - print_line("\tmv,members :\t\t Show member variables for \"this\" in frame."); - print_line("\tgv,globals :\t\t Show global variables."); - print_line("\tp,print <expr> :\t Execute and print variable in expression."); - print_line("\ts,step :\t\t Step to next line."); - print_line("\tn,next :\t\t Next line."); - print_line("\tbr,break source:line :\t Place a breakpoint."); - print_line("\tdelete [source:line]:\t\t Delete one/all breakpoints."); + print_line("\tlv,locals\t\t Show local variables for current frame."); + print_line("\tmv,members\t\t Show member variables for \"this\" in frame."); + print_line("\tgv,globals\t\t Show global variables."); + print_line("\tp,print <expr>\t\t Execute and print variable in expression."); + print_line("\ts,step\t\t\t Step to next line."); + print_line("\tn,next\t\t\t Next line."); + print_line("\tfin,finish\t\t Step out of current frame."); + print_line("\tbr,break [source:line]\t List all breakpoints or place a breakpoint."); + print_line("\tdelete [source:line]:\t Delete one/all breakpoints."); + print_line("\tset [key=value]:\t List all options, or set one."); + print_line("\tq,quit\t\t\t Quit application."); } else { print_line("Error: Invalid command, enter \"help\" for assistance."); } } } +void ScriptDebuggerLocal::print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix) { + + String value; + Vector<String> value_lines; + const List<Variant>::Element *V = values.front(); + for (const List<String>::Element *E = names.front(); E; E = E->next()) { + + value = String(V->get()); + + if (variable_prefix.empty()) { + print_line(E->get() + ": " + String(V->get())); + } else { + + print_line(E->get() + ":"); + value_lines = value.split("\n"); + for (int i = 0; i < value_lines.size(); ++i) { + print_line(variable_prefix + value_lines[i]); + } + } + + V = V->next(); + } +} + +Pair<String, int> ScriptDebuggerLocal::to_breakpoint(const String &p_line) { + + String breakpoint_part = p_line.get_slicec(' ', 1); + Pair<String, int> breakpoint; + + int last_colon = breakpoint_part.rfind(":"); + if (last_colon < 0) { + print_line("Error: Invalid breakpoint format. Expected [source:line]"); + return breakpoint; + } + + breakpoint.first = breakpoint_find_source(breakpoint_part.left(last_colon).strip_edges()); + breakpoint.second = breakpoint_part.right(last_colon).strip_edges().to_int(); + + return breakpoint; +} + struct _ScriptDebuggerLocalProfileInfoSort { bool operator()(const ScriptLanguage::ProfilingInfo &A, const ScriptLanguage::ProfilingInfo &B) const { @@ -304,4 +420,5 @@ ScriptDebuggerLocal::ScriptDebuggerLocal() { profiling = false; idle_accum = OS::get_singleton()->get_ticks_usec(); + options["variable_prefix"] = ""; } diff --git a/core/script_debugger_local.h b/core/script_debugger_local.h index c87bc90bb4..7eea6ef215 100644 --- a/core/script_debugger_local.h +++ b/core/script_debugger_local.h @@ -31,6 +31,7 @@ #ifndef SCRIPT_DEBUGGER_LOCAL_H #define SCRIPT_DEBUGGER_LOCAL_H +#include "list.h" #include "script_language.h" class ScriptDebuggerLocal : public ScriptDebugger { @@ -38,9 +39,14 @@ class ScriptDebuggerLocal : public ScriptDebugger { bool profiling; float frame_time, idle_time, physics_time, physics_frame_time; uint64_t idle_accum; + String target_function; + Map<String, String> options; Vector<ScriptLanguage::ProfilingInfo> pinfo; + Pair<String, int> to_breakpoint(const String &p_line); + void print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix); + public: void debug(ScriptLanguage *p_script, bool p_can_continue); virtual void send_message(const String &p_message, const Array &p_args); diff --git a/core/script_debugger_remote.cpp b/core/script_debugger_remote.cpp index 632285f48d..0473e2cc71 100644 --- a/core/script_debugger_remote.cpp +++ b/core/script_debugger_remote.cpp @@ -37,6 +37,7 @@ #include "os/os.h" #include "project_settings.h" #include "scene/main/node.h" +#include "scene/resources/packed_scene.h" void ScriptDebuggerRemote::_send_video_memory() { @@ -148,6 +149,16 @@ void ScriptDebuggerRemote::_put_variable(const String &p_name, const Variant &p_ } } +void ScriptDebuggerRemote::_save_node(ObjectID id, const String &p_path) { + + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(id)); + ERR_FAIL_COND(!node); + + Ref<PackedScene> ps = memnew(PackedScene); + ps->pack(node); + ResourceSaver::save(p_path, ps); +} + void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue) { //this function is called when there is a debugger break (bug on script) @@ -322,6 +333,8 @@ void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue) else remove_breakpoint(cmd[2], cmd[1]); + } else if (command == "save_node") { + _save_node(cmd[1], cmd[2]); } else { _parse_live_edit(cmd); } @@ -554,22 +567,46 @@ void ScriptDebuggerRemote::_send_object_id(ObjectID p_id) { if (ScriptInstance *si = obj->get_script_instance()) { if (!si->get_script().is_null()) { - Set<StringName> members; - si->get_script()->get_members(&members); - for (Set<StringName>::Element *E = members.front(); E; E = E->next()) { + typedef Map<const Script *, Set<StringName> > ScriptMemberMap; + typedef Map<const Script *, Map<StringName, Variant> > ScriptConstantsMap; + + ScriptMemberMap members; + members[si->get_script().ptr()] = Set<StringName>(); + si->get_script()->get_members(&(members[si->get_script().ptr()])); + + ScriptConstantsMap constants; + constants[si->get_script().ptr()] = Map<StringName, Variant>(); + si->get_script()->get_constants(&(constants[si->get_script().ptr()])); + + Ref<Script> base = si->get_script()->get_base_script(); + while (base.is_valid()) { + + members[base.ptr()] = Set<StringName>(); + base->get_members(&(members[base.ptr()])); - Variant m; - if (si->get(E->get(), m)) { - PropertyInfo pi(m.get_type(), String("Members/") + E->get()); - properties.push_back(PropertyDesc(pi, m)); + constants[base.ptr()] = Map<StringName, Variant>(); + base->get_constants(&(constants[base.ptr()])); + + base = base->get_base_script(); + } + + for (ScriptMemberMap::Element *sm = members.front(); sm; sm = sm->next()) { + for (Set<StringName>::Element *E = sm->get().front(); E; E = E->next()) { + Variant m; + if (si->get(E->get(), m)) { + String script_path = sm->key() == si->get_script().ptr() ? "" : sm->key()->get_path().get_file() + "/"; + PropertyInfo pi(m.get_type(), "Members/" + script_path + E->get()); + properties.push_back(PropertyDesc(pi, m)); + } } } - Map<StringName, Variant> constants; - si->get_script()->get_constants(&constants); - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - PropertyInfo pi(E->value().get_type(), (String("Constants/") + E->key())); - properties.push_back(PropertyDesc(pi, E->value())); + for (ScriptConstantsMap::Element *sc = constants.front(); sc; sc = sc->next()) { + for (Map<StringName, Variant>::Element *E = sc->get().front(); E; E = E->next()) { + String script_path = sc->key() == si->get_script().ptr() ? "" : sc->key()->get_path().get_file() + "/"; + PropertyInfo pi(E->value().get_type(), "Constants/" + script_path + E->key()); + properties.push_back(PropertyDesc(pi, E->value())); + } } } } @@ -645,8 +682,10 @@ void ScriptDebuggerRemote::_set_object_property(ObjectID p_id, const String &p_p return; String prop_name = p_property; - if (p_property.begins_with("Members/")) - prop_name = p_property.substr(8, p_property.length()); + if (p_property.begins_with("Members/")) { + Vector<String> ss = p_property.split("/"); + prop_name = ss[ss.size() - 1]; + } obj->set(prop_name, p_value); } diff --git a/core/script_debugger_remote.h b/core/script_debugger_remote.h index 2c4e29f172..cc12d978d6 100644 --- a/core/script_debugger_remote.h +++ b/core/script_debugger_remote.h @@ -133,6 +133,8 @@ class ScriptDebuggerRemote : public ScriptDebugger { void _put_variable(const String &p_name, const Variant &p_variable); + void _save_node(ObjectID id, const String &p_path); + public: struct ResourceUsage { diff --git a/core/script_language.cpp b/core/script_language.cpp index 1dab58e29e..acbe3b34db 100644 --- a/core/script_language.cpp +++ b/core/script_language.cpp @@ -347,6 +347,20 @@ Variant::Type PlaceHolderScriptInstance::get_property_type(const StringName &p_n return Variant::NIL; } +void PlaceHolderScriptInstance::get_method_list(List<MethodInfo> *p_list) const { + + if (script.is_valid()) { + script->get_script_method_list(p_list); + } +} +bool PlaceHolderScriptInstance::has_method(const StringName &p_method) const { + + if (script.is_valid()) { + return script->has_method(p_method); + } + return false; +} + void PlaceHolderScriptInstance::update(const List<PropertyInfo> &p_properties, const Map<StringName, Variant> &p_values) { Set<StringName> new_values; diff --git a/core/script_language.h b/core/script_language.h index d1da0a3b72..e7748f93e2 100644 --- a/core/script_language.h +++ b/core/script_language.h @@ -31,6 +31,7 @@ #ifndef SCRIPT_LANGUAGE_H #define SCRIPT_LANGUAGE_H +#include "io/multiplayer_api.h" #include "map.h" #include "pair.h" #include "resource.h" @@ -157,16 +158,8 @@ public: virtual bool is_placeholder() const { return false; } - enum RPCMode { - RPC_MODE_DISABLED, - RPC_MODE_REMOTE, - RPC_MODE_SYNC, - RPC_MODE_MASTER, - RPC_MODE_SLAVE, - }; - - virtual RPCMode get_rpc_mode(const StringName &p_method) const = 0; - virtual RPCMode get_rset_mode(const StringName &p_variable) const = 0; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const = 0; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const = 0; virtual ScriptLanguage *get_language() = 0; virtual ~ScriptInstance(); @@ -203,6 +196,7 @@ public: virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {} virtual bool is_using_templates() { return false; } virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = NULL) const = 0; + virtual String validate_path(const String &p_path) const { return ""; } virtual Script *create_script() const = 0; virtual bool has_named_classes() const = 0; virtual bool supports_builtin_mode() const = 0; @@ -220,7 +214,9 @@ public: RESULT_CLASS, RESULT_CLASS_CONSTANT, RESULT_CLASS_PROPERTY, - RESULT_CLASS_METHOD + RESULT_CLASS_METHOD, + RESULT_CLASS_ENUM, + RESULT_CLASS_TBD_GLOBALSCOPE }; Type type; Ref<Script> script; @@ -233,6 +229,8 @@ public: virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const = 0; virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) = 0; + virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) {} + virtual void remove_named_global_constant(const StringName &p_name) {} /* MULTITHREAD FUNCTIONS */ @@ -306,8 +304,8 @@ public: virtual void get_property_list(List<PropertyInfo> *p_properties) const; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = NULL) const; - virtual void get_method_list(List<MethodInfo> *p_list) const {} - virtual bool has_method(const StringName &p_method) const { return false; } + virtual void get_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName &p_method) const; virtual Variant call(const StringName &p_method, VARIANT_ARG_LIST) { return Variant(); } virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; @@ -327,8 +325,8 @@ public: virtual bool is_placeholder() const { return true; } - virtual RPCMode get_rpc_mode(const StringName &p_method) const { return RPC_MODE_DISABLED; } - virtual RPCMode get_rset_mode(const StringName &p_variable) const { return RPC_MODE_DISABLED; } + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const { return MultiplayerAPI::RPC_MODE_DISABLED; } + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const { return MultiplayerAPI::RPC_MODE_DISABLED; } PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner); ~PlaceHolderScriptInstance(); @@ -383,6 +381,7 @@ public: bool is_breakpoint(int p_line, const StringName &p_source) const; bool is_breakpoint_line(int p_line) const; void clear_breakpoints(); + const Map<int, Set<StringName> > &get_breakpoints() const { return breakpoints; } virtual void debug(ScriptLanguage *p_script, bool p_can_continue = true) = 0; virtual void idle_poll(); diff --git a/core/self_list.h b/core/self_list.h index e83afb66ef..6e84e1cd5f 100644 --- a/core/self_list.h +++ b/core/self_list.h @@ -39,6 +39,7 @@ public: class List { SelfList<T> *_first; + SelfList<T> *_last; public: void add(SelfList<T> *p_elem) { @@ -48,47 +49,54 @@ public: p_elem->_root = this; p_elem->_next = _first; p_elem->_prev = NULL; - if (_first) + + if (_first) { _first->_prev = p_elem; + + } else { + _last = p_elem; + } + _first = p_elem; } + void add_last(SelfList<T> *p_elem) { ERR_FAIL_COND(p_elem->_root); - if (!_first) { - add(p_elem); - return; - } + p_elem->_root = this; + p_elem->_next = NULL; + p_elem->_prev = _last; - SelfList<T> *e = _first; + if (_last) { + _last->_next = p_elem; - while (e->next()) { - e = e->next(); + } else { + _first = p_elem; } - e->_next = p_elem; - p_elem->_prev = e->_next; - p_elem->_root = this; + _last = p_elem; } void remove(SelfList<T> *p_elem) { ERR_FAIL_COND(p_elem->_root != this); if (p_elem->_next) { - p_elem->_next->_prev = p_elem->_prev; } - if (p_elem->_prev) { + if (p_elem->_prev) { p_elem->_prev->_next = p_elem->_next; } if (_first == p_elem) { - _first = p_elem->_next; } + if (_last == p_elem) { + _last = p_elem->_prev; + } + p_elem->_next = NULL; p_elem->_prev = NULL; p_elem->_root = NULL; @@ -96,7 +104,10 @@ public: _FORCE_INLINE_ SelfList<T> *first() { return _first; } _FORCE_INLINE_ const SelfList<T> *first() const { return _first; } - _FORCE_INLINE_ List() { _first = NULL; } + _FORCE_INLINE_ List() { + _first = NULL; + _last = NULL; + } _FORCE_INLINE_ ~List() { ERR_FAIL_COND(_first != NULL); } }; diff --git a/core/string_buffer.h b/core/string_buffer.h index b148e45544..7e9b151bea 100644 --- a/core/string_buffer.h +++ b/core/string_buffer.h @@ -39,7 +39,7 @@ class StringBuffer { CharType short_buffer[SHORT_BUFFER_SIZE]; String buffer; - int string_length = 0; + int string_length; _FORCE_INLINE_ CharType *current_buffer_ptr() { return static_cast<Vector<CharType> &>(buffer).empty() ? short_buffer : buffer.ptrw(); @@ -79,6 +79,10 @@ public: _FORCE_INLINE_ operator String() { return as_string(); } + + StringBuffer() { + string_length = 0; + } }; template <int SHORT_BUFFER_SIZE> diff --git a/core/string_builder.h b/core/string_builder.h index 9e2599ac32..596b3bf730 100644 --- a/core/string_builder.h +++ b/core/string_builder.h @@ -37,7 +37,7 @@ class StringBuilder { - uint32_t string_length = 0; + uint32_t string_length; Vector<String> strings; Vector<const char *> c_strings; @@ -75,6 +75,10 @@ public: _FORCE_INLINE_ operator String() const { return as_string(); } + + StringBuilder() { + string_length = 0; + } }; #endif // STRING_BUILDER_H diff --git a/core/string_db.cpp b/core/string_db.cpp index 6e1f887754..2475cbe3e8 100644 --- a/core/string_db.cpp +++ b/core/string_db.cpp @@ -164,21 +164,14 @@ void StringName::operator=(const StringName &p_name) { _data = p_name._data; } } -/* was inlined -StringName::operator String() const { - if (_data) - return _data->get_name(); - - return ""; -} -*/ StringName::StringName(const StringName &p_name) { - ERR_FAIL_COND(!configured); _data = NULL; - if (p_name._data && p_name._data->refcount.ref()) { + ERR_FAIL_COND(!configured); + + if (p_name._data && p_name._data->refcount.ref()) { _data = p_name._data; } } diff --git a/core/string_db.h b/core/string_db.h index 28ca812a45..965385b136 100644 --- a/core/string_db.h +++ b/core/string_db.h @@ -31,7 +31,6 @@ #ifndef STRING_DB_H #define STRING_DB_H -#include "hash_map.h" #include "os/mutex.h" #include "safe_refcount.h" #include "ustring.h" @@ -67,6 +66,7 @@ class StringName { _Data() { cname = NULL; next = prev = NULL; + idx = 0; hash = 0; } }; @@ -167,11 +167,6 @@ public: ~StringName(); }; -struct StringNameHasher { - - static _FORCE_INLINE_ uint32_t hash(const StringName &p_string) { return p_string.hash(); } -}; - StringName _scs_create(const char *p_chr); #endif diff --git a/core/type_info.h b/core/type_info.h index c1af4fac69..bf497f1e5f 100644 --- a/core/type_info.h +++ b/core/type_info.h @@ -194,6 +194,7 @@ MAKE_TEMPLATE_TYPE_INFO(Vector, Color, Variant::POOL_COLOR_ARRAY) MAKE_TEMPLATE_TYPE_INFO(Vector, Variant, Variant::ARRAY) MAKE_TEMPLATE_TYPE_INFO(Vector, RID, Variant::ARRAY) MAKE_TEMPLATE_TYPE_INFO(Vector, Plane, Variant::ARRAY) +MAKE_TEMPLATE_TYPE_INFO(Vector, StringName, Variant::POOL_STRING_ARRAY) MAKE_TEMPLATE_TYPE_INFO(PoolVector, Plane, Variant::ARRAY) MAKE_TEMPLATE_TYPE_INFO(PoolVector, Face3, Variant::POOL_VECTOR3_ARRAY) diff --git a/core/undo_redo.cpp b/core/undo_redo.cpp index b3f9dd818d..b9a2fdd0ac 100644 --- a/core/undo_redo.cpp +++ b/core/undo_redo.cpp @@ -299,26 +299,30 @@ void UndoRedo::_process_operation_list(List<Operation>::Element *E) { } } -void UndoRedo::redo() { +bool UndoRedo::redo() { - ERR_FAIL_COND(action_level > 0); + ERR_FAIL_COND_V(action_level > 0, false); if ((current_action + 1) >= actions.size()) - return; //nothing to redo + return false; //nothing to redo current_action++; _process_operation_list(actions[current_action].do_ops.front()); version++; + + return true; } -void UndoRedo::undo() { +bool UndoRedo::undo() { - ERR_FAIL_COND(action_level > 0); + ERR_FAIL_COND_V(action_level > 0, false); if (current_action < 0) - return; //nothing to redo + return false; //nothing to redo _process_operation_list(actions[current_action].undo_ops.front()); current_action--; version--; + + return true; } void UndoRedo::clear_history() { diff --git a/core/undo_redo.h b/core/undo_redo.h index a373296b73..3a17c78851 100644 --- a/core/undo_redo.h +++ b/core/undo_redo.h @@ -109,8 +109,8 @@ public: void commit_action(); - void redo(); - void undo(); + bool redo(); + bool undo(); String get_current_action_name() const; void clear_history(); diff --git a/core/ustring.cpp b/core/ustring.cpp index d445e4ed47..51f05468e2 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -753,6 +753,46 @@ Vector<String> String::split(const String &p_splitter, bool p_allow_empty, int p return ret; } +Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const { + + Vector<String> ret; + const int len = length(); + int from = len; + + while (true) { + + int end = rfind(p_splitter, from); + if (end < 0) + end = 0; + + if (p_allow_empty || (end < from)) { + const String str = substr(end > 0 ? end + p_splitter.length() : end, end > 0 ? from - end : from + 2); + + if (p_maxsplit <= 0) { + ret.push_back(str); + } else if (p_maxsplit > 0) { + + // Put rest of the string and leave cycle. + if (p_maxsplit == ret.size()) { + ret.push_back(substr(0, from + 2)); + break; + } + + // Otherwise, push items until positive limit is reached. + ret.push_back(str); + } + } + + if (end == 0) + break; + + from = end - p_splitter.length(); + } + + ret.invert(); + return ret; +} + Vector<float> String::split_floats(const String &p_splitter, bool p_allow_empty) const { Vector<float> ret; @@ -945,8 +985,8 @@ String String::num(double p_num, int p_decimals) { #ifndef NO_USE_STDLIB - if (p_decimals > 12) - p_decimals = 12; + if (p_decimals > 16) + p_decimals = 16; char fmt[7]; fmt[0] = '%'; @@ -1135,6 +1175,36 @@ String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { return s; } +String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) { + + uint64_t n = p_num; + + int chars = 0; + do { + n /= base; + chars++; + } while (n); + + String s; + s.resize(chars + 1); + CharType *c = s.ptrw(); + c[chars] = 0; + n = p_num; + do { + int mod = n % base; + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars] = a + (mod - 10); + } else { + c[--chars] = '0' + mod; + } + + n /= base; + } while (n); + + return s; +} + String String::num_real(double p_num) { String s; @@ -1520,8 +1590,7 @@ String::String(const StrRange &p_range) { int String::hex_to_int(bool p_with_prefix) const { - int l = length(); - if (p_with_prefix && l < 3) + if (p_with_prefix && length() < 3) return 0; const CharType *s = ptr(); @@ -1530,17 +1599,13 @@ int String::hex_to_int(bool p_with_prefix) const { if (sign < 0) { s++; - l--; - if (p_with_prefix && l < 2) - return 0; } if (p_with_prefix) { if (s[0] != '0' || s[1] != 'x') return 0; s += 2; - l -= 2; - }; + } int hex = 0; @@ -1566,8 +1631,7 @@ int String::hex_to_int(bool p_with_prefix) const { int64_t String::hex_to_int64(bool p_with_prefix) const { - int l = length(); - if (p_with_prefix && l < 3) + if (p_with_prefix && length() < 3) return 0; const CharType *s = ptr(); @@ -1576,17 +1640,13 @@ int64_t String::hex_to_int64(bool p_with_prefix) const { if (sign < 0) { s++; - l--; - if (p_with_prefix && l < 2) - return 0; } if (p_with_prefix) { if (s[0] != '0' || s[1] != 'x') return 0; s += 2; - l -= 2; - }; + } int64_t hex = 0; @@ -2967,6 +3027,40 @@ String String::strip_escapes() const { return substr(beg, end - beg); } +String String::lstrip(const Vector<CharType> &p_chars) const { + + int len = length(); + int beg; + + for (beg = 0; beg < len; beg++) { + + if (p_chars.find(operator[](beg)) == -1) + break; + } + + if (beg == 0) + return *this; + + return substr(beg, len - beg); +} + +String String::rstrip(const Vector<CharType> &p_chars) const { + + int len = length(); + int end; + + for (end = len - 1; end >= 0; end--) { + + if (p_chars.find(operator[](end)) == -1) + break; + } + + if (end == len - 1) + return *this; + + return substr(0, end + 1); +} + String String::simplify_path() const { String s = *this; @@ -3138,8 +3232,8 @@ String String::word_wrap(int p_chars_per_line) const { String String::http_escape() const { const CharString temp = utf8(); String res; - for (int i = 0; i < length(); ++i) { - CharType ord = temp[i]; + for (int i = 0; i < temp.length(); ++i) { + char ord = temp[i]; if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || (ord >= 'a' && ord <= 'z') || (ord >= 'A' && ord <= 'Z') || @@ -3148,9 +3242,9 @@ String String::http_escape() const { } else { char h_Val[3]; #if defined(__GNUC__) || defined(_MSC_VER) - snprintf(h_Val, 3, "%.2X", ord); + snprintf(h_Val, 3, "%hhX", ord); #else - sprintf(h_Val, "%.2X", ord); + sprintf(h_Val, "%hhX", ord); #endif res += "%"; res += h_Val; @@ -3418,6 +3512,24 @@ String String::pad_zeros(int p_digits) const { return s; } +String String::trim_prefix(const String &p_prefix) const { + + String s = *this; + if (s.begins_with(p_prefix)) { + return s.substr(p_prefix.length(), s.length() - p_prefix.length()); + } + return s; +} + +String String::trim_suffix(const String &p_suffix) const { + + String s = *this; + if (s.ends_with(p_suffix)) { + return s.substr(0, s.length() - p_suffix.length()); + } + return s; +} + bool String::is_valid_integer() const { int len = length(); @@ -3448,13 +3560,13 @@ bool String::is_valid_hex_number(bool p_with_prefix) const { if (p_with_prefix) { - if (len < 2) + if (len < 3) return false; if (operator[](from) != '0' || operator[](from + 1) != 'x') { return false; - }; + } from += 2; - }; + } for (int i = from; i < len; i++) { @@ -3462,7 +3574,7 @@ bool String::is_valid_hex_number(bool p_with_prefix) const { if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) continue; return false; - }; + } return true; }; @@ -3683,8 +3795,8 @@ String String::get_file() const { String String::get_extension() const { int pos = find_last("."); - if (pos < 0) - return *this; + if (pos < 0 || pos < MAX(find_last("/"), find_last("\\"))) + return ""; return substr(pos + 1, length()); } @@ -3762,7 +3874,7 @@ String String::percent_decode() const { String String::get_basename() const { int pos = find_last("."); - if (pos < 0) + if (pos < 0 || pos < MAX(find_last("/"), find_last("\\"))) return *this; return substr(0, pos); diff --git a/core/ustring.h b/core/ustring.h index 90496b71b6..b57e9629d9 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -137,6 +137,8 @@ public: String insert(int p_at_pos, const String &p_string) const; String pad_decimals(int p_digits) const; String pad_zeros(int p_digits) const; + String trim_prefix(const String &p_prefix) const; + String trim_suffix(const String &p_suffix) const; String lpad(int min_length, const String &character = " ") const; String rpad(int min_length, const String &character = " ") const; String sprintf(const Array &values, bool *error) const; @@ -146,6 +148,7 @@ public: static String num_scientific(double p_num); static String num_real(double p_num); static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false); + static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false); static String chr(CharType p_char); static String md5(const uint8_t *p_md5); static String hex_encode_buffer(const uint8_t *p_buffer, int p_len); @@ -169,6 +172,7 @@ public: String get_slicec(CharType p_splitter, int p_slice) const; Vector<String> split(const String &p_splitter, bool p_allow_empty = true, int p_maxsplit = 0) const; + Vector<String> rsplit(const String &p_splitter, bool p_allow_empty = true, int p_maxsplit = 0) const; Vector<String> split_spaces() const; Vector<float> split_floats(const String &p_splitter, bool p_allow_empty = true) const; Vector<float> split_floats_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const; @@ -187,6 +191,8 @@ public: String dedent() const; String strip_edges(bool left = true, bool right = true) const; String strip_escapes() const; + String lstrip(const Vector<CharType> &p_chars) const; + String rstrip(const Vector<CharType> &p_chars) const; String get_extension() const; String get_basename() const; String plus_file(const String &p_file) const; diff --git a/core/variant.cpp b/core/variant.cpp index 5d48c8785e..c48aa57652 100644 --- a/core/variant.cpp +++ b/core/variant.cpp @@ -2012,6 +2012,19 @@ Variant::operator Vector<String>() const { } return to; } +Variant::operator Vector<StringName>() const { + + PoolVector<String> from = operator PoolVector<String>(); + Vector<StringName> to; + int len = from.size(); + to.resize(len); + for (int i = 0; i < len; i++) { + + to[i] = from[i]; + } + return to; +} + Variant::operator Vector<Vector3>() const { PoolVector<Vector3> from = operator PoolVector<Vector3>(); @@ -2444,6 +2457,17 @@ Variant::Variant(const Vector<String> &p_array) { *this = v; } +Variant::Variant(const Vector<StringName> &p_array) { + + type = NIL; + PoolVector<String> v; + int len = p_array.size(); + v.resize(len); + for (int i = 0; i < len; i++) + v.set(i, p_array[i]); + *this = v; +} + Variant::Variant(const Vector<Vector3> &p_array) { type = NIL; @@ -3167,7 +3191,11 @@ String Variant::get_call_error_text(Object *p_base, const StringName &p_method, if (ce.error == Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) { int errorarg = ce.argument; - err_text = "Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(p_argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(ce.expected) + "."; + if (p_argptrs) { + err_text = "Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(p_argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(ce.expected) + "."; + } else { + err_text = "Cannot convert argument " + itos(errorarg + 1) + " from [missing argptr, type unknown] to " + Variant::get_type_name(ce.expected) + "."; + } } else if (ce.error == Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { err_text = "Method expected " + itos(ce.argument) + " arguments, but called with " + itos(p_argcount) + "."; } else if (ce.error == Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { diff --git a/core/variant.h b/core/variant.h index 0a4afada5b..4b245d25e6 100644 --- a/core/variant.h +++ b/core/variant.h @@ -140,7 +140,6 @@ private: ::AABB *_aabb; Basis *_basis; Transform *_transform; - RefPtr *_resource; void *_ptr; //generic pointer uint8_t _mem[sizeof(ObjData) > (sizeof(real_t) * 4) ? sizeof(ObjData) : (sizeof(real_t) * 4)]; } _data; @@ -217,6 +216,7 @@ public: operator Vector<int>() const; operator Vector<real_t>() const; operator Vector<String>() const; + operator Vector<StringName>() const; operator Vector<Vector3>() const; operator Vector<Color>() const; operator Vector<RID>() const; @@ -281,6 +281,7 @@ public: Variant(const Vector<int> &p_int_array); Variant(const Vector<real_t> &p_real_array); Variant(const Vector<String> &p_string_array); + Variant(const Vector<StringName> &p_string_array); Variant(const Vector<Vector3> &p_vector3_array); Variant(const Vector<Color> &p_color_array); Variant(const Vector<Plane> &p_array); // helper @@ -338,6 +339,7 @@ public: } void zero(); + Variant duplicate(bool deep = false) const; 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); diff --git a/core/variant_call.cpp b/core/variant_call.cpp index d1d45f0e7c..e6f36ecbf1 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -153,9 +153,9 @@ struct _VariantCall { funcdata.func = p_func; funcdata.default_args = p_defaultarg; funcdata._const = p_const; + funcdata.returns = p_has_return; #ifdef DEBUG_ENABLED funcdata.return_type = p_return; - funcdata.returns = p_has_return; #endif if (p_argtype1.name) { @@ -257,6 +257,7 @@ struct _VariantCall { VCALL_LOCALMEM2R(String, insert); VCALL_LOCALMEM0R(String, capitalize); VCALL_LOCALMEM3R(String, split); + VCALL_LOCALMEM3R(String, rsplit); VCALL_LOCALMEM2R(String, split_floats); VCALL_LOCALMEM0R(String, to_upper); VCALL_LOCALMEM0R(String, to_lower); @@ -264,6 +265,8 @@ struct _VariantCall { VCALL_LOCALMEM1R(String, right); VCALL_LOCALMEM0R(String, dedent); VCALL_LOCALMEM2R(String, strip_edges); + VCALL_LOCALMEM1R(String, lstrip); + VCALL_LOCALMEM1R(String, rstrip); VCALL_LOCALMEM0R(String, get_extension); VCALL_LOCALMEM0R(String, get_basename); VCALL_LOCALMEM1R(String, plus_file); @@ -296,6 +299,8 @@ struct _VariantCall { VCALL_LOCALMEM0R(String, hex_to_int); VCALL_LOCALMEM1R(String, pad_decimals); VCALL_LOCALMEM1R(String, pad_zeros); + VCALL_LOCALMEM1R(String, trim_prefix); + VCALL_LOCALMEM1R(String, trim_suffix); static void _call_String_to_ascii(Variant &r_ret, Variant &p_self, const Variant **p_args) { @@ -336,10 +341,13 @@ struct _VariantCall { VCALL_LOCALMEM1R(Vector2, angle_to); VCALL_LOCALMEM1R(Vector2, angle_to_point); VCALL_LOCALMEM2R(Vector2, linear_interpolate); + VCALL_LOCALMEM2R(Vector2, slerp); VCALL_LOCALMEM4R(Vector2, cubic_interpolate); VCALL_LOCALMEM1R(Vector2, rotated); VCALL_LOCALMEM0R(Vector2, tangent); VCALL_LOCALMEM0R(Vector2, floor); + VCALL_LOCALMEM0R(Vector2, ceil); + VCALL_LOCALMEM0R(Vector2, round); VCALL_LOCALMEM1R(Vector2, snapped); VCALL_LOCALMEM0R(Vector2, aspect); VCALL_LOCALMEM1R(Vector2, dot); @@ -347,7 +355,7 @@ struct _VariantCall { VCALL_LOCALMEM1R(Vector2, bounce); VCALL_LOCALMEM1R(Vector2, reflect); VCALL_LOCALMEM0R(Vector2, angle); - //VCALL_LOCALMEM1R(Vector2,cross); + VCALL_LOCALMEM1R(Vector2, cross); VCALL_LOCALMEM0R(Vector2, abs); VCALL_LOCALMEM1R(Vector2, clamped); @@ -374,6 +382,7 @@ struct _VariantCall { VCALL_LOCALMEM1R(Vector3, snapped); VCALL_LOCALMEM2R(Vector3, rotated); VCALL_LOCALMEM2R(Vector3, linear_interpolate); + VCALL_LOCALMEM2R(Vector3, slerp); VCALL_LOCALMEM4R(Vector3, cubic_interpolate); VCALL_LOCALMEM1R(Vector3, dot); VCALL_LOCALMEM1R(Vector3, cross); @@ -382,6 +391,7 @@ struct _VariantCall { VCALL_LOCALMEM0R(Vector3, abs); VCALL_LOCALMEM0R(Vector3, floor); VCALL_LOCALMEM0R(Vector3, ceil); + VCALL_LOCALMEM0R(Vector3, round); VCALL_LOCALMEM1R(Vector3, distance_to); VCALL_LOCALMEM1R(Vector3, distance_squared_to); VCALL_LOCALMEM1R(Vector3, angle_to); @@ -432,6 +442,9 @@ struct _VariantCall { VCALL_LOCALMEM2R(Quat, slerp); VCALL_LOCALMEM2R(Quat, slerpni); VCALL_LOCALMEM4R(Quat, cubic_slerp); + VCALL_LOCALMEM0R(Quat, get_euler); + VCALL_LOCALMEM1(Quat, set_euler); + VCALL_LOCALMEM2(Quat, set_axis_angle); VCALL_LOCALMEM0R(Color, to_rgba32); VCALL_LOCALMEM0R(Color, to_argb32); @@ -465,7 +478,7 @@ struct _VariantCall { VCALL_LOCALMEM0R(Dictionary, hash); VCALL_LOCALMEM0R(Dictionary, keys); VCALL_LOCALMEM0R(Dictionary, values); - VCALL_LOCALMEM0R(Dictionary, duplicate); + VCALL_LOCALMEM1R(Dictionary, duplicate); VCALL_LOCALMEM2(Array, set); VCALL_LOCALMEM1R(Array, get); @@ -494,7 +507,7 @@ struct _VariantCall { VCALL_LOCALMEM0(Array, shuffle); VCALL_LOCALMEM2R(Array, bsearch); VCALL_LOCALMEM4R(Array, bsearch_custom); - VCALL_LOCALMEM0R(Array, duplicate); + VCALL_LOCALMEM1R(Array, duplicate); VCALL_LOCALMEM0(Array, invert); static void _call_PoolByteArray_get_string_from_ascii(Variant &r_ret, Variant &p_self, const Variant **p_args) { @@ -750,6 +763,7 @@ struct _VariantCall { VCALL_PTR1R(Basis, xform_inv); VCALL_PTR0R(Basis, get_orthogonal_index); VCALL_PTR0R(Basis, orthonormalized); + VCALL_PTR2R(Basis, slerp); VCALL_PTR0R(Transform, inverse); VCALL_PTR0R(Transform, affine_inverse); @@ -869,6 +883,11 @@ struct _VariantCall { r_ret = Quat(((Vector3)(*p_args[0])), ((float)(*p_args[1]))); } + static void Quat_init3(Variant &r_ret, const Variant **p_args) { + + r_ret = Quat(((Vector3)(*p_args[0]))); + } + static void Color_init1(Variant &r_ret, const Variant **p_args) { r_ret = Color(*p_args[0], *p_args[1], *p_args[2], *p_args[3]); @@ -1143,7 +1162,7 @@ Variant Variant::construct(const Variant::Type p_type, const Variant **p_args, i case RECT2: return (Rect2(*p_args[0])); case VECTOR3: return (Vector3(*p_args[0])); case PLANE: return (Plane(*p_args[0])); - case QUAT: return (Quat(*p_args[0])); + case QUAT: return (p_args[0]->operator Quat()); case AABB: return (::AABB(*p_args[0])); // 10 case BASIS: return (Basis(p_args[0]->operator Basis())); @@ -1452,6 +1471,7 @@ void register_variant_methods() { ADDFUNC2R(STRING, STRING, String, insert, INT, "position", STRING, "what", varray()); ADDFUNC0R(STRING, STRING, String, capitalize, varray()); ADDFUNC3R(STRING, POOL_STRING_ARRAY, String, split, STRING, "divisor", BOOL, "allow_empty", INT, "maxsplit", varray(true, 0)); + ADDFUNC3R(STRING, POOL_STRING_ARRAY, String, rsplit, STRING, "divisor", BOOL, "allow_empty", INT, "maxsplit", varray(true, 0)); ADDFUNC2R(STRING, POOL_REAL_ARRAY, String, split_floats, STRING, "divisor", BOOL, "allow_empty", varray(true)); ADDFUNC0R(STRING, STRING, String, to_upper, varray()); @@ -1460,6 +1480,8 @@ void register_variant_methods() { ADDFUNC1R(STRING, STRING, String, left, INT, "position", varray()); ADDFUNC1R(STRING, STRING, String, right, INT, "position", varray()); ADDFUNC2R(STRING, STRING, String, strip_edges, BOOL, "left", BOOL, "right", varray(true, true)); + ADDFUNC1R(STRING, STRING, String, lstrip, STRING, "chars", varray()); + ADDFUNC1R(STRING, STRING, String, rstrip, STRING, "chars", varray()); ADDFUNC0R(STRING, STRING, String, get_extension, varray()); ADDFUNC0R(STRING, STRING, String, get_basename, varray()); ADDFUNC1R(STRING, STRING, String, plus_file, STRING, "file", varray()); @@ -1493,6 +1515,8 @@ void register_variant_methods() { ADDFUNC0R(STRING, INT, String, hex_to_int, varray()); ADDFUNC1R(STRING, STRING, String, pad_decimals, INT, "digits", varray()); ADDFUNC1R(STRING, STRING, String, pad_zeros, INT, "digits", varray()); + ADDFUNC1R(STRING, STRING, String, trim_prefix, STRING, "prefix", varray()); + ADDFUNC1R(STRING, STRING, String, trim_suffix, STRING, "suffix", varray()); ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, to_ascii, varray()); ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, to_utf8, varray()); @@ -1507,17 +1531,20 @@ void register_variant_methods() { ADDFUNC1R(VECTOR2, REAL, Vector2, angle_to, VECTOR2, "to", varray()); ADDFUNC1R(VECTOR2, REAL, Vector2, angle_to_point, VECTOR2, "to", varray()); ADDFUNC2R(VECTOR2, VECTOR2, Vector2, linear_interpolate, VECTOR2, "b", REAL, "t", varray()); + ADDFUNC2R(VECTOR2, VECTOR2, Vector2, slerp, VECTOR2, "b", REAL, "t", varray()); ADDFUNC4R(VECTOR2, VECTOR2, Vector2, cubic_interpolate, VECTOR2, "b", VECTOR2, "pre_a", VECTOR2, "post_b", REAL, "t", varray()); ADDFUNC1R(VECTOR2, VECTOR2, Vector2, rotated, REAL, "phi", varray()); ADDFUNC0R(VECTOR2, VECTOR2, Vector2, tangent, varray()); ADDFUNC0R(VECTOR2, VECTOR2, Vector2, floor, varray()); + ADDFUNC0R(VECTOR2, VECTOR2, Vector2, ceil, varray()); + ADDFUNC0R(VECTOR2, VECTOR2, Vector2, round, varray()); ADDFUNC1R(VECTOR2, VECTOR2, Vector2, snapped, VECTOR2, "by", varray()); ADDFUNC0R(VECTOR2, REAL, Vector2, aspect, varray()); ADDFUNC1R(VECTOR2, REAL, Vector2, dot, VECTOR2, "with", varray()); ADDFUNC1R(VECTOR2, VECTOR2, Vector2, slide, VECTOR2, "n", varray()); ADDFUNC1R(VECTOR2, VECTOR2, Vector2, bounce, VECTOR2, "n", varray()); ADDFUNC1R(VECTOR2, VECTOR2, Vector2, reflect, VECTOR2, "n", varray()); - //ADDFUNC1R(VECTOR2,REAL,Vector2,cross,VECTOR2,"with",varray()); + ADDFUNC1R(VECTOR2, REAL, Vector2, cross, VECTOR2, "with", varray()); ADDFUNC0R(VECTOR2, VECTOR2, Vector2, abs, varray()); ADDFUNC1R(VECTOR2, VECTOR2, Vector2, clamped, REAL, "length", varray()); @@ -1544,6 +1571,7 @@ void register_variant_methods() { ADDFUNC1R(VECTOR3, VECTOR3, Vector3, snapped, VECTOR3, "by", varray()); ADDFUNC2R(VECTOR3, VECTOR3, Vector3, rotated, VECTOR3, "axis", REAL, "phi", varray()); ADDFUNC2R(VECTOR3, VECTOR3, Vector3, linear_interpolate, VECTOR3, "b", REAL, "t", varray()); + ADDFUNC2R(VECTOR3, VECTOR3, Vector3, slerp, VECTOR3, "b", REAL, "t", varray()); ADDFUNC4R(VECTOR3, VECTOR3, Vector3, cubic_interpolate, VECTOR3, "b", VECTOR3, "pre_a", VECTOR3, "post_b", REAL, "t", varray()); ADDFUNC1R(VECTOR3, REAL, Vector3, dot, VECTOR3, "b", varray()); ADDFUNC1R(VECTOR3, VECTOR3, Vector3, cross, VECTOR3, "b", varray()); @@ -1552,6 +1580,7 @@ void register_variant_methods() { ADDFUNC0R(VECTOR3, VECTOR3, Vector3, abs, varray()); ADDFUNC0R(VECTOR3, VECTOR3, Vector3, floor, varray()); ADDFUNC0R(VECTOR3, VECTOR3, Vector3, ceil, varray()); + ADDFUNC0R(VECTOR3, VECTOR3, Vector3, round, varray()); ADDFUNC1R(VECTOR3, REAL, Vector3, distance_to, VECTOR3, "b", varray()); ADDFUNC1R(VECTOR3, REAL, Vector3, distance_squared_to, VECTOR3, "b", varray()); ADDFUNC1R(VECTOR3, REAL, Vector3, angle_to, VECTOR3, "to", varray()); @@ -1580,6 +1609,9 @@ void register_variant_methods() { ADDFUNC2R(QUAT, QUAT, Quat, slerp, QUAT, "b", REAL, "t", varray()); ADDFUNC2R(QUAT, QUAT, Quat, slerpni, QUAT, "b", REAL, "t", varray()); ADDFUNC4R(QUAT, QUAT, Quat, cubic_slerp, QUAT, "b", QUAT, "pre_a", QUAT, "post_b", REAL, "t", varray()); + ADDFUNC0R(QUAT, VECTOR3, Quat, get_euler, varray()); + ADDFUNC1(QUAT, NIL, Quat, set_euler, VECTOR3, "euler", varray()); + ADDFUNC2(QUAT, NIL, Quat, set_axis_angle, VECTOR3, "axis", REAL, "angle", varray()); ADDFUNC0R(COLOR, INT, Color, to_rgba32, varray()); ADDFUNC0R(COLOR, INT, Color, to_argb32, varray()); @@ -1613,7 +1645,7 @@ void register_variant_methods() { ADDFUNC0R(DICTIONARY, INT, Dictionary, hash, varray()); ADDFUNC0R(DICTIONARY, ARRAY, Dictionary, keys, varray()); ADDFUNC0R(DICTIONARY, ARRAY, Dictionary, values, varray()); - ADDFUNC0R(DICTIONARY, DICTIONARY, Dictionary, duplicate, varray()); + ADDFUNC1R(DICTIONARY, DICTIONARY, Dictionary, duplicate, BOOL, "deep", varray(false)); ADDFUNC0R(ARRAY, INT, Array, size, varray()); ADDFUNC0R(ARRAY, BOOL, Array, empty, varray()); @@ -1641,7 +1673,7 @@ void register_variant_methods() { ADDFUNC2R(ARRAY, INT, Array, bsearch, NIL, "value", BOOL, "before", varray(true)); ADDFUNC4R(ARRAY, INT, Array, bsearch_custom, NIL, "value", OBJECT, "obj", STRING, "func", BOOL, "before", varray(true)); ADDFUNC0NC(ARRAY, NIL, Array, invert, varray()); - ADDFUNC0RNC(ARRAY, ARRAY, Array, duplicate, varray()); + ADDFUNC1R(ARRAY, ARRAY, Array, duplicate, BOOL, "deep", varray(false)); ADDFUNC0R(POOL_BYTE_ARRAY, INT, PoolByteArray, size, varray()); ADDFUNC2(POOL_BYTE_ARRAY, NIL, PoolByteArray, set, INT, "idx", INT, "byte", varray()); @@ -1772,6 +1804,7 @@ void register_variant_methods() { ADDFUNC1R(BASIS, VECTOR3, Basis, xform, VECTOR3, "v", varray()); ADDFUNC1R(BASIS, VECTOR3, Basis, xform_inv, VECTOR3, "v", varray()); ADDFUNC0R(BASIS, INT, Basis, get_orthogonal_index, varray()); + ADDFUNC2R(BASIS, BASIS, Basis, slerp, BASIS, "b", REAL, "t", varray()); ADDFUNC0R(TRANSFORM, TRANSFORM, Transform, inverse, varray()); ADDFUNC0R(TRANSFORM, TRANSFORM, Transform, affine_inverse, varray()); @@ -1802,6 +1835,7 @@ void register_variant_methods() { _VariantCall::add_constructor(_VariantCall::Quat_init1, Variant::QUAT, "x", Variant::REAL, "y", Variant::REAL, "z", Variant::REAL, "w", Variant::REAL); _VariantCall::add_constructor(_VariantCall::Quat_init2, Variant::QUAT, "axis", Variant::VECTOR3, "angle", Variant::REAL); + _VariantCall::add_constructor(_VariantCall::Quat_init3, Variant::QUAT, "euler", Variant::VECTOR3); _VariantCall::add_constructor(_VariantCall::Color_init1, Variant::COLOR, "r", Variant::REAL, "g", Variant::REAL, "b", Variant::REAL, "a", Variant::REAL); _VariantCall::add_constructor(_VariantCall::Color_init2, Variant::COLOR, "r", Variant::REAL, "g", Variant::REAL, "b", Variant::REAL); diff --git a/core/variant_op.cpp b/core/variant_op.cpp index 97b469861c..bfa69b1fde 100644 --- a/core/variant_op.cpp +++ b/core/variant_op.cpp @@ -177,7 +177,7 @@ bool Variant::booleanize() const { CASE_TYPE(m_prefix, m_op_name, m_name) { \ if (p_b.type == INT) _RETURN(p_a._data.m_type m_op p_b._data._int); \ if (p_b.type == REAL) _RETURN(p_a._data.m_type m_op p_b._data._real); \ - if (p_b.type == NIL) _RETURN(!p_b.type m_op NIL); \ + if (p_b.type == NIL) _RETURN(!(p_b.type m_op NIL)); \ \ _RETURN_FAIL \ }; @@ -252,7 +252,7 @@ bool Variant::booleanize() const { CASE_TYPE(m_prefix, m_op_name, m_name) { \ if (p_b.type == STRING) _RETURN(*reinterpret_cast<const m_type *>(p_a._data._mem) m_op *reinterpret_cast<const String *>(p_b._data._mem)); \ if (p_b.type == NODE_PATH) _RETURN(*reinterpret_cast<const m_type *>(p_a._data._mem) m_op *reinterpret_cast<const NodePath *>(p_b._data._mem)); \ - if (p_b.type == NIL) _RETURN(!p_b.type m_op NIL); \ + if (p_b.type == NIL) _RETURN(!(p_b.type m_op NIL)); \ \ _RETURN_FAIL \ }; @@ -278,7 +278,7 @@ bool Variant::booleanize() const { if (p_b.type == m_name) \ _RETURN(*reinterpret_cast<const m_type *>(p_a._data._mem) m_op *reinterpret_cast<const m_type *>(p_b._data._mem)); \ if (p_b.type == NIL) \ - _RETURN(!p_b.type m_op NIL); \ + _RETURN(!(p_b.type m_op NIL)); \ \ _RETURN_FAIL \ }; @@ -323,7 +323,7 @@ bool Variant::booleanize() const { if (p_b.type == m_name) \ _RETURN(*p_a._data.m_sub m_op *p_b._data.m_sub); \ if (p_b.type == NIL) \ - _RETURN(!p_b.type m_op NIL); \ + _RETURN(!(p_b.type m_op NIL)); \ \ _RETURN_FAIL \ } @@ -1459,13 +1459,13 @@ void Variant::set_named(const StringName &p_index, const Variant &p_value, bool v->a = p_value._data._int / 255.0; valid = true; } else if (p_index == CoreStringNames::singleton->h) { - v->set_hsv(p_value._data._int, v->get_s(), v->get_v()); + v->set_hsv(p_value._data._int, v->get_s(), v->get_v(), v->a); valid = true; } else if (p_index == CoreStringNames::singleton->s) { - v->set_hsv(v->get_h(), p_value._data._int, v->get_v()); + v->set_hsv(v->get_h(), p_value._data._int, v->get_v(), v->a); valid = true; } else if (p_index == CoreStringNames::singleton->v) { - v->set_hsv(v->get_h(), v->get_v(), p_value._data._int); + v->set_hsv(v->get_h(), v->get_v(), p_value._data._int, v->a); valid = true; } } else if (p_value.type == Variant::REAL) { @@ -1495,13 +1495,13 @@ void Variant::set_named(const StringName &p_index, const Variant &p_value, bool v->a = p_value._data._real / 255.0; valid = true; } else if (p_index == CoreStringNames::singleton->h) { - v->set_hsv(p_value._data._real, v->get_s(), v->get_v()); + v->set_hsv(p_value._data._real, v->get_s(), v->get_v(), v->a); valid = true; } else if (p_index == CoreStringNames::singleton->s) { - v->set_hsv(v->get_h(), p_value._data._real, v->get_v()); + v->set_hsv(v->get_h(), p_value._data._real, v->get_v(), v->a); valid = true; } else if (p_index == CoreStringNames::singleton->v) { - v->set_hsv(v->get_h(), v->get_s(), p_value._data._real); + v->set_hsv(v->get_h(), v->get_s(), p_value._data._real, v->a); valid = true; } } @@ -2117,15 +2117,15 @@ void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid) return; } else if (*str == "h") { valid = true; - v->set_hsv(p_value, v->get_s(), v->get_v()); + v->set_hsv(p_value, v->get_s(), v->get_v(), v->a); return; } else if (*str == "s") { valid = true; - v->set_hsv(v->get_h(), p_value, v->get_v()); + v->set_hsv(v->get_h(), p_value, v->get_v(), v->a); return; } else if (*str == "v") { valid = true; - v->set_hsv(v->get_h(), v->get_s(), p_value); + v->set_hsv(v->get_h(), v->get_s(), p_value, v->a); return; } else if (*str == "r8") { valid = true; @@ -3415,6 +3415,28 @@ Variant Variant::iter_get(const Variant &r_iter, bool &r_valid) const { return Variant(); } +Variant Variant::duplicate(bool deep) const { + switch (type) { + case OBJECT: { + /* breaks stuff :( + if (deep && !_get_obj().ref.is_null()) { + Ref<Resource> resource = _get_obj().ref; + if (resource.is_valid()) { + return resource->duplicate(true); + } + } + */ + return *this; + } break; + case DICTIONARY: + return operator Dictionary().duplicate(deep); + case ARRAY: + return operator Array().duplicate(deep); + default: + return *this; + } +} + void Variant::blend(const Variant &a, const Variant &b, float c, Variant &r_dst) { if (a.type != b.type) { if (a.is_num() && b.is_num()) { @@ -3715,8 +3737,9 @@ static const char *_op_names[Variant::OP_MAX] = { "*", "/", "- (negation)", + "+ (positive)", "%", - "..", + "+ (concatenation)", "<<", ">>", "&", diff --git a/core/version.h b/core/version.h index 7a55d69ad7..d39172865a 100644 --- a/core/version.h +++ b/core/version.h @@ -30,9 +30,32 @@ #include "version_generated.gen.h" +// Godot versions are of the form <major>.<minor> for the initial release, +// and then <major>.<minor>.<patch> for subsequent bugfix releases where <patch> != 0 +// That's arbitrary, but we find it pretty and it's the current policy. + +// Defines the main "branch" version. Patch versions in this branch should be +// forward-compatible. +// Example: "3.1" +#define VERSION_BRANCH "" _MKSTR(VERSION_MAJOR) "." _MKSTR(VERSION_MINOR) #ifdef VERSION_PATCH -#define VERSION_MKSTRING "" _MKSTR(VERSION_MAJOR) "." _MKSTR(VERSION_MINOR) "." _MKSTR(VERSION_PATCH) "." VERSION_STATUS "." VERSION_BUILD VERSION_MODULE_CONFIG +// Example: "3.1.4" +#define VERSION_NUMBER "" VERSION_BRANCH "." _MKSTR(VERSION_PATCH) #else -#define VERSION_MKSTRING "" _MKSTR(VERSION_MAJOR) "." _MKSTR(VERSION_MINOR) "." VERSION_STATUS "." VERSION_BUILD VERSION_MODULE_CONFIG +// Example: "3.1" +#define VERSION_NUMBER "" VERSION_BRANCH #endif // VERSION_PATCH -#define VERSION_FULL_NAME "" VERSION_NAME " v" VERSION_MKSTRING + +// Describes the full configuration of that Godot version, including the version number, +// the status (beta, stable, etc.) and potential module-specific features (e.g. mono). +// Example: "3.1.4.stable.mono" +#define VERSION_FULL_CONFIG "" VERSION_NUMBER "." VERSION_STATUS VERSION_MODULE_CONFIG + +// Similar to VERSION_FULL_CONFIG, but also includes the (potentially custom) VERSION_BUILD +// description (e.g. official, custom_build, etc.). +// Example: "3.1.4.stable.mono.official" +#define VERSION_FULL_BUILD "" VERSION_FULL_CONFIG "." VERSION_BUILD + +// Same as above, but prepended with Godot's name and a cosmetic "v" for "version". +// Example: "Godot v3.1.4.stable.official.mono" +#define VERSION_FULL_NAME "" VERSION_NAME " v" VERSION_FULL_BUILD |