2
0
Эх сурвалжийг харах

Merge pull request #61142 from bruvzg/rtl_threaded

Rémi Verschelde 3 жил өмнө
parent
commit
b9bb3de6a1

+ 27 - 0
doc/classes/RichTextLabel.xml

@@ -60,6 +60,7 @@
 			<argument index="0" name="character" type="int" />
 			<description>
 				Returns the line number of the character position provided.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="get_character_paragraph">
@@ -67,24 +68,28 @@
 			<argument index="0" name="character" type="int" />
 			<description>
 				Returns the paragraph number of the character position provided.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="get_content_height" qualifiers="const">
 			<return type="int" />
 			<description>
 				Returns the height of the content.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="get_content_width" qualifiers="const">
 			<return type="int" />
 			<description>
 				Returns the width of the content.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="get_line_count" qualifiers="const">
 			<return type="int" />
 			<description>
 				Returns the total number of lines in the text. Wrapped text is counted as multiple lines.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="get_line_offset">
@@ -92,6 +97,7 @@
 			<argument index="0" name="line" type="int" />
 			<description>
 				Returns the vertical offset of the line found at the provided index.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="get_menu" qualifiers="const">
@@ -112,6 +118,7 @@
 			<argument index="0" name="paragraph" type="int" />
 			<description>
 				Returns the vertical offset of the paragraph found at the provided index.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="get_parsed_text" qualifiers="const">
@@ -155,12 +162,14 @@
 			<return type="int" />
 			<description>
 				Returns the number of visible lines.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="get_visible_paragraph_count" qualifiers="const">
 			<return type="int" />
 			<description>
 				Returns the number of visible paragraphs. A paragraph is considered visible if at least one of its lines is visible.
