Skip to content

Commit 7ab7cc1

Browse files
committed
Adding structs to Godot
Cleanup
1 parent ca77be8 commit 7ab7cc1

39 files changed

+903
-19
lines changed

core/variant/array.cpp

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,42 @@ STATIC_ASSERT_INCOMPLETE_TYPE(class, String);
3838
#include "core/math/math_funcs.h"
3939
#include "core/object/script_language.h"
4040
#include "core/templates/hashfuncs.h"
41-
#include "core/templates/vector.h"
4241
#include "core/variant/callable.h"
4342
#include "core/variant/dictionary.h"
43+
#include "core/variant/struct.h"
4444

4545
struct ArrayPrivate {
4646
SafeRefCount refcount;
4747
Vector<Variant> array;
4848
Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values.
4949
ContainerTypeValidate typed;
5050

51+
uint32_t struct_size = 0;
52+
StringName *struct_member_names = nullptr;
53+
5154
ArrayPrivate() {}
5255
ArrayPrivate(std::initializer_list<Variant> p_init) :
5356
array(p_init) {}
57+
58+
~ArrayPrivate() {
59+
if (struct_member_names) {
60+
memdelete_arr(struct_member_names);
61+
}
62+
}
63+
64+
_FORCE_INLINE_ bool is_struct() const { return struct_size > 0; }
65+
66+
_FORCE_INLINE_ int32_t find_member_index(const StringName &p_member) const {
67+
if (!struct_member_names) {
68+
return -1;
69+
}
70+
for (uint32_t i = 0; i < struct_size; i++) {
71+
if (p_member == struct_member_names[i]) {
72+
return (int32_t)i;
73+
}
74+
}
75+
return -1;
76+
}
5477
};
5578

