-
Notifications
You must be signed in to change notification settings - Fork 211
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
Simplify often-used and currently verbose APIs #758
Comments
A think I do often is store references to nodes inside my structs. Currently this requires a lot of boilerplate to setup. A derive macro could be helpful for the most common cases of getting nodes, similar to struct MyScript {
#[get_node("Camera", Unique)]
camera: Option<Camera>
} which would expand to something like struct MyScript {
camera: Option<Ref<Camera, Unique>>
}
impl MyScript {
pub fn new(_owner: &Node) -> Self {
Self {
camera: None
}
}
// generated:
fn _ready() {
self.camera = unsafe { Some(get_node_as::<Camera>("Camera").unwrap()) };
}
} Another possibility could be to introduce a similar struct MyScript {
#[onready]
camera: Option<Ref<Camera, Unique>>
} Of course this introduces the issue that you mentioned regarding generating unsafe code from a macro, rather than explicitly. But I think it makes sense for godot-rust to provide macros that may generate unsafe code for convenience sake. Macros could be prefixed or suffixed with the word |
I'd prefix macros with As for verbosity: something that annoys me is the need to add AFAICT it's also not possible to return It would also be nice if I could use Not directly related to verbosity, but I wonder if it would make sense to implicitly convert between integers and floats? Godot implicitly converts between them when using static typing in debug/editor builds but in release builds it strips that info and no longers casts them, which can lead to some unexpected issues ("expected I64, got F64" kinda deal). Working around it would require using |
Here's a few things that I found too boilerplate-y and ended up building macros / helper traits for, in no particular order: Calling GDScript methods on NodesThe current syntax for call is a bit too cumbersome. Due to requirements in the type system, the function has to take a slice of variants. This often requires writing I ended up not using this one a lot, not because it's not useful, but rather because I try to avoid using Note that it uses unsafe internally. This makes sense to me because I know when I see macro_rules! gdcall {
( $node:expr, $method:ident ) => {
#[allow(unused_unsafe)] // Necessary because sometimes we can't check if we're already on an unsafe block.
unsafe { $node.call(stringify!($method), &[]) } };
( $node:expr, $method:ident $(, $arg:expr )+) => {
#[allow(unused_unsafe)]
unsafe { $node.call(stringify!($method), &[ $( $arg.to_variant() , )+ ]) }
};
} The
|
In my case, I'm trying to simplify connecting a signal: let signals = &mut owner.get_node("../Signals").unwrap();
let signals = unsafe { signals.assume_safe() };
signals
.connect("move", owner, "_on_player_input_move", VariantArray::new_shared(), 0)
.unwrap(); And emitting one (looks similar). The proposals here look promising. |
One of the papercuts I had today was trying to create a Since I ended up with something like this to fill out the exclude list on let exclude = VariantArray::new();
exclude.push(&unsafe { owner.assume_shared() }.to_variant());
exclude.push(&cube.to_variant());
let query = PhysicsShapeQueryParameters::new();
query.set_exclude(exclude.into_shared()); The I would love to see a |
This pattern happens a whole lot while creating GUI hierarchies: let label = unsafe { Label::new().into_shared().assume_safe() }; Here it is in context: #[methods]
impl MyPanel {
#[export]
fn _enter_tree(&self, parent: &Panel) {
parent.set_custom_minimum_size(Vector2::new(256.0, 256.0));
let tabs = unsafe { TabContainer::new().into_shared().assume_safe() };
tabs.set_anchors_preset(Control::PRESET_WIDE, false);
tabs.set_tab_align(TabContainer::ALIGN_LEFT);
parent.add_child(tabs, false);
let vbox = unsafe { VBoxContainer::new().into_shared().assume_safe() };
tabs.add_child(vbox, false);
tabs.set_tab_title(0, "Tab 1");
let label = unsafe { Label::new().into_shared().assume_safe() };
label.set_text("Contents 1");
vbox.add_child(label, false);
let vbox = unsafe { VBoxContainer::new().into_shared().assume_safe() };
tabs.add_child(vbox, false);
tabs.set_tab_title(1, "Tab 2");
let label = unsafe { Label::new().into_shared().assume_safe() };
label.set_text("Contents 2");
vbox.add_child(label, false);
}
}
I have a macro that I am currently using for this. It's a macro because a method cannot return a reference to a temporary. It would be nicer with postfix position. #[macro_export]
macro_rules! new_shared {
($object:ty) => {
unsafe { <$object>::new().into_shared().assume_safe() }
};
} And its usage: let label = new_shared!(Label); |
Chatted about this in discord a couple days ago and wanted to share some abstractions I've been tinkering with. If these would be useful to contribute I'll take any feedback and polish them up in a PR. use gdnative::prelude::*;
#[macro_export]
macro_rules! get_tree {
($node:expr) => {
unsafe { $node.get_tree().expect("could not retreive Scene Tree").assume_safe() }
};
}
#[macro_export]
macro_rules! get_node {
($node:expr, $name:literal, $cast_type:ty) => {{
let fetched: Option<TRef<$cast_type>> = unsafe { $node.get_node_as($name) };
fetched.expect(format!("Failed to find node {:#?}", $node).as_str())
}};
}
#[macro_export]
macro_rules! connect_signal {
($signal_node:expr, $signal:literal, $receiver:expr, $method:literal, $args:expr) => {
$signal_node.connect(
$signal,
$receiver,
$method,
$args,
0,
)
.expect(format!("Failed to connect '{}' signal to node {:#?}", $signal, $signal_node).as_str());
}
}
pub fn load_scene(path: &str) -> Ref<PackedScene> {
let loader = ResourceLoader::godot_singleton();
loader.load(path, "PackedScene", false)
.expect("Failed to load scene")
.cast().expect("Failed to loaded resource to PackedScene")
}
pub fn instance_scene(path: &str) -> Ref<Node> {
let scene = load_scene(path);
let scene = unsafe { scene.assume_safe() };
scene.instance(0).expect("Failed to instance scene")
} Some example usages: [derive(NativeClass, Debug)]
#[inherit(Node2D)]
pub struct Game {
node: Ref<Node2D>
}
#[methods]
impl Game {
fn new(node: TRef<Node2D>) -> Self {
Self {
node: node.claim()
}
}
#[export]
fn _ready(&mut self, _node: TRef<Node2D>) {
self.load_connection_menu();
}
fn get_node(&mut self) -> TRef<Node2D> {
unsafe { self.node.assume_safe() }
}
fn get_ui(&mut self) -> TRef<CenterContainer> {
get_node!(self.get_node(), "canvas/ui", CenterContainer)
}
fn load_connection_menu(&mut self) {
let ui = self.get_ui();
let instance = instance_scene("res://ui/connection_menu.tscn");
ui.add_child(instance, true);
}
} #[export]
fn _ready(&mut self, node: TRef<Node>) {
node.set_name("Network");
let tree = get_tree!(node);
connect_signal!(tree, "network_peer_connected", node, "network_peer_connected", VariantArray::new_shared());
let peer = NetworkedMultiplayerENet::new();
peer.create_server(self.port, self.max_clients, 0, 0)
.unwrap();
tree.set_network_peer(peer);
godot_print!("listening!");
} |
Thanks everyone for the great examples so far. Such condensed user feedback is rare and very helpful to identify pain points! 👍 I also stumbled upon a very simple, yet very interesting example: exiting. GDScript: get_tree().quit() Rust: let scene_tree = base.get_tree().unwrap();
unsafe { scene_tree.assume_safe() }.quit(0); This is beautiful. 😬 And I don't mean the syntax, but it shows several dimensions of verbosity in an example that's just 2 identifiers in GDScript:
Some comments on those:
|
Some operations that are simple in GDScript are quite verbose in godot-rust. This has mostly to do with static typing, safety, and the need to emulate inheritance, so it's partly justified. Still, I think we don't gain much if we force users to abstract these often-used chores on their own; having them in one central place would allow for code reuse and more robust code, as many contributors use it and give their feedback.
A discussion that needs to take place, is whether macros should be able to implicitly use
unsafe
code, which somewhat defeats the "user code must explicitly opt-in to unsafe behavior". Interesting resources in this regard:Please respond in the comments with functionality that you use on a regular basis, but is cumbersome or overly verbose with current godot-rust syntax.
The text was updated successfully, but these errors were encountered: