Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/object/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ enum PropertyHint {
PROPERTY_HINT_INPUT_NAME,
PROPERTY_HINT_FILE_PATH,
PROPERTY_HINT_MAX,
PROPERTY_HINT_STRUCT, // The hint string would be like "script_path::struct_name"
};

enum PropertyUsageFlags {
Expand Down
99 changes: 98 additions & 1 deletion core/variant/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,42 @@ STATIC_ASSERT_INCOMPLETE_TYPE(class, String);
#include "core/math/math_funcs.h"
#include "core/object/script_language.h"
#include "core/templates/hashfuncs.h"
#include "core/templates/vector.h"
#include "core/variant/callable.h"
#include "core/variant/dictionary.h"
#include "core/variant/struct.h"

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

uint32_t struct_size = 0;
StringName *struct_member_names = nullptr;

ArrayPrivate() {}
ArrayPrivate(std::initializer_list<Variant> p_init) :
array(p_init) {}

~ArrayPrivate() {
if (struct_member_names) {
memdelete_arr(struct_member_names);
}
}

_FORCE_INLINE_ bool is_struct() const { return struct_size > 0; }

_FORCE_INLINE_ int32_t find_member_index(const StringName &p_member) const {
if (!struct_member_names) {
return -1;
}
for (uint32_t i = 0; i < struct_size; i++) {
if (p_member == struct_member_names[i]) {
return (int32_t)i;
}
}
return -1;
}
};

void Array::_ref(const Array &p_from) const {
Expand Down Expand Up @@ -949,6 +972,62 @@ Span<Variant> Array::span() const {
return _p->array.span();
}

bool Array::is_struct() const {
return _p && _p->is_struct();
}

Variant Array::get_named(const StringName &p_member) const {
ERR_FAIL_COND_V(!_p->is_struct(), Variant());
int32_t offset = _p->find_member_index(p_member);
ERR_FAIL_COND_V(offset == -1, Variant());
ERR_FAIL_INDEX_V(offset, _p->array.size(), Variant());
return _p->array[offset];
}

void Array::set_named(const StringName &p_member, const Variant &p_value) {
ERR_FAIL_COND(!_p->is_struct());
int32_t offset = _p->find_member_index(p_member);
ERR_FAIL_COND(offset == -1);
ERR_FAIL_INDEX(offset, _p->array.size());
_p->array.write[offset] = p_value;
}

void Array::set_as_struct(const Vector<StringName> &p_names, const Vector<Variant> &p_default_values) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
//ERR_FAIL_COND_MSG(_p->array.size() > 0, "Type can only be set when array is empty.");
ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when array has no more than one user.");
ERR_FAIL_COND_MSG(_p->typed.type != Variant::NIL || is_struct(), "Type can only be set once.");

uint32_t p_member_count = p_names.size();
ERR_FAIL_COND_MSG(p_default_values.size() != p_member_count, "Struct member vectors must have the same size.");

// Added this, and commented out the line above as a temporary fix for the exporting.
_p->array.clear();

_p->struct_size = p_member_count;
if (p_member_count > 0) {
_p->struct_member_names = memnew_arr(StringName, p_member_count);
_p->array.resize(p_member_count);

for (uint32_t i = 0; i < p_member_count; i++) {
_p->struct_member_names[i] = p_names[i];
_p->array.write[i] = p_default_values[i];
}
}
}

Variant Array::get_struct_member_by_offset(uint32_t p_offset) const {
ERR_FAIL_COND_V(!_p->is_struct(), Variant());
ERR_FAIL_UNSIGNED_INDEX_V(p_offset, (uint32_t)_p->array.size(), Variant());
return _p->array[p_offset];
}

void Array::set_struct_member_by_offset(uint32_t p_offset, const Variant &p_value) {
ERR_FAIL_COND(!_p->is_struct());
ERR_FAIL_UNSIGNED_INDEX(p_offset, (uint32_t)_p->array.size());
_p->array.write[p_offset] = p_value;
}

Array::Array(const Array &p_from) {
_p = nullptr;
_ref(p_from);
Expand All @@ -960,6 +1039,24 @@ Array::Array(std::initializer_list<Variant> p_init) {
_p->array = Vector<Variant>(p_init);
}

Array::Array(uint32_t p_member_count, const StructMember &(*p_get_member)(uint32_t)) {
_p = memnew(ArrayPrivate);
_p->refcount.init();
_p->struct_size = p_member_count;
_p->struct_member_names = memnew_arr(StringName, p_member_count);
_p->array.resize(p_member_count);

for (uint32_t i = 0; i < p_member_count; i++) {
const StructMember &m = p_get_member(i);
_p->struct_member_names[i] = m.name;
_p->array.write[i] = m.default_value;
}
}

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) {
assign(p_from);
}

