Replies: 8 comments 14 replies
-
Does that mean structs would be Dictionaries internally? The struct's fields are known, so storing the keys in each instance would be a waste. |
Beta Was this translation helpful? Give feedback.
-
I personally see no point in keeping a Struct compatible to a Dictionary. They should be convertible between each other considering the existing systems, but not more than that.
Why? There's really no use case for this and no one would expect it to work this way. I assume this is because of compatibility with existing API (so dictionaries can be replaced with structs) but this is not necessary. The current API constructs dictionaries on the fly anyway (for things like PropertyInfo), so nobody is keeping a reference to that, so they don't need reference equality.
Same as above, why do we need something like this?
This is reasonable for compatibility. However, how can you have an "empty struct"? Even if you not set any field, they are still there. We'll probably need a different way of communicating a missed raycast, which will break compatibility anyway. I see two problems here. Some Dictionary methods may not make sense (like
Seems like you're promising a runtime validation of the struct interface. I don't see how this is feasible (you would have to check each key/field to make sure they match in type and in name). Also, casting objects to structs seems a bit too much. There's not really any use case for this, it just make the system more complex since you need to validate that they match in structure. And you cannot (or should not) treat them as the same reference, that would be even more chaos. Anyone who needs this type of conversion can do so manually. Dictionaries and structs should be different types. So asking if Overall, I think this is trying too much in making things compatible to the point of almost making it a worse option. If we're going to have all that trouble it would be easier to create "dictionary interfaces" and call it that instead of "structs". |
Beta Was this translation helpful? Give feedback.
-
Adding this in a separate comment to split the discussion.
This sounds very similar to current PR on structs godotengine/godot#82198. Is there anything different or are you implementing a similar thing?
My response to this is similar to my other comment: why do we need to have this? Are people really getting a dictionary from the API and adding their own keys to pass it around? Even if they are, they should still be able to convert the struct into a dictionary and pass it around, no need to keep referential equality between different types. Note that the current work on structs is stalled mostly because the system is too complex. It seems here you are proposing something even more complex, so it's quite difficult to accept as it is. |
Beta Was this translation helpful? Give feedback.
-
Small comment on this, i only use Resources if i intend saving and loading from disk, otherwise i use RefCounted, which are just a little smaller |
Beta Was this translation helpful? Give feedback.
-
A very strange situation we've found ourselves in if a Dictionary, of all things, is considered faster than using a custom Class.
This isn't why people typically want to use structs. To me, there's one very important use case: I want a function to return short-lived, structured information from a query, such as an OS or Physics query, without having to deal with all the usual costs associated with memory allocation, hashing, etc. The calling function gets the information it needs from the result, and then it gets cleaned up along with the stack. If the data needs to live longer, it can be "boxed" up inside a class object where it would live on the heap.
That's not how structs work in C#. Structs are passed by value there, as if they live on the stack (though this is more of an implementation detail in the CLR, which uses a garbage-collected memory model). And some form of stack-based value is necessary if we want to fix intersect_ray() and friends and have it perform well without needing to reuse an existing result object by passing as an in/out parameter.
What's the use case here? If it's for compatibility, then we'd already break compatibility by changing the return type of the function. Better to make a new function called It seems like most of this proposal is based around making said breaking changes to the existing API, and that's not how change control typically works with this stuff. |
Beta Was this translation helpful? Give feedback.
-
I believe the reason for easily casting structs from and to dictionary is for backward compatibility with all the methods we already have that takes or returns dictionary or arrays of dictionaries like get_property_list, wich is screaming to use a struct PropertyInfo instead About passing structs as value instead of reference, i believe that is doable but the coast in memory allocation every time you pass the value around would be too high for no gain, since unlike other Variants, structs would not be 64bits (this is why arrays and dictionaries works as pointers to memory allocated in the heap, and accessed from the variant) The way the feature is described, structs are just a wrapper to access the dictionaries we already have, but seems to be more than sugar sintax I expected structs to be sugar sintax for dictionaries, just like enums (possibly forbidding to add functions to the struct, this is probably fine since they are meant to be just data containers) |
Beta Was this translation helpful? Give feedback.
-
For those following this discussion, please know I have submitted a draft PR proof-of-concept. This implementation does not assume the Dictionary-compatability approach (which is the basis of this discussion), but it does make use of the underlying struct-definition approach I have been working on. If nothing else, I would like to know if such an approach is acceptable (even if Dictionary-compatability is decided against). |
Beta Was this translation helpful? Give feedback.
-
I have a question: would the proposal allow structs to be @export ? Make it possible to edit values of struct members in the editor (even nested struct ?) and to serialize them when part of a resource ? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Structs Proposal
Central Philosphy
This proposal for Structs in Godot is taken with the following central philosophy in mind:
"Because we don't have Structs, we have been forced to make do"
What I mean by this is: Structs do not exist in Godot, and because they do not exist, much of Godot has been built around using Dictionaries and Resources instead. Everything from in-built engine functionality, through to the games themselves. However, Dictionaries lack a method for specifying a predetermined structure, which means losing out on safety and usability. As a result, many people opt to use Resources instead, however this runs into another problem that is: performance and memory. Which means gamedevs have been forced to choose between performance (by using a Dictionary) or ease-of-use and confidence of typing (by using a Resource) due to the lack of Structs in Godot.
It is my view that any implementation of Structs in Godot needs to respond to this and solve these problems. Which gives us some faily basic requirements to start from. Any implementation of Struct in Godot would need to do the following things:
Sounds pretty hard, right? How can one thing do all of that? Well, as you will see below, I have an idea I think will work...
What is required and expected of Structs in Godot?
As a starting place, I would like to, as much as possible, be able to replace the paramaters and returns for many in-built functions with Structs (with the added challenge of breaking as few projects as possible). This gives us a lot of specific requirements and guidance that can actually help us narrow down the possibilities and questions on how Structs should be implemented in Godot. In order to maintain compatability:
This presents a bit of a problem. If part but not all of a project expects Structs and other parts Dictionaries, then wouldn't that mean that types are being changed, and wouldn't that break references or cause problems with un-typed variables?
My solution is that we need to therefore decide on the following set of requirements:
x:int
, then I perform the operationstruct.x = 4
, thendict["x"] == 4
should be true.)operator[]
on a Struct should still be able to read-write the key-values of the HashMap in the original Dictionary (even if they aren't present as members of the Struct). Meanwhile, operations involving.subscript
members on the Struct should be guaranteed to match the types specified by the Struct definition..is_empty() == true
and.size() == 0
).How do we do that? Well, we need to start by agreeing on a defition of what a Struct type really is. And in the implementation proposed here, that would be that a Struct type is a "signature" or minimum set of requirements. That is, anything that has properties with those names and matching types will satisfy the signature, and so for all intents and purposes is an instance of that StructType. Which means the following:
value is StructType
returnstrue
if (at the time of execution) every property defined byStructType
can be accessed onvalue
using.subscript
syntax, and that they have the same types as those defined byStructType
.var my_struct: StructType = some_value
was performed, andsome_value is StructType
would have evaluated tofalse
at time of execution, then the execution of any expression to get or setmy_struct.property
should result in an error.var my_struct: StructType
contains the value ofsome_value
, the execution of any expression to get or setmy_struct.property
should result in an error if the value ofmy_struct.property
does not match the type specified by theStructType
signature, and otherwise should be no different than ifsome_value.property
was executed.So, basically, they enforce behaviour around
get_named
andset_named
. Given GDScript uses ducktyping, this approach actually solves a lot of other potential problems we might have with different Struct solutions as well!if target is StructType
will cause the editor to give you code completion results for that Struct with.subscript
expressions, without any changing of the actual data within that Dictionary! And because it knows the types of those members, the editor can even give you code completition results for them too! This gives us all of the advantages of something like a Resource, but remains relatively lightweight like a Dictionary!.subscript
operations on the Struct will ensure that the signature of the Struct does not break. That is, the editor should be able to check the types of the expressions for assignments, flagging any instances where values cannot be implictly cast, and potentially making these lines safe operations now! Types can also be checked during execution as well!Sounds like you're describing Interfaces?
Well, I kind of am. But that's mainly to explain the reasoning behind the choices I've made. Because we need to maintain compatability.
At the end of the day, these struct definitions can actually be used to instantiate lightweight structs at run-time! Which means structs can be instanced in GDScript, and GDScript can make use of tightly-packed strongly-typed sets of data through Variant. These operate nearly identically to structs defined natively in c++, and can even make use of their constructors!
And I actually have a working prototype for this!
Basically, the definition of any struct defined in c++ can be easily defined and referenced using macros. Then these are wrapped in a container that allocates memory for that struct, and points to it along with the definition. The definition contains the names and types of each property, and specifies what functions to use when accessing them.
As a bonus, this is tightly integrated with Dictionary, allowing for
operator[]
executions on the Dictionary to prioritise and access the tightly-packed struct data. Meaning that structs can be effectively soft-cast to Dictionary, without the performance cost of actually applying the properties to the Dictionary as key-values! (And meanwhile, the Dictionary can also be used to house any additional key-values, which gamedevs might be in the habit of doing with the return of in-built funcitons that currently use Dictionary.)So what's next?
Well, that's why I'm here.
I feel like this implementation is really promising, and I want to make sure that I get it to completion while working with the community and the Godot team.
And as a part of that, I really want to make sure the behaviour and syntax makes sense and works as expected.
What about X?
How can Structs be used and what's the syntax?
Notes:
: Variant
.[] vs {}
It has been suggested in other discussion to swap my use of
[ ]
above{ }
. I like that idea, and think it might be useful to better distinguish these from Dictionaries. I leave the decision ultimately up to the community.When should two structs be considered equal?
Given that Godot still relies heavily on ducktyping, and that by this approach Structs are enforced signatures, it is my opinion that comparisons on structs should mirror the resulting operations on their underlying types. Except in cases of comparisons to any rvalue structs.
Beta Was this translation helpful? Give feedback.
All reactions