summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/object/message_queue.cpp465
-rw-r--r--core/object/message_queue.h65
-rw-r--r--doc/classes/ProjectSettings.xml2
-rw-r--r--doc/classes/RenderingDevice.xml2
-rw-r--r--doc/classes/TextServer.xml3
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp7
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp12
-rw-r--r--drivers/gles3/shaders/particles.glsl153
-rw-r--r--drivers/gles3/storage/particles_storage.cpp9
-rw-r--r--editor/editor_log.cpp5
-rw-r--r--editor/editor_properties_array_dict.cpp68
-rw-r--r--modules/text_server_adv/text_server_adv.cpp47
-rw-r--r--modules/text_server_adv/text_server_adv.h1
-rw-r--r--modules/text_server_fb/text_server_fb.cpp2
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/range.cpp23
-rw-r--r--scene/gui/range.h2
-rw-r--r--scene/gui/rich_text_label.cpp13
-rw-r--r--scene/gui/rich_text_label.h2
-rw-r--r--scene/gui/spin_box.cpp19
-rw-r--r--scene/gui/spin_box.h2
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/gui/tree.cpp4
-rw-r--r--scene/resources/packed_scene.cpp77
-rw-r--r--scene/resources/packed_scene.h2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/particles_storage.cpp4
-rw-r--r--servers/rendering/rendering_device.cpp2
-rw-r--r--servers/rendering/shader_language.cpp64
-rw-r--r--servers/text_server.cpp3
-rw-r--r--servers/text_server.h9
30 files changed, 684 insertions, 387 deletions
diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp
index 1542decc6f..7a8dae2341 100644
--- a/core/object/message_queue.cpp
+++ b/core/object/message_queue.cpp
@@ -35,94 +35,55 @@
#include "core/object/class_db.h"
#include "core/object/script_language.h"
-MessageQueue *MessageQueue::singleton = nullptr;
-
-MessageQueue *MessageQueue::get_singleton() {
- return singleton;
-}
-
-Error MessageQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
- return push_callablep(Callable(p_id, p_method), p_args, p_argcount, p_show_error);
-}
-
-Error MessageQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) {
- _THREAD_SAFE_METHOD_
-
- uint8_t room_needed = sizeof(Message) + sizeof(Variant);
-
- if ((buffer_end + room_needed) >= buffer_size) {
- String type;
- if (ObjectDB::get_instance(p_id)) {
- type = ObjectDB::get_instance(p_id)->get_class();
- }
- ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. Try increasing \"memory/limits/message_queue/max_size_kb\" in project settings.");
- statistics();
- return ERR_OUT_OF_MEMORY;
+void CallQueue::_add_page() {
+ if (pages_used == page_messages.size()) {
+ pages.push_back(allocator->alloc());
+ page_messages.push_back(0);
}
-
- Message *msg = memnew_placement(&buffer[buffer_end], Message);
- msg->args = 1;
- msg->callable = Callable(p_id, p_prop);
- msg->type = TYPE_SET;
-
- buffer_end += sizeof(Message);
-
- Variant *v = memnew_placement(&buffer[buffer_end], Variant);
- buffer_end += sizeof(Variant);
- *v = p_value;
-
- return OK;
+ page_messages[pages_used] = 0;
+ pages_used++;
+ page_offset = 0;
}
-Error MessageQueue::push_notification(ObjectID p_id, int p_notification) {
- _THREAD_SAFE_METHOD_
-
- ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER);
-
- uint8_t room_needed = sizeof(Message);
-
- if ((buffer_end + room_needed) >= buffer_size) {
- ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. Try increasing \"memory/limits/message_queue/max_size_kb\" in project settings.");
- statistics();
- return ERR_OUT_OF_MEMORY;
- }
-
- Message *msg = memnew_placement(&buffer[buffer_end], Message);
-
- msg->type = TYPE_NOTIFICATION;
- msg->callable = Callable(p_id, CoreStringNames::get_singleton()->notification); //name is meaningless but callable needs it
- //msg->target;
- msg->notification = p_notification;
-
- buffer_end += sizeof(Message);
-
- return OK;
+Error CallQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
+ return push_callablep(Callable(p_id, p_method), p_args, p_argcount, p_show_error);
}
-Error MessageQueue::push_callp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
+Error CallQueue::push_callp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
return push_callp(p_object->get_instance_id(), p_method, p_args, p_argcount, p_show_error);
}
-Error MessageQueue::push_notification(Object *p_object, int p_notification) {
+Error CallQueue::push_notification(Object *p_object, int p_notification) {
return push_notification(p_object->get_instance_id(), p_notification);
}
-Error MessageQueue::push_set(Object *p_object, const StringName &p_prop, const Variant &p_value) {
+Error CallQueue::push_set(Object *p_object, const StringName &p_prop, const Variant &p_value) {
return push_set(p_object->get_instance_id(), p_prop, p_value);
}
-Error MessageQueue::push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) {
- _THREAD_SAFE_METHOD_
+Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) {
+ mutex.lock();
+ uint32_t room_needed = sizeof(Message) + sizeof(Variant) * p_argcount;
- int room_needed = sizeof(Message) + sizeof(Variant) * p_argcount;
+ ERR_FAIL_COND_V_MSG(room_needed > uint32_t(PAGE_SIZE_BYTES), ERR_INVALID_PARAMETER, "Message is too large to fit on a page (" + itos(PAGE_SIZE_BYTES) + " bytes), consider passing less arguments.");
- if ((buffer_end + room_needed) >= buffer_size) {
- ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. Try increasing \"memory/limits/message_queue/max_size_kb\" in project settings.");
- statistics();
- return ERR_OUT_OF_MEMORY;
+ _ensure_first_page();
+
+ if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
+ if (room_needed > uint32_t(PAGE_SIZE_BYTES) || pages_used == max_pages) {
+ ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. " + error_text);
+ statistics();
+ mutex.unlock();
+ return ERR_OUT_OF_MEMORY;
+ }
+ _add_page();
}
- Message *msg = memnew_placement(&buffer[buffer_end], Message);
+ Page *page = pages[pages_used - 1];
+
+ uint8_t *buffer_end = &page->data[page_offset];
+
+ Message *msg = memnew_placement(buffer_end, Message);
msg->args = p_argcount;
msg->callable = p_callable;
msg->type = TYPE_CALL;
@@ -133,89 +94,95 @@ Error MessageQueue::push_callablep(const Callable &p_callable, const Variant **p
buffer_end += sizeof(Message);
for (int i = 0; i < p_argcount; i++) {
- Variant *v = memnew_placement(&buffer[buffer_end], Variant);
+ Variant *v = memnew_placement(buffer_end, Variant);
buffer_end += sizeof(Variant);
*v = *p_args[i];
}
+ page_messages[pages_used - 1]++;
+ page_offset += room_needed;
+
+ mutex.unlock();
+
return OK;
}
-void MessageQueue::statistics() {
- HashMap<StringName, int> set_count;
- HashMap<int, int> notify_count;
- HashMap<Callable, int> call_count;
- int null_count = 0;
+Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) {
+ mutex.lock();
+ uint32_t room_needed = sizeof(Message) + sizeof(Variant);
- uint32_t read_pos = 0;
- while (read_pos < buffer_end) {
- Message *message = (Message *)&buffer[read_pos];
+ _ensure_first_page();
- Object *target = message->callable.get_object();
+ if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
+ if (pages_used == max_pages) {
+ String type;
+ if (ObjectDB::get_instance(p_id)) {
+ type = ObjectDB::get_instance(p_id)->get_class();
+ }
+ ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text);
+ statistics();
- if (target != nullptr) {
- switch (message->type & FLAG_MASK) {
- case TYPE_CALL: {
- if (!call_count.has(message->callable)) {
- call_count[message->callable] = 0;
- }
+ mutex.unlock();
+ return ERR_OUT_OF_MEMORY;
+ }
+ _add_page();
+ }
- call_count[message->callable]++;
+ Page *page = pages[pages_used - 1];
+ uint8_t *buffer_end = &page->data[page_offset];
- } break;
- case TYPE_NOTIFICATION: {
- if (!notify_count.has(message->notification)) {
- notify_count[message->notification] = 0;
- }
+ Message *msg = memnew_placement(buffer_end, Message);
+ msg->args = 1;
+ msg->callable = Callable(p_id, p_prop);
+ msg->type = TYPE_SET;
- notify_count[message->notification]++;
+ buffer_end += sizeof(Message);
- } break;
- case TYPE_SET: {
- StringName t = message->callable.get_method();
- if (!set_count.has(t)) {
- set_count[t] = 0;
- }
+ Variant *v = memnew_placement(buffer_end, Variant);
+ *v = p_value;
- set_count[t]++;
+ page_messages[pages_used - 1]++;
+ page_offset += room_needed;
+ mutex.unlock();
- } break;
- }
+ return OK;
+}
- } else {
- //object was deleted
- print_line("Object was deleted while awaiting a callback");
+Error CallQueue::push_notification(ObjectID p_id, int p_notification) {
+ ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER);
+ mutex.lock();
+ uint32_t room_needed = sizeof(Message);
- null_count++;
- }
+ _ensure_first_page();
- read_pos += sizeof(Message);
- if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
- read_pos += sizeof(Variant) * message->args;
+ if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
+ if (pages_used == max_pages) {
+ ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text);
+ statistics();
+ mutex.unlock();
+ return ERR_OUT_OF_MEMORY;
}
+ _add_page();
}
- print_line("TOTAL BYTES: " + itos(buffer_end));
- print_line("NULL count: " + itos(null_count));
+ Page *page = pages[pages_used - 1];
+ uint8_t *buffer_end = &page->data[page_offset];
- for (const KeyValue<StringName, int> &E : set_count) {
- print_line("SET " + E.key + ": " + itos(E.value));
- }
+ Message *msg = memnew_placement(buffer_end, Message);
- for (const KeyValue<Callable, int> &E : call_count) {
- print_line("CALL " + E.key + ": " + itos(E.value));
- }
+ msg->type = TYPE_NOTIFICATION;
+ msg->callable = Callable(p_id, CoreStringNames::get_singleton()->notification); //name is meaningless but callable needs it
+ //msg->target;
+ msg->notification = p_notification;
- for (const KeyValue<int, int> &E : notify_count) {
- print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value));
- }
-}
+ page_messages[pages_used - 1]++;
+ page_offset += room_needed;
+ mutex.unlock();
-int MessageQueue::get_max_buffer_usage() const {
- return buffer_max_used;
+ return OK;
}
-void MessageQueue::_call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error) {
+void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error) {
const Variant **argptrs = nullptr;
if (p_argcount) {
argptrs = (const Variant **)alloca(sizeof(Variant *) * p_argcount);
@@ -232,26 +199,32 @@ void MessageQueue::_call_function(const Callable &p_callable, const Variant *p_a
}
}
-void MessageQueue::flush() {
- if (buffer_end > buffer_max_used) {
- buffer_max_used = buffer_end;
- }
+Error CallQueue::flush() {
+ mutex.lock();
- uint32_t read_pos = 0;
-
- //using reverse locking strategy
- _THREAD_SAFE_LOCK_
+ if (pages.size() == 0) {
+ // Never allocated
+ mutex.unlock();
+ return OK; // Do nothing.
+ }
if (flushing) {
- _THREAD_SAFE_UNLOCK_
- ERR_FAIL_COND(flushing); //already flushing, you did something odd
+ mutex.unlock();
+ return ERR_BUSY;
}
+
flushing = true;
- while (read_pos < buffer_end) {
+ uint32_t i = 0;
+ uint32_t j = 0;
+ uint32_t offset = 0;
+
+ while (i < pages_used && j < page_messages[i]) {
+ Page *page = pages[i];
+
//lock on each iteration, so a call can re-add itself to the message queue
- Message *message = (Message *)&buffer[read_pos];
+ Message *message = (Message *)&page->data[offset];
uint32_t advance = sizeof(Message);
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
@@ -259,12 +232,12 @@ void MessageQueue::flush() {
}
//pre-advance so this function is reentrant
- read_pos += advance;
-
- _THREAD_SAFE_UNLOCK_
+ offset += advance;
Object *target = message->callable.get_object();
+ mutex.unlock();
+
if (target != nullptr) {
switch (message->type & FLAG_MASK) {
case TYPE_CALL: {
@@ -291,54 +264,208 @@ void MessageQueue::flush() {
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
Variant *args = (Variant *)(message + 1);
- for (int i = 0; i < message->args; i++) {
- args[i].~Variant();
+ for (int k = 0; k < message->args; k++) {
+ args[k].~Variant();
}
}
message->~Message();
- _THREAD_SAFE_LOCK_
+ mutex.lock();
+ j++;
+ if (j == page_messages[i]) {
+ j = 0;
+ i++;
+ offset = 0;
+ }
}
- buffer_end = 0; // reset buffer
+ page_messages[0] = 0;
+ page_offset = 0;
+ pages_used = 1;
+
flushing = false;
- _THREAD_SAFE_UNLOCK_
+ mutex.unlock();
+ return OK;
}
-bool MessageQueue::is_flushing() const {
- return flushing;
-}
+void CallQueue::clear() {
+ mutex.lock();
-MessageQueue::MessageQueue() {
- ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists.");
- singleton = this;
+ if (pages.size() == 0) {
+ mutex.unlock();
+ return; // Nothing to clear.
+ }
- buffer_size = GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_kb", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater"), DEFAULT_QUEUE_SIZE_KB);
- buffer_size *= 1024;
- buffer = memnew_arr(uint8_t, buffer_size);
-}
+ for (uint32_t i = 0; i < pages_used; i++) {
+ uint32_t offset = 0;
+ for (uint32_t j = 0; j < page_messages[i]; j++) {
+ Page *page = pages[i];
-MessageQueue::~MessageQueue() {
- uint32_t read_pos = 0;
+ //lock on each iteration, so a call can re-add itself to the message queue
- while (read_pos < buffer_end) {
- Message *message = (Message *)&buffer[read_pos];
- Variant *args = (Variant *)(message + 1);
- int argc = message->args;
- if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
- for (int i = 0; i < argc; i++) {
- args[i].~Variant();
+ Message *message = (Message *)&page->data[offset];
+
+ uint32_t advance = sizeof(Message);
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ advance += sizeof(Variant) * message->args;
}
+
+ //pre-advance so this function is reentrant
+ offset += advance;
+
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ Variant *args = (Variant *)(message + 1);
+ for (int k = 0; k < message->args; k++) {
+ args[k].~Variant();
+ }
+ }
+
+ message->~Message();
}
- message->~Message();
+ }
- read_pos += sizeof(Message);
- if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
- read_pos += sizeof(Variant) * message->args;
+ pages_used = 1;
+ page_offset = 0;
+ page_messages[0] = 0;
+
+ mutex.unlock();
+}
+
+void CallQueue::statistics() {
+ mutex.lock();
+ HashMap<StringName, int> set_count;
+ HashMap<int, int> notify_count;
+ HashMap<Callable, int> call_count;
+ int null_count = 0;
+
+ for (uint32_t i = 0; i < pages_used; i++) {
+ uint32_t offset = 0;
+ for (uint32_t j = 0; j < page_messages[i]; j++) {
+ Page *page = pages[i];
+
+ //lock on each iteration, so a call can re-add itself to the message queue
+
+ Message *message = (Message *)&page->data[offset];
+
+ uint32_t advance = sizeof(Message);
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ advance += sizeof(Variant) * message->args;
+ }
+
+ Object *target = message->callable.get_object();
+
+ if (target != nullptr) {
+ switch (message->type & FLAG_MASK) {
+ case TYPE_CALL: {
+ if (!call_count.has(message->callable)) {
+ call_count[message->callable] = 0;
+ }
+
+ call_count[message->callable]++;
+
+ } break;
+ case TYPE_NOTIFICATION: {
+ if (!notify_count.has(message->notification)) {
+ notify_count[message->notification] = 0;
+ }
+
+ notify_count[message->notification]++;
+
+ } break;
+ case TYPE_SET: {
+ StringName t = message->callable.get_method();
+ if (!set_count.has(t)) {
+ set_count[t] = 0;
+ }
+
+ set_count[t]++;
+
+ } break;
+ }
+
+ } else {
+ //object was deleted
+ print_line("Object was deleted while awaiting a callback");
+
+ null_count++;
+ }
+
+ //pre-advance so this function is reentrant
+ offset += advance;
+
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ Variant *args = (Variant *)(message + 1);
+ for (int k = 0; k < message->args; k++) {
+ args[k].~Variant();
+ }
+ }
+
+ message->~Message();
}
}
+ print_line("TOTAL PAGES: " + itos(pages_used) + " (" + itos(pages_used * PAGE_SIZE_BYTES) + " bytes).");
+ print_line("NULL count: " + itos(null_count));
+
+ for (const KeyValue<StringName, int> &E : set_count) {
+ print_line("SET " + E.key + ": " + itos(E.value));
+ }
+
+ for (const KeyValue<Callable, int> &E : call_count) {
+ print_line("CALL " + E.key + ": " + itos(E.value));
+ }
+
+ for (const KeyValue<int, int> &E : notify_count) {
+ print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value));
+ }
+
+ mutex.unlock();
+}
+
+bool CallQueue::is_flushing() const {
+ return flushing;
+}
+
+int CallQueue::get_max_buffer_usage() const {
+ return pages.size() * PAGE_SIZE_BYTES;
+}
+
+CallQueue::CallQueue(Allocator *p_custom_allocator, uint32_t p_max_pages, const String &p_error_text) {
+ if (p_custom_allocator) {
+ allocator = p_custom_allocator;
+ allocator_is_custom = true;
+ } else {
+ allocator = memnew(Allocator(16)); // 16 elements per allocator page, 64kb per allocator page. Anything small will do, though.
+ allocator_is_custom = false;
+ }
+ max_pages = p_max_pages;
+ error_text = p_error_text;
+}
+
+CallQueue::~CallQueue() {
+ clear();
+ // Let go of pages.
+ for (uint32_t i = 0; i < pages.size(); i++) {
+ allocator->free(pages[i]);
+ }
+ if (!allocator_is_custom) {
+ memdelete(allocator);
+ }
+}
+
+//////////////////////
+
+MessageQueue *MessageQueue::singleton = nullptr;
+
+MessageQueue::MessageQueue() :
+ CallQueue(nullptr,
+ int(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_mb", PROPERTY_HINT_RANGE, "1,512,1,or_greater"), 32)) * 1024 * 1024 / PAGE_SIZE_BYTES,
+ "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_mb' in project settings.") {
+ ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists.");
+ singleton = this;
+}
+
+MessageQueue::~MessageQueue() {
singleton = nullptr;
- memdelete_arr(buffer);
}
diff --git a/core/object/message_queue.h b/core/object/message_queue.h
index ceb6f2a3f1..525285e061 100644
--- a/core/object/message_queue.h
+++ b/core/object/message_queue.h
@@ -33,26 +33,45 @@
#include "core/object/object_id.h"
#include "core/os/thread_safe.h"
+#include "core/templates/local_vector.h"
+#include "core/templates/paged_allocator.h"
#include "core/variant/variant.h"
class Object;
-class MessageQueue {
- _THREAD_SAFE_CLASS_
-
+class CallQueue {
+public:
enum {
- DEFAULT_QUEUE_SIZE_KB = 4096
+ PAGE_SIZE_BYTES = 4096
};
+private:
enum {
TYPE_CALL,
TYPE_NOTIFICATION,
TYPE_SET,
+ TYPE_END, // End marker.
FLAG_SHOW_ERROR = 1 << 14,
- FLAG_MASK = FLAG_SHOW_ERROR - 1
+ FLAG_MASK = FLAG_SHOW_ERROR - 1,
+ };
+ struct Page {
+ uint8_t data[PAGE_SIZE_BYTES];
};
+ Mutex mutex;
+ typedef PagedAllocator<Page, true> Allocator;
+
+ Allocator *allocator = nullptr;
+ bool allocator_is_custom = false;
+
+ LocalVector<Page *> pages;
+ LocalVector<uint32_t> page_messages;
+ uint32_t max_pages = 0;
+ uint32_t pages_used = 0;
+ uint32_t page_offset = 0;
+ bool flushing = false;
+
struct Message {
Callable callable;
int16_t type;
@@ -62,20 +81,21 @@ class MessageQueue {
};
};
- uint8_t *buffer = nullptr;
- uint32_t buffer_end = 0;
- uint32_t buffer_max_used = 0;
- uint32_t buffer_size = 0;
+ _FORCE_INLINE_ void _ensure_first_page() {
+ if (unlikely(pages.is_empty())) {
+ pages.push_back(allocator->alloc());
+ page_messages.push_back(0);
+ pages_used = 1;
+ }
+ }
+
+ void _add_page();
void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error);
- static MessageQueue *singleton;
-
- bool flushing = false;
+ String error_text;
public:
- static MessageQueue *get_singleton();
-
Error push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false);
template <typename... VarArgs>
Error push_call(ObjectID p_id, const StringName &p_method, VarArgs... p_args) {
@@ -87,9 +107,9 @@ public:
return push_callp(p_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
- Error push_notification(ObjectID p_id, int p_notification);
- Error push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value);
Error push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error = false);
+ Error push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value);
+ Error push_notification(ObjectID p_id, int p_notification);
template <typename... VarArgs>
Error push_callable(const Callable &p_callable, VarArgs... p_args) {
@@ -115,13 +135,22 @@ public:
Error push_notification(Object *p_object, int p_notification);
Error push_set(Object *p_object, const StringName &p_prop, const Variant &p_value);
+ Error flush();
+ void clear();
void statistics();
- void flush();
bool is_flushing() const;
-
int get_max_buffer_usage() const;
+ CallQueue(Allocator *p_custom_allocator = 0, uint32_t p_max_pages = 8192, const String &p_error_text = String());
+ virtual ~CallQueue();
+};
+
+class MessageQueue : public CallQueue {
+ static MessageQueue *singleton;
+
+public:
+ _FORCE_INLINE_ static MessageQueue *get_singleton() { return singleton; }
MessageQueue();
~MessageQueue();
};
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1a77e7c525..710b9889f1 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1739,7 +1739,7 @@
<member name="layer_names/3d_render/layer_9" type="String" setter="" getter="" default="&quot;&quot;">
Optional name for the 3D render layer 9. If left empty, the layer will display as "Layer 9".
</member>
- <member name="memory/limits/message_queue/max_size_kb" type="int" setter="" getter="" default="4096">
+ <member name="memory/limits/message_queue/max_size_mb" type="int" setter="" getter="" default="32">
Godot uses a message queue to defer some function calls. If you run out of space on it (you will see an error), you can increase the size here.
</member>
<member name="memory/limits/multithreaded_server/rid_pool_prealloc" type="int" setter="" getter="" default="60">
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index 745926d4a8..f597cb7efc 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -113,7 +113,7 @@
</method>
<method name="compute_pipeline_is_valid">
<return type="bool" />
- <param index="0" name="compute_pieline" type="RID" />
+ <param index="0" name="compute_pipeline" type="RID" />
<description>
</description>
</method>
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 4f38785369..4118274732 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -1784,6 +1784,9 @@
<constant name="GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL" value="2048" enum="GraphemeFlag" is_bitfield="true">
It is safe to insert a U+0640 before this grapheme for elongation.
</constant>
+ <constant name="GRAPHEME_IS_EMBEDDED_OBJECT" value="4096" enum="GraphemeFlag" is_bitfield="true">
+ Grapheme is an object replacement character for the embedded object.
+ </constant>
<constant name="HINTING_NONE" value="0" enum="Hinting">
Disables font hinting (smoother but less crisp).
</constant>
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 522685bf87..19f2cfd47d 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -1310,6 +1310,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
uint32_t instance_color_offset = 0;
bool instance_uses_color = false;
bool instance_uses_custom_data = false;
+ bool use_instancing = false;
if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_MESH) {
const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(state.canvas_instance_batches[p_index].command);
@@ -1336,6 +1337,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
instance_color_offset = mesh_storage->multimesh_get_color_offset(multimesh);
instance_uses_color = mesh_storage->multimesh_uses_colors(multimesh);
instance_uses_custom_data = mesh_storage->multimesh_uses_custom_data(multimesh);
+ use_instancing = true;
} else if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_PARTICLES) {
const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(state.canvas_instance_batches[p_index].command);
@@ -1362,6 +1364,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
instance_color_offset = 8; // 8 bytes for instance transform.
instance_uses_color = true;
instance_uses_custom_data = true;
+ use_instancing = true;
}
ERR_FAIL_COND(mesh.is_null());
@@ -1397,7 +1400,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
use_index_buffer = true;
}
- if (instance_count > 1) {
+ if (use_instancing) {
if (instance_buffer == 0) {
break;
}
@@ -1426,7 +1429,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- if (instance_count > 1) {
+ if (use_instancing) {
glDisableVertexAttribArray(5);
glDisableVertexAttribArray(6);
glDisableVertexAttribArray(7);
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index b3d6b01c6c..3d8f7924a7 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -1948,7 +1948,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED;
- scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_OPAQUE;
+ scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_ALWAYS;
if (!fb_cleared) {
glClearDepth(1.0f);
@@ -1976,19 +1976,17 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
_render_list_template<PASS_MODE_COLOR>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_OPAQUE].elements.size());
+ glDepthMask(GL_FALSE);
+ scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_DISABLED;
+
if (draw_sky) {
RENDER_TIMESTAMP("Render Sky");
- if (scene_state.current_depth_test != GLES3::SceneShaderData::DEPTH_TEST_ENABLED) {
- glEnable(GL_DEPTH_TEST);
- scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED;
- }
+
glEnable(GL_DEPTH_TEST);
- glDepthMask(GL_FALSE);
glDisable(GL_BLEND);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED;
- scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_DISABLED;
scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK;
_draw_sky(render_data.environment, render_data.cam_projection, render_data.cam_transform, sky_energy_multiplier, p_camera_data->view_count > 1, flip_y);
diff --git a/drivers/gles3/shaders/particles.glsl b/drivers/gles3/shaders/particles.glsl
index f8741a22ab..40881a1808 100644
--- a/drivers/gles3/shaders/particles.glsl
+++ b/drivers/gles3/shaders/particles.glsl
@@ -300,28 +300,23 @@ void main() {
vec3 rel_vec = xform[3].xyz - attractors[i].transform[3].xyz;
vec3 local_pos = rel_vec * mat3(attractors[i].transform);
- switch (attractors[i].type) {
- case ATTRACTOR_TYPE_SPHERE: {
- dir = safe_normalize(rel_vec);
- float d = length(local_pos) / attractors[i].extents.x;
- if (d > 1.0) {
- continue;
- }
- amount = max(0.0, 1.0 - d);
- } break;
- case ATTRACTOR_TYPE_BOX: {
- dir = safe_normalize(rel_vec);
-
- vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz);
- float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z));
- if (d > 1.0) {
- continue;
- }
- amount = max(0.0, 1.0 - d);
-
- } break;
- case ATTRACTOR_TYPE_VECTOR_FIELD: {
- } break;
+ if (attractors[i].type == ATTRACTOR_TYPE_SPHERE) {
+ dir = safe_normalize(rel_vec);
+ float d = length(local_pos) / attractors[i].extents.x;
+ if (d > 1.0) {
+ continue;
+ }
+ amount = max(0.0, 1.0 - d);
+ } else if (attractors[i].type == ATTRACTOR_TYPE_BOX) {
+ dir = safe_normalize(rel_vec);
+
+ vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz);
+ float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z));
+ if (d > 1.0) {
+ continue;
+ }
+ amount = max(0.0, 1.0 - d);
+ } else if (attractors[i].type == ATTRACTOR_TYPE_VECTOR_FIELD) {
}
amount = pow(amount, attractors[i].attenuation);
dir = safe_normalize(mix(dir, attractors[i].transform[2].xyz, attractors[i].directionality));
@@ -383,80 +378,72 @@ void main() {
vec3 rel_vec = xform[3].xyz - colliders[i].transform[3].xyz;
vec3 local_pos = rel_vec * mat3(colliders[i].transform);
- switch (colliders[i].type) {
- case COLLIDER_TYPE_SPHERE: {
- float d = length(rel_vec) - (particle_size + colliders[i].extents.x);
+ if (colliders[i].type == COLLIDER_TYPE_SPHERE) {
+ float d = length(rel_vec) - (particle_size + colliders[i].extents.x);
- if (d < 0.0) {
- col = true;
- depth = -d;
- normal = normalize(rel_vec);
- }
+ if (d < 0.0) {
+ col = true;
+ depth = -d;
+ normal = normalize(rel_vec);
+ }
+ } else if (colliders[i].type == COLLIDER_TYPE_BOX) {
+ vec3 abs_pos = abs(local_pos);
+ vec3 sgn_pos = sign(local_pos);
- } break;
- case COLLIDER_TYPE_BOX: {
- vec3 abs_pos = abs(local_pos);
- vec3 sgn_pos = sign(local_pos);
-
- if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) {
- //point outside box
-
- vec3 closest = min(abs_pos, colliders[i].extents.xyz);
- vec3 rel = abs_pos - closest;
- depth = length(rel) - particle_size;
- if (depth < 0.0) {
- col = true;
- normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos);
- depth = -depth;
- }
- } else {
- //point inside box
- vec3 axis_len = colliders[i].extents.xyz - abs_pos;
- // there has to be a faster way to do this?
- if (all(lessThan(axis_len.xx, axis_len.yz))) {
- normal = vec3(1, 0, 0);
- } else if (all(lessThan(axis_len.yy, axis_len.xz))) {
- normal = vec3(0, 1, 0);
- } else {
- normal = vec3(0, 0, 1);
- }
+ if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) {
+ //point outside box
+ vec3 closest = min(abs_pos, colliders[i].extents.xyz);
+ vec3 rel = abs_pos - closest;
+ depth = length(rel) - particle_size;
+ if (depth < 0.0) {
col = true;
- depth = dot(normal * axis_len, vec3(1)) + particle_size;
- normal = mat3(colliders[i].transform) * (normal * sgn_pos);
+ normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos);
+ depth = -depth;
}
-
- } break;
- case COLLIDER_TYPE_SDF: {
- } break;
- case COLLIDER_TYPE_HEIGHT_FIELD: {
- vec3 local_pos_bottom = local_pos;
- local_pos_bottom.y -= particle_size;
-
- if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) {
- continue;
+ } else {
+ //point inside box
+ vec3 axis_len = colliders[i].extents.xyz - abs_pos;
+ // there has to be a faster way to do this?
+ if (all(lessThan(axis_len.xx, axis_len.yz))) {
+ normal = vec3(1, 0, 0);
+ } else if (all(lessThan(axis_len.yy, axis_len.xz))) {
+ normal = vec3(0, 1, 0);
+ } else {
+ normal = vec3(0, 0, 1);
}
- const float DELTA = 1.0 / 8192.0;
- vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5;
+ col = true;
+ depth = dot(normal * axis_len, vec3(1)) + particle_size;
+ normal = mat3(colliders[i].transform) * (normal * sgn_pos);
+ }
+ } else if (colliders[i].type == COLLIDER_TYPE_SDF) {
+ } else if (colliders[i].type == COLLIDER_TYPE_HEIGHT_FIELD) {
+ vec3 local_pos_bottom = local_pos;
+ local_pos_bottom.y -= particle_size;
- float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r;
+ if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) {
+ continue;
+ }
+ const float DELTA = 1.0 / 8192.0;
- if (y > uvw_pos.y) {
- //inside heightfield
+ vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5;
- vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
- vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
- vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz;
+ float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r;
- normal = normalize(cross(pos1 - pos2, pos1 - pos3));
- float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y;
+ if (y > uvw_pos.y) {
+ //inside heightfield
- col = true;
- depth = dot(normal, pos1) - dot(normal, local_pos_bottom);
- }
+ vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
+ vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
+ vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz;
- } break;
+ normal = normalize(cross(pos1 - pos2, pos1 - pos3));
+ float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y;
+
+ col = true;
+ depth = dot(normal, pos1) - dot(normal, local_pos_bottom);
+ }
}
if (col) {
diff --git a/drivers/gles3/storage/particles_storage.cpp b/drivers/gles3/storage/particles_storage.cpp
index 2b47271408..4b64e2aec1 100644
--- a/drivers/gles3/storage/particles_storage.cpp
+++ b/drivers/gles3/storage/particles_storage.cpp
@@ -919,7 +919,7 @@ void ParticlesStorage::_particles_update_instance_buffer(Particles *particles, c
glBeginTransformFeedback(GL_POINTS);
if (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME) {
- uint32_t lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1);
+ uint32_t lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount;
uint32_t stride = particles->process_buffer_stride_cache;
glBindBuffer(GL_ARRAY_BUFFER, particles->back_process_buffer);
@@ -1135,14 +1135,13 @@ void ParticlesStorage::_particles_reverse_lifetime_sort(Particles *particles) {
glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, particle_array);
#endif
- uint32_t lifetime_split = MIN(particles->amount * particles->sort_buffer_phase, particles->amount - 1);
-
+ uint32_t lifetime_split = (MIN(int(particles->amount * particles->sort_buffer_phase), particles->amount - 1) + 1) % particles->amount;
for (uint32_t i = 0; i < lifetime_split / 2; i++) {
- SWAP(particle_array[i], particle_array[lifetime_split - i]);
+ SWAP(particle_array[i], particle_array[lifetime_split - i - 1]);
}
for (uint32_t i = 0; i < (particles->amount - lifetime_split) / 2; i++) {
- SWAP(particle_array[lifetime_split + i + 1], particle_array[particles->amount - 1 - i]);
+ SWAP(particle_array[lifetime_split + i], particle_array[particles->amount - 1 - i]);
}
#ifndef __EMSCRIPTEN__
diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp
index 8d199ad989..1113a10e38 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -276,6 +276,11 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) {
return;
}
+ if (unlikely(log->is_updating())) {
+ // The new message arrived during log RTL text processing/redraw (invalid BiDi control characters / font error), ignore it to avoid RTL data corruption.
+ return;
+ }
+
// Only add the message to the log if it passes the filters.
bool filter_active = type_filter_map[p_message.type]->is_active();
String search_text = search_box->get_text();
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index f7c789a453..be66fbafac 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -38,34 +38,48 @@
#include "editor/gui/editor_spin_slider.h"
#include "editor/inspector_dock.h"
#include "scene/gui/button.h"
+#include "scene/resources/packed_scene.h"
bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) {
String name = p_name;
- if (name.begins_with("indices")) {
- int index = name.get_slicec('/', 1).to_int();
- array.set(index, p_value);
- return true;
+ if (!name.begins_with("indices") && !name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) {
+ return false;
}
- return false;
+ int index;
+ if (name.begins_with("metadata/")) {
+ index = name.get_slice("/", 2).to_int();
+ } else {
+ index = name.get_slice("/", 1).to_int();
+ }
+
+ array.set(index, p_value);
+ return true;
}
bool EditorPropertyArrayObject::_get(const StringName &p_name, Variant &r_ret) const {
String name = p_name;
- if (name.begins_with("indices")) {
- int index = name.get_slicec('/', 1).to_int();
- bool valid;
- r_ret = array.get(index, &valid);
- if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {
- r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();
- }
+ if (!name.begins_with("indices") && !name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) {
+ return false;
+ }
- return valid;
+ int index;
+ if (name.begins_with("metadata/")) {
+ index = name.get_slice("/", 2).to_int();
+ } else {
+ index = name.get_slice("/", 1).to_int();
}
- return false;
+ bool valid;
+ r_ret = array.get(index, &valid);
+
+ if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {
+ r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();
+ }
+
+ return valid;
}
void EditorPropertyArrayObject::set_array(const Variant &p_array) {
@@ -178,15 +192,22 @@ void EditorPropertyArray::initialize_array(Variant &p_array) {
}
void EditorPropertyArray::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
- if (p_property.begins_with("indices")) {
- int index = p_property.get_slice("/", 1).to_int();
-
- Variant array = object->get_array().duplicate();
- array.set(index, p_value);
+ if (!p_property.begins_with("indices") && !p_property.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) {
+ return;
+ }
- object->set_array(array);
- emit_changed(get_edited_property(), array, "", true);
+ int index;
+ if (p_property.begins_with("metadata/")) {
+ index = p_property.get_slice("/", 2).to_int();
+ } else {
+ index = p_property.get_slice("/", 1).to_int();
}
+
+ Array array;
+ array.assign(object->get_array().duplicate());
+ array.set(index, p_value);
+ object->set_array(array);
+ emit_changed(get_edited_property(), array, "", true);
}
void EditorPropertyArray::_change_type(Object *p_button, int p_index) {
@@ -690,11 +711,6 @@ EditorPropertyArray::EditorPropertyArray() {
add_child(edit);
add_focusable(edit);
- container = nullptr;
- property_vbox = nullptr;
- size_slider = nullptr;
- button_add_item = nullptr;
- paginator = nullptr;
change_type = memnew(PopupMenu);
add_child(change_type);
change_type->connect("id_pressed", callable_mp(this, &EditorPropertyArray::_change_type_menu));
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 43c7f49395..dfc820050f 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -4138,6 +4138,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
new_sd->direction = sd->direction;
new_sd->custom_punct = sd->custom_punct;
new_sd->para_direction = sd->para_direction;
+ new_sd->base_para_direction = sd->base_para_direction;
for (int i = 0; i < TextServer::SPACING_MAX; i++) {
new_sd->extra_spacing[i] = sd->extra_spacing[i];
}
@@ -4188,9 +4189,33 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
if (U_SUCCESS(err)) {
ubidi_setLine(p_sd->bidi_iter[ov], start, end, bidi_iter, &err);
if (U_FAILURE(err)) {
- ubidi_close(bidi_iter);
- bidi_iter = nullptr;
- ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err)));
+ // Line BiDi failed (string contains incompatible control characters), try full paragraph BiDi instead.
+ err = U_ZERO_ERROR;
+ const UChar *data = p_sd->utf16.get_data();
+ switch (static_cast<TextServer::Direction>(p_sd->bidi_override[ov].z)) {
+ case DIRECTION_LTR: {
+ ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err);
+ } break;
+ case DIRECTION_RTL: {
+ ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err);
+ } break;
+ case DIRECTION_INHERITED: {
+ ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err);
+ } break;
+ case DIRECTION_AUTO: {
+ UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start);
+ if (direction != UBIDI_NEUTRAL) {
+ ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err);
+ } else {
+ ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err);
+ }
+ } break;
+ }
+ if (U_FAILURE(err)) {
+ ubidi_close(bidi_iter);
+ bidi_iter = nullptr;
+ ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err)));
+ }
}
} else {
bidi_iter = nullptr;
@@ -5619,25 +5644,25 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
sd->script_iter = memnew(ScriptIterator(sd->text, 0, sd->text.length()));
}
- int base_para_direction = UBIDI_DEFAULT_LTR;
+ sd->base_para_direction = UBIDI_DEFAULT_LTR;
switch (sd->direction) {
case DIRECTION_LTR: {
sd->para_direction = DIRECTION_LTR;
- base_para_direction = UBIDI_LTR;
+ sd->base_para_direction = UBIDI_LTR;
} break;
case DIRECTION_RTL: {
sd->para_direction = DIRECTION_RTL;
- base_para_direction = UBIDI_RTL;
+ sd->base_para_direction = UBIDI_RTL;
} break;
case DIRECTION_INHERITED:
case DIRECTION_AUTO: {
UBiDiDirection direction = ubidi_getBaseDirection(data, sd->utf16.length());
if (direction != UBIDI_NEUTRAL) {
sd->para_direction = (direction == UBIDI_RTL) ? DIRECTION_RTL : DIRECTION_LTR;
- base_para_direction = direction;
+ sd->base_para_direction = direction;
} else {
sd->para_direction = DIRECTION_LTR;
- base_para_direction = UBIDI_DEFAULT_LTR;
+ sd->base_para_direction = UBIDI_DEFAULT_LTR;
}
} break;
}
@@ -5666,14 +5691,14 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err);
} break;
case DIRECTION_INHERITED: {
- ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err);
+ ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err);
} break;
case DIRECTION_AUTO: {
UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start);
if (direction != UBIDI_NEUTRAL) {
ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err);
} else {
- ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err);
+ ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err);
}
} break;
}
@@ -5760,7 +5785,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
gl.start = span.start;
gl.end = span.end;
gl.count = 1;
- gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL;
+ gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT;
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = sd->objects[span.embedded_key].rect.size.x;
} else {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index af3a0c7876..4b8c3f7cd3 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -476,6 +476,7 @@ class TextServerAdvanced : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
+ int base_para_direction = UBIDI_DEFAULT_LTR;
bool valid = false; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 9095be46af..8b210bdc38 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -3670,7 +3670,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
gl.end = span.end;
gl.count = 1;
gl.index = 0;
- gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL;
+ gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT;
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = sd->objects[span.embedded_key].rect.size.x;
} else {
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index dcd99f4374..d699236053 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -952,7 +952,7 @@ void LineEdit::_notification(int p_what) {
if (ceil(ofs.x) >= x_ofs && (ofs.x + glyphs[i].advance) <= ofs_max) {
if (glyphs[i].font_rid != RID()) {
TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color);
- } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
TS->draw_hex_code_box(ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color);
}
}
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index bd081074ec..97a1483204 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -74,16 +74,26 @@ void Range::Shared::emit_changed(const char *p_what) {
}
}
+void Range::Shared::redraw_owners() {
+ for (Range *E : owners) {
+ Range *r = E;
+ if (!r->is_inside_tree()) {
+ continue;
+ }
+ r->queue_redraw();
+ }
+}
+
void Range::set_value(double p_val) {
double prev_val = shared->val;
- set_value_no_signal(p_val);
+ _set_value_no_signal(p_val);
if (shared->val != prev_val) {
shared->emit_value_changed();
}
}
-void Range::set_value_no_signal(double p_val) {
+void Range::_set_value_no_signal(double p_val) {
if (shared->step > 0) {
p_val = Math::round((p_val - shared->min) / shared->step) * shared->step + shared->min;
}
@@ -107,6 +117,15 @@ void Range::set_value_no_signal(double p_val) {
shared->val = p_val;
}
+void Range::set_value_no_signal(double p_val) {
+ double prev_val = shared->val;
+ _set_value_no_signal(p_val);
+
+ if (shared->val != prev_val) {
+ shared->redraw_owners();
+ }
+}
+
void Range::set_min(double p_min) {
if (shared->min == p_min) {
return;
diff --git a/scene/gui/range.h b/scene/gui/range.h
index bf71fcc1c9..9b4f0707e6 100644
--- a/scene/gui/range.h
+++ b/scene/gui/range.h
@@ -48,6 +48,7 @@ class Range : public Control {
HashSet<Range *> owners;
void emit_value_changed();
void emit_changed(const char *p_what = "");
+ void redraw_owners();
};
Shared *shared = nullptr;
@@ -59,6 +60,7 @@ class Range : public Control {
void _value_changed_notify();
void _changed_notify(const char *p_what = "");
+ void _set_value_no_signal(double p_val);
protected:
virtual void _value_changed(double p_value);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 1ae24b6d70..43c68a8eca 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1323,7 +1323,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
if (!skip) {
if (frid != RID()) {
TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color);
- } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color);
}
}
@@ -2705,6 +2705,10 @@ bool RichTextLabel::is_ready() const {
return (main->first_invalid_line.load() == (int)main->lines.size() && main->first_resized_line.load() == (int)main->lines.size() && main->first_invalid_font_line.load() == (int)main->lines.size());
}
+bool RichTextLabel::is_updating() const {
+ return updating.load() || validating.load();
+}
+
void RichTextLabel::set_threaded(bool p_threaded) {
if (threaded != p_threaded) {
_stop_thread();
@@ -2729,6 +2733,7 @@ bool RichTextLabel::_validate_line_caches() {
if (updating.load()) {
return false;
}
+ validating.store(true);
if (main->first_invalid_line.load() == (int)main->lines.size()) {
MutexLock data_lock(data_mutex);
Rect2 text_rect = _get_text_rect();
@@ -2747,6 +2752,7 @@ bool RichTextLabel::_validate_line_caches() {
if (main->first_resized_line.load() == (int)main->lines.size()) {
vscroll->set_value(old_scroll);
+ validating.store(false);
return true;
}
@@ -2798,8 +2804,10 @@ bool RichTextLabel::_validate_line_caches() {
if (fit_content) {
update_minimum_size();
}
+ validating.store(false);
return true;
}
+ validating.store(false);
stop_thread.store(false);
if (threaded) {
updating.store(true);
@@ -2809,7 +2817,9 @@ bool RichTextLabel::_validate_line_caches() {
loading_started = OS::get_singleton()->get_ticks_msec();
return false;
} else {
+ updating.store(true);
_process_line_caches();
+ updating.store(false);
queue_redraw();
return true;
}
@@ -5895,6 +5905,7 @@ RichTextLabel::RichTextLabel(const String &p_text) {
set_text(p_text);
updating.store(false);
+ validating.store(false);
stop_thread.store(false);
set_clip_contents(true);
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index fef34f7260..567528652c 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -377,6 +377,7 @@ private:
bool threaded = false;
std::atomic<bool> stop_thread;
std::atomic<bool> updating;
+ std::atomic<bool> validating;
std::atomic<double> loaded;
uint64_t loading_started = 0;
@@ -678,6 +679,7 @@ public:
void deselect();
bool is_ready() const;
+ bool is_updating() const;
void set_threaded(bool p_threaded);
bool is_threaded() const;
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index f99b2edd54..e4c7be33f0 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -39,7 +39,7 @@ Size2 SpinBox::get_minimum_size() const {
return ms;
}
-void SpinBox::_value_changed(double p_value) {
+void SpinBox::_update_text() {
String value = String::num(get_value(), Math::range_step_decimals(get_step()));
if (is_localizing_numeral_system()) {
value = TS->format_number(value);
@@ -55,7 +55,6 @@ void SpinBox::_value_changed(double p_value) {
}
line_edit->set_text(value);
- Range::_value_changed(p_value);
}
void SpinBox::_text_submitted(const String &p_string) {
@@ -73,7 +72,7 @@ void SpinBox::_text_submitted(const String &p_string) {
if (value.get_type() != Variant::NIL) {
set_value(value);
}
- _value_changed(0);
+ _update_text();
}
void SpinBox::_text_changed(const String &p_string) {
@@ -192,7 +191,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
void SpinBox::_line_edit_focus_enter() {
int col = line_edit->get_caret_column();
- _value_changed(0); // Update the LineEdit's text.
+ _update_text();
line_edit->set_caret_column(col);
// LineEdit text might change and it clears any selection. Have to re-select here.
@@ -202,6 +201,10 @@ void SpinBox::_line_edit_focus_enter() {
}
void SpinBox::_line_edit_focus_exit() {
+ // Discontinue because the focus_exit was caused by left-clicking the arrows.
+ if (get_viewport()->gui_get_focus_owner() == get_line_edit()) {
+ return;
+ }
// Discontinue because the focus_exit was caused by right-click context menu.
if (line_edit->is_menu_visible()) {
return;
@@ -228,6 +231,7 @@ void SpinBox::_update_theme_item_cache() {
void SpinBox::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
+ _update_text();
_adjust_width_for_icon(theme_cache.updown_icon);
RID ci = get_canvas_item();
@@ -242,7 +246,7 @@ void SpinBox::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
_adjust_width_for_icon(theme_cache.updown_icon);
- _value_changed(0);
+ _update_text();
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -250,7 +254,6 @@ void SpinBox::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
- _value_changed(0);
queue_redraw();
} break;
@@ -279,7 +282,7 @@ void SpinBox::set_suffix(const String &p_suffix) {
}
suffix = p_suffix;
- _value_changed(0);
+ _update_text();
}
String SpinBox::get_suffix() const {
@@ -292,7 +295,7 @@ void SpinBox::set_prefix(const String &p_prefix) {
}
prefix = p_prefix;
- _value_changed(0);
+ _update_text();
}
String SpinBox::get_prefix() const {
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 0e9d424f68..29b278c50e 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -46,8 +46,8 @@ class SpinBox : public Range {
void _range_click_timeout();
void _release_mouse();
+ void _update_text();
void _text_submitted(const String &p_string);
- virtual void _value_changed(double p_value) override;
void _text_changed(const String &p_string);
String prefix;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 0839e4066d..db674c50fb 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1280,7 +1280,7 @@ void TextEdit::_notification(int p_what) {
if (glyphs[j].font_rid != RID()) {
TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color);
had_glyphs_drawn = true;
- } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ } else if (((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color);
had_glyphs_drawn = true;
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e71c236dc7..e22890562f 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -4594,8 +4594,8 @@ int Tree::get_item_offset(TreeItem *p_item) const {
return ofs;
}
- ofs += compute_item_height(it);
- if (it != root || !hide_root) {
+ if ((it != root || !hide_root) && it->is_visible()) {
+ ofs += compute_item_height(it);
ofs += theme_cache.v_separation;
}
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 1e9038139e..a1e7558653 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -44,7 +44,9 @@
#include "scene/property_utils.h"
#define PACKED_SCENE_VERSION 3
-#define META_POINTER_PROPERTY_BASE "metadata/_editor_prop_ptr_"
+
+const String PackedScene::META_POINTER_PROPERTY_BASE = "metadata/_editor_prop_ptr_";
+
bool SceneState::can_instantiate() const {
return nodes.size() > 0;
}
@@ -239,15 +241,38 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) {
uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1);
ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr);
- if (Engine::get_singleton()->is_editor_hint()) {
- // If editor, just set the metadata and be it
- node->set(META_POINTER_PROPERTY_BASE + String(snames[name_idx]), props[nprops[j].value]);
+
+ const StringName &prop_name = snames[name_idx];
+ const Variant &prop_variant = props[nprops[j].value];
+
+ if (prop_variant.get_type() == Variant::ARRAY) {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ // If editor, simply set the original array of NodePaths.
+ node->set(prop_name, prop_variant);
+ continue;
+ }
+
+ const Array &array = prop_variant;
+ for (int k = 0; k < array.size(); k++) {
+ DeferredNodePathProperties dnp;
+ dnp.path = array[k];
+ dnp.base = node;
+ // Use special property name to signify an array. This is only used in deferred_node_paths.
+ dnp.property = String(prop_name) + "/indices/" + itos(k);
+ deferred_node_paths.push_back(dnp);
+ }
+
} else {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ // If editor, just set the metadata and be it.
+ node->set(PackedScene::META_POINTER_PROPERTY_BASE + String(prop_name), prop_variant);
+ continue;
+ }
// Do an actual deferred sed of the property path.
DeferredNodePathProperties dnp;
- dnp.path = props[nprops[j].value];
+ dnp.path = prop_variant;
dnp.base = node;
- dnp.property = snames[name_idx];
+ dnp.property = prop_name;
deferred_node_paths.push_back(dnp);
}
continue;
@@ -415,8 +440,26 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
for (const DeferredNodePathProperties &dnp : deferred_node_paths) {
+ // Replace properties stored as NodePaths with actual Nodes.
Node *other = dnp.base->get_node_or_null(dnp.path);
- dnp.base->set(dnp.property, other);
+
+ const String string_property = dnp.property;
+ if (string_property.contains("/indices/")) {
+ // For properties with "/indices/", the replacement takes place inside an Array.
+ const String base_property = string_property.get_slice("/", 0);
+ const int index = string_property.get_slice("/", 2).to_int();
+
+ Array array = dnp.base->get(base_property);
+ if (array.size() >= index) {
+ array.push_back(other);
+ } else {
+ array.set(index, other);
+ }
+
+ dnp.base->set(base_property, array);
+ } else {
+ dnp.base->set(dnp.property, other);
+ }
}
for (KeyValue<Ref<Resource>, Ref<Resource>> &E : resources_local_to_scene) {
@@ -584,7 +627,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
if (E.name == META_PROPERTY_MISSING_RESOURCES) {
continue; // Ignore this property when packing.
}
- if (E.name.begins_with(META_POINTER_PROPERTY_BASE)) {
+ if (E.name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE)) {
continue; // do not save.
}
@@ -600,7 +643,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
bool use_deferred_node_path_bit = false;
if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
- value = p_node->get(META_POINTER_PROPERTY_BASE + E.name);
+ value = p_node->get(PackedScene::META_POINTER_PROPERTY_BASE + E.name);
if (value.get_type() != Variant::NODE_PATH) {
continue; //was never set, ignore.
}
@@ -611,6 +654,20 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
if (ures.is_null()) {
value = missing_resource_properties[E.name];
}
+ } else if (E.type == Variant::ARRAY && E.hint == PROPERTY_HINT_TYPE_STRING) {
+ int hint_subtype_separator = E.hint_string.find(":");
+ if (hint_subtype_separator >= 0) {
+ String subtype_string = E.hint_string.substr(0, hint_subtype_separator);
+ int slash_pos = subtype_string.find("/");
+ PropertyHint subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
+ if (slash_pos >= 0) {
+ subtype_hint = PropertyHint(subtype_string.get_slice("/", 1).to_int());
+ subtype_string = subtype_string.substr(0, slash_pos);
+ }
+ Variant::Type subtype = Variant::Type(subtype_string.to_int());
+
+ use_deferred_node_path_bit = subtype == Variant::OBJECT && subtype_hint == PROPERTY_HINT_NODE_TYPE;
+ }
}
if (!pinned_props.has(name)) {
@@ -1736,7 +1793,7 @@ void SceneState::add_editable_instance(const NodePath &p_path) {
}
String SceneState::get_meta_pointer_property(const String &p_property) {
- return META_POINTER_PROPERTY_BASE + p_property;
+ return PackedScene::META_POINTER_PROPERTY_BASE + p_property;
}
Vector<String> SceneState::_get_node_groups(int p_idx) const {
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index 5c53ffdb45..1967deab36 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -221,6 +221,8 @@ protected:
virtual void reset_state() override;
public:
+ static const String META_POINTER_PROPERTY_BASE;
+
enum GenEditState {
GEN_EDIT_STATE_DISABLED,
GEN_EDIT_STATE_INSTANCE,
diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
index d8b78de526..b36e027f07 100644
--- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
@@ -1202,7 +1202,7 @@ void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p
}
copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME);
- copy_push_constant.lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1);
+ copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount;
copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME;
copy_push_constant.frame_remainder = particles->interpolate ? particles->frame_remainder : 0.0;
@@ -1520,7 +1520,7 @@ void ParticlesStorage::update_particles() {
}
copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME);
- copy_push_constant.lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1);
+ copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount;
copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME;
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 286d1b683f..744fd051f5 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -752,7 +752,7 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("render_pipeline_is_valid", "render_pipeline"), &RenderingDevice::render_pipeline_is_valid);
ClassDB::bind_method(D_METHOD("compute_pipeline_create", "shader", "specialization_constants"), &RenderingDevice::_compute_pipeline_create, DEFVAL(TypedArray<RDPipelineSpecializationConstant>()));
- ClassDB::bind_method(D_METHOD("compute_pipeline_is_valid", "compute_pieline"), &RenderingDevice::compute_pipeline_is_valid);
+ ClassDB::bind_method(D_METHOD("compute_pipeline_is_valid", "compute_pipeline"), &RenderingDevice::compute_pipeline_is_valid);
ClassDB::bind_method(D_METHOD("screen_get_width", "screen"), &RenderingDevice::screen_get_width, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("screen_get_height", "screen"), &RenderingDevice::screen_get_height, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 084fb64a53..12e99ba606 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -10529,36 +10529,42 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
r_options->push_back(option);
}
- } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT)) && !completion_base_array) {
+ } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT))) {
Vector<String> options;
- if (current_uniform_filter == FILTER_DEFAULT) {
- options.push_back("filter_linear");
- options.push_back("filter_linear_mipmap");
- options.push_back("filter_linear_mipmap_anisotropic");
- options.push_back("filter_nearest");
- options.push_back("filter_nearest_mipmap");
- options.push_back("filter_nearest_mipmap_anisotropic");
- }
- if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) {
- options.push_back("hint_anisotropy");
- options.push_back("hint_default_black");
- options.push_back("hint_default_white");
- options.push_back("hint_default_transparent");
- options.push_back("hint_normal");
- options.push_back("hint_roughness_a");
- options.push_back("hint_roughness_b");
- options.push_back("hint_roughness_g");
- options.push_back("hint_roughness_gray");
- options.push_back("hint_roughness_normal");
- options.push_back("hint_roughness_r");
- options.push_back("hint_screen_texture");
- options.push_back("hint_normal_roughness_texture");
- options.push_back("hint_depth_texture");
- options.push_back("source_color");
- }
- if (current_uniform_repeat == REPEAT_DEFAULT) {
- options.push_back("repeat_enable");
- options.push_back("repeat_disable");
+ if (completion_base_array) {
+ if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) {
+ options.push_back("source_color");
+ }
+ } else {
+ if (current_uniform_filter == FILTER_DEFAULT) {
+ options.push_back("filter_linear");
+ options.push_back("filter_linear_mipmap");
+ options.push_back("filter_linear_mipmap_anisotropic");
+ options.push_back("filter_nearest");
+ options.push_back("filter_nearest_mipmap");
+ options.push_back("filter_nearest_mipmap_anisotropic");
+ }
+ if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) {
+ options.push_back("hint_anisotropy");
+ options.push_back("hint_default_black");
+ options.push_back("hint_default_white");
+ options.push_back("hint_default_transparent");
+ options.push_back("hint_normal");
+ options.push_back("hint_roughness_a");
+ options.push_back("hint_roughness_b");
+ options.push_back("hint_roughness_g");
+ options.push_back("hint_roughness_gray");
+ options.push_back("hint_roughness_normal");
+ options.push_back("hint_roughness_r");
+ options.push_back("hint_screen_texture");
+ options.push_back("hint_normal_roughness_texture");
+ options.push_back("hint_depth_texture");
+ options.push_back("source_color");
+ }
+ if (current_uniform_repeat == REPEAT_DEFAULT) {
+ options.push_back("repeat_enable");
+ options.push_back("repeat_disable");
+ }
}
for (int i = 0; i < options.size(); i++) {
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index 88f1e76811..36c3137472 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -547,6 +547,7 @@ void TextServer::_bind_methods() {
BIND_BITFIELD_FLAG(GRAPHEME_IS_UNDERSCORE);
BIND_BITFIELD_FLAG(GRAPHEME_IS_CONNECTED);
BIND_BITFIELD_FLAG(GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL);
+ BIND_BITFIELD_FLAG(GRAPHEME_IS_EMBEDDED_OBJECT);
/* Hinting */
BIND_ENUM_CONSTANT(HINTING_NONE);
@@ -1449,7 +1450,7 @@ void TextServer::shaped_text_draw(const RID &p_shaped, const RID &p_canvas, cons
if (glyphs[i].font_rid != RID()) {
font_draw_glyph(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
- } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) {
+ } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & GRAPHEME_IS_EMBEDDED_OBJECT) != GRAPHEME_IS_EMBEDDED_OBJECT)) {
TextServer::draw_hex_code_box(p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
}
if (orientation == ORIENTATION_HORIZONTAL) {
diff --git a/servers/text_server.h b/servers/text_server.h
index e3c668bd5c..af6efb8c7d 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -125,18 +125,19 @@ public:
};
enum GraphemeFlag {
- GRAPHEME_IS_VALID = 1 << 0, // Glyph is valid.
- GRAPHEME_IS_RTL = 1 << 1, // Glyph is right-to-left.
- GRAPHEME_IS_VIRTUAL = 1 << 2, // Glyph is not part of source string (added by fit_to_width function, do not affect caret movement).
+ GRAPHEME_IS_VALID = 1 << 0, // Grapheme is valid.
+ GRAPHEME_IS_RTL = 1 << 1, // Grapheme is right-to-left.
+ GRAPHEME_IS_VIRTUAL = 1 << 2, // Grapheme is not part of source string (added by fit_to_width function, do not affect caret movement).
GRAPHEME_IS_SPACE = 1 << 3, // Is whitespace (for justification and word breaks).
GRAPHEME_IS_BREAK_HARD = 1 << 4, // Is line break (mandatory break, e.g. "\n").
GRAPHEME_IS_BREAK_SOFT = 1 << 5, // Is line break (optional break, e.g. space).
GRAPHEME_IS_TAB = 1 << 6, // Is tab or vertical tab.
- GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), glyph can be duplicated or truncated to fit line to width.
+ GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), grapheme can be duplicated or truncated to fit line to width.
GRAPHEME_IS_PUNCTUATION = 1 << 8, // Punctuation, except underscore (can be used as word break, but not line break or justifiction).
GRAPHEME_IS_UNDERSCORE = 1 << 9, // Underscore (can be used as word break).
GRAPHEME_IS_CONNECTED = 1 << 10, // Connected to previous grapheme.
GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL = 1 << 11, // It is safe to insert a U+0640 before this grapheme for elongation.
+ GRAPHEME_IS_EMBEDDED_OBJECT = 1 << 12, // Grapheme is an object replacement character for the embedded object.
};
enum Hinting {