diff options
Diffstat (limited to 'modules')
123 files changed, 1614 insertions, 1623 deletions
diff --git a/modules/bullet/collision_object_bullet.cpp b/modules/bullet/collision_object_bullet.cpp index bce8ec8076..d9f5beb5a1 100644 --- a/modules/bullet/collision_object_bullet.cpp +++ b/modules/bullet/collision_object_bullet.cpp @@ -148,6 +148,9 @@ void CollisionObjectBullet::add_collision_exception(const CollisionObjectBullet void CollisionObjectBullet::remove_collision_exception(const CollisionObjectBullet *p_ignoreCollisionObject) { exceptions.erase(p_ignoreCollisionObject->get_self()); + if (!bt_collision_object) { + return; + } bt_collision_object->setIgnoreCollisionCheck(p_ignoreCollisionObject->bt_collision_object, false); if (space) { space->get_broadphase()->getOverlappingPairCache()->cleanProxyFromPairs(bt_collision_object->getBroadphaseHandle(), space->get_dispatcher()); @@ -155,11 +158,14 @@ void CollisionObjectBullet::remove_collision_exception(const CollisionObjectBull } bool CollisionObjectBullet::has_collision_exception(const CollisionObjectBullet *p_otherCollisionObject) const { - return !bt_collision_object->checkCollideWith(p_otherCollisionObject->bt_collision_object); + return exceptions.has(p_otherCollisionObject->get_self()); } void CollisionObjectBullet::set_collision_enabled(bool p_enabled) { collisionsEnabled = p_enabled; + if (!bt_collision_object) { + return; + } if (collisionsEnabled) { bt_collision_object->setCollisionFlags(bt_collision_object->getCollisionFlags() & (~btCollisionObject::CF_NO_CONTACT_RESPONSE)); } else { diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index dbd6d9e9f9..3beca3d12a 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -33,6 +33,7 @@ #include "core/os/os.h" #include "core/os/thread.h" #include "core/string/print_string.h" +#include "core/templates/safe_refcount.h" #include <ConvectionKernels.h> @@ -56,7 +57,7 @@ struct CVTTCompressionJobQueue { CVTTCompressionJobParams job_params; const CVTTCompressionRowTask *job_tasks; uint32_t num_tasks = 0; - uint32_t current_task = 0; + SafeNumeric<uint32_t> current_task; }; static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const CVTTCompressionRowTask &p_row_task) { @@ -131,7 +132,7 @@ static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const static void _digest_job_queue(void *p_job_queue) { CVTTCompressionJobQueue *job_queue = static_cast<CVTTCompressionJobQueue *>(p_job_queue); - for (uint32_t next_task = atomic_increment(&job_queue->current_task); next_task <= job_queue->num_tasks; next_task = atomic_increment(&job_queue->current_task)) { + for (uint32_t next_task = job_queue->current_task.increment(); next_task <= job_queue->num_tasks; next_task = job_queue->current_task.increment()) { _digest_row_task(job_queue->job_params, job_queue->job_tasks[next_task - 1]); } } @@ -263,7 +264,7 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann const CVTTCompressionRowTask *tasks_rb = tasks.ptr(); job_queue.job_tasks = &tasks_rb[0]; - job_queue.current_task = 0; + job_queue.current_task.set(0); job_queue.num_tasks = static_cast<uint32_t>(tasks.size()); for (int i = 0; i < num_job_threads; i++) { diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index 5415fcc92e..2fef576b77 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -94,7 +94,7 @@ static const DDSFormatInfo dds_format_info[DDS_MAX] = { { "GRAYSCALE_ALPHA", false, false, 1, 2, Image::FORMAT_LA8 } }; -RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_CANT_OPEN; } diff --git a/modules/dds/texture_loader_dds.h b/modules/dds/texture_loader_dds.h index 605e791969..cf93156423 100644 --- a/modules/dds/texture_loader_dds.h +++ b/modules/dds/texture_loader_dds.h @@ -36,7 +36,7 @@ class ResourceFormatDDS : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index 6c713fa1ce..c8f32ffde6 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -124,6 +124,22 @@ Configure the [CryptoKey] to use when [member use_dtls] is [code]true[/code]. Remember to also call [method set_dtls_certificate] to setup your [X509Certificate]. </description> </method> + <method name="set_peer_timeout"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="timeout_limit" type="int"> + </argument> + <argument index="2" name="timeout_min" type="int"> + </argument> + <argument index="3" name="timeout_max" type="int"> + </argument> + <description> + Sets the timeout parameters for a peer. The timeout parameters control how and when a peer will timeout from a failure to acknowledge reliable traffic. Timeout values are expressed in milliseconds. + The [code]timeout_limit[/code] is a factor that, multiplied by a value based on the avarage round trip time, will determine the timeout limit for a reliable packet. When that limit is reached, the timeout will be doubled, and the peer will be disconnected if that limit has reached [code]timeout_min[/code]. The [code]timeout_max[/code] parameter, on the other hand, defines a fixed timeout for which any packet must be acknowledged or the peer will be dropped. + </description> + </method> </methods> <members> <member name="always_ordered" type="bool" setter="set_always_ordered" getter="is_always_ordered" default="false"> diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index 91984b8928..276f13e553 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -784,6 +784,14 @@ int NetworkedMultiplayerENet::get_peer_port(int p_peer_id) const { #endif } +void NetworkedMultiplayerENet::set_peer_timeout(int p_peer_id, int p_timeout_limit, int p_timeout_min, int p_timeout_max) { + ERR_FAIL_COND_MSG(!peer_map.has(p_peer_id), vformat("Peer ID %d not found in the list of peers.", p_peer_id)); + ERR_FAIL_COND_MSG(!is_server() && p_peer_id != 1, "Can't change the timeout of peers other then the server when acting as a client."); + ERR_FAIL_COND_MSG(peer_map[p_peer_id] == nullptr, vformat("Peer ID %d found in the list of peers, but is null.", p_peer_id)); + ERR_FAIL_COND_MSG(p_timeout_limit > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less then maximum timeout"); + enet_peer_timeout(peer_map[p_peer_id], p_timeout_limit, p_timeout_min, p_timeout_max); +} + void NetworkedMultiplayerENet::set_transfer_channel(int p_channel) { ERR_FAIL_COND_MSG(p_channel < -1 || p_channel >= channel_count, vformat("The transfer channel must be set between 0 and %d, inclusive (got %d).", channel_count - 1, p_channel)); ERR_FAIL_COND_MSG(p_channel == SYSCH_CONFIG, vformat("The channel %d is reserved.", SYSCH_CONFIG)); @@ -838,6 +846,7 @@ void NetworkedMultiplayerENet::_bind_methods() { ClassDB::bind_method(D_METHOD("is_dtls_verify_enabled"), &NetworkedMultiplayerENet::is_dtls_verify_enabled); ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &NetworkedMultiplayerENet::get_peer_address); ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &NetworkedMultiplayerENet::get_peer_port); + ClassDB::bind_method(D_METHOD("set_peer_timeout", "id", "timeout_limit", "timeout_min", "timeout_max"), &NetworkedMultiplayerENet::set_peer_timeout); ClassDB::bind_method(D_METHOD("get_packet_channel"), &NetworkedMultiplayerENet::get_packet_channel); ClassDB::bind_method(D_METHOD("get_last_packet_channel"), &NetworkedMultiplayerENet::get_last_packet_channel); diff --git a/modules/enet/networked_multiplayer_enet.h b/modules/enet/networked_multiplayer_enet.h index eb70d71c2c..b99b14d218 100644 --- a/modules/enet/networked_multiplayer_enet.h +++ b/modules/enet/networked_multiplayer_enet.h @@ -127,6 +127,7 @@ public: virtual IP_Address get_peer_address(int p_peer_id) const; virtual int get_peer_port(int p_peer_id) const; + void set_peer_timeout(int p_peer_id, int p_timeout_limit, int p_timeout_min, int p_timeout_max); Error create_server(int p_port, int p_max_clients = 32, int p_in_bandwidth = 0, int p_out_bandwidth = 0); Error create_client(const String &p_address, int p_port, int p_in_bandwidth = 0, int p_out_bandwidth = 0, int p_client_port = 0); diff --git a/modules/etc/texture_loader_pkm.cpp b/modules/etc/texture_loader_pkm.cpp index 456473672d..95db9315d5 100644 --- a/modules/etc/texture_loader_pkm.cpp +++ b/modules/etc/texture_loader_pkm.cpp @@ -42,7 +42,7 @@ struct ETC1Header { uint16_t origHeight = 0; }; -RES ResourceFormatPKM::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatPKM::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_CANT_OPEN; } diff --git a/modules/etc/texture_loader_pkm.h b/modules/etc/texture_loader_pkm.h index 67fbee3a7e..2ed5e75807 100644 --- a/modules/etc/texture_loader_pkm.h +++ b/modules/etc/texture_loader_pkm.h @@ -36,7 +36,7 @@ class ResourceFormatPKM : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index e3a359e09a..86bd8b820d 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -110,6 +110,16 @@ bool GDNativeLibrary::_get(const StringName &p_name, Variant &r_property) const return false; } +void GDNativeLibrary::reset_state() { + config_file.instance(); + current_library_path = ""; + current_dependencies.clear(); + symbol_prefix = default_symbol_prefix; + load_once = default_load_once; + singleton = default_singleton; + reloadable = default_reloadable; +} + void GDNativeLibrary::_get_property_list(List<PropertyInfo> *p_list) const { // set entries List<String> entry_key_list; @@ -508,7 +518,7 @@ Error GDNative::get_symbol(StringName p_procedure_name, void *&r_handle, bool p_ return result; } -RES GDNativeLibraryResourceLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES GDNativeLibraryResourceLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { Ref<GDNativeLibrary> lib; lib.instance(); diff --git a/modules/gdnative/gdnative.h b/modules/gdnative/gdnative.h index 765087d176..a28c58ec0d 100644 --- a/modules/gdnative/gdnative.h +++ b/modules/gdnative/gdnative.h @@ -63,6 +63,8 @@ class GDNativeLibrary : public Resource { bool reloadable; public: + virtual void reset_state() override; + GDNativeLibrary(); ~GDNativeLibrary(); @@ -166,7 +168,7 @@ public: class GDNativeLibraryResourceLoader : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/gdnative/include/text/godot_text.h b/modules/gdnative/include/text/godot_text.h index 44fac3c190..86fc745134 100644 --- a/modules/gdnative/include/text/godot_text.h +++ b/modules/gdnative/include/text/godot_text.h @@ -74,11 +74,19 @@ typedef struct { godot_rid (*create_font_system)(void *, const godot_string *, int); godot_rid (*create_font_resource)(void *, const godot_string *, int); godot_rid (*create_font_memory)(void *, const uint8_t *, size_t, godot_string *, int); + godot_rid (*create_font_bitmap)(void *, float, float, int); + void (*font_bitmap_add_texture)(void *, godot_rid *, const godot_object *); + void (*font_bitmap_add_char)(void *, godot_rid *, char32_t, int, const godot_rect2 *, const godot_vector2 *, float); + void (*font_bitmap_add_kerning_pair)(void *, godot_rid *, char32_t, char32_t, int); float (*font_get_height)(void *, godot_rid *, int); float (*font_get_ascent)(void *, godot_rid *, int); float (*font_get_descent)(void *, godot_rid *, int); float (*font_get_underline_position)(void *, godot_rid *, int); float (*font_get_underline_thickness)(void *, godot_rid *, int); + int (*font_get_spacing_space)(void *, godot_rid *); + void (*font_set_spacing_space)(void *, godot_rid *, int); + int (*font_get_spacing_glyph)(void *, godot_rid *); + void (*font_set_spacing_glyph)(void *, godot_rid *, int); void (*font_set_antialiased)(void *, godot_rid *, bool); bool (*font_get_antialiased)(void *, godot_rid *); godot_dictionary (*font_get_feature_list)(void *, godot_rid *); diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index def020adad..1bdbb0b03b 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -1670,7 +1670,7 @@ void NativeScriptLanguage::defer_init_library(Ref<GDNativeLibrary> lib, NativeSc MutexLock lock(mutex); libs_to_init.insert(lib); scripts_to_register.insert(script); - has_objects_to_register = true; + has_objects_to_register.set(); } #endif @@ -1728,7 +1728,7 @@ void NativeScriptLanguage::unregister_script(NativeScript *script) { library_script_users.erase(S); Map<String, Ref<GDNative>>::Element *G = library_gdnatives.find(script->lib_path); - if (G) { + if (G && G->get()->get_library()->is_reloadable()) { G->get()->terminate(); library_gdnatives.erase(G); } @@ -1759,7 +1759,7 @@ void NativeScriptLanguage::call_libraries_cb(const StringName &name) { void NativeScriptLanguage::frame() { #ifndef NO_THREADS - if (has_objects_to_register) { + if (has_objects_to_register.is_set()) { MutexLock lock(mutex); for (Set<Ref<GDNativeLibrary>>::Element *L = libs_to_init.front(); L; L = L->next()) { init_library(L->get()); @@ -1769,7 +1769,7 @@ void NativeScriptLanguage::frame() { register_script(S->get()); } scripts_to_register.clear(); - has_objects_to_register = false; + has_objects_to_register.clear(); } #endif @@ -1940,7 +1940,7 @@ void NativeReloadNode::_notification(int p_what) { #endif } -RES ResourceFormatLoaderNativeScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderNativeScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_no_cache) { return ResourceFormatLoaderText::singleton->load(p_path, p_original_path, r_error); } diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index ea7ced6511..d6ba2bbec1 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -40,6 +40,7 @@ #include "core/os/thread_safe.h" #include "core/templates/oa_hash_map.h" #include "core/templates/ordered_hash_map.h" +#include "core/templates/safe_refcount.h" #include "core/templates/self_list.h" #include "scene/main/node.h" @@ -262,7 +263,7 @@ private: #ifndef NO_THREADS Set<Ref<GDNativeLibrary>> libs_to_init; Set<NativeScript *> scripts_to_register; - volatile bool has_objects_to_register = false; // so that we don't lock mutex every frame - it's rarely needed + SafeFlag has_objects_to_register; // so that we don't lock mutex every frame - it's rarely needed void defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script); #endif @@ -402,7 +403,7 @@ public: class ResourceFormatLoaderNativeScript : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/gdnative/pluginscript/pluginscript_loader.cpp b/modules/gdnative/pluginscript/pluginscript_loader.cpp index cd1879a13e..f2165cd225 100644 --- a/modules/gdnative/pluginscript/pluginscript_loader.cpp +++ b/modules/gdnative/pluginscript/pluginscript_loader.cpp @@ -39,7 +39,7 @@ ResourceFormatLoaderPluginScript::ResourceFormatLoaderPluginScript(PluginScriptL _language = language; } -RES ResourceFormatLoaderPluginScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderPluginScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } diff --git a/modules/gdnative/pluginscript/pluginscript_loader.h b/modules/gdnative/pluginscript/pluginscript_loader.h index 7b1a7f5423..e5d665c186 100644 --- a/modules/gdnative/pluginscript/pluginscript_loader.h +++ b/modules/gdnative/pluginscript/pluginscript_loader.h @@ -43,7 +43,7 @@ class ResourceFormatLoaderPluginScript : public ResourceFormatLoader { public: ResourceFormatLoaderPluginScript(PluginScriptLanguage *language); - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/gdnative/text/text_server_gdnative.cpp b/modules/gdnative/text/text_server_gdnative.cpp index f7a3cb8135..7cd8de5f2e 100644 --- a/modules/gdnative/text/text_server_gdnative.cpp +++ b/modules/gdnative/text/text_server_gdnative.cpp @@ -113,6 +113,28 @@ RID TextServerGDNative::create_font_memory(const uint8_t *p_data, size_t p_size, return rid; } +RID TextServerGDNative::create_font_bitmap(float p_height, float p_ascent, int p_base_size) { + ERR_FAIL_COND_V(interface == nullptr, RID()); + godot_rid result = interface->create_font_bitmap(data, p_height, p_ascent, p_base_size); + RID rid = *(RID *)&result; + return rid; +} + +void TextServerGDNative::font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) { + ERR_FAIL_COND(interface == nullptr); + interface->font_bitmap_add_texture(data, (godot_rid *)&p_font, (const godot_object *)p_texture.ptr()); +} + +void TextServerGDNative::font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { + ERR_FAIL_COND(interface == nullptr); + interface->font_bitmap_add_char(data, (godot_rid *)&p_font, p_char, p_texture_idx, (const godot_rect2 *)&p_rect, (const godot_vector2 *)&p_align, p_advance); +} + +void TextServerGDNative::font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) { + ERR_FAIL_COND(interface == nullptr); + interface->font_bitmap_add_kerning_pair(data, (godot_rid *)&p_font, p_A, p_B, p_kerning); +} + float TextServerGDNative::font_get_height(RID p_font, int p_size) const { ERR_FAIL_COND_V(interface == nullptr, 0.f); return interface->font_get_height(data, (godot_rid *)&p_font, p_size); @@ -138,6 +160,26 @@ float TextServerGDNative::font_get_underline_thickness(RID p_font, int p_size) c return interface->font_get_underline_thickness(data, (godot_rid *)&p_font, p_size); } +int TextServerGDNative::font_get_spacing_space(RID p_font) const { + ERR_FAIL_COND_V(interface == nullptr, 0); + return interface->font_get_spacing_space(data, (godot_rid *)&p_font); +} + +void TextServerGDNative::font_set_spacing_space(RID p_font, int p_value) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_spacing_space(data, (godot_rid *)&p_font, p_value); +} + +int TextServerGDNative::font_get_spacing_glyph(RID p_font) const { + ERR_FAIL_COND_V(interface == nullptr, 0); + return interface->font_get_spacing_glyph(data, (godot_rid *)&p_font); +} + +void TextServerGDNative::font_set_spacing_glyph(RID p_font, int p_value) { + ERR_FAIL_COND(interface == nullptr); + interface->font_set_spacing_glyph(data, (godot_rid *)&p_font, p_value); +} + void TextServerGDNative::font_set_antialiased(RID p_font, bool p_antialiased) { ERR_FAIL_COND(interface == nullptr); interface->font_set_antialiased(data, (godot_rid *)&p_font, p_antialiased); diff --git a/modules/gdnative/text/text_server_gdnative.h b/modules/gdnative/text/text_server_gdnative.h index 9cbb94217e..931bb44885 100644 --- a/modules/gdnative/text/text_server_gdnative.h +++ b/modules/gdnative/text/text_server_gdnative.h @@ -64,6 +64,11 @@ public: virtual RID create_font_system(const String &p_name, int p_base_size = 16) override; virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) override; virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) override; + virtual RID create_font_bitmap(float p_height, float p_ascent, int p_base_size = 16) override; + + virtual void font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) override; + virtual void font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; + virtual void font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) override; virtual float font_get_height(RID p_font, int p_size) const override; virtual float font_get_ascent(RID p_font, int p_size) const override; @@ -72,6 +77,12 @@ public: virtual float font_get_underline_position(RID p_font, int p_size) const override; virtual float font_get_underline_thickness(RID p_font, int p_size) const override; + virtual int font_get_spacing_space(RID p_font) const override; + virtual void font_set_spacing_space(RID p_font, int p_value) override; + + virtual int font_get_spacing_glyph(RID p_font) const override; + virtual void font_set_spacing_glyph(RID p_font, int p_value) override; + virtual void font_set_antialiased(RID p_font, bool p_antialiased) override; virtual bool font_get_antialiased(RID p_font) const override; diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.cpp b/modules/gdnative/videodecoder/video_stream_gdnative.cpp index 18d26a9528..f2fb0a2fdc 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.cpp +++ b/modules/gdnative/videodecoder/video_stream_gdnative.cpp @@ -250,7 +250,7 @@ void VideoStreamPlaybackGDNative::play() { playing = true; - delay_compensation = ProjectSettings::get_singleton()->get("audio/video_delay_compensation_ms"); + delay_compensation = ProjectSettings::get_singleton()->get("audio/video/video_delay_compensation_ms"); delay_compensation /= 1000.0; } @@ -360,7 +360,7 @@ void VideoStreamGDNative::set_audio_track(int p_track) { /* --- NOTE ResourceFormatLoaderVideoStreamGDNative starts here. ----- */ -RES ResourceFormatLoaderVideoStreamGDNative::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderVideoStreamGDNative::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { if (r_error) { diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.h b/modules/gdnative/videodecoder/video_stream_gdnative.h index 3db5609952..140888cd4b 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.h +++ b/modules/gdnative/videodecoder/video_stream_gdnative.h @@ -196,7 +196,7 @@ public: class ResourceFormatLoaderVideoStreamGDNative : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 6e930b6bf4..9d0d91162c 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -44,7 +44,7 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve // Search strings in AssignmentNode -> text = "__", hint_tooltip = "__" etc. Error err; - RES loaded_res = ResourceLoader::load(p_path, "", false, &err); + RES loaded_res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); if (err) { ERR_PRINT("Failed to load " + p_path); return err; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index f891145988..a129b73c1a 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2316,7 +2316,7 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na /*************** RESOURCE ***************/ -RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index ee270f6b2f..12c909fd4f 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -529,7 +529,7 @@ public: class ResourceFormatLoaderGDScript : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index b491440d4c..06d628d23f 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -255,36 +255,59 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } // Try class constants. - GDScript *owner = codegen.script; - while (owner) { - GDScript *scr = owner; - GDScriptNativeClass *nc = nullptr; - while (scr) { - if (scr->constants.has(identifier)) { - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS_CONSTANT, gen->add_or_get_name(identifier)); // TODO: Get type here. + { + GDScript *owner = codegen.script; + while (owner) { + GDScript *scr = owner; + GDScriptNativeClass *nc = nullptr; + while (scr) { + if (scr->constants.has(identifier)) { + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS_CONSTANT, gen->add_or_get_name(identifier)); // TODO: Get type here. + } + if (scr->native.is_valid()) { + nc = scr->native.ptr(); + } + scr = scr->_base; } - if (scr->native.is_valid()) { - nc = scr->native.ptr(); + + // Class C++ integer constant. + if (nc) { + bool success = false; + int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); + if (success) { + return codegen.add_constant(constant); + } } - scr = scr->_base; + + owner = owner->_owner; } + } - // Class C++ integer constant. - if (nc) { - bool success = false; - int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); - if (success) { - return codegen.add_constant(constant); + // Try signals and methods (can be made callables). + { + if (codegen.class_node->members_indices.has(identifier)) { + const GDScriptParser::ClassNode::Member &member = codegen.class_node->members[codegen.class_node->members_indices[identifier]]; + if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + // Get like it was a property. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. + GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); + + gen->write_get_named(temp, identifier, self); + return temp; } } - owner = owner->_owner; - } + // Try in native base. + GDScript *scr = codegen.script; + GDScriptNativeClass *nc = nullptr; + while (scr) { + if (scr->native.is_valid()) { + nc = scr->native.ptr(); + } + scr = scr->_base; + } - // Try signals and methods (can be made callables); - if (codegen.class_node->members_indices.has(identifier)) { - const GDScriptParser::ClassNode::Member &member = codegen.class_node->members[codegen.class_node->members_indices[identifier]]; - if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + if (nc && (ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) { // Get like it was a property. GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); @@ -1153,7 +1176,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c codegen.generator->write_and_left_operand(result_addr); // Check value equality. - codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_value_addr, expr_addr); + codegen.generator->write_binary_operator(equality_test_addr, Variant::OP_EQUAL, p_value_addr, expr_addr); codegen.generator->write_and_right_operand(equality_test_addr); // AND both type and value equality. diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index b17971cf93..a9975c8602 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2261,10 +2261,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } r_arghint = _make_arguments_hint(info, p_argidx); - return; } - if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) { + if (p_argidx == 0 && ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node")) { // Get autoloads List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a77fb14064..08645d371c 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2080,6 +2080,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN return operation; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { + // check that NOT is followed by IN by consuming it before calling parse_binary_operator which will only receive a plain IN + consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "not" in content-test operator.)"); + ExpressionNode *in_operation = parse_binary_operator(p_previous_operand, p_can_assign); + UnaryOpNode *operation = alloc_node<UnaryOpNode>(); + operation->operation = UnaryOpNode::OP_LOGIC_NOT; + operation->variant_op = Variant::OP_NOT; + operation->operand = in_operation; + return operation; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { GDScriptTokenizer::Token op = previous; BinaryOpNode *operation = alloc_node<BinaryOpNode>(); @@ -2906,7 +2917,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty // Logical { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AND, { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // OR, - { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // NOT, + { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_not_in_operator, PREC_CONTENT_TEST }, // NOT, { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AMPERSAND_AMPERSAND, { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // PIPE_PIPE, { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // BANG, @@ -3157,11 +3168,16 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node push_error(R"(Cannot use "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); return false; } - if (variable->initializer->type != Node::LITERAL) { + if (variable->initializer->type == Node::LITERAL) { + variable->export_info.type = static_cast<LiteralNode *>(variable->initializer)->value.get_type(); + } else if (variable->initializer->type == Node::ARRAY) { + variable->export_info.type = Variant::ARRAY; + } else if (variable->initializer->type == Node::DICTIONARY) { + variable->export_info.type = Variant::DICTIONARY; + } else { push_error(R"(To use "@export" annotation with type-less variable, the default value must be a literal.)", p_annotation); return false; } - variable->export_info.type = static_cast<LiteralNode *>(variable->initializer)->value.get_type(); } // else: Actual type will be set by the analyzer, which can infer the proper type. } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index d59b68b602..a4b1d4c866 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1285,6 +1285,7 @@ private: ExpressionNode *parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_array(ExpressionNode *p_previous_operand, bool p_can_assign); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 5e3d6213d3..912c9a174e 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -163,7 +163,7 @@ void GDScriptLanguageProtocol::_bind_methods() { ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized); ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected); ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected); - ClassDB::bind_method(D_METHOD("notify_client", "method", "params"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("notify_client", "method", "params", "client_id"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled); ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document); ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 96744a15d7..6635098be2 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -1661,7 +1661,7 @@ struct ServerCapabilities { signatureHelpProvider.triggerCharacters.push_back(","); signatureHelpProvider.triggerCharacters.push_back("("); dict["signatureHelpProvider"] = signatureHelpProvider.to_json(); - dict["codeLensProvider"] = false; // codeLensProvider.to_json(); + //dict["codeLensProvider"] = codeLensProvider.to_json(); dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json(); dict["renameProvider"] = renameProvider.to_json(); dict["documentLinkProvider"] = documentLinkProvider.to_json(); diff --git a/modules/gltf/gltf_skeleton.cpp b/modules/gltf/gltf_skeleton.cpp index 739779d3bd..d6c7a25eaf 100644 --- a/modules/gltf/gltf_skeleton.cpp +++ b/modules/gltf/gltf_skeleton.cpp @@ -41,7 +41,7 @@ void GLTFSkeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_godot_bone_node"), &GLTFSkeleton::get_godot_bone_node); ClassDB::bind_method(D_METHOD("set_godot_bone_node", "godot_bone_node"), &GLTFSkeleton::set_godot_bone_node); ClassDB::bind_method(D_METHOD("get_bone_attachment_count"), &GLTFSkeleton::get_bone_attachment_count); - ClassDB::bind_method(D_METHOD("get_bone_attachment"), &GLTFSkeleton::get_bone_attachment); + ClassDB::bind_method(D_METHOD("get_bone_attachment", "idx"), &GLTFSkeleton::get_bone_attachment); ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "joints"), "set_joints", "get_joints"); // Vector<GLTFNodeIndex> ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "roots"), "set_roots", "get_roots"); // Vector<GLTFNodeIndex> diff --git a/modules/gltf/gltf_skin.cpp b/modules/gltf/gltf_skin.cpp index fd39e4f45a..5a61e5778c 100644 --- a/modules/gltf/gltf_skin.cpp +++ b/modules/gltf/gltf_skin.cpp @@ -142,7 +142,7 @@ void GLTFSkin::set_joint_i_to_name(Dictionary p_joint_i_to_name) { joint_i_to_name = Map<int, StringName>(); Array keys = p_joint_i_to_name.keys(); for (int i = 0; i < keys.size(); i++) { - joint_i_to_name[keys[i]] = joint_i_to_name[keys[i]]; + joint_i_to_name[keys[i]] = p_joint_i_to_name[keys[i]]; } } diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index c2336bc913..ff9778e7d8 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -51,8 +51,8 @@ void GLTFState::_bind_methods() { ClassDB::bind_method(D_METHOD("set_accessors", "accessors"), &GLTFState::set_accessors); ClassDB::bind_method(D_METHOD("get_meshes"), &GLTFState::get_meshes); ClassDB::bind_method(D_METHOD("set_meshes", "meshes"), &GLTFState::set_meshes); - ClassDB::bind_method(D_METHOD("get_animation_players_count"), &GLTFState::get_animation_players_count); - ClassDB::bind_method(D_METHOD("get_animation_player"), &GLTFState::get_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_players_count", "idx"), &GLTFState::get_animation_players_count); + ClassDB::bind_method(D_METHOD("get_animation_player", "idx"), &GLTFState::get_animation_player); ClassDB::bind_method(D_METHOD("get_materials"), &GLTFState::get_materials); ClassDB::bind_method(D_METHOD("set_materials", "materials"), &GLTFState::set_materials); ClassDB::bind_method(D_METHOD("get_scene_name"), &GLTFState::get_scene_name); @@ -79,7 +79,7 @@ void GLTFState::_bind_methods() { ClassDB::bind_method(D_METHOD("set_skeleton_to_node", "skeleton_to_node"), &GLTFState::set_skeleton_to_node); ClassDB::bind_method(D_METHOD("get_animations"), &GLTFState::get_animations); ClassDB::bind_method(D_METHOD("set_animations", "animations"), &GLTFState::set_animations); - ClassDB::bind_method(D_METHOD("get_scene_node"), &GLTFState::get_scene_node); + ClassDB::bind_method(D_METHOD("get_scene_node", "idx"), &GLTFState::get_scene_node); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "json"), "set_json", "get_json"); // Dictionary ADD_PROPERTY(PropertyInfo(Variant::INT, "major_version"), "set_major_version", "get_major_version"); // int diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 134aadb75e..e28cc57f9b 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -41,6 +41,7 @@ <return type="Array"> </return> <description> + Returns an array of [ArrayMesh]es and [Transform] references of all bake meshes that exist within the current GridMap. </description> </method> <method name="get_cell_item" qualifiers="const"> diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 565830c16f..da9cdb9bc5 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -1172,7 +1172,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { floor->set_min(-32767); floor->set_max(32767); floor->set_step(1); - floor->get_line_edit()->add_theme_constant_override("minimum_spaces", 16); + floor->get_line_edit()->add_theme_constant_override("minimum_character_width", 16); spatial_editor_hb->add_child(floor); floor->connect("value_changed", callable_mp(this, &GridMapEditor::_floor_changed)); diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 82aaa492fc..61ebabdfb6 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -1225,23 +1225,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d switch (p_quality) { case BAKE_QUALITY_LOW: { - push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/low_quality_ray_count"); + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_ray_count"); } break; case BAKE_QUALITY_MEDIUM: { - push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/medium_quality_ray_count"); + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_ray_count"); } break; case BAKE_QUALITY_HIGH: { - push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/high_quality_ray_count"); + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_ray_count"); } break; case BAKE_QUALITY_ULTRA: { - push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/ultra_quality_ray_count"); + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_ray_count"); } break; } push_constant.ray_count = CLAMP(push_constant.ray_count, 16, 8192); - int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/gpu_lightmapper/performance/region_size"))); - int max_rays = GLOBAL_GET("rendering/gpu_lightmapper/performance/max_rays_per_pass"); + int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/lightmapping/bake_performance/region_size"))); + int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_pass"); int x_regions = (atlas_size.width - 1) / max_region_size + 1; int y_regions = (atlas_size.height - 1) / max_region_size + 1; @@ -1347,23 +1347,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d switch (p_quality) { case BAKE_QUALITY_LOW: { - push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/low_quality_probe_ray_count"); + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_probe_ray_count"); } break; case BAKE_QUALITY_MEDIUM: { - push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/medium_quality_probe_ray_count"); + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_probe_ray_count"); } break; case BAKE_QUALITY_HIGH: { - push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/high_quality_probe_ray_count"); + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_probe_ray_count"); } break; case BAKE_QUALITY_ULTRA: { - push_constant.ray_count = GLOBAL_GET("rendering/gpu_lightmapper/quality/ultra_quality_probe_ray_count"); + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count"); } break; } push_constant.atlas_size[0] = probe_positions.size(); push_constant.ray_count = CLAMP(push_constant.ray_count, 16, 8192); - int max_rays = GLOBAL_GET("rendering/gpu_lightmapper/performance/max_rays_per_probe_pass"); + int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_probe_pass"); int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1; for (int i = 0; i < ray_iterations; i++) { diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp index a7b8c063fd..191bb3d765 100644 --- a/modules/lightmapper_rd/register_types.cpp +++ b/modules/lightmapper_rd/register_types.cpp @@ -41,18 +41,18 @@ static Lightmapper *create_lightmapper_rd() { #endif void register_lightmapper_rd_types() { - GLOBAL_DEF("rendering/gpu_lightmapper/quality/low_quality_ray_count", 16); - GLOBAL_DEF("rendering/gpu_lightmapper/quality/medium_quality_ray_count", 64); - GLOBAL_DEF("rendering/gpu_lightmapper/quality/high_quality_ray_count", 256); - GLOBAL_DEF("rendering/gpu_lightmapper/quality/ultra_quality_ray_count", 1024); - GLOBAL_DEF("rendering/gpu_lightmapper/performance/max_rays_per_pass", 32); - GLOBAL_DEF("rendering/gpu_lightmapper/performance/region_size", 512); + GLOBAL_DEF("rendering/lightmapping/bake_quality/low_quality_ray_count", 16); + GLOBAL_DEF("rendering/lightmapping/bake_quality/medium_quality_ray_count", 64); + GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_ray_count", 256); + GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_ray_count", 1024); + GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_pass", 32); + GLOBAL_DEF("rendering/lightmapping/bake_performance/region_size", 512); - GLOBAL_DEF("rendering/gpu_lightmapper/quality/low_quality_probe_ray_count", 64); - GLOBAL_DEF("rendering/gpu_lightmapper/quality/medium_quality_probe_ray_count", 256); - GLOBAL_DEF("rendering/gpu_lightmapper/quality/high_quality_probe_ray_count", 512); - GLOBAL_DEF("rendering/gpu_lightmapper/quality/ultra_quality_probe_ray_count", 2048); - GLOBAL_DEF("rendering/gpu_lightmapper/performance/max_rays_per_probe_pass", 64); + GLOBAL_DEF("rendering/lightmapping/bake_quality/low_quality_probe_ray_count", 64); + GLOBAL_DEF("rendering/lightmapping/bake_quality/medium_quality_probe_ray_count", 256); + GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512); + GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048); + GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64); #ifndef _3D_DISABLED ClassDB::register_class<LightmapperRD>(); Lightmapper::create_gpu = create_lightmapper_rd; diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 8627f71987..b128b81000 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -159,7 +159,8 @@ void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) { const uint8_t *src_datar = p_data.ptr(); mp3dec_ex_t mp3d; - mp3dec_ex_open_buf(&mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE); + int err = mp3dec_ex_open_buf(&mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE); + ERR_FAIL_COND(err != 0); channels = mp3d.info.channels; sample_rate = mp3d.info.hz; diff --git a/modules/mono/Directory.Build.props b/modules/mono/Directory.Build.props new file mode 100644 index 0000000000..fbf864b11b --- /dev/null +++ b/modules/mono/Directory.Build.props @@ -0,0 +1,3 @@ +<Project> + <Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" /> +</Project> diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props new file mode 100644 index 0000000000..396443f30e --- /dev/null +++ b/modules/mono/SdkPackageVersions.props @@ -0,0 +1,6 @@ +<Project> + <PropertyGroup> + <PackageVersion_Godot_NET_Sdk>4.0.0-dev4</PackageVersion_Godot_NET_Sdk> + <PackageVersion_Godot_SourceGenerators>4.0.0-dev1</PackageVersion_Godot_SourceGenerators> + </PropertyGroup> +</Project> diff --git a/modules/mono/build_scripts/godot_net_sdk_build.py b/modules/mono/build_scripts/godot_net_sdk_build.py index 3bfba0f0f6..8c5a60d2db 100644 --- a/modules/mono/build_scripts/godot_net_sdk_build.py +++ b/modules/mono/build_scripts/godot_net_sdk_build.py @@ -21,6 +21,18 @@ def build_godot_net_sdk(source, target, env): # No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them. +def get_nupkgs_versions(props_file): + import xml.etree.ElementTree as ET + + tree = ET.parse(props_file) + root = tree.getroot() + + return { + "Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(), + "Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(), + } + + def build(env_mono): assert env_mono["tools"] @@ -30,14 +42,12 @@ def build(env_mono): module_dir = os.getcwd() - package_version_file = os.path.join( - module_dir, "editor", "Godot.NET.Sdk", "Godot.NET.Sdk", "Godot.NET.Sdk_PackageVersion.txt" - ) - - with open(package_version_file, mode="r") as f: - version = f.read().strip() + nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props")) - target_filenames = ["Godot.NET.Sdk.%s.nupkg" % version] + target_filenames = [ + "Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"], + "Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"], + ] targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames] diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index bd29dc1876..4fca80fca0 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -31,6 +31,7 @@ #include "csharp_script.h" #include <mono/metadata/threads.h> +#include <mono/metadata/tokentype.h> #include <stdint.h> #include "core/config/project_settings.h" @@ -1182,46 +1183,56 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #endif -void CSharpLanguage::_load_scripts_metadata() { - scripts_metadata.clear(); +void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) { + if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) { + return; + } - String scripts_metadata_filename = "scripts_metadata."; + MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute)); + String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr); -#ifdef TOOLS_ENABLED - scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player"; -#else -#ifdef DEBUG_ENABLED - scripts_metadata_filename += "debug"; -#else - scripts_metadata_filename += "release"; -#endif -#endif + dotnet_script_lookup_map[path] = DotNetScriptLookupInfo( + p_class->get_namespace(), p_class->get_name(), p_class); +} - String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename); +void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) { + if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) { + MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute)); + bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr); - if (FileAccess::exists(scripts_metadata_path)) { - String old_json; + if (requires_lookup) { + // This is supported for scenarios where specifying all types would be cumbersome, + // such as when disabling C# source generators (for whatever reason) or when using a + // language other than C# that has nothing similar to source generators to automate it. + MonoImage *image = p_assembly->get_image(); - Error ferr = read_all_file_utf8(scripts_metadata_path, old_json); + int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - ERR_FAIL_COND(ferr != OK); + for (int i = 1; i < rows; i++) { + // We don't search inner classes, only top-level. + MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); - Variant old_dict_var; - String err_str; - int err_line; - Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line); - if (json_err != OK) { - ERR_PRINT("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")."); - return; - } + if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { + continue; + } - scripts_metadata = old_dict_var.operator Dictionary(); - scripts_metadata_invalidated = false; + GDMonoClass *current = p_assembly->get_class(mono_class); + if (current) { + lookup_script_for_class(current); + } + } + } else { + // This is the most likely scenario as we use C# source generators + MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr); - print_verbose("Successfully loaded scripts metadata"); - } else { - if (!Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT("Missing scripts metadata file."); + int length = mono_array_length(script_types); + + for (int i = 0; i < length; i++) { + MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i); + ManagedType type = ManagedType::from_reftype(reftype); + ERR_CONTINUE(!type.type_class); + lookup_script_for_class(type.type_class); + } } } } @@ -1300,7 +1311,7 @@ void CSharpLanguage::_on_scripts_domain_unloaded() { } #endif - scripts_metadata_invalidated = true; + dotnet_script_lookup_map.clear(); } #ifdef TOOLS_ENABLED @@ -3356,45 +3367,34 @@ Error CSharpScript::reload(bool p_keep_state) { GD_MONO_SCOPE_THREAD_ATTACH; - GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly(); - - if (project_assembly) { - const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path()); - if (script_metadata_var) { - Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"]; - const Variant *namespace_ = script_metadata.getptr("namespace"); - const Variant *class_name = script_metadata.getptr("class_name"); - ERR_FAIL_NULL_V(namespace_, ERR_BUG); - ERR_FAIL_NULL_V(class_name, ERR_BUG); - GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String()); - if (klass && CACHED_CLASS(GodotObject)->is_assignable_from(klass)) { - script_class = klass; - } - } else { - // Missing script metadata. Fallback to legacy method - script_class = project_assembly->get_object_derived_class(name); + const DotNetScriptLookupInfo *lookup_info = + CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path()); + + if (lookup_info) { + GDMonoClass *klass = lookup_info->script_class; + if (klass) { + ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED); + script_class = klass; } + } - valid = script_class != nullptr; + valid = script_class != nullptr; - if (script_class) { + if (script_class) { #ifdef DEBUG_ENABLED - print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); + print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); #endif - native = GDMonoUtils::get_class_native_base(script_class); + native = GDMonoUtils::get_class_native_base(script_class); - CRASH_COND(native == nullptr); + CRASH_COND(native == nullptr); - update_script_class_info(this); + update_script_class_info(this); - _update_exports(); - } - - return OK; + _update_exports(); } - return ERR_FILE_MISSING_DEPENDENCIES; + return OK; } ScriptLanguage *CSharpScript::get_language() const { @@ -3644,7 +3644,7 @@ void CSharpScript::get_members(Set<StringName> *p_members) { /*************** RESOURCE ***************/ -RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 85edd8b9c6..40f7ed4552 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -66,6 +66,18 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { #define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst)) +struct DotNetScriptLookupInfo { + String class_namespace; + String class_name; + GDMonoClass *script_class = nullptr; + + DotNetScriptLookupInfo() {} // Required by HashMap... + + DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) : + class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) { + } +}; + class CSharpScript : public Script { GDCLASS(CSharpScript, Script); @@ -390,16 +402,15 @@ class CSharpLanguage : public ScriptLanguage { int lang_idx = -1; - Dictionary scripts_metadata; - bool scripts_metadata_invalidated = true; + HashMap<String, DotNetScriptLookupInfo> dotnet_script_lookup_map; + + void lookup_script_for_class(GDMonoClass *p_class); // For debug_break and debug_break_parse int _debug_parse_err_line = -1; String _debug_parse_err_file; String _debug_error; - void _load_scripts_metadata(); - friend class GDMono; void _on_scripts_domain_unloaded(); @@ -436,18 +447,13 @@ public: void reload_assemblies(bool p_soft_reload); #endif - _FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() { - return scripts_metadata_invalidated ? Dictionary() : scripts_metadata; - } + _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; } - _FORCE_INLINE_ const Dictionary &get_scripts_metadata() { - if (scripts_metadata_invalidated) { - _load_scripts_metadata(); - } - return scripts_metadata; - } + void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly); - _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; } + const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const { + return dotnet_script_lookup_map.getptr(p_script_path); + } String get_name() const override; @@ -542,7 +548,7 @@ public: class ResourceFormatLoaderCSharpScript : public ResourceFormatLoader { public: - RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false) override; + RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; void get_recognized_extensions(List<String> *p_extensions) const override; bool handles_type(const String &p_type) const override; String get_resource_type(const String &p_path) const override; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln index 56c0cb7703..d1868f52ef 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln @@ -2,6 +2,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +18,17 @@ Global {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU + {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.Build.0 = Release|Any CPU + {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU + {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 8304d9e321..ef8add0ba8 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -8,43 +8,33 @@ <PackageId>Godot.NET.Sdk</PackageId> <Version>4.0.0</Version> - <PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl> + <PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion> + <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl> + <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> <PackageType>MSBuildSdk</PackageType> <PackageTags>MSBuildSdk</PackageTags> + <PackageLicenseExpression>MIT</PackageLicenseExpression> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> - </PropertyGroup> - <PropertyGroup> - <NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile> - <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn> + <!-- Exclude target framework from the package dependencies as we don't include the build output --> + <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking> + <IncludeBuildOutput>false</IncludeBuildOutput> </PropertyGroup> - <Target Name="ReadGodotNETSdkVersion" BeforeTargets="BeforeBuild;BeforeRebuild;CoreCompile"> - <PropertyGroup> - <PackageVersion>$([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim())</PackageVersion> - </PropertyGroup> - </Target> + <ItemGroup> + <!-- Package Sdk\Sdk.props and Sdk\Sdk.targets file --> + <None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" Visible="false" /> + <None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" Visible="false" /> + <!-- SdkPackageVersions.props --> - <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') " DependsOnTargets="ReadGodotNETSdkVersion"> - <PropertyGroup> - <NuspecProperties> - id=$(PackageId); - description=$(Description); - authors=$(Authors); - version=$(PackageVersion); - packagetype=$(PackageType); - tags=$(PackageTags); - projecturl=$(PackageProjectUrl) - </NuspecProperties> - </PropertyGroup> - </Target> + <None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk" Visible="false" /> + </ItemGroup> <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"> <PropertyGroup> <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath> <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> </PropertyGroup> - <Copy SourceFiles="$(OutputPath)$(PackageId).$(PackageVersion).nupkg" - DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> + <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> </Target> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec deleted file mode 100644 index ba68a4da43..0000000000 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd"> - <metadata> - <id>$id$</id> - <version>$version$</version> - <description>$description$</description> - <authors>$authors$</authors> - <owners>$authors$</owners> - <projectUrl>$projecturl$</projectUrl> - <requireLicenseAcceptance>false</requireLicenseAcceptance> - <license type="expression">MIT</license> - <licenseUrl>https://licenses.nuget.org/MIT</licenseUrl> - <tags>$tags$</tags> - <packageTypes> - <packageType name="$packagetype$" /> - </packageTypes> - <repository url="$projecturl$" /> - </metadata> - <files> - <file src="Sdk\**" target="Sdk" /> - </files> -</package> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt deleted file mode 100644 index 34749489b9..0000000000 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt +++ /dev/null @@ -1 +0,0 @@ -4.0.0-dev3 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 5febcf3175..0128f5c706 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -1,4 +1,6 @@ <Project> + <Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" /> + <PropertyGroup> <!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. --> <GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk> @@ -94,6 +96,7 @@ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants> </PropertyGroup> + <!-- Godot API references --> <ItemGroup> <!-- TODO: diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets index f5afd75505..92e299d2f3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -14,4 +14,9 @@ --> <DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants> </PropertyGroup> + + <!-- C# source generators --> + <ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' "> + <PackageReference Include="Godot.SourceGenerators" Version="$(PackageVersion_Godot_SourceGenerators)" /> + </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs new file mode 100644 index 0000000000..5eaebc4474 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs @@ -0,0 +1,15 @@ +namespace Godot.SourceGenerators.Sample +{ + partial class Bar : Godot.Object + { + } + + // Foo in another file + partial class Foo + { + } + + partial class NotSameNameAsFile : Godot.Object + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs new file mode 100644 index 0000000000..21a5bfe560 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs @@ -0,0 +1,11 @@ +namespace Godot.SourceGenerators.Sample +{ + partial class Foo : Godot.Object + { + } + + // Foo again in the same file + partial class Foo + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj new file mode 100644 index 0000000000..24f7909861 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -0,0 +1,31 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + </PropertyGroup> + + <PropertyGroup> + <!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk --> + <GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir> + </PropertyGroup> + + <PropertyGroup> + <!-- The emitted files are not part of the compilation nor design. + They're only for peeking at the generated sources. Sometimes the + emitted files get corrupted, but that won't break anything. --> + <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> + <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj"> + <Private>False</Private> + </ProjectReference> + <ProjectReference Include="..\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + </ItemGroup> + + <!-- This file is imported automatically when using PackageReference to + reference Godot.SourceGenerators, but not when using ProjectReference --> + <Import Project="..\Godot.SourceGenerators\Godot.SourceGenerators.props" /> + +</Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs new file mode 100644 index 0000000000..4867c986e6 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators +{ + public static class Common + { + public static void ReportNonPartialGodotScriptClass( + GeneratorExecutionContext context, + ClassDeclarationSyntax cds, INamedTypeSymbol symbol + ) + { + string message = + "Missing partial modifier on declaration of type '" + + $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'"; + + string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " + + "declared with the partial modifier or annotated with the " + + $"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0001", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + cds.GetLocation(), + cds.SyntaxTree.FilePath)); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs new file mode 100644 index 0000000000..c3e74822d5 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators +{ + static class ExtensionMethods + { + public static bool TryGetGlobalAnalyzerProperty( + this GeneratorExecutionContext context, string property, out string? value + ) => context.AnalyzerConfigOptions.GlobalOptions + .TryGetValue("build_property." + property, out value); + + private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName) + { + if (symbol == null) + return false; + + while (true) + { + if (symbol.ToString() == baseName) + { + return true; + } + + if (symbol.BaseType != null) + { + symbol = symbol.BaseType; + continue; + } + + break; + } + + return false; + } + + private static bool IsGodotScriptClass( + this ClassDeclarationSyntax cds, Compilation compilation, + out INamedTypeSymbol? symbol + ) + { + var sm = compilation.GetSemanticModel(cds.SyntaxTree); + + var classTypeSymbol = sm.GetDeclaredSymbol(cds); + + if (classTypeSymbol?.BaseType == null + || !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object)) + { + symbol = null; + return false; + } + + symbol = classTypeSymbol; + return true; + } + + public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectGodotScriptClasses( + this IEnumerable<ClassDeclarationSyntax> source, + Compilation compilation + ) + { + foreach (var cds in source) + { + if (cds.IsGodotScriptClass(compilation, out var symbol)) + yield return (cds, symbol!); + } + } + + public static bool IsPartial(this ClassDeclarationSyntax cds) + => cds.Modifiers.Any(SyntaxKind.PartialKeyword); + + public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol) + => symbol.GetAttributes().Any(attr => + attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr); + + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + public static string FullQualifiedName(this INamedTypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj new file mode 100644 index 0000000000..224d7e5b5a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -0,0 +1,40 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>netstandard2.0</TargetFramework> + <LangVersion>8.0</LangVersion> + <Nullable>enable</Nullable> + </PropertyGroup> + <PropertyGroup> + <Description>Core C# source generator for Godot projects.</Description> + <Authors>Godot Engine contributors</Authors> + + <PackageId>Godot.SourceGenerators</PackageId> + <Version>4.0.0</Version> + <PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion> + <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl> + <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> + <PackageLicenseExpression>MIT</PackageLicenseExpression> + + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build --> + <IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency --> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" /> + </ItemGroup> + <ItemGroup> + <!-- Package the generator in the analyzer directory of the nuget package --> + <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> + + <!-- Package the props file --> + <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" /> + </ItemGroup> + + <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"> + <PropertyGroup> + <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath> + <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> + </PropertyGroup> + <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> + </Target> +</Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props new file mode 100644 index 0000000000..f9b47ad5b1 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props @@ -0,0 +1,7 @@ +<Project> + <ItemGroup> + <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk --> + <CompilerVisibleProperty Include="GodotProjectDir" /> + <CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" /> + </ItemGroup> +</Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs new file mode 100644 index 0000000000..29e41d155a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -0,0 +1,9 @@ +namespace Godot.SourceGenerators +{ + public static class GodotClasses + { + public const string Object = "Godot.Object"; + public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute"; + public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute"; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs new file mode 100644 index 0000000000..150e59e414 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptPathAttributeGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle) + && toggle == "disabled") + { + return; + } + + // NOTE: IsNullOrEmpty doesn't work well with nullable checks + // ReSharper disable once ReplaceWithStringIsNullOrEmpty + if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir) + || godotProjectDir!.Length == 0) + { + throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); + } + + var godotClasses = context.Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + // Ignore inner classes + .Where(cds => !(cds.Parent is ClassDeclarationSyntax)) + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute()) + return true; + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + ) + // Ignore classes whose name is not the same as the file name + .Where(x => Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name) + .GroupBy(x => x.symbol) + .ToDictionary(g => g.Key, g => g.Select(x => x.cds)); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, godotProjectDir, + symbol: godotClass.Key, + classDeclarations: godotClass.Value); + } + + if (godotClasses.Count <= 0) + return; + + AddScriptTypesAssemblyAttr(context, godotClasses); + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + string godotProjectDir, + INamedTypeSymbol symbol, + IEnumerable<ClassDeclarationSyntax> classDeclarations + ) + { + var attributesBuilder = new StringBuilder(); + + // Remember syntax trees for which we already added an attribute, to prevent unnecessary duplicates. + var attributedTrees = new List<SyntaxTree>(); + + foreach (var cds in classDeclarations) + { + if (attributedTrees.Contains(cds.SyntaxTree)) + continue; + + attributedTrees.Add(cds.SyntaxTree); + + if (attributesBuilder.Length != 0) + attributesBuilder.Append("\n "); + + attributesBuilder.Append(@"[ScriptPathAttribute(""res://"); + attributesBuilder.Append(RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir)); + attributesBuilder.Append(@""")]"); + } + + string classNs = symbol.ContainingNamespace.Name; + string className = symbol.Name; + + var source = $@"using Godot; +namespace {classNs} +{{ + {attributesBuilder} + partial class {className} + {{ + }} +}} +"; + context.AddSource(classNs + "." + className + "_ScriptPath_Generated", + SourceText.From(source, Encoding.UTF8)); + } + + private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context, + Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses) + { + var sourceBuilder = new StringBuilder(); + + sourceBuilder.Append("[assembly:"); + sourceBuilder.Append(GodotClasses.AssemblyHasScriptsAttr); + sourceBuilder.Append("(new System.Type[] {"); + + bool first = true; + + foreach (var godotClass in godotClasses) + { + var qualifiedName = godotClass.Key.ToDisplayString( + NullableFlowState.NotNull, SymbolDisplayFormat.FullyQualifiedFormat); + if (!first) + sourceBuilder.Append(", "); + first = false; + sourceBuilder.Append("typeof("); + sourceBuilder.Append(qualifiedName); + sourceBuilder.Append(")"); + } + + sourceBuilder.Append("})]\n"); + + context.AddSource("AssemblyScriptTypes_Generated", + SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } + + public void Initialize(GeneratorInitializationContext context) + { + } + + private static string RelativeToDir(string path, string dir) + { + // Make sure the directory ends with a path separator + dir = Path.Combine(dir, " ").TrimEnd(); + + if (Path.DirectorySeparatorChar == '\\') + dir = dir.Replace("/", "\\") + "\\"; + + var fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute); + var relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute); + + // MakeRelativeUri converts spaces to %20, hence why we need UnescapeDataString + return Uri.UnescapeDataString(relRoot.MakeRelativeUri(fullPath).ToString()); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 4e2c0f17cc..cdac9acb25 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -1,11 +1,5 @@ using System; -using GodotTools.Core; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; using Microsoft.Build.Construction; -using Microsoft.Build.Globbing; namespace GodotTools.ProjectEditor { @@ -31,47 +25,6 @@ namespace GodotTools.ProjectEditor return root != null ? new MSBuildProject(root) : null; } - private static List<string> GetAllFilesRecursive(string rootDirectory, string mask) - { - string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories); - - // We want relative paths - for (int i = 0; i < files.Length; i++) - { - files[i] = files[i].RelativeToPath(rootDirectory); - } - - return new List<string>(files); - } - - // NOTE: Assumes auto-including items. Only used by the scripts metadata generator, which will be replaced with source generators in the future. - public static IEnumerable<string> GetIncludeFiles(string projectPath, string itemType) - { - var excluded = new List<string>(); - var includedFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs"); - - var root = ProjectRootElement.Open(projectPath); - Debug.Assert(root != null); - - foreach (var item in root.Items) - { - if (string.IsNullOrEmpty(item.Condition)) - continue; - - if (item.ItemType != itemType) - continue; - - string normalizedRemove = item.Remove.NormalizePath(); - - var glob = MSBuildGlob.Parse(normalizedRemove); - excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile))); - } - - includedFiles.RemoveAll(f => excluded.Contains(f)); - - return includedFiles; - } - public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName) { var origRoot = project.Root; diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets index 1d382dcb43..aab2d73bdd 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -3,7 +3,6 @@ <Target Name="SetPropertiesForGenerateGodotNupkgsVersions"> <PropertyGroup> - <GodotNETSdkPackageVersionFile>$(SolutionDir)..\Godot.NET.Sdk\Godot.NET.Sdk\Godot.NET.Sdk_PackageVersion.txt</GodotNETSdkPackageVersionFile> <GeneratedGodotNupkgsVersionsFile>$(IntermediateOutputPath)GodotNupkgsVersions.g.cs</GeneratedGodotNupkgsVersionsFile> </PropertyGroup> </Target> @@ -18,13 +17,14 @@ </Target> <Target Name="_GenerateGodotNupkgsVersionsFile" DependsOnTargets="SetPropertiesForGenerateGodotNupkgsVersions" - Inputs="$(MSBuildProjectFile);@(GodotNETSdkPackageVersionFile)" + Inputs="$(MSBuildProjectFile);$(MSBuildThisFileDirectory);$(MSBuildProjectFile)\..\..\..\SdkPackageVersions.props" Outputs="$(GeneratedGodotNupkgsVersionsFile)"> <PropertyGroup> <GenerateGodotNupkgsVersionsCode><![CDATA[ namespace $(RootNamespace) { public class GeneratedGodotNupkgsVersions { - public const string GodotNETSdk = "$([System.IO.File]::ReadAllText('$(GodotNETSdkPackageVersionFile)').Trim())"%3b + public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b + public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b } } ]]></GenerateGodotNupkgsVersionsCode> diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index b96b0c8175..2b6f972529 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -218,43 +218,12 @@ namespace GodotTools.Build Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } - GenerateEditorScriptMetadata(); - if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) return true; // Requested play from an external editor/IDE which already built the project return BuildProjectBlocking("Debug"); } - // NOTE: This will be replaced with C# source generators in 4.0 - public static void GenerateEditorScriptMetadata() - { - string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); - string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); - - if (!File.Exists(editorScriptsMetadataPath)) - return; - - try - { - File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); - } - catch (IOException e) - { - throw new IOException("Failed to copy scripts metadata file.", innerException: e); - } - } - - // NOTE: This will be replaced with C# source generators in 4.0 - public static string GenerateExportedGameScriptMetadata(bool isDebug) - { - string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); - return scriptsMetadataPath; - } - public static void Initialize() { // Build tool settings diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 708ec73454..ed69c2b833 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -43,8 +43,6 @@ namespace GodotTools.Build GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } - BuildManager.GenerateEditorScriptMetadata(); - if (!BuildManager.BuildProjectBlocking("Debug")) return; // Build failed @@ -74,8 +72,6 @@ namespace GodotTools.Build GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } - BuildManager.GenerateEditorScriptMetadata(); - if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"})) return; // Build failed diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs index 793ef7fd71..16dd1c8c6b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -290,7 +290,8 @@ namespace GodotTools.Build private static readonly (string packageId, string packageVersion)[] PackagesToAdd = { - ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk) + ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk), + ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators), }; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 1d800b8151..e43f10804d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -1,11 +1,6 @@ using Godot; using System; -using System.Linq; -using Godot.Collections; -using GodotTools.Internals; using GodotTools.ProjectEditor; -using File = GodotTools.Utils.File; -using Directory = GodotTools.Utils.Directory; namespace GodotTools { @@ -23,86 +18,5 @@ namespace GodotTools return string.Empty; } } - - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - private static ulong ConvertToTimestamp(this DateTime value) - { - TimeSpan elapsedTime = value - Epoch; - return (ulong)elapsedTime.TotalSeconds; - } - - private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata) - { - fileMetadata = null; - - var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr); - - if (parseError != Error.Ok) - { - GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}"); - return false; - } - - string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile); - - var firstMatch = classes.FirstOrDefault(classDecl => - classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object. - classDecl.SearchName == searchName // Filter by the name we're looking for - ); - - if (firstMatch == null) - return false; // Not found - - fileMetadata = new Dictionary - { - ["modified_time"] = $"{modifiedTime}", - ["class"] = new Dictionary - { - ["namespace"] = firstMatch.Namespace, - ["class_name"] = firstMatch.Name, - ["nested"] = firstMatch.Nested - } - }; - - return true; - } - - public static void GenerateScriptsMetadata(string projectPath, string outputPath) - { - var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate(); - - bool IsUpToDate(string includeFile, ulong modifiedTime) - { - return metadataDict.TryGetValue(includeFile, out var oldFileVar) && - ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string, - out ulong storedModifiedTime) && storedModifiedTime == modifiedTime; - } - - var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile") - .Select(path => ("res://" + path).SimplifyGodotPath()) - .ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp()) - .Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value)) - .ToArray(); - - foreach (var pair in outdatedFiles) - { - metadataDict.Remove(pair.Key); - - string includeFile = pair.Key; - - if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata)) - metadataDict[includeFile] = fileMetadata; - } - - string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict); - - string baseDir = outputPath.GetBaseDir(); - - if (!Directory.Exists(baseDir)) - Directory.CreateDirectory(baseDir); - - File.WriteAllText(outputPath, json); - } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index e9bb701562..270be8b6bf 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -157,9 +157,6 @@ namespace GodotTools.Export string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; - string scriptsMetadataPath = BuildManager.GenerateExportedGameScriptMetadata(isDebug); - AddFile(scriptsMetadataPath, scriptsMetadataPath); - if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform)) throw new Exception("Failed to build project"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 7e5049e4b7..77370090ec 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -1,7 +1,5 @@ -using System; using System.Runtime.CompilerServices; using Godot; -using Godot.Collections; using GodotTools.IdeMessaging.Requests; namespace GodotTools.Internals @@ -42,9 +40,6 @@ namespace GodotTools.Internals public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen(); - public static Dictionary<string, object> GetScriptsMetadataOrNothing() => - internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>)); - public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); public static void EditorRunPlay() => internal_EditorRunPlay(); @@ -101,9 +96,6 @@ namespace GodotTools.Internals private static extern void internal_EditorNodeShowScriptScreen(); [MethodImpl(MethodImplOptions.InternalCall)] - private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType); - - [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoWindowsInstallRoot(); [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs deleted file mode 100644 index c72a84c513..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using Godot; -using Godot.Collections; - -namespace GodotTools.Internals -{ - public static class ScriptClassParser - { - public class ClassDecl - { - public string Name { get; } - public string Namespace { get; } - public bool Nested { get; } - public long BaseCount { get; } - - public string SearchName => Nested ? - Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) : - Name; - - public ClassDecl(string name, string @namespace, bool nested, long baseCount) - { - Name = name; - Namespace = @namespace; - Nested = nested; - BaseCount = baseCount; - } - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes, out string errorStr); - - public static Error ParseFile(string filePath, out IEnumerable<ClassDecl> classes, out string errorStr) - { - var classesArray = new Array<Dictionary>(); - var error = internal_ParseFile(filePath, classesArray, out errorStr); - if (error != Error.Ok) - { - classes = null; - return error; - } - - var classesList = new List<ClassDecl>(); - - foreach (var classDeclDict in classesArray) - { - classesList.Add(new ClassDecl( - (string)classDeclDict["name"], - (string)classDeclDict["namespace"], - (bool)classDeclDict["nested"], - (long)classDeclDict["base_count"] - )); - } - - classes = classesList; - - return Error.Ok; - } - } -} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 38e403b2e1..b1875aec3f 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -39,6 +39,7 @@ #include "core/os/file_access.h" #include "core/os/os.h" #include "core/string/ucaps.h" +#include "main/main.h" #include "../glue/cs_glue_version.gen.h" #include "../godotsharp_defs.h" @@ -783,6 +784,72 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { } } +void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) { + p_output.append("using System;\n\n"); + p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + // The class where we put the extensions doesn't matter, so just use "GD". + p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); + +#define ARRAY_IS_EMPTY(m_type) \ + p_output.append("\n" INDENT2 "/// <summary>\n"); \ + p_output.append(INDENT2 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \ + p_output.append(INDENT2 "/// </summary>\n"); \ + p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \ + p_output.append(INDENT2 "/// <returns>Whether or not the array is empty.</returns>\n"); \ + p_output.append(INDENT2 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \ + p_output.append(INDENT2 OPEN_BLOCK); \ + p_output.append(INDENT3 "return instance == null || instance.Length == 0;\n"); \ + p_output.append(INDENT2 CLOSE_BLOCK); + +#define ARRAY_JOIN(m_type) \ + p_output.append("\n" INDENT2 "/// <summary>\n"); \ + p_output.append(INDENT2 "/// Converts this " #m_type " array to a string delimited by the given string.\n"); \ + p_output.append(INDENT2 "/// </summary>\n"); \ + p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ + p_output.append(INDENT2 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \ + p_output.append(INDENT2 "/// <returns>A single string with all items.</returns>\n"); \ + p_output.append(INDENT2 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \ + p_output.append(INDENT2 OPEN_BLOCK); \ + p_output.append(INDENT3 "return String.Join(delimiter, instance);\n"); \ + p_output.append(INDENT2 CLOSE_BLOCK); + +#define ARRAY_STRINGIFY(m_type) \ + p_output.append("\n" INDENT2 "/// <summary>\n"); \ + p_output.append(INDENT2 "/// Converts this " #m_type " array to a string with brackets.\n"); \ + p_output.append(INDENT2 "/// </summary>\n"); \ + p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ + p_output.append(INDENT2 "/// <returns>A single string with all items.</returns>\n"); \ + p_output.append(INDENT2 "public static string Stringify(this " #m_type "[] instance)\n"); \ + p_output.append(INDENT2 OPEN_BLOCK); \ + p_output.append(INDENT3 "return \"[\" + instance.Join() + \"]\";\n"); \ + p_output.append(INDENT2 CLOSE_BLOCK); + +#define ARRAY_ALL(m_type) \ + ARRAY_IS_EMPTY(m_type) \ + ARRAY_JOIN(m_type) \ + ARRAY_STRINGIFY(m_type) + + ARRAY_ALL(byte); + ARRAY_ALL(int); + ARRAY_ALL(long); + ARRAY_ALL(float); + ARRAY_ALL(double); + ARRAY_ALL(string); + ARRAY_ALL(Color); + ARRAY_ALL(Vector2); + ARRAY_ALL(Vector2i); + ARRAY_ALL(Vector3); + ARRAY_ALL(Vector3i); + +#undef ARRAY_ALL +#undef ARRAY_IS_EMPTY +#undef ARRAY_JOIN +#undef ARRAY_STRINGIFY + + p_output.append(INDENT1 CLOSE_BLOCK); // End of GD class. + p_output.append(CLOSE_BLOCK); // End of namespace. +} + void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { // Constants (in partial GD class) @@ -926,6 +993,19 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { compile_items.push_back(output_file); } + // Generate source file for array extensions + { + StringBuilder extensions_source; + _generate_array_extensions(extensions_source); + String output_file = path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_extensions.cs"); + Error save_err = _save_file(output_file, extensions_source); + if (save_err != OK) { + return save_err; + } + + compile_items.push_back(output_file); + } + for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { const TypeInterface &itype = E.get(); @@ -1479,6 +1559,12 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG, "Property type is a singleton: '" + p_itype.name + "." + String(p_iprop.cname) + "'."); + if (p_itype.api_type == ClassDB::API_CORE) { + ERR_FAIL_COND_V_MSG(prop_itype->api_type == ClassDB::API_EDITOR, ERR_BUG, + "Property '" + p_itype.name + "." + String(p_iprop.cname) + "' has type '" + prop_itype->name + + "' from the editor API. Core API cannot have dependencies on the editor API."); + } + if (p_iprop.prop_doc && p_iprop.prop_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(p_iprop.prop_doc->description), &p_itype); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); @@ -1575,6 +1661,12 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG, "Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'."); + if (p_itype.api_type == ClassDB::API_CORE) { + ERR_FAIL_COND_V_MSG(return_type->api_type == ClassDB::API_EDITOR, ERR_BUG, + "Method '" + p_itype.name + "." + p_imethod.name + "' has return type '" + return_type->name + + "' from the editor API. Core API cannot have dependencies on the editor API."); + } + String method_bind_field = "__method_bind_" + itos(p_method_bind_count); String arguments_sig; @@ -1593,6 +1685,12 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'."); + if (p_itype.api_type == ClassDB::API_CORE) { + ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG, + "Argument '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "' has type '" + + arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API."); + } + if (iarg.default_argument.size()) { CRASH_COND_MSG(!_arg_default_value_is_assignable_to_type(iarg.def_param_value, *arg_type), "Invalid default value for parameter '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'."); @@ -1806,7 +1904,13 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, - "Argument type is a singleton: '" + iarg.name + "' of signal" + p_itype.name + "." + p_isignal.name + "'."); + "Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'."); + + if (p_itype.api_type == ClassDB::API_CORE) { + ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG, + "Argument '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "' has type '" + + arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API."); + } // Add the current arguments to the signature @@ -2932,9 +3036,9 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar } break; case Variant::FLOAT: -#ifndef REAL_T_IS_DOUBLE - r_iarg.default_argument += "f"; -#endif + if (r_iarg.type.cname == name_cache.type_float) { + r_iarg.default_argument += "f"; + } break; case Variant::STRING: case Variant::STRING_NAME: @@ -2947,23 +3051,32 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = "\"" + r_iarg.default_argument + "\""; } break; - case Variant::TRANSFORM: - if (p_val.operator Transform() == Transform()) { - r_iarg.default_argument.clear(); - } - r_iarg.default_argument = "new %s(" + r_iarg.default_argument + ")"; + case Variant::PLANE: { + Plane plane = p_val.operator Plane(); + r_iarg.default_argument = "new Plane(new Vector3(" + plane.normal.operator String() + "), " + rtos(plane.d) + ")"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; - break; - case Variant::PLANE: - case Variant::AABB: + } break; + case Variant::AABB: { + AABB aabb = p_val.operator ::AABB(); + r_iarg.default_argument = "new AABB(new Vector3(" + aabb.position.operator String() + "), new Vector3(" + aabb.position.operator String() + "))"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + } break; + case Variant::RECT2: { + Rect2 rect = p_val.operator Rect2(); + r_iarg.default_argument = "new Rect2(new Vector2(" + rect.position.operator String() + "), new Vector2(" + rect.position.operator String() + "))"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + } break; + case Variant::RECT2I: { + Rect2i rect = p_val.operator Rect2i(); + r_iarg.default_argument = "new Rect2i(new Vector2i(" + rect.position.operator String() + "), new Vector2i(" + rect.position.operator String() + "))"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + } break; case Variant::COLOR: - r_iarg.default_argument = "new Color(1, 1, 1, 1)"; + r_iarg.default_argument = "new %s(" + r_iarg.default_argument + ")"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; break; case Variant::VECTOR2: case Variant::VECTOR2I: - case Variant::RECT2: - case Variant::RECT2I: case Variant::VECTOR3: case Variant::VECTOR3I: r_iarg.default_argument = "new %s" + r_iarg.default_argument; @@ -3001,12 +3114,43 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = "new %s {}"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; break; - case Variant::TRANSFORM2D: - case Variant::BASIS: - case Variant::QUAT: - r_iarg.default_argument = Variant::get_type_name(p_val.get_type()) + ".Identity"; + case Variant::TRANSFORM2D: { + Transform2D transform = p_val.operator Transform2D(); + if (transform == Transform2D()) { + r_iarg.default_argument = "Transform2D.Identity"; + } else { + r_iarg.default_argument = "new Transform2D(new Vector2" + transform.elements[0].operator String() + ", new Vector2" + transform.elements[1].operator String() + ", new Vector2" + transform.elements[2].operator String() + ")"; + } r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; - break; + } break; + case Variant::TRANSFORM: { + Transform transform = p_val.operator Transform(); + if (transform == Transform()) { + r_iarg.default_argument = "Transform.Identity"; + } else { + Basis basis = transform.basis; + r_iarg.default_argument = "new Transform(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ", new Vector3" + transform.origin.operator String() + ")"; + } + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + } break; + case Variant::BASIS: { + Basis basis = p_val.operator Basis(); + if (basis == Basis()) { + r_iarg.default_argument = "Basis.Identity"; + } else { + r_iarg.default_argument = "new Basis(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ")"; + } + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + } break; + case Variant::QUAT: { + Quat quat = p_val.operator Quat(); + if (quat == Quat()) { + r_iarg.default_argument = "Quat.Identity"; + } else { + r_iarg.default_argument = "new Quat" + quat.operator String(); + } + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + } break; case Variant::CALLABLE: case Variant::SIGNAL: CRASH_NOW_MSG("Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value."); @@ -3546,6 +3690,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) if (!bindings_generator.initialized) { ERR_PRINT("Failed to initialize the bindings generator"); + Main::cleanup(true); ::exit(0); } @@ -3572,6 +3717,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) } // Exit once done + Main::cleanup(true); ::exit(0); } } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index b18dfb0ec4..876046176b 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -661,6 +661,7 @@ class BindingsGenerator { Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output); Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output); + void _generate_array_extensions(StringBuilder &p_output); void _generate_global_constants(StringBuilder &p_output); Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 667e4a3879..21efd58938 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -49,7 +49,6 @@ #include "../utils/osx_utils.h" #include "code_completion.h" #include "godotsharp_export.h" -#include "script_class_parser.h" MonoString *godot_icall_GodotSharpDirs_ResDataDir() { return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir()); @@ -172,36 +171,6 @@ MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_st return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh); } -int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) { - *r_error_str = nullptr; - - String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath); - - ScriptClassParser scp; - Error err = scp.parse_file(filepath); - if (err == OK) { - Array classes = GDMonoMarshal::mono_object_to_variant(p_classes); - const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes(); - - for (int i = 0; i < class_decls.size(); i++) { - const ScriptClassParser::ClassDecl &classDecl = class_decls[i]; - - Dictionary classDeclDict; - classDeclDict["name"] = classDecl.name; - classDeclDict["namespace"] = classDecl.namespace_; - classDeclDict["nested"] = classDecl.nested; - classDeclDict["base_count"] = classDecl.base.size(); - classes.push_back(classDeclDict); - } - } else { - String error_str = scp.get_error(); - if (!error_str.is_empty()) { - *r_error_str = GDMonoMarshal::mono_string_from_godot(error_str); - } - } - return err; -} - uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies, MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) { Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies); @@ -289,18 +258,6 @@ void godot_icall_Internal_EditorNodeShowScriptScreen() { EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); } -MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) { - Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing(); - - MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype); - - int type_encoding = mono_type_get_type(dict_type); - MonoClass *type_class_raw = mono_class_from_mono_type(dict_type); - GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw); - - return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class)); -} - MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { #ifdef WINDOWS_ENABLED String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; @@ -395,9 +352,6 @@ void register_editor_internal_calls() { GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose); GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step); - // ScriptClassParser - GDMonoUtils::add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", godot_icall_ScriptClassParser_ParseFile); - // ExportPlugin GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies); @@ -416,7 +370,6 @@ void register_editor_internal_calls() { GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", godot_icall_Internal_GetScriptsMetadataOrNothing); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop); diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp deleted file mode 100644 index e81cbe4ebd..0000000000 --- a/modules/mono/editor/script_class_parser.cpp +++ /dev/null @@ -1,753 +0,0 @@ -/*************************************************************************/ -/* script_class_parser.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "script_class_parser.h" - -#include "core/os/os.h" -#include "core/templates/map.h" - -#include "../utils/string_utils.h" - -const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = { - "[", - "]", - "{", - "}", - ".", - ":", - ",", - "Symbol", - "Identifier", - "String", - "Number", - "<", - ">", - "EOF", - "Error" -}; - -String ScriptClassParser::get_token_name(ScriptClassParser::Token p_token) { - ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>"); - return token_names[p_token]; -} - -ScriptClassParser::Token ScriptClassParser::get_token() { - while (true) { - switch (code[idx]) { - case '\n': { - line++; - idx++; - break; - }; - case 0: { - return TK_EOF; - } break; - case '{': { - idx++; - return TK_CURLY_BRACKET_OPEN; - }; - case '}': { - idx++; - return TK_CURLY_BRACKET_CLOSE; - }; - case '[': { - idx++; - return TK_BRACKET_OPEN; - }; - case ']': { - idx++; - return TK_BRACKET_CLOSE; - }; - case '<': { - idx++; - return TK_OP_LESS; - }; - case '>': { - idx++; - return TK_OP_GREATER; - }; - case ':': { - idx++; - return TK_COLON; - }; - case ',': { - idx++; - return TK_COMMA; - }; - case '.': { - idx++; - return TK_PERIOD; - }; - case '#': { - //compiler directive - while (code[idx] != '\n' && code[idx] != 0) { - idx++; - } - continue; - } break; - case '/': { - switch (code[idx + 1]) { - case '*': { // block comment - idx += 2; - while (true) { - if (code[idx] == 0) { - error_str = "Unterminated comment"; - error = true; - return TK_ERROR; - } else if (code[idx] == '*' && code[idx + 1] == '/') { - idx += 2; - break; - } else if (code[idx] == '\n') { - line++; - } - - idx++; - } - - } break; - case '/': { // line comment skip - while (code[idx] != '\n' && code[idx] != 0) { - idx++; - } - - } break; - default: { - value = "/"; - idx++; - return TK_SYMBOL; - } - } - - continue; // a comment - } break; - case '\'': - case '"': { - bool verbatim = idx != 0 && code[idx - 1] == '@'; - - char32_t begin_str = code[idx]; - idx++; - String tk_string = String(); - while (true) { - if (code[idx] == 0) { - error_str = "Unterminated String"; - error = true; - return TK_ERROR; - } else if (code[idx] == begin_str) { - if (verbatim && code[idx + 1] == '"') { // '""' is verbatim string's '\"' - idx += 2; // skip next '"' as well - continue; - } - - idx += 1; - break; - } else if (code[idx] == '\\' && !verbatim) { - //escaped characters... - idx++; - char32_t next = code[idx]; - if (next == 0) { - error_str = "Unterminated String"; - error = true; - return TK_ERROR; - } - char32_t res = 0; - - switch (next) { - case 'b': - res = 8; - break; - case 't': - res = 9; - break; - case 'n': - res = 10; - break; - case 'f': - res = 12; - break; - case 'r': - res = 13; - break; - case '\"': - res = '\"'; - break; - case '\\': - res = '\\'; - break; - default: { - res = next; - } break; - } - - tk_string += res; - - } else { - if (code[idx] == '\n') { - line++; - } - tk_string += code[idx]; - } - idx++; - } - - value = tk_string; - - return TK_STRING; - } break; - default: { - if (code[idx] <= 32) { - idx++; - break; - } - - if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) { - value = String::chr(code[idx]); - idx++; - return TK_SYMBOL; - } - - if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) { - //a number - const char32_t *rptr; - double number = String::to_float(&code[idx], &rptr); - idx += (rptr - &code[idx]); - value = number; - return TK_NUMBER; - - } else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) { - String id; - - id += code[idx]; - idx++; - - while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) { - id += code[idx]; - idx++; - } - - value = id; - return TK_IDENTIFIER; - } else if (code[idx] == '@' && code[idx + 1] == '"') { - // begin of verbatim string - idx++; - } else { - error_str = "Unexpected character."; - error = true; - return TK_ERROR; - } - } - } - } -} - -Error ScriptClassParser::_skip_generic_type_params() { - Token tk; - - while (true) { - tk = get_token(); - - if (tk == TK_IDENTIFIER) { - tk = get_token(); - // Type specifications can end with "?" to denote nullable types, such as IList<int?> - if (tk == TK_SYMBOL) { - tk = get_token(); - if (value.operator String() != "?") { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found unexpected symbol '" + value + "'"; - error = true; - return ERR_PARSE_ERROR; - } - if (tk != TK_OP_GREATER && tk != TK_COMMA) { - error_str = "Nullable type symbol '?' is only allowed after an identifier, but found " + get_token_name(tk) + " next."; - error = true; - return ERR_PARSE_ERROR; - } - } - - if (tk == TK_PERIOD) { - while (true) { - tk = get_token(); - - if (tk != TK_IDENTIFIER) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - tk = get_token(); - - if (tk != TK_PERIOD) { - break; - } - } - } - - if (tk == TK_OP_LESS) { - Error err = _skip_generic_type_params(); - if (err) { - return err; - } - tk = get_token(); - } - - if (tk == TK_OP_GREATER) { - return OK; - } else if (tk != TK_COMMA) { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } else if (tk == TK_OP_LESS) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found " + get_token_name(TK_OP_LESS); - error = true; - return ERR_PARSE_ERROR; - } else if (tk == TK_OP_GREATER) { - return OK; - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } -} - -Error ScriptClassParser::_parse_type_full_name(String &r_full_name) { - Token tk = get_token(); - - if (tk != TK_IDENTIFIER) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - r_full_name += String(value); - - if (code[idx] == '<') { - idx++; - - // We don't mind if the base is generic, but we skip it any ways since this information is not needed - Error err = _skip_generic_type_params(); - if (err) { - return err; - } - } - - if (code[idx] != '.') { // We only want to take the next token if it's a period - return OK; - } - - tk = get_token(); - - CRASH_COND(tk != TK_PERIOD); // Assertion - - r_full_name += "."; - - return _parse_type_full_name(r_full_name); -} - -Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) { - String name; - - Error err = _parse_type_full_name(name); - if (err) { - return err; - } - - Token tk = get_token(); - - if (tk == TK_COMMA) { - err = _parse_class_base(r_base); - if (err) { - return err; - } - } else if (tk == TK_IDENTIFIER && String(value) == "where") { - err = _parse_type_constraints(); - if (err) { - return err; - } - - // An open curly bracket was parsed by _parse_type_constraints, so we can exit - } else if (tk == TK_CURLY_BRACKET_OPEN) { - // we are finished when we hit the open curly bracket - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - r_base.push_back(name); - - return OK; -} - -Error ScriptClassParser::_parse_type_constraints() { - Token tk = get_token(); - if (tk != TK_IDENTIFIER) { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - tk = get_token(); - if (tk != TK_COLON) { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - while (true) { - tk = get_token(); - if (tk == TK_IDENTIFIER) { - if (String(value) == "where") { - return _parse_type_constraints(); - } - - tk = get_token(); - if (tk == TK_PERIOD) { - while (true) { - tk = get_token(); - - if (tk != TK_IDENTIFIER) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - tk = get_token(); - - if (tk != TK_PERIOD) { - break; - } - } - } - } - - if (tk == TK_COMMA) { - continue; - } else if (tk == TK_IDENTIFIER && String(value) == "where") { - return _parse_type_constraints(); - } else if (tk == TK_SYMBOL && String(value) == "(") { - tk = get_token(); - if (tk != TK_SYMBOL || String(value) != ")") { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } else if (tk == TK_OP_LESS) { - Error err = _skip_generic_type_params(); - if (err) { - return err; - } - } else if (tk == TK_CURLY_BRACKET_OPEN) { - return OK; - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } -} - -Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) { - Token tk = get_token(); - - if (tk == TK_IDENTIFIER) { - r_name += String(value); - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - tk = get_token(); - - if (tk == TK_PERIOD) { - r_name += "."; - return _parse_namespace_name(r_name, r_curly_stack); - } else if (tk == TK_CURLY_BRACKET_OPEN) { - r_curly_stack++; - return OK; - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } -} - -Error ScriptClassParser::parse(const String &p_code) { - code = p_code; - idx = 0; - line = 0; - error_str = String(); - error = false; - value = Variant(); - classes.clear(); - - Token tk = get_token(); - - Map<int, NameDecl> name_stack; - int curly_stack = 0; - int type_curly_stack = 0; - - while (!error && tk != TK_EOF) { - String identifier = value; - if (tk == TK_IDENTIFIER && (identifier == "class" || identifier == "struct")) { - bool is_class = identifier == "class"; - - tk = get_token(); - - if (tk == TK_IDENTIFIER) { - String name = value; - int at_level = curly_stack; - - ClassDecl class_decl; - - for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) { - const NameDecl &name_decl = E->value(); - - if (name_decl.type == NameDecl::NAMESPACE_DECL) { - if (E != name_stack.front()) { - class_decl.namespace_ += "."; - } - class_decl.namespace_ += name_decl.name; - } else { - class_decl.name += name_decl.name + "."; - } - } - - class_decl.name += name; - class_decl.nested = type_curly_stack > 0; - - bool generic = false; - - while (true) { - tk = get_token(); - - if (tk == TK_COLON) { - Error err = _parse_class_base(class_decl.base); - if (err) { - return err; - } - - curly_stack++; - type_curly_stack++; - - break; - } else if (tk == TK_CURLY_BRACKET_OPEN) { - curly_stack++; - type_curly_stack++; - break; - } else if (tk == TK_OP_LESS && !generic) { - generic = true; - - Error err = _skip_generic_type_params(); - if (err) { - return err; - } - } else if (tk == TK_IDENTIFIER && String(value) == "where") { - Error err = _parse_type_constraints(); - if (err) { - return err; - } - - // An open curly bracket was parsed by _parse_type_constraints, so we can exit - curly_stack++; - type_curly_stack++; - break; - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } - - NameDecl name_decl; - name_decl.name = name; - name_decl.type = is_class ? NameDecl::CLASS_DECL : NameDecl::STRUCT_DECL; - name_stack[at_level] = name_decl; - - if (is_class) { - if (!generic) { // no generics, thanks - classes.push_back(class_decl); - } else if (OS::get_singleton()->is_stdout_verbose()) { - String full_name = class_decl.namespace_; - if (full_name.length()) { - full_name += "."; - } - full_name += class_decl.name; - OS::get_singleton()->print("Ignoring generic class declaration: %s\n", full_name.utf8().get_data()); - } - } - } - } else if (tk == TK_IDENTIFIER && identifier == "namespace") { - if (type_curly_stack > 0) { - error_str = "Found namespace nested inside type."; - error = true; - return ERR_PARSE_ERROR; - } - - String name; - int at_level = curly_stack; - - Error err = _parse_namespace_name(name, curly_stack); - if (err) { - return err; - } - - NameDecl name_decl; - name_decl.name = name; - name_decl.type = NameDecl::NAMESPACE_DECL; - name_stack[at_level] = name_decl; - } else if (tk == TK_CURLY_BRACKET_OPEN) { - curly_stack++; - } else if (tk == TK_CURLY_BRACKET_CLOSE) { - curly_stack--; - if (name_stack.has(curly_stack)) { - if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL) { - type_curly_stack--; - } - name_stack.erase(curly_stack); - } - } - - tk = get_token(); - } - - if (!error && tk == TK_EOF && curly_stack > 0) { - error_str = "Reached EOF with missing close curly brackets."; - error = true; - } - - if (error) { - return ERR_PARSE_ERROR; - } - - return OK; -} - -static String get_preprocessor_directive(const String &p_line, int p_from) { - CRASH_COND(p_line[p_from] != '#'); - p_from++; - int i = p_from; - while (i < p_line.length() && (p_line[i] == '_' || (p_line[i] >= 'A' && p_line[i] <= 'Z') || - (p_line[i] >= 'a' && p_line[i] <= 'z') || p_line[i] > 127)) { - i++; - } - return p_line.substr(p_from, i - p_from); -} - -static void run_dummy_preprocessor(String &r_source, const String &p_filepath) { - Vector<String> lines = r_source.split("\n", /* p_allow_empty: */ true); - - bool *include_lines = memnew_arr(bool, lines.size()); - - int if_level = -1; - Vector<bool> is_branch_being_compiled; - - for (int i = 0; i < lines.size(); i++) { - const String &line = lines[i]; - - const int line_len = line.length(); - - int j; - for (j = 0; j < line_len; j++) { - if (line[j] != ' ' && line[j] != '\t') { - if (line[j] == '#') { - // First non-whitespace char of the line is '#' - include_lines[i] = false; - - String directive = get_preprocessor_directive(line, j); - - if (directive == "if") { - if_level++; - is_branch_being_compiled.push_back(if_level == 0 || is_branch_being_compiled[if_level - 1]); - } else if (directive == "elif") { - ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#elif' directive. File: '" + p_filepath + "'."); - is_branch_being_compiled.write[if_level] = false; - } else if (directive == "else") { - ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#else' directive. File: '" + p_filepath + "'."); - is_branch_being_compiled.write[if_level] = false; - } else if (directive == "endif") { - ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#endif' directive. File: '" + p_filepath + "'."); - is_branch_being_compiled.remove(if_level); - if_level--; - } - - break; - } else { - // First non-whitespace char of the line is not '#' - include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level]; - break; - } - } - } - - if (j == line_len) { - // Loop ended without finding a non-whitespace character. - // Either the line was empty or it only contained whitespaces. - include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level]; - } - } - - r_source.clear(); - - // Custom join ignoring lines removed by the preprocessor - for (int i = 0; i < lines.size(); i++) { - if (i > 0 && include_lines[i - 1]) { - r_source += '\n'; - } - - if (include_lines[i]) { - r_source += lines[i]; - } - } -} - -Error ScriptClassParser::parse_file(const String &p_filepath) { - String source; - - Error ferr = read_all_file_utf8(p_filepath, source); - - ERR_FAIL_COND_V_MSG(ferr != OK, ferr, - ferr == ERR_INVALID_DATA ? - "File '" + p_filepath + "' contains invalid unicode (UTF-8), so it was not loaded." - " Please ensure that scripts are saved in valid UTF-8 unicode." : - "Failed to read file: '" + p_filepath + "'."); - - run_dummy_preprocessor(source, p_filepath); - - return parse(source); -} - -String ScriptClassParser::get_error() { - return error_str; -} - -Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() { - return classes; -} diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h deleted file mode 100644 index 75a46bb4e5..0000000000 --- a/modules/mono/editor/script_class_parser.h +++ /dev/null @@ -1,108 +0,0 @@ -/*************************************************************************/ -/* script_class_parser.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef SCRIPT_CLASS_PARSER_H -#define SCRIPT_CLASS_PARSER_H - -#include "core/string/ustring.h" -#include "core/templates/vector.h" -#include "core/variant/variant.h" - -class ScriptClassParser { -public: - struct NameDecl { - enum Type { - NAMESPACE_DECL, - CLASS_DECL, - STRUCT_DECL - }; - - String name; - Type type = NAMESPACE_DECL; - }; - - struct ClassDecl { - String name; - String namespace_; - Vector<String> base; - bool nested = false; - }; - -private: - String code; - int idx = 0; - int line = 0; - String error_str; - bool error = false; - Variant value; - - Vector<ClassDecl> classes; - - enum Token { - TK_BRACKET_OPEN, - TK_BRACKET_CLOSE, - TK_CURLY_BRACKET_OPEN, - TK_CURLY_BRACKET_CLOSE, - TK_PERIOD, - TK_COLON, - TK_COMMA, - TK_SYMBOL, - TK_IDENTIFIER, - TK_STRING, - TK_NUMBER, - TK_OP_LESS, - TK_OP_GREATER, - TK_EOF, - TK_ERROR, - TK_MAX - }; - - static const char *token_names[TK_MAX]; - static String get_token_name(Token p_token); - - Token get_token(); - - Error _skip_generic_type_params(); - - Error _parse_type_full_name(String &r_full_name); - Error _parse_class_base(Vector<String> &r_base); - Error _parse_type_constraints(); - Error _parse_namespace_name(String &r_name, int &r_curly_stack); - -public: - Error parse(const String &p_code); - Error parse_file(const String &p_filepath); - - String get_error(); - - Vector<ClassDecl> get_classes(); -}; - -#endif // SCRIPT_CLASS_PARSER_H diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs new file mode 100644 index 0000000000..ef135da51a --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Assembly)] + public class AssemblyHasScriptsAttribute : Attribute + { + private readonly bool requiresLookup; + private readonly System.Type[] scriptTypes; + + public AssemblyHasScriptsAttribute() + { + requiresLookup = true; + } + + public AssemblyHasScriptsAttribute(System.Type[] scriptTypes) + { + requiresLookup = false; + this.scriptTypes = scriptTypes; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs new file mode 100644 index 0000000000..ac6cffceb2 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Class)] + public class DisableGodotGeneratorsAttribute : Attribute + { + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs new file mode 100644 index 0000000000..12eb1035c3 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class ScriptPathAttribute : Attribute + { + private string path; + + public ScriptPathAttribute(string path) + { + this.path = path; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs new file mode 100644 index 0000000000..763f470504 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs @@ -0,0 +1,27 @@ +namespace Godot +{ + public partial class PackedScene + { + /// <summary> + /// Instantiates the scene's node hierarchy, erroring on failure. + /// Triggers child scene instantiation(s). Triggers a + /// `Node.NotificationInstanced` notification on the root node. + /// </summary> + /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> + public T Instance<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class + { + return (T)(object)Instance(editState); + } + + /// <summary> + /// Instantiates the scene's node hierarchy, returning null on failure. + /// Triggers child scene instantiation(s). Triggers a + /// `Node.NotificationInstanced` notification on the root node. + /// </summary> + /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> + public T InstanceOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class + { + return Instance(editState) as T; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs index 5f64c09a89..74fa05d1fd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs @@ -2,7 +2,7 @@ namespace Godot { public static partial class ResourceLoader { - public static T Load<T>(string path, string typeHint = null, bool noCache = false) where T : class + public static T Load<T>(string path, string typeHint = null, CacheMode noCache = CacheMode.Reuse) where T : class { return (T)(object)Load(path, typeHint, noCache); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs new file mode 100644 index 0000000000..702a6c76ba --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Godot +{ + public static partial class GD + { + /// <summary> + /// Fires when an unhandled exception occurs, regardless of project settings. + /// </summary> + public static event EventHandler<UnhandledExceptionArgs> UnhandledException; + + private static void OnUnhandledException(Exception e) + { + UnhandledException?.Invoke(null, new UnhandledExceptionArgs(e)); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs new file mode 100644 index 0000000000..be01674568 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Godot +{ + /// <summary> + /// Event arguments for when unhandled exceptions occur. + /// </summary> + public class UnhandledExceptionArgs + { + /// <summary> + /// Exception object + /// </summary> + public Exception Exception { get; private set; } + + internal UnhandledExceptionArgs(Exception exception) + { + Exception = exception; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 42dbdf25c3..3b895bbbf6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -111,10 +111,10 @@ namespace Godot } /// <summary> - /// Returns the minimum angle to the given vector, in radians. + /// Returns the unsigned minimum angle to the given vector, in radians. /// </summary> /// <param name="to">The other vector to compare this vector to.</param> - /// <returns>The angle between the two vectors, in radians.</returns> + /// <returns>The unsigned angle between the two vectors, in radians.</returns> public real_t AngleTo(Vector3 to) { return Mathf.Atan2(Cross(to).Length(), Dot(to)); @@ -469,6 +469,23 @@ namespace Godot } /// <summary> + /// Returns the signed angle to the given vector, in radians. + /// The sign of the angle is positive in a counter-clockwise + /// direction and negative in a clockwise direction when viewed + /// from the side specified by the `axis`. + /// </summary> + /// <param name="to">The other vector to compare this vector to.</param> + /// <param name="axis">The reference axis to use for the angle sign.</param> + /// <returns>The signed angle between the two vectors, in radians.</returns> + public real_t SignedAngleTo(Vector3 to, Vector3 axis) + { + Vector3 crossTo = Cross(to); + real_t unsignedAngle = Mathf.Atan2(crossTo.Length(), Dot(to)); + real_t sign = crossTo.Dot(axis); + return (sign < 0) ? -unsignedAngle : unsignedAngle; + } + + /// <summary> /// Returns the result of the spherical linear interpolation between /// this vector and `to` by amount `weight`. /// diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 86a16c17f1..54aaaf1f92 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -14,9 +14,12 @@ <ItemGroup> <Compile Include="Core\AABB.cs" /> <Compile Include="Core\Array.cs" /> + <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" /> + <Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" /> <Compile Include="Core\Attributes\ExportAttribute.cs" /> <Compile Include="Core\Attributes\GodotMethodAttribute.cs" /> <Compile Include="Core\Attributes\RPCAttributes.cs" /> + <Compile Include="Core\Attributes\ScriptPathAttribute.cs" /> <Compile Include="Core\Attributes\SignalAttribute.cs" /> <Compile Include="Core\Attributes\ToolAttribute.cs" /> <Compile Include="Core\Basis.cs" /> @@ -30,12 +33,14 @@ <Compile Include="Core\DynamicObject.cs" /> <Compile Include="Core\Extensions\NodeExtensions.cs" /> <Compile Include="Core\Extensions\ObjectExtensions.cs" /> + <Compile Include="Core\Extensions\PackedSceneExtensions.cs" /> <Compile Include="Core\Extensions\ResourceLoaderExtensions.cs" /> <Compile Include="Core\Extensions\SceneTreeExtensions.cs" /> <Compile Include="Core\GD.cs" /> <Compile Include="Core\GodotSynchronizationContext.cs" /> <Compile Include="Core\GodotTaskScheduler.cs" /> <Compile Include="Core\GodotTraceListener.cs" /> + <Compile Include="Core\GodotUnhandledExceptionEvent.cs" /> <Compile Include="Core\Interfaces\IAwaitable.cs" /> <Compile Include="Core\Interfaces\IAwaiter.cs" /> <Compile Include="Core\Interfaces\ISerializationListener.cs" /> @@ -55,6 +60,7 @@ <Compile Include="Core\StringName.cs" /> <Compile Include="Core\Transform.cs" /> <Compile Include="Core\Transform2D.cs" /> + <Compile Include="Core\UnhandledExceptionArgs.cs" /> <Compile Include="Core\Vector2.cs" /> <Compile Include="Core\Vector2i.cs" /> <Compile Include="Core\Vector3.cs" /> diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index a39a6fe381..020a40575c 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -179,16 +179,16 @@ private: #ifdef OSX_ENABLED if (!DirAccess::exists(data_editor_tools_dir)) { - data_editor_tools_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Tools"); + data_editor_tools_dir = exe_dir.plus_file("../Resources/GodotSharp/Tools"); } if (!DirAccess::exists(data_editor_prebuilt_api_dir)) { - data_editor_prebuilt_api_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Api"); + data_editor_prebuilt_api_dir = exe_dir.plus_file("../Resources/GodotSharp/Api"); } if (!DirAccess::exists(data_mono_root_dir)) { data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc"); - data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib"); + data_mono_lib_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/lib"); } #endif @@ -218,11 +218,11 @@ private: #ifdef OSX_ENABLED if (!DirAccess::exists(data_mono_root_dir)) { data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc"); - data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib"); + data_mono_lib_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/lib"); } if (!DirAccess::exists(data_game_assemblies_dir)) { - data_game_assemblies_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Assemblies"); + data_game_assemblies_dir = exe_dir.plus_file("../Resources/GodotSharp/Assemblies"); } #endif diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 43a39a4966..560788fb3a 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -1006,6 +1006,7 @@ bool GDMono::_load_project_assembly() { if (success) { mono_assembly_set_main(project_assembly->get_assembly()); + CSharpLanguage::get_singleton()->lookup_scripts_in_assembly(project_assembly); } return success; diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 1fe06bfbee..a1556bace5 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -345,6 +345,45 @@ String GDMonoAssembly::get_path() const { return String::utf8(mono_image_get_filename(image)); } +bool GDMonoAssembly::has_attribute(GDMonoClass *p_attr_class) { +#ifdef DEBUG_ENABLED + ERR_FAIL_NULL_V(p_attr_class, false); +#endif + + if (!attrs_fetched) { + fetch_attributes(); + } + + if (!attributes) { + return false; + } + + return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); +} + +MonoObject *GDMonoAssembly::get_attribute(GDMonoClass *p_attr_class) { +#ifdef DEBUG_ENABLED + ERR_FAIL_NULL_V(p_attr_class, nullptr); +#endif + + if (!attrs_fetched) { + fetch_attributes(); + } + + if (!attributes) { + return nullptr; + } + + return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); +} + +void GDMonoAssembly::fetch_attributes() { + ERR_FAIL_COND(attributes != nullptr); + + attributes = mono_custom_attrs_from_assembly(assembly); + attrs_fetched = true; +} + GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) { ERR_FAIL_NULL_V(image, nullptr); @@ -390,70 +429,6 @@ GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) { return wrapped_class; } -GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) { - GDMonoClass *match = nullptr; - - if (gdobject_class_cache_updated) { - Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class); - - if (result) { - match = result->get(); - } - } else { - List<GDMonoClass *> nested_classes; - - int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - - for (int i = 1; i < rows; i++) { - MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); - - if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { - continue; - } - - GDMonoClass *current = get_class(mono_class); - - if (!current) { - continue; - } - - nested_classes.push_back(current); - - if (!match && current->get_name() == p_class) { - match = current; - } - - while (!nested_classes.is_empty()) { - GDMonoClass *current_nested = nested_classes.front()->get(); - nested_classes.pop_front(); - - void *iter = nullptr; - - while (true) { - MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter); - - if (!raw_nested) { - break; - } - - GDMonoClass *nested_class = get_class(raw_nested); - - if (nested_class) { - gdobject_class_cache.insert(nested_class->get_name(), nested_class); - nested_classes.push_back(nested_class); - } - } - } - - gdobject_class_cache.insert(current->get_name(), current); - } - - gdobject_class_cache_updated = true; - } - - return match; -} - GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) { if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) { return GDMono::get_singleton()->get_corlib_assembly(); diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 350fcf3210..6191c491f4 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -71,13 +71,13 @@ class GDMonoAssembly { MonoImage *image; MonoAssembly *assembly; + bool attrs_fetched = false; + MonoCustomAttrInfo *attributes = nullptr; + #ifdef GD_MONO_HOT_RELOAD uint64_t modified_time = 0; #endif - bool gdobject_class_cache_updated = false; - Map<StringName, GDMonoClass *> gdobject_class_cache; - HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes; Map<MonoClass *, GDMonoClass *> cached_raw; @@ -111,11 +111,14 @@ public: String get_path() const; + bool has_attribute(GDMonoClass *p_attr_class); + MonoObject *get_attribute(GDMonoClass *p_attr_class); + + void fetch_attributes(); + GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); GDMonoClass *get_class(MonoClass *p_mono_class); - GDMonoClass *get_object_derived_class(const StringName &p_class); - static String find_assembly(const String &p_name); static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String()); diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index aea467660f..d66cc29b9a 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -148,6 +148,11 @@ void CachedData::clear_godot_api_cache() { class_PuppetSyncAttribute = nullptr; class_GodotMethodAttribute = nullptr; field_GodotMethodAttribute_methodName = nullptr; + class_ScriptPathAttribute = nullptr; + field_ScriptPathAttribute_path = nullptr; + class_AssemblyHasScriptsAttribute = nullptr; + field_AssemblyHasScriptsAttribute_requiresLookup = nullptr; + field_AssemblyHasScriptsAttribute_scriptTypes = nullptr; field_GodotObject_ptr = nullptr; field_StringName_ptr = nullptr; @@ -272,6 +277,11 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(PuppetSyncAttribute, GODOT_API_CLASS(PuppetSyncAttribute)); CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); + CACHE_CLASS_AND_CHECK(ScriptPathAttribute, GODOT_API_CLASS(ScriptPathAttribute)); + CACHE_FIELD_AND_CHECK(ScriptPathAttribute, path, CACHED_CLASS(ScriptPathAttribute)->get_field("path")); + CACHE_CLASS_AND_CHECK(AssemblyHasScriptsAttribute, GODOT_API_CLASS(AssemblyHasScriptsAttribute)); + CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, requiresLookup, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("requiresLookup")); + CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, scriptTypes, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("scriptTypes")); CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD)); CACHE_FIELD_AND_CHECK(StringName, ptr, CACHED_CLASS(StringName)->get_field(BINDINGS_PTR_FIELD)); diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index fb75cb4b1c..51370da452 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -119,6 +119,11 @@ struct CachedData { GDMonoClass *class_PuppetSyncAttribute; GDMonoClass *class_GodotMethodAttribute; GDMonoField *field_GodotMethodAttribute_methodName; + GDMonoClass *class_ScriptPathAttribute; + GDMonoField *field_ScriptPathAttribute_path; + GDMonoClass *class_AssemblyHasScriptsAttribute; + GDMonoField *field_AssemblyHasScriptsAttribute_requiresLookup; + GDMonoField *field_AssemblyHasScriptsAttribute_scriptTypes; GDMonoField *field_GodotObject_ptr; GDMonoField *field_StringName_ptr; diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index 65e2680905..fa93c6533a 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -43,7 +43,6 @@ #include <mono/metadata/exception.h> namespace GDMonoInternals { - void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { // This method should not fail @@ -113,9 +112,11 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { void unhandled_exception(MonoException *p_exc) { mono_print_unhandled_exception((MonoObject *)p_exc); + gd_unhandled_exception_event(p_exc); if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) { // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders + mono_unhandled_exception((MonoObject *)p_exc); GDMono::unhandled_exception_hook((MonoObject *)p_exc, nullptr); GD_UNREACHABLE(); } else { @@ -127,4 +128,14 @@ void unhandled_exception(MonoException *p_exc) { #endif } } + +void gd_unhandled_exception_event(MonoException *p_exc) { + MonoImage *mono_image = GDMono::get_singleton()->get_core_api_assembly()->get_image(); + + MonoClass *gd_klass = mono_class_from_name(mono_image, "Godot", "GD"); + MonoMethod *unhandled_exception_method = mono_class_get_method_from_name(gd_klass, "OnUnhandledException", -1); + void *args[1]; + args[0] = p_exc; + mono_runtime_invoke(unhandled_exception_method, nullptr, (void **)args, nullptr); +} } // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h index 34d2d35b2d..26eb270eee 100644 --- a/modules/mono/mono_gd/gd_mono_internals.h +++ b/modules/mono/mono_gd/gd_mono_internals.h @@ -38,7 +38,6 @@ #include "core/object/class_db.h" namespace GDMonoInternals { - void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged); /** @@ -46,6 +45,8 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged); * Use GDMonoUtils::debug_unhandled_exception(MonoException *) instead. */ void unhandled_exception(MonoException *p_exc); + +void gd_unhandled_exception_event(MonoException *p_exc); } // namespace GDMonoInternals #endif // GD_MONO_INTERNALS_H diff --git a/modules/opensimplex/doc_classes/NoiseTexture.xml b/modules/opensimplex/doc_classes/NoiseTexture.xml index 7df261d2ba..86e7f9cc08 100644 --- a/modules/opensimplex/doc_classes/NoiseTexture.xml +++ b/modules/opensimplex/doc_classes/NoiseTexture.xml @@ -32,6 +32,7 @@ </member> <member name="seamless" type="bool" setter="set_seamless" getter="get_seamless" default="false"> Whether the texture can be tiled without visible seams or not. Seamless textures take longer to generate. + [b]Note:[/b] Seamless noise has a lower contrast compared to non-seamless noise. This is due to the way noise uses higher dimensions for generating seamless noise. </member> <member name="width" type="int" setter="set_width" getter="get_width" default="512"> Width of the generated texture. diff --git a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml index dcda5c2324..ad82f87213 100644 --- a/modules/opensimplex/doc_classes/OpenSimplexNoise.xml +++ b/modules/opensimplex/doc_classes/OpenSimplexNoise.xml @@ -109,6 +109,7 @@ </argument> <description> Generate a tileable noise image in [constant Image.FORMAT_L8] format, based on the current noise parameters. Generated seamless images are always square ([code]size[/code] × [code]size[/code]). + [b]Note:[/b] Seamless noise has a lower contrast compared to non-seamless noise. This is due to the way noise uses higher dimensions for generating seamless noise. </description> </method> </methods> diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index 056a923a2d..83f032ca2b 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -46,7 +46,7 @@ enum PVRFLags { PVR_VFLIP = 0x00010000 }; -RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_CANT_OPEN; } diff --git a/modules/pvr/texture_loader_pvr.h b/modules/pvr/texture_loader_pvr.h index da425c3237..26071ce30f 100644 --- a/modules/pvr/texture_loader_pvr.h +++ b/modules/pvr/texture_loader_pvr.h @@ -36,7 +36,7 @@ class ResourceFormatPVR : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path, Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path, Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp index f7bf650354..6732078efc 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp @@ -47,7 +47,7 @@ void AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_fra int mixed = stb_vorbis_get_samples_float_interleaved(ogg_stream, 2, buffer, todo * 2); if (vorbis_stream->channels == 1 && mixed > 0) { //mix mono to stereo - for (int i = start_buffer; i < mixed; i++) { + for (int i = start_buffer; i < start_buffer + mixed; i++) { p_buffer[i].r = p_buffer[i].l; } } diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 3589c8546d..b4067d41c2 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -38,6 +38,7 @@ def make_icu_data(target, source, env): # Thirdparty source files thirdparty_obj = [] +freetype_enabled = env.module_check_dependencies("text_server_adv", ["freetype"]) if env["builtin_harfbuzz"]: env_harfbuzz = env_modules.Clone() @@ -57,11 +58,9 @@ if env["builtin_harfbuzz"]: "src/hb-face.cc", "src/hb-fallback-shape.cc", "src/hb-font.cc", - "src/hb-ft.cc", #'src/hb-gdi.cc', #'src/hb-glib.cc', #'src/hb-gobject-structs.cc', - "src/hb-graphite2.cc", "src/hb-icu.cc", "src/hb-map.cc", "src/hb-number.cc", @@ -109,17 +108,29 @@ if env["builtin_harfbuzz"]: "src/hb-unicode.cc", #'src/hb-uniscribe.cc' ] + + if freetype_enabled: + thirdparty_sources += [ + "src/hb-ft.cc", + "src/hb-graphite2.cc", + ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_harfbuzz.Append( CPPPATH=[ "#thirdparty/harfbuzz/src", - "#thirdparty/freetype/include", - "#thirdparty/graphite/include", "#thirdparty/icu4c/common/", ] ) + if freetype_enabled: + env_harfbuzz.Append( + CPPPATH=[ + "#thirdparty/freetype/include", + "#thirdparty/graphite/include", + ] + ) + if env["platform"] == "android" or env["platform"] == "linuxbsd" or env["platform"] == "server": env_harfbuzz.Append(CCFLAGS=["-DHAVE_PTHREAD"]) @@ -133,12 +144,18 @@ if env["builtin_harfbuzz"]: CCFLAGS=[ "-DHAVE_ICU_BUILTIN", "-DHAVE_ICU", - "-DHAVE_FREETYPE", - "-DHAVE_GRAPHITE2", - "-DGRAPHITE2_STATIC", ] ) + if freetype_enabled: + env_harfbuzz.Append( + CCFLAGS=[ + "-DHAVE_FREETYPE", + "-DHAVE_GRAPHITE2", + "-DGRAPHITE2_STATIC", + ] + ) + lib = env_harfbuzz.add_library("harfbuzz_builtin", thirdparty_sources) thirdparty_obj += lib @@ -156,7 +173,7 @@ if env["builtin_harfbuzz"]: env.Append(LIBS=[lib]) -if env["builtin_graphite"]: +if env["builtin_graphite"] and freetype_enabled: env_graphite = env_modules.Clone() env_graphite.disable_warnings() @@ -488,12 +505,18 @@ if env_text_server_adv["tools"]: env_text_server_adv.Append( CPPPATH=[ "#thirdparty/harfbuzz/src", - "#thirdparty/freetype/include", - "#thirdparty/graphite/include", "#thirdparty/icu4c/common/", ] ) +if freetype_enabled: + env_text_server_adv.Append( + CPPPATH=[ + "#thirdparty/freetype/include", + "#thirdparty/graphite/include", + ] + ) + env_text_server_adv.add_source_files(module_obj, "*.cpp") env.modules_sources += module_obj diff --git a/modules/text_server_adv/bitmap_font_adv.cpp b/modules/text_server_adv/bitmap_font_adv.cpp index df771301e6..df7b42eac6 100644 --- a/modules/text_server_adv/bitmap_font_adv.cpp +++ b/modules/text_server_adv/bitmap_font_adv.cpp @@ -361,67 +361,71 @@ Error BitmapFontDataAdvanced::load_from_file(const String &p_filename, int p_bas base_size = height; } + if (hb_handle) { + hb_font_destroy(hb_handle); + } + hb_handle = hb_bmp_font_create(this, base_size, nullptr); valid = true; memdelete(f); return OK; } -Error BitmapFontDataAdvanced::load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(p_data == nullptr, ERR_CANT_CREATE); - ERR_FAIL_COND_V(p_size != sizeof(TextServer::BitmapFontData), ERR_CANT_CREATE); - - const TextServer::BitmapFontData *data = (const TextServer::BitmapFontData *)p_data; - - if (RenderingServer::get_singleton() != nullptr) { - Ref<Image> image = memnew(Image(data->img)); - Ref<ImageTexture> tex = memnew(ImageTexture); - tex->create_from_image(image); +Error BitmapFontDataAdvanced::bitmap_new(float p_height, float p_ascent, int p_base_size) { + height = p_height; + ascent = p_ascent; - textures.push_back(tex); + base_size = p_base_size; + if (base_size == 0) { + base_size = height; } - for (int i = 0; i < data->charcount; i++) { - const int *c = &data->char_rects[i * 8]; - - Character chr; - chr.rect.position.x = c[1]; - chr.rect.position.y = c[2]; - chr.rect.size.x = c[3]; - chr.rect.size.y = c[4]; - if (c[7] < 0) { - chr.advance.x = c[3]; - } else { - chr.advance.x = c[7]; - } - chr.align = Vector2(c[6], c[5]); - char_map[c[0]] = chr; + char_map.clear(); + textures.clear(); + kerning_map.clear(); + if (hb_handle) { + hb_font_destroy(hb_handle); } + hb_handle = hb_bmp_font_create(this, base_size, nullptr); + valid = true; - for (int i = 0; i < data->kerning_count; i++) { - KerningPairKey kpk; - kpk.A = data->kernings[i * 3 + 0]; - kpk.B = data->kernings[i * 3 + 1]; + return OK; +} - if (data->kernings[i * 3 + 2] == 0 && kerning_map.has(kpk)) { - kerning_map.erase(kpk); - } else { - kerning_map[kpk] = data->kernings[i * 3 + 2]; - } - } +void BitmapFontDataAdvanced::bitmap_add_texture(const Ref<Texture> &p_texture) { + ERR_FAIL_COND(!valid); + ERR_FAIL_COND_MSG(p_texture.is_null(), "It's not a reference to a valid Texture object."); - height = data->height; - ascent = data->ascent; + textures.push_back(p_texture); +} - base_size = p_base_size; - if (base_size == 0) { - base_size = height; +void BitmapFontDataAdvanced::bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { + ERR_FAIL_COND(!valid); + + Character chr; + chr.rect = p_rect; + chr.texture_idx = p_texture_idx; + if (p_advance < 0) { + chr.advance.x = chr.rect.size.x; + } else { + chr.advance.x = p_advance; } + chr.align = p_align; + char_map[p_char] = chr; +} - valid = true; +void BitmapFontDataAdvanced::bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { + ERR_FAIL_COND(!valid); - return OK; + KerningPairKey kpk; + kpk.A = p_A; + kpk.B = p_B; + + if (p_kerning == 0 && kerning_map.has(kpk)) { + kerning_map.erase(kpk); + } else { + kerning_map[kpk] = p_kerning; + } } float BitmapFontDataAdvanced::get_height(int p_size) const { @@ -464,10 +468,7 @@ float BitmapFontDataAdvanced::get_base_size() const { hb_font_t *BitmapFontDataAdvanced::get_hb_handle(int p_size) { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!valid, nullptr); - if (!cache.has(p_size)) { - cache[p_size] = hb_bmp_font_create(this, p_size, nullptr); - } - return cache[p_size]; + return hb_handle; } bool BitmapFontDataAdvanced::has_char(char32_t p_char) const { @@ -514,6 +515,10 @@ Vector2 BitmapFontDataAdvanced::get_size(uint32_t p_char, int p_size) const { return c->rect.size * (float(p_size) / float(base_size)); } +float BitmapFontDataAdvanced::get_font_scale(int p_size) const { + return float(p_size) / float(base_size); +} + Vector2 BitmapFontDataAdvanced::get_kerning(uint32_t p_char, uint32_t p_next, int p_size) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!valid, Vector2()); @@ -541,13 +546,13 @@ Vector2 BitmapFontDataAdvanced::draw_glyph(RID p_canvas, int p_size, const Vecto ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), Vector2()); if (c->texture_idx != -1) { Point2i cpos = p_pos; - cpos += c->align * (float(p_size) / float(base_size)); - cpos.y -= ascent * (float(p_size) / float(base_size)); + cpos += (c->align + Vector2(0, -ascent)) * (float(p_size) / float(base_size)); + Size2i csize = c->rect.size * (float(p_size) / float(base_size)); if (RenderingServer::get_singleton() != nullptr) { //if (distance_field_hint) { // Not implemented. // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, true); //} - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, c->rect.size * (float(p_size) / float(base_size))), textures[c->texture_idx]->get_rid(), c->rect, p_color, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), textures[c->texture_idx]->get_rid(), c->rect, p_color, false, false); //if (distance_field_hint) { // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, false); //} @@ -574,7 +579,7 @@ Vector2 BitmapFontDataAdvanced::draw_glyph_outline(RID p_canvas, int p_size, int } BitmapFontDataAdvanced::~BitmapFontDataAdvanced() { - for (Map<float, hb_font_t *>::Element *E = cache.front(); E; E = E->next()) { - hb_font_destroy(E->get()); + if (hb_handle) { + hb_font_destroy(hb_handle); } } diff --git a/modules/text_server_adv/bitmap_font_adv.h b/modules/text_server_adv/bitmap_font_adv.h index c314f1b087..7b620021e1 100644 --- a/modules/text_server_adv/bitmap_font_adv.h +++ b/modules/text_server_adv/bitmap_font_adv.h @@ -63,18 +63,22 @@ private: HashMap<uint32_t, Character> char_map; Map<KerningPairKey, int> kerning_map; - Map<float, hb_font_t *> cache; + hb_font_t *hb_handle = nullptr; float height = 0.f; float ascent = 0.f; - float base_size = 0.f; + int base_size = 0; bool distance_field_hint = false; public: virtual void clear_cache() override{}; virtual Error load_from_file(const String &p_filename, int p_base_size) override; - virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) override; + virtual Error bitmap_new(float p_height, float p_ascent, int p_base_size) override; + + virtual void bitmap_add_texture(const Ref<Texture> &p_texture) override; + virtual void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; + virtual void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) override; virtual float get_height(int p_size) const override; virtual float get_ascent(int p_size) const override; @@ -97,6 +101,7 @@ public: virtual bool has_outline() const override { return false; }; virtual float get_base_size() const override; + virtual float get_font_scale(int p_size) const override; virtual hb_font_t *get_hb_handle(int p_size) override; diff --git a/modules/text_server_adv/config.py b/modules/text_server_adv/config.py index 22482fce24..d22f9454ed 100644 --- a/modules/text_server_adv/config.py +++ b/modules/text_server_adv/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return env.module_check_dependencies("text_server_adv", ["freetype"]) + return True def configure(env): diff --git a/modules/text_server_adv/dynamic_font_adv.cpp b/modules/text_server_adv/dynamic_font_adv.cpp index 5a16158c0f..b60b9ddaec 100644 --- a/modules/text_server_adv/dynamic_font_adv.cpp +++ b/modules/text_server_adv/dynamic_font_adv.cpp @@ -30,6 +30,8 @@ #include "dynamic_font_adv.h" +#ifdef MODULE_FREETYPE_ENABLED + #include FT_STROKER_H #include FT_ADVANCES_H #include FT_MULTIPLE_MASTERS_H @@ -124,8 +126,8 @@ DynamicFontDataAdvanced::DataAtSize *DynamicFontDataAdvanced::get_data_for_size( fds->size = p_size; fds->ascent = (fds->face->size->metrics.ascender / 64.0) / oversampling * fds->scale_color_font; fds->descent = (-fds->face->size->metrics.descender / 64.0) / oversampling * fds->scale_color_font; - fds->underline_position = -fds->face->underline_position / 64.0 / oversampling * fds->scale_color_font; - fds->underline_thickness = fds->face->underline_thickness / 64.0 / oversampling * fds->scale_color_font; + fds->underline_position = (-FT_MulFix(fds->face->underline_position, fds->face->size->metrics.y_scale) / 64.0) / oversampling * fds->scale_color_font; + fds->underline_thickness = (FT_MulFix(fds->face->underline_thickness, fds->face->size->metrics.y_scale) / 64.0) / oversampling * fds->scale_color_font; //Load os2 TTF pable fds->os2 = (TT_OS2 *)FT_Get_Sfnt_Table(fds->face, FT_SFNT_OS2); @@ -1001,3 +1003,5 @@ DynamicFontDataAdvanced::~DynamicFontDataAdvanced() { FT_Done_FreeType(library); } } + +#endif // MODULE_FREETYPE_ENABLED diff --git a/modules/text_server_adv/dynamic_font_adv.h b/modules/text_server_adv/dynamic_font_adv.h index cd538cb8e1..d69a30b321 100644 --- a/modules/text_server_adv/dynamic_font_adv.h +++ b/modules/text_server_adv/dynamic_font_adv.h @@ -33,6 +33,10 @@ #include "font_adv.h" +#include "modules/modules_enabled.gen.h" + +#ifdef MODULE_FREETYPE_ENABLED + #include <ft2build.h> #include FT_FREETYPE_H #include FT_TRUETYPE_TABLES_H @@ -185,4 +189,6 @@ public: virtual ~DynamicFontDataAdvanced() override; }; +#endif // MODULE_FREETYPE_ENABLED + #endif // DYNAMIC_FONT_ADV_H diff --git a/modules/text_server_adv/font_adv.h b/modules/text_server_adv/font_adv.h index 4bbd2dd4bf..2b6d977451 100644 --- a/modules/text_server_adv/font_adv.h +++ b/modules/text_server_adv/font_adv.h @@ -39,11 +39,18 @@ struct FontDataAdvanced { Map<String, bool> lang_support_overrides; Map<String, bool> script_support_overrides; bool valid = false; + int spacing_space = 0; + int spacing_glyph = 0; virtual void clear_cache() = 0; - virtual Error load_from_file(const String &p_filename, int p_base_size) = 0; - virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) = 0; + virtual Error load_from_file(const String &p_filename, int p_base_size) { return ERR_CANT_CREATE; }; + virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) { return ERR_CANT_CREATE; }; + virtual Error bitmap_new(float p_height, float p_ascent, int p_base_size) { return ERR_CANT_CREATE; }; + + virtual void bitmap_add_texture(const Ref<Texture> &p_texture) { ERR_FAIL_MSG("Not supported."); }; + virtual void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { ERR_FAIL_MSG("Not supported."); }; + virtual void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { ERR_FAIL_MSG("Not supported."); }; virtual float get_height(int p_size) const = 0; virtual float get_ascent(int p_size) const = 0; @@ -58,6 +65,18 @@ struct FontDataAdvanced { virtual float get_underline_position(int p_size) const = 0; virtual float get_underline_thickness(int p_size) const = 0; + virtual int get_spacing_space() const { return spacing_space; }; + virtual void set_spacing_space(int p_value) { + spacing_space = p_value; + clear_cache(); + }; + + virtual int get_spacing_glyph() const { return spacing_glyph; }; + virtual void set_spacing_glyph(int p_value) { + spacing_glyph = p_value; + clear_cache(); + }; + virtual void set_antialiased(bool p_antialiased) = 0; virtual bool get_antialiased() const = 0; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 8e4771685d..2e3c2d1cab 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -529,10 +529,12 @@ RID TextServerAdvanced::create_font_system(const String &p_name, int p_base_size RID TextServerAdvanced::create_font_resource(const String &p_filename, int p_base_size) { _THREAD_SAFE_METHOD_ FontDataAdvanced *fd = nullptr; - if (p_filename.get_extension() == "ttf" || p_filename.get_extension() == "otf" || p_filename.get_extension() == "woff") { - fd = memnew(DynamicFontDataAdvanced); - } else if (p_filename.get_extension() == "fnt" || p_filename.get_extension() == "font") { + if (p_filename.get_extension() == "fnt" || p_filename.get_extension() == "font") { fd = memnew(BitmapFontDataAdvanced); +#ifdef MODULE_FREETYPE_ENABLED + } else if (p_filename.get_extension() == "ttf" || p_filename.get_extension() == "otf" || p_filename.get_extension() == "woff") { + fd = memnew(DynamicFontDataAdvanced); +#endif } else { return RID(); } @@ -549,10 +551,12 @@ RID TextServerAdvanced::create_font_resource(const String &p_filename, int p_bas RID TextServerAdvanced::create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size) { _THREAD_SAFE_METHOD_ FontDataAdvanced *fd = nullptr; - if (p_type == "ttf" || p_type == "otf" || p_type == "woff") { - fd = memnew(DynamicFontDataAdvanced); - } else if (p_type == "fnt" || p_type == "font") { + if (p_type == "fnt" || p_type == "font") { fd = memnew(BitmapFontDataAdvanced); +#ifdef MODULE_FREETYPE_ENABLED + } else if (p_type == "ttf" || p_type == "otf" || p_type == "woff") { + fd = memnew(DynamicFontDataAdvanced); +#endif } else { return RID(); } @@ -566,6 +570,39 @@ RID TextServerAdvanced::create_font_memory(const uint8_t *p_data, size_t p_size, return font_owner.make_rid(fd); } +RID TextServerAdvanced::create_font_bitmap(float p_height, float p_ascent, int p_base_size) { + _THREAD_SAFE_METHOD_ + FontDataAdvanced *fd = memnew(BitmapFontDataAdvanced); + Error err = fd->bitmap_new(p_height, p_ascent, p_base_size); + if (err != OK) { + memdelete(fd); + return RID(); + } + + return font_owner.make_rid(fd); +} + +void TextServerAdvanced::font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) { + _THREAD_SAFE_METHOD_ + FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->bitmap_add_texture(p_texture); +} + +void TextServerAdvanced::font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { + _THREAD_SAFE_METHOD_ + FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->bitmap_add_char(p_char, p_texture_idx, p_rect, p_align, p_advance); +} + +void TextServerAdvanced::font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) { + _THREAD_SAFE_METHOD_ + FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->bitmap_add_kerning_pair(p_A, p_B, p_kerning); +} + float TextServerAdvanced::font_get_height(RID p_font, int p_size) const { _THREAD_SAFE_METHOD_ const FontDataAdvanced *fd = font_owner.getornull(p_font); @@ -601,6 +638,34 @@ float TextServerAdvanced::font_get_underline_thickness(RID p_font, int p_size) c return fd->get_underline_thickness(p_size); } +int TextServerAdvanced::font_get_spacing_space(RID p_font) const { + _THREAD_SAFE_METHOD_ + const FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND_V(!fd, 0); + return fd->get_spacing_space(); +} + +void TextServerAdvanced::font_set_spacing_space(RID p_font, int p_value) { + _THREAD_SAFE_METHOD_ + FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->set_spacing_space(p_value); +} + +int TextServerAdvanced::font_get_spacing_glyph(RID p_font) const { + _THREAD_SAFE_METHOD_ + const FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND_V(!fd, 0); + return fd->get_spacing_glyph(); +} + +void TextServerAdvanced::font_set_spacing_glyph(RID p_font, int p_value) { + _THREAD_SAFE_METHOD_ + FontDataAdvanced *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->set_spacing_glyph(p_value); +} + void TextServerAdvanced::font_set_antialiased(RID p_font, bool p_antialiased) { _THREAD_SAFE_METHOD_ FontDataAdvanced *fd = font_owner.getornull(p_font); @@ -2049,6 +2114,11 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star gl.x_off = Math::round(glyph_pos[i].x_offset / (64.0 / fd->get_font_scale(fs))); gl.y_off = -Math::round(glyph_pos[i].y_offset / (64.0 / fd->get_font_scale(fs))); } + if (fd->get_spacing_space() && is_whitespace(p_sd->text[glyph_info[i].cluster])) { + gl.advance += fd->get_spacing_space(); + } else { + gl.advance += fd->get_spacing_glyph(); + } if (p_sd->preserve_control) { last_cluster_valid = last_cluster_valid && ((glyph_info[i].codepoint != 0) || is_whitespace(p_sd->text[glyph_info[i].cluster]) || is_linebreak(p_sd->text[glyph_info[i].cluster])); diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 89fae477f9..b53b5716e5 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -126,6 +126,11 @@ public: virtual RID create_font_system(const String &p_name, int p_base_size = 16) override; virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) override; virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) override; + virtual RID create_font_bitmap(float p_height, float p_ascent, int p_base_size = 16) override; + + virtual void font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) override; + virtual void font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; + virtual void font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) override; virtual float font_get_height(RID p_font, int p_size) const override; virtual float font_get_ascent(RID p_font, int p_size) const override; @@ -134,6 +139,12 @@ public: virtual float font_get_underline_position(RID p_font, int p_size) const override; virtual float font_get_underline_thickness(RID p_font, int p_size) const override; + virtual int font_get_spacing_space(RID p_font) const override; + virtual void font_set_spacing_space(RID p_font, int p_value) override; + + virtual int font_get_spacing_glyph(RID p_font) const override; + virtual void font_set_spacing_glyph(RID p_font, int p_value) override; + virtual void font_set_antialiased(RID p_font, bool p_antialiased) override; virtual bool font_get_antialiased(RID p_font) const override; diff --git a/modules/text_server_fb/bitmap_font_fb.cpp b/modules/text_server_fb/bitmap_font_fb.cpp index c9a9cc6eba..313f170f04 100644 --- a/modules/text_server_fb/bitmap_font_fb.cpp +++ b/modules/text_server_fb/bitmap_font_fb.cpp @@ -175,61 +175,58 @@ Error BitmapFontDataFallback::load_from_file(const String &p_filename, int p_bas return OK; } -Error BitmapFontDataFallback::load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND_V(p_data == nullptr, ERR_CANT_CREATE); - ERR_FAIL_COND_V(p_size != sizeof(TextServer::BitmapFontData), ERR_CANT_CREATE); +Error BitmapFontDataFallback::bitmap_new(float p_height, float p_ascent, int p_base_size) { + height = p_height; + ascent = p_ascent; - const TextServer::BitmapFontData *data = (const TextServer::BitmapFontData *)p_data; + base_size = p_base_size; + if (base_size == 0) { + base_size = height; + } - if (RenderingServer::get_singleton() != nullptr) { - Ref<Image> image = memnew(Image(data->img)); - Ref<ImageTexture> tex = memnew(ImageTexture); - tex->create_from_image(image); + char_map.clear(); + textures.clear(); + kerning_map.clear(); - textures.push_back(tex); - } + valid = true; - for (int i = 0; i < data->charcount; i++) { - const int *c = &data->char_rects[i * 8]; - - Character chr; - chr.rect.position.x = c[1]; - chr.rect.position.y = c[2]; - chr.rect.size.x = c[3]; - chr.rect.size.y = c[4]; - if (c[7] < 0) { - chr.advance.x = c[3]; - } else { - chr.advance.x = c[7]; - } - chr.align = Vector2(c[6], c[5]); - char_map[c[0]] = chr; - } + return OK; +} - for (int i = 0; i < data->kerning_count; i++) { - KerningPairKey kpk; - kpk.A = data->kernings[i * 3 + 0]; - kpk.B = data->kernings[i * 3 + 1]; +void BitmapFontDataFallback::bitmap_add_texture(const Ref<Texture> &p_texture) { + ERR_FAIL_COND(!valid); + ERR_FAIL_COND_MSG(p_texture.is_null(), "It's not a reference to a valid Texture object."); - if (data->kernings[i * 3 + 2] == 0 && kerning_map.has(kpk)) { - kerning_map.erase(kpk); - } else { - kerning_map[kpk] = data->kernings[i * 3 + 2]; - } - } + textures.push_back(p_texture); +} - height = data->height; - ascent = data->ascent; +void BitmapFontDataFallback::bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { + ERR_FAIL_COND(!valid); - base_size = p_base_size; - if (base_size == 0) { - base_size = height; + Character chr; + chr.rect = p_rect; + chr.texture_idx = p_texture_idx; + if (p_advance < 0) { + chr.advance.x = chr.rect.size.x; + } else { + chr.advance.x = p_advance; } + chr.align = p_align; + char_map[p_char] = chr; +} - valid = true; +void BitmapFontDataFallback::bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { + ERR_FAIL_COND(!valid); - return OK; + KerningPairKey kpk; + kpk.A = p_A; + kpk.B = p_B; + + if (p_kerning == 0 && kerning_map.has(kpk)) { + kerning_map.erase(kpk); + } else { + kerning_map[kpk] = p_kerning; + } } float BitmapFontDataFallback::get_height(int p_size) const { @@ -322,14 +319,13 @@ Vector2 BitmapFontDataFallback::draw_glyph(RID p_canvas, int p_size, const Vecto ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), Vector2()); if (c->texture_idx != -1) { Point2i cpos = p_pos; - cpos += c->align * (float(p_size) / float(base_size)); - cpos.y -= ascent * (float(p_size) / float(base_size)); - + cpos += (c->align + Vector2(0, -ascent)) * (float(p_size) / float(base_size)); + Size2i csize = c->rect.size * (float(p_size) / float(base_size)); if (RenderingServer::get_singleton() != nullptr) { //if (distance_field_hint) { // Not implemented. // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, true); //} - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, c->rect.size * (float(p_size) / float(base_size))), textures[c->texture_idx]->get_rid(), c->rect, p_color, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), textures[c->texture_idx]->get_rid(), c->rect, p_color, false, false); //if (distance_field_hint) { // RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(p_canvas, false); //} diff --git a/modules/text_server_fb/bitmap_font_fb.h b/modules/text_server_fb/bitmap_font_fb.h index 33401b85fa..7cd7507ebc 100644 --- a/modules/text_server_fb/bitmap_font_fb.h +++ b/modules/text_server_fb/bitmap_font_fb.h @@ -70,7 +70,11 @@ public: virtual void clear_cache() override{}; virtual Error load_from_file(const String &p_filename, int p_base_size) override; - virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) override; + virtual Error bitmap_new(float p_height, float p_ascent, int p_base_size) override; + + virtual void bitmap_add_texture(const Ref<Texture> &p_texture) override; + virtual void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; + virtual void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) override; virtual float get_height(int p_size) const override; virtual float get_ascent(int p_size) const override; diff --git a/modules/text_server_fb/config.py b/modules/text_server_fb/config.py index 491377a369..7a73080ae9 100644 --- a/modules/text_server_fb/config.py +++ b/modules/text_server_fb/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return env.module_check_dependencies("text_server_fb", ["freetype"]) + return True def configure(env): diff --git a/modules/text_server_fb/dynamic_font_fb.cpp b/modules/text_server_fb/dynamic_font_fb.cpp index df7756cbd0..66d36bc885 100644 --- a/modules/text_server_fb/dynamic_font_fb.cpp +++ b/modules/text_server_fb/dynamic_font_fb.cpp @@ -30,6 +30,8 @@ #include "dynamic_font_fb.h" +#ifdef MODULE_FREETYPE_ENABLED + #include FT_STROKER_H #include FT_ADVANCES_H @@ -124,8 +126,9 @@ DynamicFontDataFallback::DataAtSize *DynamicFontDataFallback::get_data_for_size( fds->size = p_size; fds->ascent = (fds->face->size->metrics.ascender / 64.0) / oversampling * fds->scale_color_font; fds->descent = (-fds->face->size->metrics.descender / 64.0) / oversampling * fds->scale_color_font; - fds->underline_position = -fds->face->underline_position / 64.0 / oversampling * fds->scale_color_font; - fds->underline_thickness = fds->face->underline_thickness / 64.0 / oversampling * fds->scale_color_font; + fds->underline_position = (-FT_MulFix(fds->face->underline_position, fds->face->size->metrics.y_scale) / 64.0) / oversampling * fds->scale_color_font; + fds->underline_thickness = (FT_MulFix(fds->face->underline_thickness, fds->face->size->metrics.y_scale) / 64.0) / oversampling * fds->scale_color_font; + if (p_outline_size != 0) { size_cache_outline[id] = fds; } else { @@ -683,3 +686,5 @@ DynamicFontDataFallback::~DynamicFontDataFallback() { FT_Done_FreeType(library); } } + +#endif // MODULE_FREETYPE_ENABLED diff --git a/modules/text_server_fb/dynamic_font_fb.h b/modules/text_server_fb/dynamic_font_fb.h index 81b18f6af3..eb70f46666 100644 --- a/modules/text_server_fb/dynamic_font_fb.h +++ b/modules/text_server_fb/dynamic_font_fb.h @@ -33,6 +33,10 @@ #include "font_fb.h" +#include "modules/modules_enabled.gen.h" + +#ifdef MODULE_FREETYPE_ENABLED + #include <ft2build.h> #include FT_FREETYPE_H @@ -163,4 +167,6 @@ public: virtual ~DynamicFontDataFallback() override; }; +#endif // MODULE_FREETYPE_ENABLED + #endif // DYNAMIC_FONT_FALLBACK_H diff --git a/modules/text_server_fb/font_fb.h b/modules/text_server_fb/font_fb.h index cc72919542..218f3df03a 100644 --- a/modules/text_server_fb/font_fb.h +++ b/modules/text_server_fb/font_fb.h @@ -37,11 +37,18 @@ struct FontDataFallback { Map<String, bool> lang_support_overrides; Map<String, bool> script_support_overrides; bool valid = false; + int spacing_space = 0; + int spacing_glyph = 0; virtual void clear_cache() = 0; - virtual Error load_from_file(const String &p_filename, int p_base_size) = 0; - virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) = 0; + virtual Error load_from_file(const String &p_filename, int p_base_size) { return ERR_CANT_CREATE; }; + virtual Error load_from_memory(const uint8_t *p_data, size_t p_size, int p_base_size) { return ERR_CANT_CREATE; }; + virtual Error bitmap_new(float p_height, float p_ascent, int p_base_size) { return ERR_CANT_CREATE; }; + + virtual void bitmap_add_texture(const Ref<Texture> &p_texture) { ERR_FAIL_MSG("Not supported."); }; + virtual void bitmap_add_char(char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { ERR_FAIL_MSG("Not supported."); }; + virtual void bitmap_add_kerning_pair(char32_t p_A, char32_t p_B, int p_kerning) { ERR_FAIL_MSG("Not supported."); }; virtual float get_height(int p_size) const = 0; virtual float get_ascent(int p_size) const = 0; @@ -50,6 +57,18 @@ struct FontDataFallback { virtual float get_underline_position(int p_size) const = 0; virtual float get_underline_thickness(int p_size) const = 0; + virtual int get_spacing_space() const { return spacing_space; }; + virtual void set_spacing_space(int p_value) { + spacing_space = p_value; + clear_cache(); + }; + + virtual int get_spacing_glyph() const { return spacing_glyph; }; + virtual void set_spacing_glyph(int p_value) { + spacing_glyph = p_value; + clear_cache(); + }; + virtual void set_antialiased(bool p_antialiased) = 0; virtual bool get_antialiased() const = 0; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index e60d269408..60ab14738a 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -107,10 +107,12 @@ RID TextServerFallback::create_font_system(const String &p_name, int p_base_size RID TextServerFallback::create_font_resource(const String &p_filename, int p_base_size) { _THREAD_SAFE_METHOD_ FontDataFallback *fd = nullptr; - if (p_filename.get_extension() == "ttf" || p_filename.get_extension() == "otf" || p_filename.get_extension() == "woff") { - fd = memnew(DynamicFontDataFallback); - } else if (p_filename.get_extension() == "fnt" || p_filename.get_extension() == "font") { + if (p_filename.get_extension() == "fnt" || p_filename.get_extension() == "font") { fd = memnew(BitmapFontDataFallback); +#ifdef MODULE_FREETYPE_ENABLED + } else if (p_filename.get_extension() == "ttf" || p_filename.get_extension() == "otf" || p_filename.get_extension() == "woff") { + fd = memnew(DynamicFontDataFallback); +#endif } else { return RID(); } @@ -127,10 +129,12 @@ RID TextServerFallback::create_font_resource(const String &p_filename, int p_bas RID TextServerFallback::create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size) { _THREAD_SAFE_METHOD_ FontDataFallback *fd = nullptr; - if (p_type == "ttf" || p_type == "otf" || p_type == "woff") { - fd = memnew(DynamicFontDataFallback); - } else if (p_type == "fnt" || p_type == "font") { + if (p_type == "fnt" || p_type == "font") { fd = memnew(BitmapFontDataFallback); +#ifdef MODULE_FREETYPE_ENABLED + } else if (p_type == "ttf" || p_type == "otf" || p_type == "woff") { + fd = memnew(DynamicFontDataFallback); +#endif } else { return RID(); } @@ -144,6 +148,39 @@ RID TextServerFallback::create_font_memory(const uint8_t *p_data, size_t p_size, return font_owner.make_rid(fd); } +RID TextServerFallback::create_font_bitmap(float p_height, float p_ascent, int p_base_size) { + _THREAD_SAFE_METHOD_ + FontDataFallback *fd = memnew(BitmapFontDataFallback); + Error err = fd->bitmap_new(p_height, p_ascent, p_base_size); + if (err != OK) { + memdelete(fd); + return RID(); + } + + return font_owner.make_rid(fd); +} + +void TextServerFallback::font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) { + _THREAD_SAFE_METHOD_ + FontDataFallback *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->bitmap_add_texture(p_texture); +} + +void TextServerFallback::font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) { + _THREAD_SAFE_METHOD_ + FontDataFallback *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->bitmap_add_char(p_char, p_texture_idx, p_rect, p_align, p_advance); +} + +void TextServerFallback::font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) { + _THREAD_SAFE_METHOD_ + FontDataFallback *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->bitmap_add_kerning_pair(p_A, p_B, p_kerning); +} + float TextServerFallback::font_get_height(RID p_font, int p_size) const { _THREAD_SAFE_METHOD_ const FontDataFallback *fd = font_owner.getornull(p_font); @@ -179,6 +216,34 @@ float TextServerFallback::font_get_underline_thickness(RID p_font, int p_size) c return fd->get_underline_thickness(p_size); } +int TextServerFallback::font_get_spacing_space(RID p_font) const { + _THREAD_SAFE_METHOD_ + const FontDataFallback *fd = font_owner.getornull(p_font); + ERR_FAIL_COND_V(!fd, 0); + return fd->get_spacing_space(); +} + +void TextServerFallback::font_set_spacing_space(RID p_font, int p_value) { + _THREAD_SAFE_METHOD_ + FontDataFallback *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->set_spacing_space(p_value); +} + +int TextServerFallback::font_get_spacing_glyph(RID p_font) const { + _THREAD_SAFE_METHOD_ + const FontDataFallback *fd = font_owner.getornull(p_font); + ERR_FAIL_COND_V(!fd, 0); + return fd->get_spacing_glyph(); +} + +void TextServerFallback::font_set_spacing_glyph(RID p_font, int p_value) { + _THREAD_SAFE_METHOD_ + FontDataFallback *fd = font_owner.getornull(p_font); + ERR_FAIL_COND(!fd); + fd->set_spacing_glyph(p_value); +} + void TextServerFallback::font_set_antialiased(RID p_font, bool p_antialiased) { _THREAD_SAFE_METHOD_ FontDataFallback *fd = font_owner.getornull(p_font); @@ -1184,6 +1249,11 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { sd->descent = MAX(sd->descent, Math::round(fd->get_advance(gl.index, gl.font_size).x * 0.5)); } } + if (fd->get_spacing_space() && is_whitespace(sd->text[j])) { + gl.advance += fd->get_spacing_space(); + } else { + gl.advance += fd->get_spacing_glyph(); + } sd->upos = MAX(sd->upos, fd->get_underline_position(gl.font_size)); sd->uthk = MAX(sd->uthk, fd->get_underline_thickness(gl.font_size)); diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index d142b320e4..b10369d172 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -81,6 +81,11 @@ public: virtual RID create_font_system(const String &p_name, int p_base_size = 16) override; virtual RID create_font_resource(const String &p_filename, int p_base_size = 16) override; virtual RID create_font_memory(const uint8_t *p_data, size_t p_size, const String &p_type, int p_base_size = 16) override; + virtual RID create_font_bitmap(float p_height, float p_ascent, int p_base_size = 16) override; + + virtual void font_bitmap_add_texture(RID p_font, const Ref<Texture> &p_texture) override; + virtual void font_bitmap_add_char(RID p_font, char32_t p_char, int p_texture_idx, const Rect2 &p_rect, const Size2 &p_align, float p_advance) override; + virtual void font_bitmap_add_kerning_pair(RID p_font, char32_t p_A, char32_t p_B, int p_kerning) override; virtual float font_get_height(RID p_font, int p_size) const override; virtual float font_get_ascent(RID p_font, int p_size) const override; @@ -89,6 +94,12 @@ public: virtual float font_get_underline_position(RID p_font, int p_size) const override; virtual float font_get_underline_thickness(RID p_font, int p_size) const override; + virtual int font_get_spacing_space(RID p_font) const override; + virtual void font_set_spacing_space(RID p_font, int p_value) override; + + virtual int font_get_spacing_glyph(RID p_font) const override; + virtual void font_set_spacing_glyph(RID p_font, int p_value) override; + virtual void font_set_antialiased(RID p_font, bool p_antialiased) override; virtual bool font_get_antialiased(RID p_font) const override; diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index afe26d4a5f..c5f6dc0d99 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -554,7 +554,7 @@ void VideoStreamPlaybackTheora::play() { } playing = true; - delay_compensation = ProjectSettings::get_singleton()->get("audio/video_delay_compensation_ms"); + delay_compensation = ProjectSettings::get_singleton()->get("audio/video/video_delay_compensation_ms"); delay_compensation /= 1000.0; }; @@ -677,7 +677,7 @@ void VideoStreamTheora::_bind_methods() { //////////// -RES ResourceFormatLoaderTheora::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderTheora::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { if (r_error) { diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index 71e56e2ee8..2685a8a013 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -36,6 +36,7 @@ #include "core/os/semaphore.h" #include "core/os/thread.h" #include "core/templates/ring_buffer.h" +#include "core/templates/safe_refcount.h" #include "scene/resources/video_stream.h" #include "servers/audio_server.h" @@ -113,7 +114,7 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback { bool thread_eof = false; Semaphore *thread_sem; Thread thread; - volatile bool thread_exit = false; + SafeFlag thread_exit; static void _streaming_thread(void *ud); @@ -185,7 +186,7 @@ public: class ResourceFormatLoaderTheora : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 39726a4a58..fcd55b3049 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -4274,13 +4274,13 @@ VisualScriptEditor::VisualScriptEditor() { edit_menu->set_shortcut_context(this); edit_menu->set_text(TTR("Edit")); edit_menu->set_switch_on_hover(true); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/delete_selected"), EDIT_DELETE_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_graph_delete"), EDIT_DELETE_NODES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/toggle_breakpoint"), EDIT_TOGGLE_BREAKPOINT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/find_node_type"), EDIT_FIND_NODE_TYPE); edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/copy_nodes"), EDIT_COPY_NODES); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/cut_nodes"), EDIT_CUT_NODES); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/paste_nodes"), EDIT_PASTE_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE_NODES); edit_menu->get_popup()->add_separator(); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/create_function"), EDIT_CREATE_FUNCTION); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/refresh_nodes"), REFRESH_GRAPH); @@ -4520,12 +4520,8 @@ void VisualScriptEditor::free_clipboard() { static void register_editor_callback() { ScriptEditor::register_create_script_editor_function(create_editor); - ED_SHORTCUT("visual_script_editor/delete_selected", TTR("Delete Selected"), KEY_DELETE); ED_SHORTCUT("visual_script_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_F9); ED_SHORTCUT("visual_script_editor/find_node_type", TTR("Find Node Type"), KEY_MASK_CMD + KEY_F); - ED_SHORTCUT("visual_script_editor/copy_nodes", TTR("Copy Nodes"), KEY_MASK_CMD + KEY_C); - ED_SHORTCUT("visual_script_editor/cut_nodes", TTR("Cut Nodes"), KEY_MASK_CMD + KEY_X); - ED_SHORTCUT("visual_script_editor/paste_nodes", TTR("Paste Nodes"), KEY_MASK_CMD + KEY_V); ED_SHORTCUT("visual_script_editor/create_function", TTR("Make Function"), KEY_MASK_CMD + KEY_G); ED_SHORTCUT("visual_script_editor/refresh_nodes", TTR("Refresh Graph"), KEY_MASK_CMD + KEY_R); ED_SHORTCUT("visual_script_editor/edit_member", TTR("Edit Member"), KEY_MASK_CMD + KEY_E); diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index 5fc2ccf6e3..f673cbb06d 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -1506,6 +1506,19 @@ VisualScriptNodeInstance *VisualScriptExpression::instance(VisualScriptInstance return instance; } +void VisualScriptExpression::reset_state() { + if (nodes) { + memdelete(nodes); + nodes = nullptr; + root = nullptr; + } + + error_str = String(); + error_set = false; + str_ofs = 0; + inputs.clear(); +} + VisualScriptExpression::VisualScriptExpression() { } diff --git a/modules/visual_script/visual_script_expression.h b/modules/visual_script/visual_script_expression.h index bbf76cef3b..c35075ea53 100644 --- a/modules/visual_script/visual_script_expression.h +++ b/modules/visual_script/visual_script_expression.h @@ -256,6 +256,8 @@ protected: void _get_property_list(List<PropertyInfo> *p_list) const; public: + virtual void reset_state() override; + virtual int get_output_sequence_port_count() const override; virtual bool has_input_sequence_port() const override; diff --git a/modules/visual_script/visual_script_flow_control.cpp b/modules/visual_script/visual_script_flow_control.cpp index cfe283d9df..e2a8323509 100644 --- a/modules/visual_script/visual_script_flow_control.cpp +++ b/modules/visual_script/visual_script_flow_control.cpp @@ -677,6 +677,10 @@ void VisualScriptSwitch::_get_property_list(List<PropertyInfo> *p_list) const { } } +void VisualScriptSwitch::reset_state() { + case_values.clear(); +} + void VisualScriptSwitch::_bind_methods() { } diff --git a/modules/visual_script/visual_script_flow_control.h b/modules/visual_script/visual_script_flow_control.h index 46a72bb92d..d9c4dedafd 100644 --- a/modules/visual_script/visual_script_flow_control.h +++ b/modules/visual_script/visual_script_flow_control.h @@ -202,6 +202,8 @@ protected: static void _bind_methods(); public: + virtual void reset_state() override; + virtual int get_output_sequence_port_count() const override; virtual bool has_input_sequence_port() const override; diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index e3ca5ef855..fed6637acb 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -303,6 +303,14 @@ VisualScriptNodeInstance *VisualScriptFunction::instance(VisualScriptInstance *p return instance; } +void VisualScriptFunction::reset_state() { + arguments.clear(); + stack_size = 256; + stack_less = false; + sequenced = true; + rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; +} + VisualScriptFunction::VisualScriptFunction() { stack_size = 256; stack_less = false; @@ -687,6 +695,13 @@ bool VisualScriptLists::is_sequenced() const { return sequenced; } +void VisualScriptLists::reset_state() { + inputports.clear(); + outputports.clear(); + sequenced = false; + flags = 0; +} + VisualScriptLists::VisualScriptLists() { // initialize sequenced = false; diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index ae5e04d096..7392443e4e 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -99,6 +99,8 @@ public: virtual VisualScriptNodeInstance *instance(VisualScriptInstance *p_instance) override; + virtual void reset_state() override; + VisualScriptFunction(); }; @@ -134,6 +136,8 @@ protected: static void _bind_methods(); public: + virtual void reset_state() override; + virtual bool is_output_port_editable() const; virtual bool is_output_port_name_editable() const; virtual bool is_output_port_type_editable() const; diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp index 5d8245c64c..101001cba0 100644 --- a/modules/webm/video_stream_webm.cpp +++ b/modules/webm/video_stream_webm.cpp @@ -156,7 +156,7 @@ void VideoStreamPlaybackWebm::stop() { void VideoStreamPlaybackWebm::play() { stop(); - delay_compensation = ProjectSettings::get_singleton()->get("audio/video_delay_compensation_ms"); + delay_compensation = ProjectSettings::get_singleton()->get("audio/video/video_delay_compensation_ms"); delay_compensation /= 1000.0; playing = true; @@ -429,7 +429,7 @@ void VideoStreamWebm::set_audio_track(int p_track) { //////////// -RES ResourceFormatLoaderWebm::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { +RES ResourceFormatLoaderWebm::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { if (r_error) { diff --git a/modules/webm/video_stream_webm.h b/modules/webm/video_stream_webm.h index cb3cf58850..60e02ab38b 100644 --- a/modules/webm/video_stream_webm.h +++ b/modules/webm/video_stream_webm.h @@ -126,7 +126,7 @@ public: class ResourceFormatLoaderWebm : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js index 03dc05bc83..9ea105aa93 100644 --- a/modules/webxr/native/webxr.externs.js +++ b/modules/webxr/native/webxr.externs.js @@ -33,7 +33,7 @@ XR.prototype.ondevicechanged; * * @return {!Promise<boolean>} */ -XR.prototype.isSessionSupported = function(mode) {} +XR.prototype.isSessionSupported = function(mode) {}; /** * @param {string} mode @@ -41,7 +41,7 @@ XR.prototype.isSessionSupported = function(mode) {} * * @return {!Promise<XRSession>} */ -XR.prototype.requestSession = function(mode, options) {} +XR.prototype.requestSession = function(mode, options) {}; /** * @constructor diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index 880fe2df61..fb4df10904 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -154,6 +154,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver float h = *r_size_hint_y; if (w == 0 || h == 0) { + xatlas::Destroy(atlas); return false; //could not bake because there is no area } |