-
Notifications
You must be signed in to change notification settings - Fork 311
Implement Structs in GDScript #1152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 9 commits
412d1f9
907dedb
5aa9cc4
2cd8d99
f027c2a
7f43c0f
126783a
6ea6d00
01a4c05
ef89c7f
ed04cb2
4c99518
d540e6d
259393e
072ced0
8c4de69
433113f
1f1bb67
58e756b
6b2239e
ead222f
97bdc7b
f816969
88a80e9
fd1ad97
ae725fb
b89e06b
65568c3
bfb7dc8
0fe2915
61d1aa6
9f3a822
9cf04bf
991fdad
22bc867
8b9aa20
3dfe741
af0b101
358ef84
3c0446f
9a2e1bf
2190c83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -390,3 +390,4 @@ $RECYCLE.BIN/ | |
| *.msp | ||
| *.lnk | ||
| *.generated.props | ||
| .gdbinit | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,9 @@ | |
| #include "core/templates/pair.h" | ||
| #include "core/version.h" | ||
|
|
||
| // Forward declaration for struct type | ||
| class GDScriptStructInstance; | ||
|
|
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about this methodology |
||
| #ifdef TOOLS_ENABLED | ||
| #include "editor/doc/editor_help.h" | ||
|
|
||
|
|
@@ -210,6 +213,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { | |
| { Variant::NODE_PATH, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 }, | ||
| { Variant::RID, sizeof(uint64_t), sizeof(uint64_t), sizeof(uint64_t), sizeof(uint64_t) }, | ||
| { Variant::OBJECT, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 }, | ||
| { Variant::STRUCT, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 }, | ||
| { Variant::CALLABLE, sizeof(Callable), sizeof(Callable), sizeof(Callable), sizeof(Callable) }, // Hardcoded align. | ||
| { Variant::SIGNAL, sizeof(Signal), sizeof(Signal), sizeof(Signal), sizeof(Signal) }, // Hardcoded align. | ||
| { Variant::DICTIONARY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 }, | ||
|
|
@@ -252,6 +256,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { | |
| static_assert(type_size_array[Variant::NODE_PATH][sizeof(void *)] == sizeof(NodePath), "Size of NodePath mismatch"); | ||
| static_assert(type_size_array[Variant::RID][sizeof(void *)] == sizeof(RID), "Size of RID mismatch"); | ||
| static_assert(type_size_array[Variant::OBJECT][sizeof(void *)] == sizeof(Object *), "Size of Object mismatch"); | ||
| static_assert(type_size_array[Variant::STRUCT][sizeof(void *)] == sizeof(GDScriptStructInstance *), "Size of Struct mismatch"); | ||
| static_assert(type_size_array[Variant::CALLABLE][sizeof(void *)] == sizeof(Callable), "Size of Callable mismatch"); | ||
| static_assert(type_size_array[Variant::SIGNAL][sizeof(void *)] == sizeof(Signal), "Size of Signal mismatch"); | ||
| static_assert(type_size_array[Variant::DICTIONARY][sizeof(void *)] == sizeof(Dictionary), "Size of Dictionary mismatch"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,38 @@ | |
| #include "core/config/engine.h" | ||
| #include "core/object/script_language.h" | ||
| #include "core/variant/container_type_validate.h" | ||
| #include "core/variant/variant_internal.h" | ||
|
|
||
| // GDScript struct serialization support | ||
| // Guarded with MODULE_GDSCRIPT_ENABLED since core/io can't depend on modules/gdscript | ||
| #ifdef MODULE_GDSCRIPT_ENABLED | ||
| // Forward declaration for GDScript struct serialization | ||
| class GDScriptStructInstance; | ||
| class GDScriptStruct; | ||
|
|
||
| // We need to declare these methods without including the GDScript headers | ||
| // This is a bit of a hack, but necessary due to layering (core/io can't include modules/gdscript) | ||
| // In the future, this should be replaced with a proper Variant-level API | ||
|
mcdubhghlas marked this conversation as resolved.
Outdated
|
||
|
|
||
| extern "C" { | ||
| // These will be defined in gdscript.cpp with C linkage to avoid name mangling issues | ||
| Dictionary gdscript_struct_instance_serialize(const GDScriptStructInstance *p_instance); | ||
| bool gdscript_struct_instance_get_type_name(const GDScriptStructInstance *p_instance, String &r_name); | ||
| } | ||
|
|
||
| // Helper function to serialize a struct | ||
| static Dictionary _serialize_struct(const GDScriptStructInstance *p_instance) { | ||
| ERR_FAIL_NULL_V(p_instance, Dictionary()); | ||
|
|
||
| // Call the GDScript helper function | ||
| return gdscript_struct_instance_serialize(p_instance); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| #else | ||
| // Stub implementation when GDScript module is disabled | ||
| static Dictionary _serialize_struct(const void *p_instance) { | ||
| ERR_FAIL_V_MSG(Dictionary(), "STRUCT serialization requires MODULE_GDSCRIPT_ENABLED."); | ||
| } | ||
| #endif // MODULE_GDSCRIPT_ENABLED | ||
|
|
||
| const char *JSON::tk_name[TK_MAX] = { | ||
| "'{'", | ||
|
|
@@ -824,6 +856,50 @@ Variant JSON::_from_native(const Variant &p_variant, bool p_full_objects, int p_ | |
| return ret; | ||
| } break; | ||
|
|
||
| #ifdef MODULE_GDSCRIPT_ENABLED | ||
| case Variant::STRUCT: { | ||
| // Serialize struct as tagged dictionary with __type__ metadata | ||
| // This allows round-trip deserialization | ||
| const GDScriptStructInstance *struct_instance = reinterpret_cast<const GDScriptStructInstance *>(VariantInternal::get_struct(&p_variant)); | ||
| ERR_FAIL_NULL_V(struct_instance, Variant()); | ||
|
|
||
| Dictionary ret; | ||
| ret[TYPE] = Variant::get_type_name(p_variant.get_type()); | ||
|
|
||
| // Get the serialized data from the struct instance using helper | ||
| Dictionary struct_data = _serialize_struct(struct_instance); | ||
|
|
||
| // Ensure __type__ field exists (it should from serialize()) | ||
| if (!struct_data.has("__type__")) { | ||
| ERR_FAIL_V_MSG(Variant(), "Struct serialization failed: missing __type__ field."); | ||
| } | ||
|
|
||
| // Encode field values using _from_native to ensure proper JSON encoding | ||
| // This handles non-JSON-native types (nested structs, objects, typed arrays, etc.) | ||
| Dictionary encoded_struct_data; | ||
| encoded_struct_data["__type__"] = struct_data["__type__"]; // Copy type identifier as-is | ||
|
|
||
| for (const KeyValue<Variant, Variant> &kv : struct_data) { | ||
| if (kv.key == "__type__") { | ||
| continue; // Already copied above | ||
| } | ||
| // Encode the value using _from_native, keys are strings (field names) and don't need encoding | ||
| encoded_struct_data[kv.key] = _from_native(kv.value, p_full_objects, p_depth + 1); | ||
| } | ||
|
|
||
| // Wrap the encoded serialized data in the expected format | ||
| Array args; | ||
| args.push_back(encoded_struct_data); | ||
| ret[ARGS] = args; | ||
|
|
||
| return ret; | ||
| } break; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| #else | ||
| case Variant::STRUCT: { | ||
| ERR_FAIL_V_MSG(Variant(), "STRUCT serialization requires MODULE_GDSCRIPT_ENABLED."); | ||
| } break; | ||
| #endif // MODULE_GDSCRIPT_ENABLED | ||
|
|
||
| case Variant::DICTIONARY: { | ||
| const Dictionary dict = p_variant; | ||
|
|
||
|
|
@@ -1297,6 +1373,44 @@ Variant JSON::_to_native(const Variant &p_json, bool p_allow_objects, int p_dept | |
| // Nothing to do at this stage. `Object` should be treated as a class, not as a built-in type. | ||
| } break; | ||
|
|
||
| #ifdef MODULE_GDSCRIPT_ENABLED | ||
| case Variant::STRUCT: { | ||
| // Deserialize struct from tagged dictionary | ||
| LOAD_ARGS(); | ||
|
|
||
| ERR_FAIL_COND_V_MSG(args.size() != 1, Variant(), "Invalid struct data: expected single dictionary argument."); | ||
|
|
||
| Dictionary encoded_data = args[0]; | ||
| ERR_FAIL_COND_V_MSG(!encoded_data.has("__type__"), Variant(), "Invalid struct data: missing __type__ field."); | ||
|
|
||
| String type_name = encoded_data["__type__"]; | ||
| ERR_FAIL_COND_V_MSG(type_name.is_empty(), Variant(), "Invalid struct data: empty __type__ field."); | ||
|
|
||
| // Decode field values using _to_native to properly reconstruct non-JSON-native types | ||
| // This handles nested structs, objects, typed arrays, etc. | ||
| Dictionary decoded_struct_data; | ||
| decoded_struct_data["__type__"] = encoded_data["__type__"]; // Copy type identifier as-is | ||
|
|
||
| for (const KeyValue<Variant, Variant> &kv : encoded_data) { | ||
| if (kv.key == "__type__") { | ||
| continue; // Already copied above | ||
| } | ||
| // Decode the value using _to_native, keys are strings (field names) and don't need decoding | ||
| decoded_struct_data[kv.key] = _to_native(kv.value, p_allow_objects, p_depth + 1); | ||
| } | ||
|
|
||
| // Use ScriptServer to create the struct instance with decoded data | ||
| Variant struct_instance = ScriptServer::create_struct_instance(type_name, decoded_struct_data); | ||
| ERR_FAIL_COND_V_MSG(struct_instance.get_type() != Variant::STRUCT, Variant(), vformat("Failed to create struct instance for type '%s'.", type_name)); | ||
|
|
||
| return struct_instance; | ||
| } break; | ||
| #else | ||
| case Variant::STRUCT: { | ||
| ERR_FAIL_V_MSG(Variant(), "STRUCT deserialization requires MODULE_GDSCRIPT_ENABLED."); | ||
| } break; | ||
| #endif // MODULE_GDSCRIPT_ENABLED | ||
|
|
||
|
Comment on lines
+1380
to
+1416
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harden decoding: validate Right now Targeted hardening LOAD_ARGS();
ERR_FAIL_COND_V_MSG(args.size() != 1, Variant(), "Invalid struct data: expected single dictionary argument.");
- Dictionary encoded_data = args[0];
+ ERR_FAIL_COND_V_MSG(((Variant)args[0]).get_type() != Variant::DICTIONARY, Variant(), "Invalid struct data: expected Dictionary argument.");
+ Dictionary encoded_data = args[0];
ERR_FAIL_COND_V_MSG(!encoded_data.has("__type__"), Variant(), "Invalid struct data: missing __type__ field.");🤖 Prompt for AI Agents |
||
| case Variant::DICTIONARY: { | ||
| LOAD_ARGS_CHECK_FACTOR(2); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.