diff --git a/README.md b/README.md index f6a021c..5346306 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,11 @@ Some macros exist in reg.h that provide automated registration for classes, func Here is how to use them: * ``REG_CLASS()`` - Use this macro instead of GDCLASS(). * ``REG_FUNCTION()`` - Put this in front of your function. - * ``REG_PROPERTY()`` - Put this in front of your property. Supports ``PropertyInfo`` meta parameters. + * ``REG_PROPERTY()`` - Put this in front of your property. Supports ``PropertyInfo`` meta parameters. The property's type must be one of the following: + * [Variant types](https://docs.godotengine.org/en/stable/classes/index.html#variant-types) + * `Node` or its subclass: The property must be a pointer with a default value (for instance, `nullptr`). + * `Resource` or its subclass: the property must be wrapped by Godot's `Ref` template. + * `TypedArray`: `T` must be one of the three above types. * ``REG_ENUM()`` - Put this in front of your enum. There is an example class called GDExample that you can use for reference. @@ -35,6 +39,7 @@ Registration-code will be injected into ``extension.cpp``. Class bindings will b ## Known issues / Future work * The debugger does not attach automatically to the godot process. You can still attach manually. + * Multi-dimensional array properties cannot be exported. For now, we recommend registering functions that allow GDScript to interact with the multi-dimensional array instead. ## Notes * Intellisense / Intellij will only work after first compile. diff --git a/source/RegAutomation/RegAutomation.Core.Tests/Test_Parser.cs b/source/RegAutomation/RegAutomation.Core.Tests/Test_Parser.cs index c5dc914..16d7f13 100644 --- a/source/RegAutomation/RegAutomation.Core.Tests/Test_Parser.cs +++ b/source/RegAutomation/RegAutomation.Core.Tests/Test_Parser.cs @@ -9,19 +9,21 @@ internal class Test_Parser : ParserBase public void TestFindParams() { var result = FindParams(""" - REG_TEST(REG_T_KEY_1 = 0, REG_T_KEY_2 = "hi!!!", REG_T_KEY_3 = (x, y = "1")) + REG_TEST(REG_T_KEY_1 = 0, REG_T_KEY_2 = "hi!!!", REG_T_KEY_3 = (x, y = "1"), REG_T_KEY_4 = "1,2,3,4") """, 0); Assert.That(result.Start, Is.EqualTo(9)); - Assert.That(result.End, Is.EqualTo(75)); + Assert.That(result.End, Is.EqualTo(100)); Assert.That(result.Content.Keys, Is.EquivalentTo(new string[] { "REG_T_KEY_1", "REG_T_KEY_2", "REG_T_KEY_3", + "REG_T_KEY_4", })); Assert.That(result.Content["REG_T_KEY_1"], Is.EqualTo("0")); Assert.That(result.Content["REG_T_KEY_2"], Is.EqualTo("\"hi!!!\"")); Assert.That(result.Content["REG_T_KEY_3"], Is.EqualTo("(x, y = \"1\")")); + Assert.That(result.Content["REG_T_KEY_4"], Is.EqualTo("\"1,2,3,4\"")); } [Test] public void TestFindLineNumber() diff --git a/source/RegAutomation/RegAutomation.Core.Tests/Test_PropertyParser.cs b/source/RegAutomation/RegAutomation.Core.Tests/Test_PropertyParser.cs index fa4bc70..230b3fa 100644 --- a/source/RegAutomation/RegAutomation.Core.Tests/Test_PropertyParser.cs +++ b/source/RegAutomation/RegAutomation.Core.Tests/Test_PropertyParser.cs @@ -15,29 +15,108 @@ public void TestParse() float my_nonregistered_float = 1; - REG_PROPERTY(REG_P_Info=(PROPERTY_HINT_RANGE, "0,20,0.01")) + REG_PROPERTY( + REG_P_HintType=PROPERTY_HINT_RANGE, + REG_P_HintString="0,20,0.01", + REG_P_UsageFlags=PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY) float my_float = 0; REG_PROPERTY() String my_string = "my_str"; + + REG_PROPERTY( + REG_P_HintType=PROPERTY_HINT_RANGE, + REG_P_HintString="0,1,0.001") + TypedArray float_array; + + REG_PROPERTY(REG_P_ExportAsResource) + Ref image_ref; + + REG_PROPERTY(REG_P_ExportAsResource) + TypedArray image_array; + + REG_PROPERTY(REG_P_ExportAsNode) + Node3D *node_pointer = nullptr; + + REG_PROPERTY(REG_P_ExportAsNode) + TypedArray node_array; """).ToArray(); Assert.That(result.Select(macro => macro.Name), Is.EquivalentTo(new string[] { "my_int", "my_float", "my_string", + "float_array", + "image_ref", + "image_array", + "node_pointer", + "node_array" })); Assert.That(result.Select(macro => macro.Type), Is.EquivalentTo(new string[] { "int", "float", "String", + "float", + "Image", + "Image", + "Node3D", + "Node3D", + })); + Assert.That(result.Select(macro => macro.ReferenceType), Is.EquivalentTo(new PropertyReferenceType[] + { + PropertyReferenceType.Value, + PropertyReferenceType.Value, + PropertyReferenceType.Value, + PropertyReferenceType.Value, + PropertyReferenceType.Ref, + PropertyReferenceType.Value, + PropertyReferenceType.Pointer, + PropertyReferenceType.Value, + })); + Assert.That(result.Select(macro => macro.ExportFlags), Is.EquivalentTo(new PropertyExportFlags[] + { + PropertyExportFlags.Variant, + PropertyExportFlags.Variant, + PropertyExportFlags.Variant, + PropertyExportFlags.Variant | PropertyExportFlags.Array, + PropertyExportFlags.Resource, + PropertyExportFlags.Resource | PropertyExportFlags.Array, + PropertyExportFlags.Node, + PropertyExportFlags.Node | PropertyExportFlags.Array, + })); + Assert.That(result.Select(macro => macro.HintType), Is.EquivalentTo(new string[] + { + "PROPERTY_HINT_NONE", + "PROPERTY_HINT_RANGE", + "PROPERTY_HINT_NONE", + "PROPERTY_HINT_RANGE", + "PROPERTY_HINT_NONE", + "PROPERTY_HINT_NONE", + "PROPERTY_HINT_NONE", + "PROPERTY_HINT_NONE", + })); + Assert.That(result.Select(macro => macro.HintString), Is.EquivalentTo(new string[] + { + "\"\"", + "\"0,20,0.01\"", + "\"\"", + "\"0,1,0.001\"", + "\"\"", + "\"\"", + "\"\"", + "\"\"", })); - Assert.That(result.Select(macro => macro.Meta), Is.EquivalentTo(new string[] + Assert.That(result.Select(macro => macro.UsageFlags), Is.EquivalentTo(new string[] { - string.Empty, - "PROPERTY_HINT_RANGE, \"0,20,0.01\"", - string.Empty, + "PROPERTY_USAGE_DEFAULT", + "PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY", + "PROPERTY_USAGE_DEFAULT", + "PROPERTY_USAGE_DEFAULT", + "PROPERTY_USAGE_DEFAULT", + "PROPERTY_USAGE_DEFAULT", + "PROPERTY_USAGE_DEFAULT", + "PROPERTY_USAGE_DEFAULT", })); } } diff --git a/source/RegAutomation/RegAutomation.Core/ParserBase.cs b/source/RegAutomation/RegAutomation.Core/ParserBase.cs index 5119d06..4a5716d 100644 --- a/source/RegAutomation/RegAutomation.Core/ParserBase.cs +++ b/source/RegAutomation/RegAutomation.Core/ParserBase.cs @@ -41,14 +41,35 @@ protected static Params FindParams(string content, int startIndex) Dictionary properties = new Dictionary(); int level = 0; List propertySeparatorIndices = new List(); + List equalOperatorIndices = new List(); + bool isParsingString = false; for(int i = startIndex; i < content.Length; i++) { - if(level > 0) + // Handle strings here. + if(content[i] == '"') { - if (level == 1 && content[i] == ',') + isParsingString = !isParsingString; + continue; + } + else if(i < content.Length - 1 && content[i] == '\\' && content[i + 1] == '"') + { + i++; // Skip the escaped double-quotes. + continue; + } + else if(isParsingString) + { + continue; // Passing through string literal, so don't check for separators until another double-quote is found. + } + if(level == 1) + { + if (content[i] == ',') { propertySeparatorIndices.Add(i); } + else if (content[i] == '=') + { + equalOperatorIndices.Add(i); + } } if (content[i] == '(') { @@ -72,19 +93,24 @@ protected static Params FindParams(string content, int startIndex) throw new Exception("Opening parenthesis not found."); if(level != 0) throw new Exception("Closing parenthesis not found."); + int equalOperatorIndexPointer = 0; for(int i = 0; i < propertySeparatorIndices.Count - 1; i++) { int start = propertySeparatorIndices[i] + 1; // Skip the '(' or ',' int end = propertySeparatorIndices[i + 1]; // Skip the ')' or ',' - string property = content.Substring(start, end - start).Trim(); - int equalOperatorIndex = property.IndexOf('='); - if(equalOperatorIndex != -1) + while(equalOperatorIndexPointer < equalOperatorIndices.Count && equalOperatorIndices[equalOperatorIndexPointer] <= start) + { + equalOperatorIndexPointer++; + } + if(equalOperatorIndexPointer < equalOperatorIndices.Count && equalOperatorIndices[equalOperatorIndexPointer] < end) { - properties[property.Substring(0, equalOperatorIndex).Trim()] = property.Substring(equalOperatorIndex + 1).Trim(); + int equalOperatorIndex = equalOperatorIndices[equalOperatorIndexPointer]; + properties[content[start..equalOperatorIndex].Trim()] = content[(equalOperatorIndex + 1)..end].Trim(); + equalOperatorIndexPointer++; } else { - properties[property.Trim()] = ""; + properties[content[start..end].Trim()] = ""; } } diff --git a/source/RegAutomation/RegAutomation.Core/PropertyBinder.cs b/source/RegAutomation/RegAutomation.Core/PropertyBinder.cs new file mode 100644 index 0000000..012091d --- /dev/null +++ b/source/RegAutomation/RegAutomation.Core/PropertyBinder.cs @@ -0,0 +1,150 @@ +using System.Text; +using System.Text.RegularExpressions; + +namespace RegAutomation.Core +{ + public static class PropertyBinder + { + public static (string propertyBindings, string functionBindings, string functionInject) GenerateBindings( + string className, + string propertyName, + string propertyType, + PropertyReferenceType propertyReferenceType, + PropertyExportFlags propertyExportFlags, + string propertyHintType, + string propertyHintString, + string propertyUsageFlags) + { + // Figure out the variant type of the property first. + // Note that even for TypedArrays, we use the same code path to figure out the element type here as well. + string variantType; + if(CppTypeToVariantType.ContainsKey(propertyType)) + { + if(propertyReferenceType is PropertyReferenceType.Pointer) + throw new NotSupportedException("Pointer to Variant type cannot be registered!"); + variantType = CppTypeToVariantType[propertyType]; + } + else if(propertyExportFlags.HasFlag(PropertyExportFlags.Node)) + { + variantType = "OBJECT"; + propertyHintType = "PROPERTY_HINT_NODE_TYPE"; + propertyHintString = $"\"{propertyType}\""; + } + else if(propertyExportFlags.HasFlag(PropertyExportFlags.Resource)) + { + variantType = "OBJECT"; + propertyHintType = "PROPERTY_HINT_RESOURCE_TYPE"; + propertyHintString = $"\"{propertyType}\""; + } + else + { + throw new NotSupportedException($"Unable to map {propertyType} to a GDScript-accessible type. Please check if it is a Variant type or a registered class that is a subclass of Node or Resource."); + } + + // For TypedArrays, move the element type's info inside the hint string, and add Array property info. + if (propertyExportFlags.HasFlag(PropertyExportFlags.Array)) + { + propertyHintString = $"""vformat("%s/%s:%s", Variant::{variantType}, {propertyHintType}, {propertyHintString})"""; + propertyHintType = "PROPERTY_HINT_ARRAY_TYPE"; + propertyType = $"TypedArray<{propertyType}>"; + variantType = "ARRAY"; + } + + // Combining the information above gives us the property info. + string propertyInfo = $"Variant::{variantType}, \"{propertyName}\", {propertyHintType}, {propertyHintString}, {propertyUsageFlags}"; + + var (getter, setter) = GetGetterSetter(variantType, propertyName); + // With the getter and setter name decided, and the property info computed, we can construct the binding code. + string addPropertyStatement = $"ClassDB::add_property(\"{className}\", PropertyInfo({propertyInfo}), \"{setter}\", \"{getter}\");\n\t"; + string bindGetterStatement = $"ClassDB::bind_method(D_METHOD(\"{getter}\"), &{className}::_gen_{getter});\n\t"; + string bindSetterStatement = $"ClassDB::bind_method(D_METHOD(\"{setter}\", \"p\"), &{className}::_gen_{setter});\n\t"; + + // For Refs, re-wrap the property type with Ref to match with C++ definition. + if(propertyReferenceType is PropertyReferenceType.Ref) + propertyType = $"Ref<{propertyType}>"; + // For pointers, we insert the pointer asterisk back into the function declarations. + // We can't put this on propertyType because the asterisk is placed right next to the type for the getter, + // but right next to the parameter for the setter. + string pointerSymbol; + if (propertyReferenceType is PropertyReferenceType.Pointer) + pointerSymbol = "*"; + else + pointerSymbol = ""; + + // Finally, construct the getters and setters. + string genGetterDeclaration = $"\t{propertyType}{pointerSymbol} _gen_{getter}() const {{ return {propertyName}; }}\n"; + string genSetterDeclaration = $"\tvoid _gen_{setter}({propertyType} {pointerSymbol}p) {{ {propertyName} = p; }}\n"; + + return new ( + addPropertyStatement, + bindGetterStatement + bindSetterStatement, + genGetterDeclaration + genSetterDeclaration); + } + private static (string get, string set) GetGetterSetter(string variant, string property) + { + switch (variant) + { + case "BOOL": + { + if (property.StartsWith("is_")) + property = property[3..]; + return ("is_" + property, "set_" + property); + } + } + return ("get_" + property, "set_" + property); + } + // Rather than coming up with a hacky solution based on naming patterns that have no guarantees, + // we just make the mapping explicit so it's easier to maintain. + // This also lets us check if a Cpp type can be converted into a variant. + // See also: list of variant types (https://docs.godotengine.org/en/stable/classes/index.html#variant-types). + private static readonly Dictionary CppTypeToVariantType = new() + { + ["AABB"] = "AABB", + ["Array"] = "ARRAY", + ["Basis"] = "BASIS", + ["bool"] = "BOOL", + ["Callable"] = "CALLABLE", + ["Color"] = "COLOR", + ["Dictionary"] = "DICTIONARY", + + // Aliases for float. + ["float"] = "FLOAT", + ["real_t"] = "FLOAT", + ["double"] = "FLOAT", + + // Aliases for int. + ["int"] = "INT", + ["int32_t"] = "INT", + ["int64_t"] = "INT", + + ["NodePath"] = "NODE_PATH", + ["Object"] = "OBJECT", + ["PackedByteArray"] = "PACKED_BYTE_ARRAY", + ["PackedColorArray"] = "PACKED_COLOR_ARRAY", + ["PackedFloat32Array"] = "PACKED_FLOAT32_ARRAY", + ["PackedFloat64Array"] = "PACKED_FLOAT64_ARRAY", + ["PackedInt32Array"] = "PACKED_INT32_ARRAY", + ["PackedInt64Array"] = "PACKED_INT64_ARRAY", + ["PackedStringArray"] = "PACKED_STRING_ARRAY", + ["PackedVector2Array"] = "PACKED_VECTOR2_ARRAY", + ["PackedVector3Array"] = "PACKED_VECTOR3_ARRAY", + ["Plane"] = "PLANE", + ["Projection"] = "PROJECTION", + ["Quaternion"] = "QUATERNION", + ["Rect2"] = "RECT2", + ["Rect2i"] = "RECT2I", + ["RID"] = "RID", + ["Signal"] = "SIGNAL", + ["String"] = "STRING", + ["StringName"] = "STRING_NAME", + ["Transform2D"] = "TRANSFORM2D", + ["Transform3D"] = "TRANSFORM3D", + ["Vector2"] = "VECTOR2", + ["Vector2i"] = "VECTOR2I", + ["Vector3"] = "VECTOR3", + ["Vector3i"] = "VECTOR3I", + ["Vector4"] = "VECTOR4", + ["Vector4i"] = "VECTOR4I", + }; + } +} diff --git a/source/RegAutomation/RegAutomation.Core/PropertyParser.cs b/source/RegAutomation/RegAutomation.Core/PropertyParser.cs index fee2e72..e30979b 100644 --- a/source/RegAutomation/RegAutomation.Core/PropertyParser.cs +++ b/source/RegAutomation/RegAutomation.Core/PropertyParser.cs @@ -1,18 +1,36 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Metadata; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace RegAutomation.Core +namespace RegAutomation.Core { + /// + /// Flags used to determine how the property should be exported. + /// + [Flags] + public enum PropertyExportFlags : byte + { + None = 0, + Variant = 1 << 0, + Node = 1 << 1, + Resource = 1 << 2, + Array = 1 << 3, + } + /// + /// How the property's type is treated. Note that Ref here means Godot's Ref, not C++ reference types. + /// C++ reference types are not supported. + /// + public enum PropertyReferenceType : byte + { + Value, // e.g., int, Vector2, usually corresponds to Variant types. + Ref, // e.g., Ref, usually corresponds to Resource types. + Pointer, // e.g., Node3D*, usually corresponds to Node types. + } public class PropertyMacro { public string Name = ""; - public string Type = ""; - public string Meta = ""; + public string Type = ""; // The property's type. Or, if PropertyExportFlags.Array is set in ExportFlags, the array's element type. + public PropertyReferenceType ReferenceType = PropertyReferenceType.Value; + public PropertyExportFlags ExportFlags = PropertyExportFlags.None; + public string HintType = ""; // PropertyHint in string form. + public string HintString = ""; // The hint string used by the PropertyHint. Should be enclosed by a pair of double-quotes ("). + public string UsageFlags = ""; // Constexpr that evaluates to the property's usage flag value. } public class PropertyParser : MacroParser { @@ -20,25 +38,108 @@ public class PropertyParser : MacroParser protected override string MacroKey => "REG_PROPERTY"; protected override PropertyMacro ParseMacroInstance(string content, Params parameters, int macroStart, int contextStart) { - int declEnd = content.IndexOf(';', contextStart); - var tokens = Tokenize(content[contextStart..declEnd]); - if(tokens.Contains("const")) - throw new Exception("Const properties are not allowed to be registered!"); - if(tokens.Contains("static")) - throw new Exception("Static properties are not allowed to be registered!"); - string name = tokens[1]; - string type = tokens[0]; - string meta = parameters.Content.GetValueOrDefault("REG_P_Info", ""); - if(meta.Length > 0) + // Properties can be " ;" or " = ;" + // We call " " the declaration, and the following code extracts the declaration. + int equalOperatorIndex = content.IndexOf('=', contextStart); + bool foundEqualOperator = equalOperatorIndex >= 0; + int semicolonIndex = content.IndexOf(';', contextStart); + int declEnd; + bool hasDefaultValue = false; + if (foundEqualOperator && equalOperatorIndex < semicolonIndex) + { + hasDefaultValue = true; + declEnd = equalOperatorIndex; + } + else + declEnd = semicolonIndex; + string declaration = content[contextStart..declEnd]; + + // Detect pointer types here. + bool isPointer = false; + int pointerCount = declaration.Where(x => x is '*').Count(); + if(pointerCount > 1) + throw new Exception("Pointer to pointer cannot be registered!"); + if(pointerCount == 1) + { + isPointer = true; + // We have to replace pointer with whitespace to cover cases like "Node3D*node", which is unfortunately legal C++. + declaration = declaration.Replace('*', ' '); + } + if(declaration.Contains('&')) + throw new Exception("C++ references (&) cannot be registered!"); + // Trim here so we can use IndexOf(' ') to find an inner whitespace, which separates type and name. + declaration = declaration.Trim(); + int typeNameSeparatorIndex = declaration.IndexOf(' '); + // (Error detection) Here we use index + 1 to include the separating whitespace. + if(declaration[..(typeNameSeparatorIndex + 1)].Contains("const ")) + throw new NotSupportedException("Const properties are not allowed to be registered!"); + if(declaration[..(typeNameSeparatorIndex + 1)].Contains("static ")) + throw new NotSupportedException("Static properties are not allowed to be registered!"); + // As there could be one or more inner whitespaces, we still have to trim again here. + string name = declaration[(typeNameSeparatorIndex + 1)..].Trim(); + string type = declaration[..typeNameSeparatorIndex].Trim(); + // Check if the property's type is a template, and extract the template type, so we can handle Ref and TypedArray. + // Note that for template types, the "type" variable now means the type used in the template. + string templateType = ""; + if(type.Contains('<')) { - // Strip the opening and closing parentheses - meta = meta.Substring(1, meta.Length - 2); + templateType = type[..type.IndexOf('<')].Trim(); + type = type[(type.IndexOf('<') + 1)..type.LastIndexOf('>')].Trim(); } + bool isRef = templateType == "Ref"; + if(isPointer && isRef) + throw new NotSupportedException("Pointer to Ref cannot be registered!"); + + string hintType = parameters.Content.GetValueOrDefault("REG_P_HintType", "PROPERTY_HINT_NONE"); + string hintString = parameters.Content.GetValueOrDefault("REG_P_HintString", "\"\""); + if(!hintString.StartsWith('"') && !hintString.EndsWith('"')) + throw new ArgumentException("The key REG_P_HintString expects a C++ string literal as its value."); + string usageFlags = parameters.Content.GetValueOrDefault("REG_P_UsageFlags", "PROPERTY_USAGE_DEFAULT"); + + PropertyExportFlags exportFlags = PropertyExportFlags.None; + if(templateType == "TypedArray") + { + // Note that the pointer could've been inside or outside TypedArray, hence the message here. + if (isPointer) + throw new NotSupportedException("Pointer to TypedArray or Array of pointers cannot be registered!"); + if(type.StartsWith("Ref<")) + throw new NotSupportedException("Array of Refs cannot be registered!"); + exportFlags |= PropertyExportFlags.Array; + } + // Note that Variant flag, Node flag, and Resource flag are mutually exclusive. This is made clear with the else-if. + if(parameters.Content.ContainsKey("REG_P_ExportAsNode")) + { + if(!isPointer && !exportFlags.HasFlag(PropertyExportFlags.Array)) + throw new NotSupportedException("Properties exported as nodes must be pointers."); + if(!hasDefaultValue && !exportFlags.HasFlag(PropertyExportFlags.Array)) + throw new Exception("Pointer properties without default values will crash the editor. Consider assigning nullptr to them."); + exportFlags |= PropertyExportFlags.Node; + } + else if(parameters.Content.ContainsKey("REG_P_ExportAsResource")) + { + if(!isRef && !exportFlags.HasFlag(PropertyExportFlags.Array)) + throw new NotSupportedException("Properties exported as resources must be Refs."); + exportFlags |= PropertyExportFlags.Resource; + } + else + exportFlags |= PropertyExportFlags.Variant; + + // Convert the bools isPointer and isRef into PropertyReferenceType as we've made sure they're mutually exclusive now. + PropertyReferenceType referenceType = PropertyReferenceType.Value; + if(isPointer) + referenceType = PropertyReferenceType.Pointer; + else if(isRef) + referenceType = PropertyReferenceType.Ref; + return new PropertyMacro() { Name = name, Type = type, - Meta = meta, + ReferenceType = referenceType, + ExportFlags = exportFlags, + HintType = hintType, + HintString = hintString, + UsageFlags = usageFlags, }; } } diff --git a/source/RegAutomation/RegAutomation/Database.cs b/source/RegAutomation/RegAutomation/Database.cs index 5b08fd3..db2a3b3 100644 --- a/source/RegAutomation/RegAutomation/Database.cs +++ b/source/RegAutomation/RegAutomation/Database.cs @@ -1,3 +1,4 @@ +using RegAutomation.Core; namespace RegAutomation { @@ -12,7 +13,11 @@ public class Func public class Prop { public string Type = ""; - public string Meta = ""; + public PropertyReferenceType ReferenceType = PropertyReferenceType.Value; + public PropertyExportFlags ExportFlags = PropertyExportFlags.None; + public string HintType = ""; + public string HintString = ""; + public string UsageFlags = ""; } public class Enum diff --git a/source/RegAutomation/RegAutomation/Pattern_Property.cs b/source/RegAutomation/RegAutomation/Pattern_Property.cs index b596ddd..10cf48a 100644 --- a/source/RegAutomation/RegAutomation/Pattern_Property.cs +++ b/source/RegAutomation/RegAutomation/Pattern_Property.cs @@ -15,44 +15,34 @@ public static void ProcessType(DB.Type type) type.Properties[macro.Name] = new DB.Prop() { Type = macro.Type, - Meta = macro.Meta, + ReferenceType = macro.ReferenceType, + ExportFlags = macro.ExportFlags, + HintType = macro.HintType, + HintString = macro.HintString, + UsageFlags = macro.UsageFlags, }; } } public static void GenerateBindings(DB.Type type, StringBuilder bindings, StringBuilder inject) { - string propertyBindings = ""; - string functionBindings = ""; + StringBuilder propertyBindings = new(); + StringBuilder functionBindings = new(); - foreach (var func in type.Properties) + foreach (var prop in type.Properties) { - string variant = func.Value.Type.ToUpper(); - if (variant == "") - { - Console.WriteLine("Unknown type: " + func.Value.Type); - continue; - } - - // Property bindings - propertyBindings += $"ClassDB::add_property(\"{type.Name}\", "; - string meta = func.Value.Meta == "" ? "" : $", {func.Value.Meta}"; - propertyBindings += $"PropertyInfo(Variant::{variant}, \"{func.Key}\"{meta}), "; - - var (get, set) = GetGetterSetter(variant, func.Key); - propertyBindings += $"\"{set}\", "; - propertyBindings += $"\"{get}\");\n\t"; - - // Function generation - inject.Append("\t" + func.Value.Type + " _gen_" + get + "() const { return " + func.Key + "; }\n"); - inject.Append("\tvoid _gen_" + set + "(" + func.Value.Type + " p) { " + func.Key + " = p; }\n"); - - // Function bindings - // The auto-generated C++ getters and setters still use the get_/set_ prefixes to avoid name collision. - functionBindings += $"ClassDB::bind_method(D_METHOD(\"{get}\"), "; - functionBindings += $"&{type.Name}::_gen_{get});\n\t"; - functionBindings += $"ClassDB::bind_method(D_METHOD(\"{set}\", \"p\"), "; - functionBindings += $"&{type.Name}::_gen_{set});\n\t"; + var(properties, functions, functionInjects) = PropertyBinder.GenerateBindings( + type.Name, + prop.Key, + prop.Value.Type, + prop.Value.ReferenceType, + prop.Value.ExportFlags, + prop.Value.HintType, + prop.Value.HintString, + prop.Value.UsageFlags); + propertyBindings.Append(properties); + functionBindings.Append(functions); + inject.Append(functionInjects); } bindings.Append(functionBindings); bindings.Append(propertyBindings); diff --git a/source/example_project/GDExample/gdexample.h b/source/example_project/GDExample/gdexample.h index cfa585b..bbeac3c 100644 --- a/source/example_project/GDExample/gdexample.h +++ b/source/example_project/GDExample/gdexample.h @@ -3,6 +3,7 @@ #include "example_project/registration.h" #include +#include namespace godot { @@ -33,7 +34,9 @@ namespace godot REG_FUNCTION() static double multiply(double x, double y); - REG_PROPERTY(REG_P_Info=(PROPERTY_HINT_RANGE, "0,20,0.01")) + REG_PROPERTY( + REG_P_HintType=PROPERTY_HINT_RANGE, + REG_P_HintString="0,20,0.01") float property = 0; REG_PROPERTY() @@ -44,6 +47,31 @@ namespace godot * (TEST COMMENT) */ bool angry; + + REG_PROPERTY( + REG_P_HintType=PROPERTY_HINT_FILE, + REG_P_HintString="*.png,*.webp,*.svg") + TypedArray image_filepath_array; + + REG_PROPERTY( + REG_P_HintType=PROPERTY_HINT_RANGE, + REG_P_HintString="0,1,0.001") + TypedArray float_array; + + REG_PROPERTY() + PackedInt32Array packed_int32_array; + + REG_PROPERTY(REG_P_ExportAsResource) + TypedArray image_array; + + REG_PROPERTY(REG_P_ExportAsResource) + Ref image_ref; + + REG_PROPERTY(REG_P_ExportAsNode) + TypedArray node_array; + + REG_PROPERTY(REG_P_ExportAsNode) + Node3D *node_pointer = nullptr; private: diff --git a/source/example_project/registration.h b/source/example_project/registration.h index 2275a36..dd57106 100644 --- a/source/example_project/registration.h +++ b/source/example_project/registration.h @@ -20,6 +20,31 @@ * float property = 0.0f; */ #define REG_PROPERTY(...) +enum REG_PROPERTY_PROPERTIES +{ + // [Type: PropertyHint] + // Specify the property's PropertyHint (default is PROPERTY_HINT_NONE). + // Automatically overridden if ExportAsNode/ExportAsResource is used. + // Can also be used on TypedArrays of variant types. + REG_P_HintType, + // [Type: String] + // Specify the hint string used by the property hint (default is empty string). + // Automatically overridden if the property is a TypedArray or if ExportAsNode/ExportAsResource is used. + // Can also be used on TypedArrays of variant types. + REG_P_HintString, + // [Type: PropertyUsageFlags] + // Specify the property's PropertyUsageFlags (default is PROPERTY_USAGE_DEFAULT). + REG_P_UsageFlags, + // [Type: None] + // Make it so you can expose pointers to classes that inherit from Node. Do not use together with ExportAsResource. + // Use with TypedArrays to export an array of nodes. + REG_P_ExportAsNode, + // [Type: None] + // Make it so you can expose Refs to classes that inherit from Resource. Do not use together with ExportAsNode. + // Use with TypedArrays to export an array of resources. + REG_P_ExportAsResource, + +}; /** * Usage: @@ -35,6 +60,7 @@ #define REG_ENUM(...) enum REG_ENUM_PROPERTIES { + // [Type: None] // If passed into REG_ENUM, interpret the enum as a Godot bitfield. REG_P_Bitfield, }; diff --git a/source/solution.sln b/source/solution.sln index e8103e4..41d286e 100644 --- a/source/solution.sln +++ b/source/solution.sln @@ -4,9 +4,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.7.34202.233 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_project", "example_project\example_project.vcxproj", "{426BA961-580F-4B6B-9D56-8BAD4AEF88CD}" + ProjectSection(ProjectDependencies) = postProject + {008A9867-2168-40B3-BC40-B1CD74E67877} = {008A9867-2168-40B3-BC40-B1CD74E67877} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectLauncher", "ProjectLauncher\ProjectLauncher.csproj", "{635C1291-3EBA-48B4-B9F5-801AE4FB78EC}" ProjectSection(ProjectDependencies) = postProject + {008A9867-2168-40B3-BC40-B1CD74E67877} = {008A9867-2168-40B3-BC40-B1CD74E67877} {426BA961-580F-4B6B-9D56-8BAD4AEF88CD} = {426BA961-580F-4B6B-9D56-8BAD4AEF88CD} EndProjectSection EndProject