From 0d17aa0c7226f09bcba2cb3a21c6ba9627b96460 Mon Sep 17 00:00:00 2001 From: Synzorasize Date: Fri, 3 Jan 2025 19:47:33 -0600 Subject: [PATCH] Implement @no_storage annotation. --- modules/gdscript/doc_classes/@GDScript.xml | 22 ++++++++++++- modules/gdscript/gdscript_parser.cpp | 32 +++++++++++++++++-- modules/gdscript/gdscript_parser.h | 2 ++ .../tests/scripts/parser/errors/no_storage.gd | 11 +++++++ .../scripts/parser/errors/no_storage.out | 7 ++++ .../parser/features/export_variable.gd | 7 ++++ .../parser/features/export_variable.out | 10 ++++++ 7 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 modules/gdscript/tests/scripts/parser/errors/no_storage.gd create mode 100644 modules/gdscript/tests/scripts/parser/errors/no_storage.out diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 1f7bb9d2fb3..ebab747c451 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -643,10 +643,12 @@ Export a property with [constant PROPERTY_USAGE_STORAGE] flag. The property is not displayed in the editor, but it is serialized and stored in the scene or resource file. This can be useful for [annotation @tool] scripts. Also the property value is copied when [method Resource.duplicate] or [method Node.duplicate] is called, unlike non-exported variables. + See also [annotation @no_storage]. [codeblock] var a # Not stored in the file, not displayed in the editor. @export_storage var b # Stored in the file, not displayed in the editor. - @export var c: int # Stored in the file, displayed in the editor. + @export @no_storage var c: int # Not stored in the file, displayed in the editor. + @export var d: int # Stored in the file, displayed in the editor. [/codeblock] @@ -729,6 +731,24 @@ [b]Note:[/b] Unlike most other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported). + + + + Marks the exported property as editor-only (not saved to disk). Can be applied to a variable with a regular export annotation applied. The property value will also not be copied when [method Resource.duplicate] or [method Node.duplicate] is called, unlike serialized variables. This can be useful for [annotation @tool] scripts. + See also [annotation @export_storage]. + [codeblock] + @tool + + @export var string = "" + # This variable can be used to edit "string" in the Inspector. + @export_range(0, 10) @no_storage var int_to_string: int: + set(value): + string = str(value) + get: + return string.to_int() + [/codeblock] + + diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index cd37cd0452f..5f879359490 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -121,6 +121,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations); register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation); register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT)); + register_annotation(MethodInfo("@no_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::no_storage_annotation); register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray("")); // Export grouping annotations. register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations); @@ -4404,7 +4405,7 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta return false; } if (variable->exported) { - push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); + push_error(vformat(R"(Annotation "%s" cannot be used with another export annotation.)", p_annotation->name), p_annotation); return false; } @@ -4739,7 +4740,7 @@ bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Nod return false; } if (variable->exported) { - push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); + push_error(vformat(R"(Annotation "%s" cannot be used with another export annotation.)", p_annotation->name), p_annotation); return false; } @@ -4762,7 +4763,7 @@ bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node return false; } if (variable->exported) { - push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); + push_error(vformat(R"(Annotation "%s" cannot be used with another export annotation.)", p_annotation->name), p_annotation); return false; } @@ -4780,6 +4781,31 @@ bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node return true; } +bool GDScriptParser::no_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); + + VariableNode *variable = static_cast(p_target); + for (const AnnotationNode *annotation_node : variable->annotations) { + if (annotation_node->name == SNAME("@export_storage") || annotation_node->name == SNAME("@export_custom") || annotation_node->name == SNAME("@export_tool_button")) { + push_error(vformat(R"(Annotation "%s" cannot be used with "%s".)", p_annotation->name, annotation_node->name), p_annotation); + return false; + } + } + if (!variable->exported) { + push_error(vformat(R"(Annotation "%s" must be placed after an export annotation.)", p_annotation->name), p_annotation); + return false; + } + if (variable->exported_no_storage) { + push_error(vformat(R"(Annotation "%s" should only be used once.)", p_annotation->name), p_annotation); + return false; + } + + // Set variable to only show in editor and keep the previous annotation's export_info changes. + variable->export_info.usage &= ~PROPERTY_USAGE_STORAGE; + variable->exported_no_storage = true; + return true; +} + bool GDScriptParser::export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef TOOLS_ENABLED ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 22b14e8bb5c..03156c28846 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1262,6 +1262,7 @@ public: }; bool exported = false; + bool exported_no_storage = false; bool onready = false; PropertyInfo export_info; int assignments = 0; @@ -1513,6 +1514,7 @@ private: bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool no_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); diff --git a/modules/gdscript/tests/scripts/parser/errors/no_storage.gd b/modules/gdscript/tests/scripts/parser/errors/no_storage.gd new file mode 100644 index 00000000000..1a22a5079fa --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/no_storage.gd @@ -0,0 +1,11 @@ +@tool + +@export_storage @no_storage var int_number = 0 +@export_custom(PROPERTY_HINT_NONE, "") @no_storage var float_number = 0.0 +@export_tool_button("") @no_storage var callable = test +@no_storage var string = "" +@no_storage @export var string_name = &"" +@export @no_storage @no_storage var bool_var = false + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/no_storage.out b/modules/gdscript/tests/scripts/parser/errors/no_storage.out new file mode 100644 index 00000000000..1917237ea0d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/no_storage.out @@ -0,0 +1,7 @@ +GDTEST_ANALYZER_ERROR +>> ERROR at line 3: Annotation "@no_storage" cannot be used with "@export_storage". +>> ERROR at line 4: Annotation "@no_storage" cannot be used with "@export_custom". +>> ERROR at line 5: Annotation "@no_storage" cannot be used with "@export_tool_button". +>> ERROR at line 6: Annotation "@no_storage" must be placed after an export annotation. +>> ERROR at line 7: Annotation "@no_storage" must be placed after an export annotation. +>> ERROR at line 8: Annotation "@no_storage" should only be used once. diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index 8aa449f6024..e3d366723c1 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -48,6 +48,13 @@ const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd") @export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_weak_int = 5 @export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_hard_int: int = 6 +# `@no_storage`. +@export @no_storage var test_no_storage_int = 1 +@export_range(0, 10) @no_storage var test_no_storage_range = 1 +@export_enum("A", "B", "C") @no_storage var test_range_no_storage_enum = "A" +@export @no_storage var test_no_storage_array = [] +@export @no_storage var test_no_storage_dictionary = {} + # `@export_tool_button`. @export_tool_button("Click me!") var test_tool_button_1: Callable @export_tool_button("Click me!", "ColorRect") var test_tool_button_2: Callable diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out index c0bf4d6e065..0ce4c955931 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.out +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out @@ -55,6 +55,16 @@ var test_export_custom_weak_int: int = 5 hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" var test_export_custom_hard_int: int = 6 hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&"" +var test_no_storage_int: int = 1 + hint=NONE hint_string="" usage=EDITOR|SCRIPT_VARIABLE class_name=&"" +var test_no_storage_range: int = 1 + hint=RANGE hint_string="0.0,10.0" usage=EDITOR|SCRIPT_VARIABLE class_name=&"" +var test_range_no_storage_enum: String = "A" + hint=ENUM hint_string="A,B,C" usage=EDITOR|SCRIPT_VARIABLE class_name=&"" +var test_no_storage_array: Array = [] + hint=NONE hint_string="" usage=EDITOR|SCRIPT_VARIABLE class_name=&"" +var test_no_storage_dictionary: Dictionary = {} + hint=NONE hint_string="" usage=EDITOR|SCRIPT_VARIABLE class_name=&"" var test_tool_button_1: Callable = Callable() hint=TOOL_BUTTON hint_string="Click me!" usage=EDITOR|SCRIPT_VARIABLE class_name=&"" var test_tool_button_2: Callable = Callable()