Array::Array() {
_p = memnew(ArrayPrivate);
_p->refcount.init();
Expand Down
12 changes: 12 additions & 0 deletions core/variant/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#pragma once

#include "core/templates/span.h"
#include "core/templates/vector.h"
#include "core/typedefs.h"
#include "core/variant/variant_deep_duplicate.h"

Expand Down Expand Up @@ -195,9 +196,20 @@ class Array {
return this->span();
}

bool is_struct() const;
Variant get_named(const StringName &p_member) const;
void set_named(const StringName &p_member, const Variant &p_value);

void set_as_struct(const Vector<StringName> &p_names, const Vector<Variant> &p_default_values);

Variant get_struct_member_by_offset(uint32_t p_offset) const;
void set_struct_member_by_offset(uint32_t p_offset, const Variant &p_value);

Array(const Array &p_base, uint32_t p_type, const StringName &p_class_name, const Variant &p_script);
Array(const Array &p_from);
Array(std::initializer_list<Variant> p_init);
Array(uint32_t p_member_count, const struct StructMember &(*p_get_member)(uint32_t));
Array(uint32_t p_member_count, const struct StructMember &(*p_get_member)(uint32_t), const Array &p_from);
Array();
~Array();
};
82 changes: 82 additions & 0 deletions core/variant/struct.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**************************************************************************/
/* struct.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#pragma once

#include "core/variant/array.h"
#include "core/variant/variant.h"

struct StructMember {
StringName name;
StringName class_name;
Variant::Type type;
Variant default_value;
Variant script;

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()) {
name = p_name;
type = p_type;
default_value = p_default_value;
class_name = p_class_name;
script = p_script;
}
};

#define STRUCT_MEMBER(m_name, m_type, ...) StructMember(SNAME(m_name), m_type, ##__VA_ARGS__)
#define STRUCT_CLASS_MEMBER(m_name, m_class) StructMember(SNAME(m_name), Variant::OBJECT, Variant(), m_class)

#define STRUCT_LAYOUT(m_class, m_name, ...) \
struct m_name { \
_FORCE_INLINE_ static StringName get_class() { return SNAME(#m_class); } \
_FORCE_INLINE_ static StringName get_name() { return SNAME(#m_name); } \
_FORCE_INLINE_ static uint32_t get_member_count() { \
static const StructMember members[] = { __VA_ARGS__ }; \
return std_size(members); \
} \
_FORCE_INLINE_ static const StructMember *get_members() { \
static const StructMember members[] = { __VA_ARGS__ }; \
return members; \
} \
_FORCE_INLINE_ static const StructMember &get_member(uint32_t p_index) { \
CRASH_BAD_INDEX(p_index, get_member_count()); \
return get_members()[p_index]; \
} \
};

template <class T>
class Struct : public Array {
public:
typedef T Layout;

_FORCE_INLINE_ void operator=(const Array &p_array) { Array::operator=(p_array); }
_FORCE_INLINE_ Struct(const Variant &p_variant) : Array(T::get_member_count(), T::get_member, Array(p_variant)) {}
_FORCE_INLINE_ Struct(const Array &p_array) : Array(T::get_member_count(), T::get_member, p_array) {}
_FORCE_INLINE_ Struct() : Array(T::get_member_count(), T::get_member) {}
};
21 changes: 21 additions & 0 deletions core/variant/variant_setget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool
} else if (type == Variant::DICTIONARY) {
Dictionary &dict = VariantInternalAccessor<Dictionary>::get(this);
r_valid = dict.set(p_member, p_value);
} else if (type == Variant::ARRAY) {
Array &arr = VariantInternalAccessor<Array>::get(this);
if (arr.is_struct()) {
arr.set_named(p_member, p_value);
r_valid = true;
return;
}
r_valid = false;
} else {
r_valid = false;
}
Expand Down Expand Up @@ -293,6 +301,19 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const {
return *v;
}
} break;
case Variant::ARRAY: {
const Array &arr = VariantInternalAccessor<Array>::get(this);
if (arr.is_struct()) {
r_valid = true;
return arr.get_named(p_member);
}

// Fallback in case of a normal array.
if (Variant::has_builtin_method(type, p_member)) {
r_valid = true;
return Callable(memnew(VariantCallable(*this, p_member)));
}
} break;
default: {
if (Variant::has_builtin_method(type, p_member)) {
r_valid = true;
Expand Down
1 change: 1 addition & 0 deletions doc/classes/Array.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
[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].
[b]Note:[/b] Erasing elements while iterating over arrays is [b]not[/b] supported and will result in unpredictable behavior.
[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].
[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.
[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.
</description>
<tutorials>
Expand Down
1 change: 1 addition & 0 deletions doc/classes/Dictionary.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
[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].
[b]Note:[/b] Erasing elements while iterating over dictionaries is [b]not[/b] supported and will result in unpredictable behavior.
[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].
[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].
</description>
<tutorials>
<link title="GDScript basics: Dictionary">$DOCS_URL/tutorials/scripting/gdscript/gdscript_basics.html#dictionary</link>
Expand Down
5 changes: 5 additions & 0 deletions editor/inspector/editor_properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4253,6 +4253,11 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
}
} break;
case Variant::ARRAY: {
if (p_hint == PROPERTY_HINT_STRUCT) {
EditorPropertyStruct *editor = memnew(EditorPropertyStruct);
editor->setup(p_hint_text);
return editor;
}
EditorPropertyArray *editor = memnew(EditorPropertyArray);
editor->setup(Variant::ARRAY, p_hint_text);
return editor;
Expand Down
Loading
Loading