Skip to content
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

Array Functions(new and append) #55

Closed
thebigG opened this issue Dec 19, 2022 · 7 comments
Closed

Array Functions(new and append) #55

thebigG opened this issue Dec 19, 2022 · 7 comments
Labels
status: duplicate This issue or pull request already exists

Comments

@thebigG
Copy link
Contributor

thebigG commented Dec 19, 2022

Hi there,

In the spirit of starting a discussion first rather than just implementing(potentially the wrong thing), I'm sharing my progress in this issue.

I started using the port and realized that Arrays are not complete. So I took a stab at implementing a ::new function and this is what I got so far:

    pub fn new() -> Array {
        // For now, this unnit business seems to be required. But I'd like to study it more
        //and really understand what it does.
        let mut uninit = std::mem::MaybeUninit::<Array>::uninit();
        unsafe {
            let self_ptr = (*uninit.as_mut_ptr()).sys_mut();
            let ctor = sys::method_table().array_construct_default;
            ctor(self_ptr, std::ptr::null_mut());

            uninit.assume_init()
        }
    }

This seems to work from GdScript.

From Rust code I'm sticking to the following pattern(not sure if this is the best way of doing this):

let mut points = Array::new();
let v = Vector2Array::from(&points);

Ok, so we can create an empty array. So the next question is how do we append items to it? I thought that I might be able to do something like the following:

    pub fn append(&mut self, v: Vector2) -> () {
        unsafe {
            let class_name = StringName::from("PackedVector2Array");
            let method_name = StringName::from("append");
            let method_bind = {
                unsafe {
                    ::godot_ffi::get_interface()
                        .classdb_get_method_bind
                        .unwrap_unchecked()
                }
            }(
                class_name.string_sys(),
                method_name.string_sys(),
                4188891560i64,
            );
            let call_fn = {
                unsafe {
                    ::godot_ffi::get_interface()
                        .object_method_bind_ptrcall
                        .unwrap_unchecked()
                }
            };
            let args = [
                <Vector2 as sys::GodotFfi>::sys(&v),
            ];
            let args_ptr = args.as_ptr();
            <Vector2Array as sys::GodotFfi>::from_sys_init(|return_ptr| {
                call_fn(method_bind, self.sys() as GDNativeObjectPtr, args_ptr, return_ptr);
            });

        }
    }

But it looks like ClassDB does not have "non-Node" classes such as Arrays? So I can't really just reach out to the engine and have it "append" something for me.

Or am I wrong about that?
Is there a way to call the append/push_back functions from Rust at the moment? It seems like Rust GDNative did something similar with the godot_pool_vector2_array_append_array family of functions? But there might be something else going in the case of GDNative that I don't know since these are new APIs to me.

Hope this is clear enough.

Thanks in advance.

@Bromeon
Copy link
Member

Bromeon commented Dec 20, 2022

For now, this unnit business seems to be required. But I'd like to study it more and really understand what it does.

It's mostly equivalent to this C code:

RawArray raw; // the internal representation of an array
method_table->array_construct_default(&raw, nullptr);

But Rust forces you to initialize variables and clearly state which ones are potentially uninitialized, so it's more ceremony. Also, typically we encapsulate this operation with from_sys_init().

From Rust code I'm sticking to the following pattern(not sure if this is the best way of doing this):

let mut points = Array::new();
let v = Vector2Array::from(&points);

There is packed_vector2_array_construct_default method in the global table, so you can construct it directly; no need to go via Array.

But it looks like ClassDB does not have "non-Node" classes such as Arrays? So I can't really just reach out to the engine and have it "append" something for me.

Those classes and methods are also available in the extension_api.json file, under JSON key builtin_classes. It's just that there is no codegen for them implemented yet -- so they're not exposed to Rust at the moment. So maybe omit them for now, this could be added in a later step.

@tangentstorm
Copy link

I believe the implementation with from_sys_init looks something like this:

#[test]
pub fn test_create_from_sys_init() {
    use godot::prelude::Array;
    use godot::sys;
    use godot::sys::GodotFfi;  // for from_sys_init

    fn new_array() -> Array {
        unsafe {
            Array::from_sys_init(|self_ptr|{
                let ctor = sys::method_table().array_construct_default;
                ctor(self_ptr, std::ptr::null_mut());
            })
        }
    }
    let a = new_array();
    assert!(a.get(0).is_none());
}