5679
void Array::_ref(const Array &p_from) const {
@@ -949,6 +972,59 @@ Span<Variant> Array::span() const {
949972
return _p->array.span();
950973
}
951974

975+
bool Array::is_struct() const {
976+
return _p && _p->is_struct();
977+
}
978+
979+
Variant Array::get_named(const StringName &p_member) const {
980+
ERR_FAIL_COND_V(!_p->is_struct(), Variant());
981+
int32_t offset = _p->find_member_index(p_member);
982+
ERR_FAIL_COND_V(offset == -1, Variant());
983+
ERR_FAIL_INDEX_V(offset, _p->array.size(), Variant());
984+
return _p->array[offset];
985+
}
986+
987+
void Array::set_named(const StringName &p_member, const Variant &p_value) {
988+
ERR_FAIL_COND(!_p->is_struct());
989+
int32_t offset = _p->find_member_index(p_member);
990+
ERR_FAIL_COND(offset == -1);
991+
ERR_FAIL_INDEX(offset, _p->array.size());
992+
_p->array.write[offset] = p_value;
993+
}
994+
995+
void Array::set_as_struct(const Vector<StringName> &p_names, const Vector<Variant> &p_default_values) {
996+
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
997+
ERR_FAIL_COND_MSG(_p->array.size() > 0, "Type can only be set when array is empty.");
998+
ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when array has no more than one user.");
999+
ERR_FAIL_COND_MSG(_p->typed.type != Variant::NIL || is_struct(), "Type can only be set once.");
1000+
1001+
uint32_t p_member_count = p_names.size();
1002+
ERR_FAIL_COND_MSG(p_default_values.size() != p_member_count, "Struct member vectors must have the same size.");
1003+
1004+
_p->struct_size = p_member_count;
1005+
if (p_member_count > 0) {
1006+
_p->struct_member_names = memnew_arr(StringName, p_member_count);
1007+
_p->array.resize(p_member_count);
1008+
1009+
for (uint32_t i = 0; i < p_member_count; i++) {
1010+
_p->struct_member_names[i] = p_names[i];
1011+
_p->array.write[i] = p_default_values[i];
1012+
}
1013+
}
1014+
}
1015+
1016+
Variant Array::get_struct_member_by_offset(uint32_t p_offset) const {
1017+
ERR_FAIL_COND_V(!_p->is_struct(), Variant());
1018+
ERR_FAIL_UNSIGNED_INDEX_V(p_offset, (uint32_t)_p->array.size(), Variant());
1019+
return _p->array[p_offset];
1020+
}
1021+
1022+
void Array::set_struct_member_by_offset(uint32_t p_offset, const Variant &p_value) {
1023+
ERR_FAIL_COND(!_p->is_struct());
1024+
ERR_FAIL_UNSIGNED_INDEX(p_offset, (uint32_t)_p->array.size());
1025+
_p->array.write[p_offset] = p_value;
1026+
}
1027+
9521028
Array::Array(const Array &p_from) {
9531029
_p = nullptr;
9541030
_ref(p_from);
@@ -960,6 +1036,24 @@ Array::Array(std::initializer_list<Variant> p_init) {
9601036
_p->array = Vector<Variant>(p_init);
9611037
}
9621038

1039+
Array::Array(uint32_t p_member_count, const StructMember &(*p_get_member)(uint32_t)) {
1040+
_p = memnew(ArrayPrivate);
1041+
_p->refcount.init();
1042+
_p->struct_size = p_member_count;
1043+
_p->struct_member_names = memnew_arr(StringName, p_member_count);
1044+
_p->array.resize(p_member_count);
1045+
1046+
for (uint32_t i = 0; i < p_member_count; i++) {
1047+
const StructMember &m = p_get_member(i);
1048+
_p->struct_member_names[i] = m.name;
1049+
_p->array.write[i] = m.default_value;
1050+
}
1051+
}
1052+
1053+
Array::Array(uint32_t p_member_count, const StructMember &(*p_get_member)(uint32_t), const Array &p_from) : Array(p_member_count, p_get_member) {
1054+
assign(p_from);
1055+
}
1056+
9631057
Array::Array() {
9641058
_p = memnew(ArrayPrivate);
9651059
_p->refcount.init();

core/variant/array.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#pragma once
3232

3333
#include "core/templates/span.h"
34+
#include "core/templates/vector.h"
3435
#include "core/typedefs.h"
3536
#include "core/variant/variant_deep_duplicate.h"
3637

@@ -195,9 +196,20 @@ class Array {
195196
return this->span();
196197
}
197198

199+
bool is_struct() const;
200+
Variant get_named(const StringName &p_member) const;
201+
void set_named(const StringName &p_member, const Variant &p_value);
202+
203+
void set_as_struct(const Vector<StringName> &p_names, const Vector<Variant> &p_default_values);
204+
205+
Variant get_struct_member_by_offset(uint32_t p_offset) const;
206+
void set_struct_member_by_offset(uint32_t p_offset, const Variant &p_value);
207+
198208
Array(const Array &p_base, uint32_t p_type, const StringName &p_class_name, const Variant &p_script);
199209
Array(const Array &p_from);
200210
Array(std::initializer_list<Variant> p_init);
211+
Array(uint32_t p_member_count, const struct StructMember &(*p_get_member)(uint32_t));
212+
Array(uint32_t p_member_count, const struct StructMember &(*p_get_member)(uint32_t), const Array &p_from);
201213
Array();
202214
~Array();
203215
};

core/variant/struct.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**************************************************************************/
2+
/* struct.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#pragma once
32+
33+
#include "core/variant/array.h"
34+
#include "core/variant/variant.h"
35+
36+
struct StructMember {
37+
StringName name;
38+
StringName class_name;
39+
Variant::Type type;
40+
Variant default_value;
41+
Variant script;
42+
43+
StructMember(const StringName &p_name, Variant::Type p_type, const Variant &p_default_value = Variant(), const StringName &p_class_name = StringName(), const Variant &p_script = Variant()) {
44+
name = p_name;
45+
type = p_type;
46+
default_value = p_default_value;
47+
class_name = p_class_name;
48+
script = p_script;
49+
}
50+
};
51+
52+
#define STRUCT_MEMBER(m_name, m_type, ...) StructMember(SNAME(m_name), m_type, ##__VA_ARGS__)
53+
#define STRUCT_CLASS_MEMBER(m_name, m_class) StructMember(SNAME(m_name), Variant::OBJECT, Variant(), m_class)
54+
55+
#define STRUCT_LAYOUT(m_class, m_name, ...) \
56+
struct m_name { \
57+
_FORCE_INLINE_ static StringName get_class() { return SNAME(#m_class); } \
58+
_FORCE_INLINE_ static StringName get_name() { return SNAME(#m_name); } \
59+
_FORCE_INLINE_ static uint32_t get_member_count() { \
60+
static const StructMember members[] = { __VA_ARGS__ }; \
61+
return std_size(members); \
62+
} \
63+
_FORCE_INLINE_ static const StructMember *get_members() { \
64+
static const StructMember members[] = { __VA_ARGS__ }; \
65+
return members; \
66+
} \
67+
_FORCE_INLINE_ static const StructMember &get_member(uint32_t p_index) { \
68+
CRASH_BAD_INDEX(p_index, get_member_count()); \
69+
return get_members()[p_index]; \
70+
} \
71+
};
72+
73+
template <class T>
74+
class Struct : public Array {
75+
public:
76+
typedef T Layout;
77+
78+
_FORCE_INLINE_ void operator=(const Array &p_array) { Array::operator=(p_array); }
79+
_FORCE_INLINE_ Struct(const Variant &p_variant) : Array(T::get_member_count(), T::get_member, Array(p_variant)) {}
80+
_FORCE_INLINE_ Struct(const Array &p_array) : Array(T::get_member_count(), T::get_member, p_array) {}
81+
_FORCE_INLINE_ Struct() : Array(T::get_member_count(), T::get_member) {}
82+
};

core/variant/variant_setget.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,14 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool
258258
} else if (type == Variant::DICTIONARY) {
259259
Dictionary &dict = VariantInternalAccessor<Dictionary>::get(this);
260260
r_valid = dict.set(p_member, p_value);
261+
} else if (type == Variant::ARRAY) {
262+
Array &arr = VariantInternalAccessor<Array>::get(this);
263+
if (arr.is_struct()) {
264+
arr.set_named(p_member, p_value);
265+
r_valid = true;
266+
return;
267+
}
268+
r_valid = false;
261269
} else {
262270
r_valid = false;
263271
}
@@ -293,6 +301,19 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const {
293301
return *v;
294302
}
295303
} break;
304+
case Variant::ARRAY: {
305+
const Array &arr = VariantInternalAccessor<Array>::get(this);
306+
if (arr.is_struct()) {
307+
r_valid = true;
308+
return arr.get_named(p_member);
309+
}
310+
311+
// Fallback in case of a normal array.
312+
if (Variant::has_builtin_method(type, p_member)) {
313+
r_valid = true;
314+
return Callable(memnew(VariantCallable(*this, p_member)));
315+
}
316+
} break;
296317
default: {
297318
if (Variant::has_builtin_method(type, p_member)) {
298319
r_valid = true;

doc/classes/Array.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
[b]Note:[/b] Arrays are always passed by [b]reference[/b]. To get a copy of an array that can be modified independently of the original array, use [method duplicate].
3939
[b]Note:[/b] Erasing elements while iterating over arrays is [b]not[/b] supported and will result in unpredictable behavior.
4040
[b]Note:[/b] In a boolean context, an array will evaluate to [code]false[/code] if it's empty ([code][][/code]). Otherwise, an array will always evaluate to [code]true[/code].
41+
[b]Note:[/b] [GDScript] structs are implemented as typed arrays under the hood. While they offer property access and code completion, they share the performance and memory characteristics of typed arrays.
4142
[b]Differences between packed arrays, typed arrays, and untyped arrays:[/b] Packed arrays are generally faster to iterate on and modify compared to a typed array of the same type (e.g. [PackedInt64Array] versus [code]Array[int][/code]). Also, packed arrays consume less memory. As a downside, packed arrays are less flexible as they don't offer as many convenience methods such as [method Array.map]. Typed arrays are in turn faster to iterate on and modify than untyped arrays.
4243
</description>
4344
<tutorials>

doc/classes/Dictionary.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169
[b]Note:[/b] Dictionaries are always passed by reference. To get a copy of a dictionary which can be modified independently of the original dictionary, use [method duplicate].
170170
[b]Note:[/b] Erasing elements while iterating over dictionaries is [b]not[/b] supported and will result in unpredictable behavior.
171171
[b]Note:[/b] In a boolean context, a dictionary will evaluate to [code]false[/code] if it's empty ([code]{}[/code]). Otherwise, a dictionary will always evaluate to [code]true[/code].
172+
[b]Note:[/b] If you require a fixed-schema data container that provides code completion and better type safety, consider using a GDScript struct instead of a [Dictionary].
172173
</description>
173174
<tutorials>
174175
<link title="GDScript basics: Dictionary">$DOCS_URL/tutorials/scripting/gdscript/gdscript_basics.html#dictionary</link>

modules/gdscript/editor/gdscript_docgen.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
131131
}
132132
r_type = _get_class_name(*p_gdtype.class_type);
133133
return;
134+
case GDType::STRUCT:
135+
r_type = p_gdtype.native_type;
136+
return;
134137
case GDType::ENUM:
135138
if (p_gdtype.is_meta_type) {
136139
r_type = "Dictionary";

modules/gdscript/gdscript.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2617,6 +2617,7 @@ Vector<String> GDScriptLanguage::get_reserved_words() const {
26172617
"namespace", // Reserved for potential future use.
26182618
"signal",
26192619
"static",
2620+
"struct",
26202621
"trait", // Reserved for potential future use.
26212622
"var",
26222623
// Other keywords.

modules/gdscript/gdscript.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,25 @@ class GDScriptNativeClass : public RefCounted {
5757
GDScriptNativeClass(const StringName &p_name);
5858
};
5959

60+
class GDScriptStruct : public RefCounted {
61+
GDCLASS(GDScriptStruct, RefCounted);
62+
63+
protected:
64+
static void _bind_methods() {}
65+
66+
public:
67+
StringName name;
68+
69+
struct Field {
70+
StringName name;
71+
GDScriptDataType data_type;
72+
Variant default_value;
73+
};
74+
75+
Vector<Field> fields;
76+
GDScriptStruct() {}
77+
};
78+
6079
class GDScript : public Script {
6180
GDCLASS(GDScript, Script);
6281
bool tool = false;

0 commit comments

Comments
 (0)