Open lilizoey opened 1 year ago
I've been checking the vectors and these is what I've found so far:
Vector2
and Vector3
have little or no documentation on their methods.Vector2
's methods appears to be complete (there also was a TODO note of slerp
to compare with GDNative).Vector3
is lacking slerp
.Vector2i
, Vector3i
, Vector4
, and Vector4i
are lacking most of their methods.im working on GodotString
and StringName
.
im working on
GodotString
andStringName
.
I've gotten a bit side-tracked. Feel free to have a look here if someone else wants to continue, https://github.com/lilizoey/gdextension/tree/feature/stringy-types. Though there are some things i'm not sure if should go into a PR as is (like the indexing operators).
Godot Method | Rust Method |
---|---|
clear() -> void (mutates) |
clear(&mut self) |
duplicate(deep: bool) -> Dictionary |
duplicate_deep() -> Dictionary duplicate_shallow() -> Dictionary |
erase(key: Variant) -> bool (mutates) |
remove<K>(&mut self, key: K) -> Option<Variant> |
find_key(value: Variant) -> Variant |
find_key_by_value<V>(value: V) -> Option<Variant> |
get(key: Variant, default: Optional<Variant>) -> Variant |
get<K>(key: K) -> Option<Variant> get_or_nil<K>(k: K) -> Variant |
has(key: Variant) -> bool |
contains_key<K>(key: K) -> bool |
has_all(keys: Array<Variant>) -> bool |
contains_all_keys(key: Array<Variant>) -> bool |
hash() -> int |
hash() -> u32 |
is_empty() -> bool |
is_empty() -> bool |
is_read_only() -> bool |
|
keys() -> Array<Variant> |
keys_array() -> Array<Variant> |
make_read_only() -> void (mutates) |
|
merge(dictionary: Dictionary, overwrites: Optional<bool>) -> void (mutates) |
extend_dictionary(&mut self, other: Dictionary, overwrite: bool) |
"set" dict[k] = v |
set<K, V>(&mut self, key: K, value: V) |
size() -> int |
len() -> usize |
values() -> Array |
values_array() -> Array<Variant> |
No GDScript Equivalent | insert(&mut self) iter_shared keys_shared |
The only functions that aren't present are is_read_only()
and make_read_only()
. All rust functions have the same mutability rules. Rust has three functions that aren't present in gdscript. Information was compiled from gdscript docs and gdext docs
The only different function is erase()
. It has a different return type (Option<Variant>
) which conveys all the same information and some. It doesn't make sense to reduce it to mirror gdscript
exactly.
If we believe it is worthwhile to copy the read_only
semantics of gdscript, then that is all that remains. If such functionality is not something gdext
is interested in then dictionaries are complete.
Godot Method | Rust Method |
---|---|
array[i] -> ? |
get(index:usize) -> T |
array[i] = V mutates |
set(index:usize, value:T) mutates |
all(Callable) -> bool |
|
any(Callable) -> bool |
|
append(Variant) -> void mutates |
This is an alias of push_back() |
append_array(Array) -> void mutates |
extend_array(other:Array<T>) mutates |
assign(Array) -> void mutates |
|
back() -> Variant |
last() -> Option<T> |
bsearch(Variant, before=true) -> int |
binary_search(v: &T) -> usize |
bsearch_custom(Variant, Callable, before=true) -> int |
|
clear() -> void mutates |
clear() mutates |
count(Variant) -> int |
count(V:&T) -> usize |
duplicate(deep=false) -> Array |
duplicate_shallow() duplicate_deep() |
erase(Variant) -> void mutates |
erase(v:&T) |
fill(Variant) -> void mutates |
fill(v:&T) |
filter(Callable) -> Array |
Iter |
find(Variant, from=0) -> int |
find(v:&T, from:Option<usize>) -> Option<usize> |
front() -> Variant |
first() -> Option<T> |
get_typed_builtin() -> int |
|
get_typed_class_name() -> StringName |
|
get_typed_script() -> Variant |
|
has(Variant) -> bool |
contains(v:&T) -> bool |
hash() -> int |
hash() -> u32 |
insert(int, Variant) -> int mutates |
insert(index:usize, v:T) |
is_empty() -> bool |
is_empty() -> bool |
is_read_only() -> bool |
|
is_same_typed(Array) -> bool` |
|
is_typed() -> bool |
|
make_read_only() -> void mutates |
|
map(Callable) -> Array |
Iter |
max() -> Variant |
max() -> Option<T> |
min() -> Variant |
min() -> Option<T> |
pick_random() -> Variant |
pick_random() -> Option<T> |
pop_at(int) -> Variant mutates |
remove(index:usize) -> T |
pop_back() -> Variant mutates |
pop() -> Option<T> |
pop_front() -> Variant mutates |
pop_front() -> Option<T> |
push_back(Variant) -> void mutates |
push(v:T) |
push_front(Variant) -> void mutates |
push_front(v:T) |
reduce(Callable, Variant accum=null) -> Variant |
Iter |
remove_at(int) -> void mutates |
remove(index:usize) -> T |
resize(int) -> int mutates |
resize(size:usize) -> () mutates |
reverse() -> void mutates |
reverse() -> () mutates |
rfind(Variant, int from=-1) -> int |
rfind(value:&T, from:Option<usize>) -> Option<usize> |
shuffle() -> void mutates |
shuffle() mutates |
size() -> int |
len() -> usize |
slice(begin:int, end:int=2147483647, step:int=1, deep:bool=false) -> Array |
subarray_shallow(begin: usize, end:usize, step:Option<isize>) subarray_deep(begin: usize, end:usize, step:Option<isize>) |
sort() -> void |
sort_unstable() mutates |
sort_custom(Callable) -> void |
|
No GDScript Equivalent | try_get(index:usize) |
Like dictionary there is no is_read_only()
and make_read_only()
.
The "type" methods don't make sense in the context of gdext
. Though there are similar things present on variants, so it may be worth bringing it to this level.
all()
and any()
don't have mirrors within gdext
, but can be achieved relatively easily due to the Iter
trait. I'm not finding slice()
in gdext, though perhaps it is present via a trait._custom()
variants for sort
and bsearch
don't exist. assign()
doesn't have an analogue, but also perhaps shouldn't. assign()
doesn't strike me as a very useful method, but maybe my personal belief is irrelevant here.
slice()
bsearch_custom()
sort_custom()
seem like good candidates.
all()
and any()
strike me as an example of "won't do". Iter
exposes all you need to implement that yourself.
I personally don't see value in assign()
but we may want it purely for mirroring gdscript.
I don't know about the typed
methods or read_only
and would appreciate someone else making a call in that area.
edit: slice()
is subarray_shallow()
or subarray_deep()
Godot Method | Rust Method |
---|---|
array[index] -> int |
get(index:usize) -> u8 |
append(int) -> bool |
push(value:u8) "docs call this out explicitly |
append_array(PackedByteArray) |
extend_array(&packedByteArray) |
bsearch(value:int, before=true) -> int |
binary_search(value:u8) -> usize |
clear() |
clear() |
compress(compression_mode:int=0) -> PackedByteArray |
|
count(int) -> int |
count(value:u8) -> usize |
decode_double(int) -> float |
|
decode_float(int) -> float |
|
decode_half(int) -> float |
|
decode_s8(int) -> int |
|
decode_s16(int) -> int |
|
decode_s32(int) -> int |
|
decode_s64(int) -> int |
|
decode_u8(int) -> int |
|
decode_u16(int) -> int |
|
decode_u32(int) -> int |
|
decode_u64(int) -> int |
|
decode_var(byte_offset:int, allow_objects:bool=false) -> Variant |
|
decode_var_size(byte_offset:int, allow_objects:bool=false) -> int |
|
decompress(buffer_size: int, compression_mode:int = 0) -> PackedByteArray |
|
decompress_dynamic(max_output_size:int, compression_mode:int=0) -> PackedByteArray |
|
duplicate() -> PackedByteArray |
probably clone |
encode_double(byte_offset:int, value:float) |
|
encode_float(byte_offset:int, value:float) |
|
encode_half(int, float) |
|
encode_s8(int, int) |
|
encode_s16(int, int) |
|
encode_s32(int, int) |
|
encode_s64(int, int) |
|
encode_u8(int, int) |
|
encode_u16(int, int) |
|
encode_u32(int, int) |
|
encode_u64(int, int) |
|
encode_var(int, Variant, bool=false) -> int |
|
fill(int) |
fill(value:u8) |
find(int, int=0) -> int |
find(value:u8, from:Option<usize>) -> Option<usize> |
get_string_from_ascii() -> string |
|
get_string_from_utf8() -> string |
|
get_string_from_utf16() -> string |
|
get_string_from_utf32() -> string |
|
get_string_from_wchar() -> string |
|
has(int) -> bool |
contains(value:u8) -> bool |
has_encoded_var(int, bool=false) -> bool |
|
hex_encode() -> String |
|
insert(int, int) -> int |
insert(index:usize, value:u8) |
is_empty() -> bool |
is_empty() -> bool |
push_back(int) -> bool |
push(value:u8) "docs call out this explicitly" |
remove_at(int) |
remove(index:usize) -> u8 |
resize(int) -> int |
resize(size:usize) -> () |
reverse() |
reverse() |
rfind(int, from:int=-1) -> int |
rfind(value:u8, from:Option<usize>) -> Option<usize> |
set(int, int) |
set(index: usize, value:u8) |
size() -> int |
len() -> usize |
slice(begin:int, end:int=2147483647) -> PackedByteArray |
subarray(begin:usize, end:usize) -> PackedByteArrayand as_slice()-> &[u8]and as_mut_slice()-> &mut [u8]` |
sort() |
sort() |
to_float32_array() -> PackedFloat32Array |
|
to_float64_array() -> PackedFloat64Array |
|
to_int32_array() -> PackedInt32Array |
|
to_int64_array() -> PackedInt64Array |
|
No Godot Equivalent | to_vec() -> Vec<u8> |
This is a tough one. It could be far more complete than described here. For example the to_<T>_array()
is likely covered by the trait into
. Does that also cover encode_<T>()
? What then of compress
and decompress
? I don't know enough to say.
There is also minor strangeness. push()
being push_back()
and append()
. Typically there is a semantic difference there. Begging the question of accuracy of implementation or if naming is appropriate.
I don't know for sure, but it looks like lots.
Outside of packed byte array every godot Packed |
Godot Method | Rust Method |
---|---|---|
array[index] |
get(usize) -> <T> |
|
append(<T>) -> bool |
push<T> |
|
append_array(Packed<T>Array) |
extend_array(Packed<T>Array) |
|
bsearch(<T>, bool=true) -> int |
binary_search(<T>) -> usize |
|
clear() |
clear() |
|
count(<T>) -> int |
count(<T>) -> usize |
|
duplicate() -> Packed<T>Array |
clone() |
|
fill(<T>) |
fill(<T>) |
|
find(<T>, int=0) -> int |
find(<T>, Option<usize>) -> Option<usize> |
|
has(<T>) -> bool |
contains(<T>) -> bool |
|
insert(int, <T>) -> int |
insert(usize,<T>) |
|
is_empty() -> bool |
is_empty() -> bool |
|
push_back(<T>) -> bool |
push(<T>) |
|
remove_at(int) |
remove(usize) -> <T> |
|
resize(int) -> int |
resize(usize) |
|
reverse() |
reverse() |
|
rfind(<T>, int) -> int |
rfind(<T>, Option<usize>) -> Option<usize> |
|
set(int, <T>) |
set(usize, <T>) |
|
size() -> int |
len() -> usize |
|
slice(int, end=int_max) -> Packed<T>Array |
subarray(usize,usize) as_slice() as_mut_slice() |
|
sort() |
sort() |
|
to_byte_array() -> PackedByteArray |
||
no godot method | to_vec() |
This is mostly if not entirely complete. to_byte_array()
is probably covered by from
/into
. duplicate()
and clone()
might be slightly different. push_back()
and append()
may be slightly different than push()
validate append
and push_back()
are the same as push()
. Validate duplicate()
and clone()
are the same. Validate to_byte_array()
exists as a result of from
/into
.
Godot Method | Rust Method |
---|---|
angle_to(Quaternion) -> float |
angle_to(Quaternion) -> f32 |
dot(Quaternion) -> float |
dot(Quaternion) -> f32 |
exp() -> Quaternion |
to_exp() -> Quaternion |
from_euler(Vector3) -> Quaternion |
from_euler(Vector3) -> Quaternion |
get_angle() -> float |
get_angle() -> f32 |
get_axis() -> Vector3 |
get_axis() -> Vector3 |
get_euler(int=2) -> Vector3 |
to_euler(EulerOrder) -> Vector3 |
inverse() -> Quaternion |
inverse() -> Quaternion |
is_equal_approx(Quaternion) -> bool |
|
is_finite() -> bool |
is_finite() -> bool |
is_normalize() -> bool |
is_normalized() -> f32 |
length() -> float |
length() -> f32 |
length_squared() -> float |
length_squared() -> f32 |
log() -> Quaternion |
log() -> Quaternion |
normalized() -> Quaternion |
normalized() -> Quaternion |
slerp(Quaternion, float) -> Quaternion |
slerp(Quaternion, f32) -> Quaternion |
slerpni(Quaternion, float) -> Quaternion |
slerpni(Quaternion, f32) -> Quaternion |
spherical_cubic_interpolate(Quaternion, Quaternion, Quaternion, float) -> Quaternion |
|
spherical_cubic_interpolate_in_time(Quaternion, Quaternion, Quaternion, float, float, float) -> Quaternion |
Spherical_cubic functions need to be implemented. is_equal_approx
should also be implemented, unless another trait accomplishes this. (PartialEq
)
Called out in status
...
is_equal_approx
should also be implemented, unless another trait accomplishes this. (PartialEq
)
That would be ApproxEq
.
Working on the Quaternions
. The code calls out an interest in implementing it in rust. So I dug into the engine code for the implementation. I can re-implement that in rust, but it doesn't look like the best algorithm. (I might be wrong about that, but ogre has a simpler implementation) If I opted for a different algorithm we wouldn't necessarily have parity (though we should). Is the preference here identical outcomes or best performance?
It also relies on these hand rolled cubic interpolation functions. Is there an appetite for using a crate to supply cubic interpolation functions, or is the preference maintaining minimal dependencies?
In the past, we may have been a bit overzealous at porting Godot functionality to Rust, in an attempt to improve performance. There was also a (only partially correct) understanding that FFI calls would be inherently slow and to be avoided at all costs. However, RIIR (rewrite it in Rust) doesn't come for free: effort for testing and maintenance, risk of discrepancies in behavior, added compile-time overhead. If Godot fixes something, we are either left with different behavior or need to follow suit.
Since these re-implementations were mostly done without measuring, it remains to be seen whether they're actually worth it. For simpler things like vector operations that are delegated to glam
, using Rust is more justified, also because this domain is so central to any gamedev code that it's hard to deny savings. But for more involved operations, we would need more evidence, ideally backed by a real application.
Is the preference here identical outcomes or best performance?
Ideally identical, or at least very similar outcomes. That may already not be the case for some of the geometric types we built -- which can lead to subtle bugs in larger programs 🤔 but it's hard to say in general. If an algorithm is 10x faster than another while retaining 99.7% accuracy, then that's likely the better choice.
It also relies on these hand rolled cubic interpolation functions. Is there an appetite for using a crate to supply cubic interpolation functions, or is the preference maintaining minimal dependencies?
I'm generally quite hesitant to add new dependencies, because they have an impact not only on users, but also our CI workflows, slowing down contributions. For something where the added value is unclear, even more so. And massive crates with type shenanigans (nalgebra
& Co.) are an absolute non-starter 😀
Yet, as mentioned above, I also don't want to make people do silly code-porting exercises and significantly increase the maintenance surface of the library, when there's no clear need. Using Godot's impl until it causes actual problems is a reasonable approach in my view.
TLDR: I think the first step would be to prove that RIIR is bringing real-world benefits, or else focus on other tasks. We already have a massive library at hand that has parity with Godot and needs zero maintenance and zero compile time on our side, it's called Godot 😉
Using Godot's impl until it causes actual problems is a reasonable approach in my view.
Sounds good. Given that it probably makes most sense to get 100% builtin parity before considering optimizations. Per Quaternions
I'll see if I can use the godot builtins. The file is 'special' as it appears to be mostly RIIR. Where other builtins are heavily reliant on godot engine code. That may prove a problem for avoiding RIIR. But I'll give it a go and report back.
The file is 'special' as it appears to be mostly RIIR. Where other builtins are heavily reliant on godot engine code.
As mentioned, the RIIR may also have been a mistake in some cases. If we run into problems due to it, or are faced with increased maintenance burden, I'm happy to yeet that code and revert back to Godot's implementation.
from_opaque
is a reasonable substitute for from_signal
may need a from_signal
Each can be updated to green
Vector3 should be green because #316 implemented slerp
Thanks, updated according to your last two comments.
There are a few entries that mention documentation. In cases where the Rust API is the same as Godot's, we can reuse the provided docs from Godot. I created https://github.com/godot-rust/gdext/issues/584 for it.
Is there an argument for implementing std::ops::Index<usize>
(and IndexMut
) on PackedArray
types? get
semantics usually return an Option
in rust, and don't panic, and Index
would keep syntax and expectations wrt out of bounds consistent to both rust and godot.
I guess there's the difference between returning T
vs &T
but with the smart pointer types those semantics are blurred, and the Output
type can reflect that in any case.
Is there an argument for implementing
std::ops::Index<usize>
(andIndexMut
) onPackedArray
types?
We'd need to double-check that the current implementation of as_slice
and as_mut_slice
is truly sound -- packed arrays have CoW semantics but we need to ensure that there's no scenario where aliasing can happen.
If that is the case, Index[Mut]
traits should also be possible.
More detailed breakdown of the builtin types than present in #24.
Similar to #209 and #143, there are more builtins missing functions, here's a list of our progress when it comes to implementing all functionality.
The missing functionality here is largely going to be easy to reimplement, unless otherwise specified. We should try to reimplement what we can in rust, but for many types (like for instance the stringy types) it's likely not going to be very easy to do so. In those cases we can simply make a wrapper around a call to the
InnerX
type, (for instanceInnerGString
forGString
). Which simply makes a call to godot to call that function.For anything labelled :grey_question: it would be good to check if all the godot methods are implemented yet. Feel free to leave a comment on this post if you do. Keep in mind that not everything is directly implemented as a method in rust, some things for instance are trait implementations, for instance
Projection(Transform3D from)
was reimplemented as aimpl From<Transform3D> for Projection { .. }
.Legend
:red_square: bare-bones, missing almost everything :yellow_circle: most functions re-implemented :green_heart: all planned functions re-implemented, (could still mean there are QOL things we could add) :construction: work already in progress :grey_question: to be confirmed if everything is re-implemented
Stringy Types
GString
:red_square: https://github.com/godot-rust/gdext/issues/310#issuecomment-1625668892StringName
:red_square: https://github.com/godot-rust/gdext/issues/310#issuecomment-1625668892NodePath
:red_square:Bounding Boxes
Aabb
:green_heart: #280Rect2
:green_heart: #242Rect2i
:green_heart:Matrices
Basis
:green_heart: #124Transform2D
:green_heart: #124Transform3D
:green_heart: #124Projection
:yellow_circle: #179fovy_of
is currently a wrapper aroundInnerProjection::get_fovy
create_fit_aabb
, initially not implemented becauseAabb
wasn't implementedVectors
Vector2
:green_heart:slerp
with gdnativeVector2i
:green_heart:Vector3
:green_heart:Vector3i
:green_heart:Vector4
:green_heart:Vector4i
:green_heart:snapped
for int vectors 🟥Misc Math
Plane
:green_heart: #268Quaternion
:green_heart: #68spherical_cubic_interpolate
spherical_cubic_interpolate_in_time
Arrays
Packed*Array
:green_heart:to_byte_array()
len()
should have a doc-alias forsize()
#232FromIterator
impl for&T
for the copy-arraysPackedByteArray
:yellow_circle:compress/decompress
should likely just defer to godot implementationencode_*/decode_*
andget_string_from_*
can be done throughslice()
, is this sufficient?has_encoded_var
hex_encode
Standard Collections
Dictionary
:yellow_circle:is_read_only()
andmake_read_only()
Array
:yellow_circle:bsearch_custom()
sort_custom()
is_read_only()
andmake_read_only()
Other
Rid
:green_heart: #171Callable
:yellow_circle: #231, the unimplemented functionality is currently broken and cannot be used in gdextensionSignal
💚, not going to be easy to reimplement