+				[b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded.
 			</description>
 		</method>
 		<method name="install_effect">
@@ -176,6 +185,12 @@
 				Returns whether the menu is visible. Use this instead of [code]get_menu().visible[/code] to improve performance (so the creation of the menu is avoided).
 			</description>
 		</method>
+		<method name="is_ready" qualifiers="const">
+			<return type="bool" />
+			<description>
+				If [member threaded] is enabled, returns [code]true[/code] if the background thread has finished text processing, otherwise always return [code]true[/code].
+			</description>
+		</method>
 		<method name="newline">
 			<return type="void" />
 			<description>
@@ -476,6 +491,10 @@
 			The range of characters to display, as a [float] between 0.0 and 1.0. When assigned an out of range value, it's the same as assigning 1.0.
 			[b]Note:[/b] Setting this property updates [member visible_characters] based on current [method get_total_character_count].
 		</member>
+		<member name="progress_bar_delay" type="int" setter="set_progress_bar_delay" getter="get_progress_bar_delay" default="1000">
+			The delay after which the loading progress bar is displayed, in milliseconds. Set to [code]-1[/code] to disable progress bar entirely.
+			[b]Note:[/b] Progress bar is dislayed only if [member threaded] is enabled.
+		</member>
 		<member name="scroll_active" type="bool" setter="set_scroll_active" getter="is_scroll_active" default="true">
 			If [code]true[/code], the scrollbar is visible. Setting this to [code]false[/code] does not block scrolling completely. See [method scroll_to_line].
 		</member>
@@ -504,6 +523,9 @@
 		<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0">
 			Base text writing direction.
 		</member>
+		<member name="threaded" type="bool" setter="set_threaded" getter="is_threaded" default="false">
+			If [code]true[/code], text processing is done in a background thread.
+		</member>
 		<member name="visible_characters" type="int" setter="set_visible_characters" getter="get_visible_characters" default="-1">
 			The restricted number of characters to display in the label. If [code]-1[/code], all characters will be displayed.
 			[b]Note:[/b] Setting this property updates [member percent_visible] based on current [method get_total_character_count].
@@ -513,6 +535,11 @@
 		</member>
 	</members>
 	<signals>
+		<signal name="finished">
+			<description>
+				Triggered when the document is fully loaded.
+			</description>
+		</signal>
 		<signal name="meta_clicked">
 			<argument index="0" name="meta" type="Variant" />
 			<description>

+ 2 - 0
editor/editor_about.cpp

@@ -196,6 +196,7 @@ EditorAbout::EditorAbout() {
 	// License
 
 	_license_text = memnew(RichTextLabel);
+	_license_text->set_threaded(true);
 	_license_text->set_name(TTR("License"));
 	_license_text->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	_license_text->set_v_size_flags(Control::SIZE_EXPAND_FILL);
@@ -272,6 +273,7 @@ EditorAbout::EditorAbout() {
 	tpl_hbc->add_child(_tpl_tree);
 
 	_tpl_text = memnew(RichTextLabel);
+	_tpl_text->set_threaded(true);
 	_tpl_text->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	_tpl_text->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	tpl_hbc->add_child(_tpl_text);

+ 23 - 22
editor/editor_help.cpp

@@ -235,7 +235,7 @@ String EditorHelp::_fix_constant(const String &p_constant) const {
 }
 
 void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview) {
-	method_line[p_method.name] = class_desc->get_line_count() - 2; //gets overridden if description
+	method_line[p_method.name] = class_desc->get_paragraph_count() - 2; //gets overridden if description
 
 	const bool is_vararg = p_method.qualifiers.contains("vararg");
 
@@ -593,8 +593,8 @@ void EditorHelp::_update_doc() {
 
 	// Class description
 	if (!cd.description.is_empty()) {
-		section_line.push_back(Pair<String, int>(TTR("Description"), class_desc->get_line_count() - 2));
-		description_line = class_desc->get_line_count() - 2;
+		section_line.push_back(Pair<String, int>(TTR("Description"), class_desc->get_paragraph_count() - 2));
+		description_line = class_desc->get_paragraph_count() - 2;
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Description"));
@@ -664,7 +664,7 @@ void EditorHelp::_update_doc() {
 	}
 
 	if (has_properties) {
-		section_line.push_back(Pair<String, int>(TTR("Properties"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Properties"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Properties"));
@@ -682,7 +682,7 @@ void EditorHelp::_update_doc() {
 			if (cd.properties[i].name.begins_with("_") && cd.properties[i].description.is_empty()) {
 				continue;
 			}
-			property_line[cd.properties[i].name] = class_desc->get_line_count() - 2; //gets overridden if description
+			property_line[cd.properties[i].name] = class_desc->get_paragraph_count() - 2; //gets overridden if description
 
 			// Property type.
 			class_desc->push_cell();
@@ -828,7 +828,7 @@ void EditorHelp::_update_doc() {
 			cd.constructors.sort();
 		}
 
-		section_line.push_back(Pair<String, int>(TTR("Constructors"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Constructors"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Constructors"));
@@ -839,7 +839,7 @@ void EditorHelp::_update_doc() {
 		if (sort_methods) {
 			methods.sort();
 		}
-		section_line.push_back(Pair<String, int>(TTR("Methods"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Methods"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Methods"));
@@ -851,7 +851,7 @@ void EditorHelp::_update_doc() {
 			cd.operators.sort();
 		}
 
-		section_line.push_back(Pair<String, int>(TTR("Operators"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Operators"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Operators"));
@@ -860,7 +860,7 @@ void EditorHelp::_update_doc() {
 
 	// Theme properties
 	if (!cd.theme_properties.is_empty()) {
-		section_line.push_back(Pair<String, int>(TTR("Theme Properties"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Theme Properties"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Theme Properties"));
@@ -882,7 +882,7 @@ void EditorHelp::_update_doc() {
 		data_type_names["style"] = TTR("Styles");
 
 		for (int i = 0; i < cd.theme_properties.size(); i++) {
-			theme_property_line[cd.theme_properties[i].name] = class_desc->get_line_count() - 2; // Gets overridden if description.
+			theme_property_line[cd.theme_properties[i].name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
 
 			if (theme_data_type != cd.theme_properties[i].data_type) {
 				theme_data_type = cd.theme_properties[i].data_type;
@@ -954,7 +954,7 @@ void EditorHelp::_update_doc() {
 			cd.signals.sort();
 		}
 
-		section_line.push_back(Pair<String, int>(TTR("Signals"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Signals"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Signals"));
@@ -967,7 +967,7 @@ void EditorHelp::_update_doc() {
 		class_desc->push_indent(1);
 
 		for (int i = 0; i < cd.signals.size(); i++) {
-			signal_line[cd.signals[i].name] = class_desc->get_line_count() - 2; // Gets overridden if description.
+			signal_line[cd.signals[i].name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
 
 			class_desc->push_font(doc_code_font); // monofont
 			class_desc->push_color(headline_color);
@@ -1040,7 +1040,7 @@ void EditorHelp::_update_doc() {
 
 		// Enums
 		if (enums.size()) {
-			section_line.push_back(Pair<String, int>(TTR("Enumerations"), class_desc->get_line_count() - 2));
+			section_line.push_back(Pair<String, int>(TTR("Enumerations"), class_desc->get_paragraph_count() - 2));
 			class_desc->push_color(title_color);
 			class_desc->push_font(doc_title_font);
 			class_desc->add_text(TTR("Enumerations"));
@@ -1051,7 +1051,7 @@ void EditorHelp::_update_doc() {
 			class_desc->add_newline();
 
 			for (KeyValue<String, Vector<DocData::ConstantDoc>> &E : enums) {
-				enum_line[E.key] = class_desc->get_line_count() - 2;
+				enum_line[E.key] = class_desc->get_paragraph_count() - 2;
 
 				class_desc->push_font(doc_code_font);
 				class_desc->push_color(title_color);
@@ -1098,7 +1098,7 @@ void EditorHelp::_update_doc() {
 					}
 
 					// Add the enum constant line to the constant_line map so we can locate it as a constant.
-					constant_line[enum_list[i].name] = class_desc->get_line_count() - 2;
+					constant_line[enum_list[i].name] = class_desc->get_paragraph_count() - 2;
 
 					class_desc->push_font(doc_code_font);
 					class_desc->push_color(headline_color);
@@ -1144,7 +1144,7 @@ void EditorHelp::_update_doc() {
 
 		// Constants
 		if (constants.size()) {
-			section_line.push_back(Pair<String, int>(TTR("Constants"), class_desc->get_line_count() - 2));
+			section_line.push_back(Pair<String, int>(TTR("Constants"), class_desc->get_paragraph_count() - 2));
 			class_desc->push_color(title_color);
 			class_desc->push_font(doc_title_font);
 			class_desc->add_text(TTR("Constants"));
@@ -1155,7 +1155,7 @@ void EditorHelp::_update_doc() {
 			class_desc->add_newline();
 
 			for (int i = 0; i < constants.size(); i++) {
-				constant_line[constants[i].name] = class_desc->get_line_count() - 2;
+				constant_line[constants[i].name] = class_desc->get_paragraph_count() - 2;
 				class_desc->push_font(doc_code_font);
 
 				if (constants[i].value.begins_with("Color(") && constants[i].value.ends_with(")")) {
@@ -1205,7 +1205,7 @@ void EditorHelp::_update_doc() {
 
 	// Property descriptions
 	if (property_descr) {
-		section_line.push_back(Pair<String, int>(TTR("Property Descriptions"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Property Descriptions"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Property Descriptions"));
@@ -1220,7 +1220,7 @@ void EditorHelp::_update_doc() {
 				continue;
 			}
 
-			property_line[cd.properties[i].name] = class_desc->get_line_count() - 2;
+			property_line[cd.properties[i].name] = class_desc->get_paragraph_count() - 2;
 
 			class_desc->push_table(2);
 			class_desc->set_table_column_expand(1, true);
@@ -1371,7 +1371,7 @@ void EditorHelp::_update_doc() {
 
 	// Constructor descriptions
 	if (constructor_descriptions) {
-		section_line.push_back(Pair<String, int>(TTR("Constructor Descriptions"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Constructor Descriptions"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Constructor Descriptions"));
@@ -1380,7 +1380,7 @@ void EditorHelp::_update_doc() {
 
 	// Method descriptions
 	if (method_descriptions) {
-		section_line.push_back(Pair<String, int>(TTR("Method Descriptions"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Method Descriptions"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Method Descriptions"));
@@ -1389,7 +1389,7 @@ void EditorHelp::_update_doc() {
 
 	// Operator descriptions
 	if (operator_descriptions) {
-		section_line.push_back(Pair<String, int>(TTR("Operator Descriptions"), class_desc->get_line_count() - 2));
+		section_line.push_back(Pair<String, int>(TTR("Operator Descriptions"), class_desc->get_paragraph_count() - 2));
 		class_desc->push_color(title_color);
 		class_desc->push_font(doc_title_font);
 		class_desc->add_text(TTR("Operator Descriptions"));
@@ -1868,6 +1868,7 @@ EditorHelp::EditorHelp() {
 
 	class_desc = memnew(RichTextLabel);
 	add_child(class_desc);
+	class_desc->set_threaded(true);
 	class_desc->set_v_size_flags(SIZE_EXPAND_FILL);
 	class_desc->add_theme_color_override("selection_color", get_theme_color(SNAME("accent_color"), SNAME("Editor")) * Color(1, 1, 1, 0.4));
 

+ 5 - 0
modules/text_server_adv/text_server_adv.cpp

@@ -1691,6 +1691,8 @@ hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_
 }
 
 RID TextServerAdvanced::create_font() {
+	_THREAD_SAFE_METHOD_
+
 	FontDataAdvanced *fd = memnew(FontDataAdvanced);
 
 	return font_owner.make_rid(fd);
@@ -3359,6 +3361,7 @@ void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) {
 
 RID TextServerAdvanced::create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) {
 	_THREAD_SAFE_METHOD_
+
 	ShapedTextDataAdvanced *sd = memnew(ShapedTextDataAdvanced);
 	sd->hb_buffer = hb_buffer_create();
 	sd->direction = p_direction;
@@ -3748,6 +3751,8 @@ void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const {
 }
 
 RID TextServerAdvanced::shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const {
+	_THREAD_SAFE_METHOD_
+
 	const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_COND_V(!sd, RID());
 

+ 5 - 0
modules/text_server_fb/text_server_fb.cpp

@@ -853,6 +853,8 @@ _FORCE_INLINE_ void TextServerFallback::_font_clear_cache(FontDataFallback *p_fo
 }
 
 RID TextServerFallback::create_font() {
+	_THREAD_SAFE_METHOD_
+
 	FontDataFallback *fd = memnew(FontDataFallback);
 
 	return font_owner.make_rid(fd);
@@ -2429,6 +2431,7 @@ void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
 
 RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) {
 	_THREAD_SAFE_METHOD_
+
 	ShapedTextDataFallback *sd = memnew(ShapedTextDataFallback);
 	sd->direction = p_direction;
 	sd->orientation = p_orientation;
@@ -2807,6 +2810,8 @@ void TextServerFallback::_realign(ShapedTextDataFallback *p_sd) const {
 }
 
 RID TextServerFallback::shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const {
+	_THREAD_SAFE_METHOD_
+
 	const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_COND_V(!sd, RID());
 

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 336 - 212
scene/gui/rich_text_label.cpp


+ 39 - 11
scene/gui/rich_text_label.h

@@ -141,10 +141,10 @@ private:
 	struct ItemFrame : public Item {
 		bool cell = false;
 
-		Vector<Line> lines;
-		int first_invalid_line = 0;
-		int first_invalid_font_line = 0;
-		int first_resized_line = 0;
+		LocalVector<Line> lines;
+		std::atomic<int> first_invalid_line;
+		std::atomic<int> first_invalid_font_line;
+		std::atomic<int> first_resized_line;
 
 		ItemFrame *parent_frame = nullptr;
 
@@ -155,7 +155,12 @@ private:
 		Size2 max_size_over = Size2(-1, -1);
 		Rect2 padding;
 
-		ItemFrame() { type = ITEM_FRAME; }
+		ItemFrame() {
+			type = ITEM_FRAME;
+			first_invalid_line.store(0);
+			first_invalid_font_line.store(0);
+			first_resized_line.store(0);
+		}
 	};
 
 	struct ItemText : public Item {
@@ -263,8 +268,8 @@ private:
 			int width = 0;
 		};
 
-		Vector<Column> columns;
-		Vector<float> rows;
+		LocalVector<Column> columns;
+		LocalVector<float> rows;
 
 		int total_width = 0;
 		int total_height = 0;
@@ -363,6 +368,16 @@ private:
 	Item *current = nullptr;
 	ItemFrame *current_frame = nullptr;
 
+	Thread thread;
+	Mutex data_mutex;
+	bool threaded = false;
+	std::atomic<bool> stop_thread;
+	std::atomic<bool> updating;
+	std::atomic<double> loaded;
+
+	uint64_t loading_started = 0;
+	int progress_delay = 1000;
+
 	VScrollBar *vscroll = nullptr;
 
 	AutowrapMode autowrap_mode = AUTOWRAP_WORD_SMART;
@@ -392,7 +407,11 @@ private:
 	Array custom_effects;
 
 	void _invalidate_current_line(ItemFrame *p_frame);
-	void _validate_line_caches(ItemFrame *p_frame);
+
+	static void _thread_function(void *self);
+	void _stop_thread();
+	bool _validate_line_caches();
+	void _process_line_caches();
 
 	void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false);
 	void _remove_item(Item *p_item, const int p_line, const int p_subitem_line);
@@ -446,8 +465,9 @@ private:
 	bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search);
 	bool _search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search);
 
-	void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset);
-	void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width);
+	float _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset);
+	float _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h);
+
 	void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
 	int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
 	float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false);
@@ -480,9 +500,9 @@ private:
 	bool _find_layout_subitem(Item *from, Item *to);
 	void _fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack);
 
-	void _update_scroll();
 	void _update_fx(ItemFrame *p_frame, double p_delta_time);
 	void _scroll_changed(double);
+	int _find_first_line(int p_from, int p_to, int p_vofs) const;
 
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 	virtual String get_tooltip(const Point2 &p_pos) const override;
@@ -611,6 +631,14 @@ public:
 	bool is_deselect_on_focus_loss_enabled() const;
 	void deselect();
 
+	bool is_ready() const;
+
+	void set_threaded(bool p_threaded);
+	bool is_threaded() const;
+
+	void set_progress_bar_delay(int p_delay_ms);
+	int get_progress_bar_delay() const;
+
 	// Context menu.
 	PopupMenu *get_menu() const;
 	bool is_menu_visible() const;

+ 113 - 25
scene/resources/text_paragraph.cpp

@@ -139,7 +139,7 @@ void TextParagraph::_bind_methods() {
 
 void TextParagraph::_shape_lines() {
 	if (lines_dirty) {
-		for (int i = 0; i < lines_rid.size(); i++) {
+		for (int i = 0; i < (int)lines_rid.size(); i++) {
 			TS->free_rid(lines_rid[i]);
 		}
 		lines_rid.clear();
@@ -218,14 +218,14 @@ void TextParagraph::_shape_lines() {
 
 		// Fill after min_size calculation.
 		if (autowrap_enabled) {
-			int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size();
-			bool lines_hidden = visible_lines > 0 && visible_lines < lines_rid.size();
+			int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size();
+			bool lines_hidden = visible_lines > 0 && visible_lines < (int)lines_rid.size();
 			if (lines_hidden) {
 				overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS;
 			}
 			if (alignment == HORIZONTAL_ALIGNMENT_FILL) {
-				for (int i = 0; i < lines_rid.size(); i++) {
-					if (i < visible_lines - 1 || lines_rid.size() == 1) {
+				for (int i = 0; i < (int)lines_rid.size(); i++) {
+					if (i < visible_lines - 1 || (int)lines_rid.size() == 1) {
 						TS->shaped_text_fit_to_width(lines_rid[i], width, flags);
 					} else if (i == (visible_lines - 1)) {
 						TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
@@ -238,7 +238,7 @@ void TextParagraph::_shape_lines() {
 
 		} else {
 			// Autowrap disabled.
-			for (int i = 0; i < lines_rid.size(); i++) {
+			for (int i = 0; i < (int)lines_rid.size(); i++) {
 				if (alignment == HORIZONTAL_ALIGNMENT_FILL) {
 					TS->shaped_text_fit_to_width(lines_rid[i], width, flags);
 					overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE;
@@ -258,8 +258,10 @@ RID TextParagraph::get_rid() const {
 }
 
 RID TextParagraph::get_line_rid(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), RID());
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), RID());
 	return lines_rid[p_line];
 }
 
@@ -268,9 +270,11 @@ RID TextParagraph::get_dropcap_rid() const {
 }
 
 void TextParagraph::clear() {
+	_THREAD_SAFE_METHOD_
+
 	spacing_top = 0;
 	spacing_bottom = 0;
-	for (int i = 0; i < lines_rid.size(); i++) {
+	for (int i = 0; i < (int)lines_rid.size(); i++) {
 		TS->free_rid(lines_rid[i]);
 	}
 	lines_rid.clear();
@@ -279,57 +283,79 @@ void TextParagraph::clear() {
 }
 
 void TextParagraph::set_preserve_invalid(bool p_enabled) {
+	_THREAD_SAFE_METHOD_
+
 	TS->shaped_text_set_preserve_invalid(rid, p_enabled);
 	TS->shaped_text_set_preserve_invalid(dropcap_rid, p_enabled);
 	lines_dirty = true;
 }
 
 bool TextParagraph::get_preserve_invalid() const {
+	_THREAD_SAFE_METHOD_
+
 	return TS->shaped_text_get_preserve_invalid(rid);
 }
 
 void TextParagraph::set_preserve_control(bool p_enabled) {
+	_THREAD_SAFE_METHOD_
+
 	TS->shaped_text_set_preserve_control(rid, p_enabled);
 	TS->shaped_text_set_preserve_control(dropcap_rid, p_enabled);
 	lines_dirty = true;
 }
 
 bool TextParagraph::get_preserve_control() const {
+	_THREAD_SAFE_METHOD_
+
 	return TS->shaped_text_get_preserve_control(rid);
 }
 
 void TextParagraph::set_direction(TextServer::Direction p_direction) {
+	_THREAD_SAFE_METHOD_
+
 	TS->shaped_text_set_direction(rid, p_direction);
 	TS->shaped_text_set_direction(dropcap_rid, p_direction);
 	lines_dirty = true;
 }
 
 TextServer::Direction TextParagraph::get_direction() const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
 	return TS->shaped_text_get_direction(rid);
 }
 
 void TextParagraph::set_custom_punctuation(const String &p_punct) {
+	_THREAD_SAFE_METHOD_
+
 	TS->shaped_text_set_custom_punctuation(rid, p_punct);
 	lines_dirty = true;
 }
 
 String TextParagraph::get_custom_punctuation() const {
+	_THREAD_SAFE_METHOD_
+
 	return TS->shaped_text_get_custom_punctuation(rid);
 }
 
 void TextParagraph::set_orientation(TextServer::Orientation p_orientation) {
+	_THREAD_SAFE_METHOD_
+
 	TS->shaped_text_set_orientation(rid, p_orientation);
 	TS->shaped_text_set_orientation(dropcap_rid, p_orientation);
 	lines_dirty = true;
 }
 
 TextServer::Orientation TextParagraph::get_orientation() const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
 	return TS->shaped_text_get_orientation(rid);
 }
 
 bool TextParagraph::set_dropcap(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Rect2 &p_dropcap_margins, const Dictionary &p_opentype_features, const String &p_language) {
+	_THREAD_SAFE_METHOD_
+
 	ERR_FAIL_COND_V(p_fonts.is_null(), false);
 	TS->shaped_text_clear(dropcap_rid);
 	dropcap_margins = p_dropcap_margins;
@@ -339,12 +365,16 @@ bool TextParagraph::set_dropcap(const String &p_text, const Ref<Font> &p_fonts,
 }
 
 void TextParagraph::clear_dropcap() {
+	_THREAD_SAFE_METHOD_
+
 	dropcap_margins = Rect2();
 	TS->shaped_text_clear(dropcap_rid);
 	lines_dirty = true;
 }
 
 bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
+	_THREAD_SAFE_METHOD_
+
 	ERR_FAIL_COND_V(p_fonts.is_null(), false);
 	bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language, p_meta);
 	spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP);
@@ -362,23 +392,31 @@ int TextParagraph::get_spacing_bottom() const {
 }
 
 void TextParagraph::set_bidi_override(const Array &p_override) {
+	_THREAD_SAFE_METHOD_
+
 	TS->shaped_text_set_bidi_override(rid, p_override);
 	lines_dirty = true;
 }
 
 bool TextParagraph::add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, int p_length) {
+	_THREAD_SAFE_METHOD_
+
 	bool res = TS->shaped_text_add_object(rid, p_key, p_size, p_inline_align, p_length);
 	lines_dirty = true;
 	return res;
 }
 
 bool TextParagraph::resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) {
+	_THREAD_SAFE_METHOD_
+
 	bool res = TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align);
 	lines_dirty = true;
 	return res;
 }
 
 void TextParagraph::set_alignment(HorizontalAlignment p_alignment) {
+	_THREAD_SAFE_METHOD_
+
 	if (alignment != p_alignment) {
 		if (alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) {
 			alignment = p_alignment;
@@ -394,11 +432,15 @@ HorizontalAlignment TextParagraph::get_alignment() const {
 }
 
 void TextParagraph::tab_align(const Vector<float> &p_tab_stops) {
+	_THREAD_SAFE_METHOD_
+
 	tab_stops = p_tab_stops;
 	lines_dirty = true;
 }
 
 void TextParagraph::set_flags(uint16_t p_flags) {
+	_THREAD_SAFE_METHOD_
+
 	if (flags != p_flags) {
 		flags = p_flags;
 		lines_dirty = true;
@@ -410,6 +452,8 @@ uint16_t TextParagraph::get_flags() const {
 }
 
 void TextParagraph::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior) {
+	_THREAD_SAFE_METHOD_
+
 	if (overrun_behavior != p_behavior) {
 		overrun_behavior = p_behavior;
 		lines_dirty = true;
@@ -421,6 +465,8 @@ TextParagraph::OverrunBehavior TextParagraph::get_text_overrun_behavior() const
 }
 
 void TextParagraph::set_width(float p_width) {
+	_THREAD_SAFE_METHOD_
+
 	if (width != p_width) {
 		width = p_width;
 		lines_dirty = true;
@@ -432,6 +478,8 @@ float TextParagraph::get_width() const {
 }
 
 Size2 TextParagraph::get_non_wrapped_size() const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
 	if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) {
 		return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y + spacing_top + spacing_bottom);
@@ -441,9 +489,11 @@ Size2 TextParagraph::get_non_wrapped_size() const {
 }
 
 Size2 TextParagraph::get_size() const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
 	Size2 size;
-	int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size();
+	int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size();
 	for (int i = 0; i < visible_lines; i++) {
 		Size2 lsize = TS->shaped_text_get_size(lines_rid[i]);
 		if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
@@ -458,11 +508,15 @@ Size2 TextParagraph::get_size() const {
 }
 
 int TextParagraph::get_line_count() const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	return lines_rid.size();
+	return (int)lines_rid.size();
 }
 
 void TextParagraph::set_max_lines_visible(int p_lines) {
+	_THREAD_SAFE_METHOD_
+
 	if (p_lines != max_lines_visible) {
 		max_lines_visible = p_lines;
 		lines_dirty = true;
@@ -474,14 +528,18 @@ int TextParagraph::get_max_lines_visible() const {
 }
 
 Array TextParagraph::get_line_objects(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Array());
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Array());
 	return TS->shaped_text_get_objects(lines_rid[p_line]);
 }
 
 Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Rect2());
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Rect2());
 	Rect2 xrect = TS->shaped_text_get_object_rect(lines_rid[p_line], p_key);
 	for (int i = 0; i < p_line; i++) {
 		Size2 lsize = TS->shaped_text_get_size(lines_rid[i]);
@@ -495,8 +553,10 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const {
 }
 
 Size2 TextParagraph::get_line_size(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Size2());
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Size2());
 	if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) {
 		return Size2(TS->shaped_text_get_size(lines_rid[p_line]).x, TS->shaped_text_get_size(lines_rid[p_line]).y + spacing_top + spacing_bottom);
 	} else {
@@ -505,42 +565,56 @@ Size2 TextParagraph::get_line_size(int p_line) const {
 }
 
 Vector2i TextParagraph::get_line_range(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Vector2i());
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Vector2i());
 	return TS->shaped_text_get_range(lines_rid[p_line]);
 }
 
 float TextParagraph::get_line_ascent(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f);
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
 	return TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top;
 }
 
 float TextParagraph::get_line_descent(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f);
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
 	return TS->shaped_text_get_descent(lines_rid[p_line]) + spacing_bottom;
 }
 
 float TextParagraph::get_line_width(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f);
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
 	return TS->shaped_text_get_width(lines_rid[p_line]);
 }
 
 float TextParagraph::get_line_underline_position(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f);
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
 	return TS->shaped_text_get_underline_position(lines_rid[p_line]);
 }
 
 float TextParagraph::get_line_underline_thickness(int p_line) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f);
+	ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), 0.f);
 	return TS->shaped_text_get_underline_thickness(lines_rid[p_line]);
 }
 
 Size2 TextParagraph::get_dropcap_size() const {
+	_THREAD_SAFE_METHOD_
+
 	return TS->shaped_text_get_size(dropcap_rid) + dropcap_margins.size + dropcap_margins.position;
 }
 
@@ -549,6 +623,8 @@ int TextParagraph::get_dropcap_lines() const {
 }
 
 void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_color, const Color &p_dc_color) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
 	Vector2 ofs = p_pos;
 	float h_offset = 0.f;
@@ -571,7 +647,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo
 		TS->shaped_text_draw(dropcap_rid, p_canvas, dc_off + Vector2(0, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.size.y + dropcap_margins.position.y / 2), -1, -1, p_dc_color);
 	}
 
-	int lines_visible = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size();
+	int lines_visible = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size();
 
 	for (int i = 0; i < lines_visible; i++) {
 		float l_width = width;
@@ -650,6 +726,8 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo
 }
 
 void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outline_size, const Color &p_color, const Color &p_dc_color) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
 	Vector2 ofs = p_pos;
 
@@ -673,7 +751,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
 		TS->shaped_text_draw_outline(dropcap_rid, p_canvas, dc_off + Vector2(dropcap_margins.position.x, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.position.y), -1, -1, p_outline_size, p_dc_color);
 	}
 
-	for (int i = 0; i < lines_rid.size(); i++) {
+	for (int i = 0; i < (int)lines_rid.size(); i++) {
 		float l_width = width;
 		if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
 			ofs.x = p_pos.x;
@@ -750,6 +828,8 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
 }
 
 int TextParagraph::hit_test(const Point2 &p_coords) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
 	Vector2 ofs;
 	if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) {
@@ -761,7 +841,7 @@ int TextParagraph::hit_test(const Point2 &p_coords) const {
 			return 0;
 		}
 	}
-	for (int i = 0; i < lines_rid.size(); i++) {
+	for (int i = 0; i < (int)lines_rid.size(); i++) {
 		if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
 			if ((p_coords.y >= ofs.y) && (p_coords.y <= ofs.y + TS->shaped_text_get_size(lines_rid[i]).y)) {
 				return TS->shaped_text_hit_test_position(lines_rid[i], p_coords.x);
@@ -778,6 +858,8 @@ int TextParagraph::hit_test(const Point2 &p_coords) const {
 }
 
 void TextParagraph::draw_dropcap(RID p_canvas, const Vector2 &p_pos, const Color &p_color) const {
+	_THREAD_SAFE_METHOD_
+
 	Vector2 ofs = p_pos;
 	float h_offset = 0.f;
 	if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
@@ -800,6 +882,8 @@ void TextParagraph::draw_dropcap(RID p_canvas, const Vector2 &p_pos, const Color
 }
 
 void TextParagraph::draw_dropcap_outline(RID p_canvas, const Vector2 &p_pos, int p_outline_size, const Color &p_color) const {
+	_THREAD_SAFE_METHOD_
+
 	Vector2 ofs = p_pos;
 	float h_offset = 0.f;
 	if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
@@ -822,8 +906,10 @@ void TextParagraph::draw_dropcap_outline(RID p_canvas, const Vector2 &p_pos, int
 }
 
 void TextParagraph::draw_line(RID p_canvas, const Vector2 &p_pos, int p_line, const Color &p_color) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND(p_line < 0 || p_line >= lines_rid.size());
+	ERR_FAIL_COND(p_line < 0 || p_line >= (int)lines_rid.size());
 
 	Vector2 ofs = p_pos;
 
@@ -836,8 +922,10 @@ void TextParagraph::draw_line(RID p_canvas, const Vector2 &p_pos, int p_line, co
 }
 
 void TextParagraph::draw_line_outline(RID p_canvas, const Vector2 &p_pos, int p_line, int p_outline_size, const Color &p_color) const {
+	_THREAD_SAFE_METHOD_
+
 	const_cast<TextParagraph *>(this)->_shape_lines();
-	ERR_FAIL_COND(p_line < 0 || p_line >= lines_rid.size());
+	ERR_FAIL_COND(p_line < 0 || p_line >= (int)lines_rid.size());
 
 	Vector2 ofs = p_pos;
 	if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) {
@@ -862,7 +950,7 @@ TextParagraph::TextParagraph() {
 }
 
 TextParagraph::~TextParagraph() {
-	for (int i = 0; i < lines_rid.size(); i++) {
+	for (int i = 0; i < (int)lines_rid.size(); i++) {
 		TS->free_rid(lines_rid[i]);
 	}
 	lines_rid.clear();

+ 5 - 1
scene/resources/text_paragraph.h

@@ -31,6 +31,7 @@
 #ifndef TEXT_PARAGRAPH_H
 #define TEXT_PARAGRAPH_H
 
+#include "core/templates/local_vector.h"
 #include "scene/resources/font.h"
 #include "servers/text_server.h"
 
@@ -38,6 +39,7 @@
 
 class TextParagraph : public RefCounted {
 	GDCLASS(TextParagraph, RefCounted);
+	_THREAD_SAFE_CLASS_
 
 public:
 	enum OverrunBehavior {
@@ -54,7 +56,7 @@ private:
 	Rect2 dropcap_margins;
 
 	RID rid;
-	Vector<RID> lines_rid;
+	LocalVector<RID> lines_rid;
 	int spacing_top = 0;
 	int spacing_bottom = 0;
 
@@ -156,6 +158,8 @@ public:
 
 	int hit_test(const Point2 &p_coords) const;
 
+	Mutex &get_mutex() const { return _thread_safe_; };
+
 	TextParagraph(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", float p_width = -1.f, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL);
 	TextParagraph();
 	~TextParagraph();

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно