diff --git a/godot-core/src/registry/property.rs b/godot-core/src/registry/property.rs index 69734d4da..35cb71d94 100644 --- a/godot-core/src/registry/property.rs +++ b/godot-core/src/registry/property.rs @@ -517,6 +517,7 @@ pub mod export_info_functions { // right side are the corresponding property hint. Godot is not always consistent between the two, such // as `export_multiline` being `PROPERTY_HINT_MULTILINE_TEXT`. default_export_funcs!( + export_storage => NONE, // Storage exports don't display in the editor. export_flags_2d_physics => LAYERS_2D_PHYSICS, export_flags_2d_render => LAYERS_2D_RENDER, export_flags_2d_navigation => LAYERS_2D_NAVIGATION, diff --git a/godot-macros/src/class/data_models/field_export.rs b/godot-macros/src/class/data_models/field_export.rs index 13cc75d8f..0bed3b467 100644 --- a/godot-macros/src/class/data_models/field_export.rs +++ b/godot-macros/src/class/data_models/field_export.rs @@ -9,7 +9,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use std::collections::{HashMap, HashSet}; -use crate::util::{KvParser, ListParser}; +use crate::util::{ident, KvParser, ListParser}; use crate::ParseResult; pub struct FieldExport { @@ -27,6 +27,10 @@ impl FieldExport { pub fn to_export_hint(&self) -> Option { self.export_type.to_export_hint() } + + pub fn to_export_usage(&self) -> Option { + self.export_type.to_export_usage() + } } /// Store info from `#[export]` attribute. @@ -40,6 +44,20 @@ pub enum ExportType { /// Can become other property hints, depends on context. Default, + /// ### GDScript annotations + /// - `@export_storage` + /// + /// ### Property hints + /// - `NONE` + /// + /// ### Property usage + /// - `STORAGE` + /// + /// This is used to indicate that the property should be exported + /// but should not be visible in the editor. Therefore, it does not + /// have a property hint, but uses the `STORAGE` property usage. + Storage, + /// ### GDScript annotations /// - `@export_range` /// @@ -150,6 +168,10 @@ impl ExportType { /// becomes /// `#[export(flags/enum = (elem1, elem2 = key2, ...))]` pub(crate) fn new_from_kv(parser: &mut KvParser) -> ParseResult { + if parser.handle_alone("storage")? { + return Self::new_storage(); + } + if let Some(list_parser) = parser.handle_list("range")? { return Self::new_range_list(list_parser); } @@ -273,6 +295,10 @@ impl ExportType { Ok(Self::Default) } + fn new_storage() -> ParseResult { + Ok(Self::Storage) + } + fn new_range_list(mut parser: ListParser) -> ParseResult { const FLAG_OPTIONS: [&str; 7] = [ "or_greater", @@ -404,6 +430,8 @@ impl ExportType { match self { Self::Default => None, + Self::Storage => quote_export_func! { export_storage() }, + Self::Range { min, max, @@ -519,6 +547,13 @@ impl ExportType { Self::ColorNoAlpha => quote_export_func! { export_color_no_alpha() }, } } + + pub fn to_export_usage(&self) -> Option { + match self { + Self::Storage => Some(ident("STORAGE")), + _ => None, + } + } } /// The dimension of a `@export_flags_{dimension}_{layer}` annotation. diff --git a/godot-macros/src/class/data_models/property.rs b/godot-macros/src/class/data_models/property.rs index c9b8aa748..5c50efe00 100644 --- a/godot-macros/src/class/data_models/property.rs +++ b/godot-macros/src/class/data_models/property.rs @@ -53,10 +53,17 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream { // Ensure we add a var if the user only provided a `#[export]`. let var = match (export, var) { - (Some(_), None) => Some(FieldVar { - usage_flags: UsageFlags::InferredExport, - ..Default::default() - }), + (Some(export), None) => { + let usage_flags = if let Some(usage) = export.to_export_usage() { + UsageFlags::Custom(vec![usage]) + } else { + UsageFlags::InferredExport + }; + Some(FieldVar { + usage_flags, + ..Default::default() + }) + } (_, var) => var.clone(), }; diff --git a/godot-macros/src/lib.rs b/godot-macros/src/lib.rs index 84a65f423..d20e74d0b 100644 --- a/godot-macros/src/lib.rs +++ b/godot-macros/src/lib.rs @@ -243,7 +243,11 @@ use crate::util::{bail, ident, KvParser}; /// // @export /// #[export] /// float: f64, -/// +/// +/// // @export_storage +/// #[export(storage)] +/// hidden_string: GString, +/// /// // @export_range(0.0, 10.0, or_greater) /// #[export(range = (0.0, 10.0, or_greater))] /// range_f64: f64, diff --git a/itest/rust/build.rs b/itest/rust/build.rs index 31cc5501c..3069f4647 100644 --- a/itest/rust/build.rs +++ b/itest/rust/build.rs @@ -472,6 +472,9 @@ fn generate_property_template(inputs: &[Input]) -> PropertyTests { TokenStream::new() } else { quote! { + #[export(storage)] + export_storage: GString, + #[export(file)] export_file_array: Array, #[export(file)] @@ -597,6 +600,7 @@ fn generate_property_template(inputs: &[Input]) -> PropertyTests { // Only available in Godot 4.3+. let advanced_exports_4_3 = r#" +@export_storage var export_storage: String @export_file var export_file_array: Array[String] @export_file var export_file_parray: PackedStringArray @export_file("*.txt") var export_file_wildcard_array: Array[String] diff --git a/itest/rust/src/object_tests/property_template_test.rs b/itest/rust/src/object_tests/property_template_test.rs index af458a4db..4a5f1814d 100644 --- a/itest/rust/src/object_tests/property_template_test.rs +++ b/itest/rust/src/object_tests/property_template_test.rs @@ -70,11 +70,15 @@ fn property_template_test(ctx: &TestContext) { let mut rust_usage = rust_prop.at("usage").to::(); - // the GDSscript variables are script variables, and so have `PROPERTY_USAGE_SCRIPT_VARIABLE` set. - if rust_usage == PropertyUsageFlags::STORAGE.ord() as i64 { - // `PROPERTY_USAGE_SCRIPT_VARIABLE` does the same thing as `PROPERTY_USAGE_STORAGE` and so - // GDScript doesn't set both if it doesn't need to. - rust_usage = PropertyUsageFlags::SCRIPT_VARIABLE.ord() as i64 + // The GDSscript variables are script variables, and so have `PROPERTY_USAGE_SCRIPT_VARIABLE` set. + // Before 4.3, `PROPERTY_USAGE_SCRIPT_VARIABLE` did the same thing as `PROPERTY_USAGE_STORAGE` and + // so GDScript didn't set both if it didn't need to. + if GdextBuild::before_api("4.3") { + if rust_usage == PropertyUsageFlags::STORAGE.ord() as i64 { + rust_usage = PropertyUsageFlags::SCRIPT_VARIABLE.ord() as i64 + } else { + rust_usage |= PropertyUsageFlags::SCRIPT_VARIABLE.ord() as i64; + } } else { rust_usage |= PropertyUsageFlags::SCRIPT_VARIABLE.ord() as i64; } @@ -102,11 +106,11 @@ fn property_template_test(ctx: &TestContext) { errors.push(format!( "mismatch in property {name}:\n GDScript: {gdscript_prop:?}\n Rust: {rust_prop:?}" )); - } /*else { - println!("matching property {name}\n GDScript: {gdscript_prop:?}\n Rust: {rust_prop:?}"); - }*/ + } } + rust_properties.free(); + assert!( properties.is_empty(), "not all properties were matched, missing: {properties:?}" @@ -118,6 +122,4 @@ fn property_template_test(ctx: &TestContext) { errors.len(), errors.join("\n") ); - - rust_properties.free(); }