(this compiles, but fails as a unit test, as I get "unchecked access to Option::None")

from_sys_init is provided by trait GodotFfi (which is marked as #[doc(hide)])... And generated from impl_builtin_stub and ffi_methods macros in godot-core.

@tangentstorm
Copy link

Okay, the following code works to implement append. I don't know if it's the "right way" to do things, but it seems to work.

   // i put this in the ready() function of my GodotExt impl:

    use godot::{sys, builtin::StringName};
    use sys::GodotFfi; // for Array.sys()

    let a = Array::new();
    assert!(a.get(0).is_none(), "the array should be empty!"); // (console complains out of bounds)
    let vgpb = sys::interface_fn!(variant_get_ptr_builtin_method);
    let name = StringName::from("append");
    let hash = 3316032543; // hash for 'append' taken from the .json file
    let meth = unsafe { vgpb(sys::VariantType::Array as i32, name.string_sys(), hash).unwrap() };
    godot_print!("append method seems to exist! let's call it.");
    for i in 0..10 {
      let args = [ Variant::from(i) ];
      let args_ptr : sys::GDExtensionConstTypePtr = args.as_ptr().cast();
      let ret_null = std::ptr::null_mut();
      unsafe { meth(a.sys(), &args_ptr, ret_null, args.len() as i32 ); }}
    godot_print!("value after append(s): {}", a.to_variant());  // "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"" !! :)

the only required changes to gdextension are to add the new and default functions and then implement ToVariant for arrays. (Variants want new to be called default):

// godot-core/src/builtin/arrays.rs
impl Array {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn default() -> Self {
        unsafe {
            Self::from_sys_init(|self_ptr| {
                let ctor = sys::builtin_fn!(array_construct_default);
                ctor(self_ptr, std::ptr::null_mut());
            })
        }
    }
}

The implementation of ToVariant is one line in godot-core/src/builtin/variant/impls.rs:

impl_variant_traits!(Array, array_to_variant, array_from_variant, Array);

@thebigG
Copy link
Contributor Author

thebigG commented Jan 18, 2023

let vgpb = sys::interface_fn!(variant_get_ptr_builtin_method);

Interesting...if this does what I think it does(essentially looks up a method inside of a variant?), then this could be very useful for all kinds of methods.

Thanks for sharing @tangentstorm!

@tangentstorm
Copy link

@thebigG thanks for posting your work-in-progress. I referred to it repeatedly while working this out yesterday!

@Bromeon , @thebigG also seem to be correct about the ClassDB not containing the methods:

    let cdb_gmb = sys::interface_fn!(classdb_get_method_bind);
    let ptrcall = sys::interface_fn!(object_method_bind_ptrcall);
    let meth = unsafe { cdb_gmb(StringName::from("PackedInt32Array").string_sys(),
         StringName::from("append").string_sys(),
         694024632) }; // hash seems to be for each class/method pair, not just method name
    if meth == std::ptr::null_mut() { godot_print!("PackedInt32Array::append not found in ClassDB!") }

It says it's not found... This might kind of make sense because the Array classes don't descend from Object... (?)

As a sanity check, I went looking for ColorRect::set_color and it shows up fine:

    let meth = unsafe { cdb_gmb(StringName::from("ColorRect").string_sys(),
         StringName::from("set_color").string_sys(),
         2920490490) }; // hash seems to be for each class/method pair, not just method name
    if meth == std::ptr::null_mut() { godot_print!("method not found in ClassDB!") }
    else { godot_print!("method found!") }

@ttencate
Copy link
Contributor

Duplicate of #33? Array should now be usable! Note that TypedArray, which you get from several Godot APIs, is not usable... yet.

@Bromeon
Copy link
Member

Bromeon commented Jan 28, 2023

Indeed, arrays are now supported! TypedArray support is ongoing, but this can be tracked in #33.

@thebigG @tangentstorm regarding confusion, Array and other built-in types are not "classes" in the Godot sense, as such they don't appear in ClassDB.

@Bromeon Bromeon added the status: duplicate This issue or pull request already exists label Jan 28, 2023
@Bromeon Bromeon closed this as completed Jan 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

4 participants