Open 31 opened 2 years ago
A few things:
[Export] public Node n;
? Will need to check, but I'm not sure how it would.
Waiting for the final state of 4.0 to see how it ends up. But so far, it does look like Godot's adding the bare minimum and some people will still find GodotOnReady useful.
It turns out 4.0 broke _Ready
method generation by having a source generator that scans the list of methods: https://github.com/godotengine/godot/blob/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs.
If a _Ready
method isn't on that list, it isn't called, and since source generators run independently and don't (can't!) read each other's sources, the method generated by GodotOnReady won't be found, and won't be called.
The obvious workaround is that you need to write a _Ready
method in each script to get it to call GodotOnReady's setup:
public override void _Ready()
{
OnReadySetup();
}
Could use a partial method to let Godot's generator find the method and then fill it in with GodotOnReady:
partial override void _Ready();
I prefer the former because:
[OnReady]
to do things on ready, like connect to signals from the nodes you've just set up. (Although you still could use [OnReady]
.)partial
_Ready
method and conflicting.In either case, analyzers could make sure that when [OnReadyGet]
is being used, one of these is implemented. This would hopefully mean you don't get surprise null reference exceptions. But no matter what, the boilerplate regression is frustrating.
(I haven't tried any of this yet, this is from discussion in the Discord C# channel.)
I just tried it out in 4.0 and there's a more fundamental problem: GodotOnReady generates [Export]
members, and there's now a Godot source generator that detects exported members. So, the generated members aren't included and don't show up in the inspector.
Any new version of GodotOnReady that works with 4.x will contain significantly more boilerplate than it previously did, and need a significantly different API because of that, so I don't see how it would be possible to continue the project in any recognizable form in 4.x+.
As for myself, I'm going to go forward with only the new [Export] public Node Foo { get; set; }
feature and see what happens. I've edited the readme to indicate 3.x compatibility only.
If Roslyn implements something like this, then GodotOnReady would have a way forward:
Hello! I am super interested in using source generation to achieve what you've done here in Godot 4. I have a similar library where I use reflection to find Nodes with matching names instead of source generation: https://github.com/firebelley/GodotUtilities/blob/master/GodotUtilities/src/ChildNodeAttribute.cs
The reason I bring this up is I am wondering if there's an alternate approach to generating the [Export]
attribute. Is there a way to use a hybrid of source generation and name-guessing?
For instance, I define like so:
[OnReadyGet]
private MyNode myNode;
Under the hood, this tool could generate the following code (psuedocode example):
myNode = GetNodeOrNull<MyNode>("MyNode") ?? GetNodeOrNull<MyNode>("myNode") ?? GetNodeOrNull<MyNode>("my_node")
Basically generate every reasonable string that could be valid as a node name. Additionally the ability to provide a node path in the OnReadyGet
attribute could be preserved. I realize that the user loses control over specifying exact node paths with this proposal but there are a couple of benefits in my opinion:
I'm sure calling some kind of setup method would still be necessary. In my reflection code I still require the user to call WireNodes
.
Anyway, apologies if any of this is under-informed. But I am super curious about this project and how it could be used in Godot 4. Would appreciate your thoughts on anything I've suggested here! I'm thinking about potentially forking this code and trying my hand at implementing this.
myNode = GetNodeOrNull<MyNode>("MyNode") ?? GetNodeOrNull<MyNode>("myNode") ?? GetNodeOrNull<MyNode>("my_node")
How would the user specify a node that's nested somewhat deeply in the node hierarchy? (How would you represent /
in the field name, or would you?) This is frequent in UI, where scene structure defines behavior.
- Most nodes defined in scenes don't change and have a relatively static structure.
In my experience, nodes defined in scenes rarely have a static structure, and change frequently. π I suppose this depends on the nature of the project being worked on.
Conceptually, I admit I prefer the purity of a node script defining its dependencies (which set of nodes it needs to be able to fetch, for what reasons), then hooking them up in the scene editor. This is probably why [Export] public Node Foo { get; set; }
has been working fine for me.
This part of GodotOnReady isn't addressed here yet, and it's probably the part I'll actually miss:
[OnReadyGet("AnimationTree", Property = "parameters/playback")]
private AnimationNodeStateMachinePlayback _playback;
I'm sure calling some kind of setup method would still be necessary.
Yeah, this is my conclusion in https://github.com/31/GodotOnReady/issues/49#issuecomment-1262777481. I think including an analyzer with a fixup is critical to make this approach user-friendly (whether for a reflection or source generation backed implementation).
Thanks for the interest. π I don't think those solutions are right for GodotOnReady in 4.x. (Although I would support adding [OnReadyGuess]
for the member-name multi-lookup suggestion!) I would prefer to wait for a solution that lets the library keep the [OnReadyGet]
semantics (by generating [Export]
members), or leave this library behind if that never becomes possible.
No argument from me about making a fork for 4.x with different semantics. It's MIT licensed, after all. π I'm just happy it seems like reasonable enough code to fork.
How would the user specify a node that's nested somewhat deeply in the node hierarchy? (How would you represent / in the field name, or would you?) This is frequent in UI, where scene structure defines behavior.
The new scene unique nodes are perfect for this, and could be used like so:
[OnReadyGet("%MyNodeSomewhereDeep")]
private Node node;
I think where we differ is I think many scenes (like a player scene) would have a large number of static nodes that all make up the player's behavior. I agree that when a node has external dependencies those should be configured using [Export]
. But for all of the nodes that are sort of "internal" to the scene file itself I personally would prefer to not have to configure those paths.
I definitely see where you're coming from though! Thanks for the response, it's helpfulπ
For what it's worth, here's a design that I believe would work in 4.x without dropping features, but significantly clogs up the syntax:
public partial class Foo : Node
{
[Export, GodotOnReady<Player>] public NodePath playerPath = "somewhere/is/my/player";
public override void _Ready()
{
base._Ready();
OnReadySetup();
}
public override void _Process(double dt)
{
player.Happy();
}
}
Note: player
is a generated member made by chopping Path off playerPath. I'm not too happy about using a variable that isn't declared in this class. It functions, but I want the generator to be invisible. (Also, this means Godot can't access it, e.g. for GDScript interop.)
The new scene unique nodes are perfect for this, and could be used like so:
[OnReadyGet("%MyNodeSomewhereDeep")] private Node node;
The name of the field wouldn't realistically be node
though, right? Rather, myNodeSomewhereDeep
. That question was about the name convention in particular, avoiding that name duplication.
I guess it's not a big deal--here's what I came up with a little later, which I think I'd actually be happy with:
[OnReadyGet(In = "my/node/somewhere")] private Node deep;
[OnReadyGet(Unique = true)] private Node myNodeSomewhereDeep;
I see what you're saying regarding generated members. If I understand right, I think the approach I'm thinking of is that the user would specify the Node
members (like private Node node
), rather than NodePath
members, and then those Node
members could just be assigned in the generated code.
The name of the field wouldn't realistically be node though, right? Rather, myNodeSomewhereDeep. That question was about the name convention in particular, avoiding that name duplication.
Ah, I don't think I'm describing myself very well. Essentially I would expect that if a node path/name is provided, then there would be no need to make a best-guess effort. The guess would only be necessary if no node path information was made available.
So for example:
[OnReadyGet("%MyNodeSomewhereDeep")]
private Node node;
Would generate the following code before doing any guessing:
node = GetNode<Node>("%MyNodeSomewhereDeep");
So in essence, there would be multi-step assignment logic that first checks if there is an supplied node path. If not, then move on to trying assignment via guessing the node name.
If I understand right, I think the approach I'm thinking of is that the user would specify the
Node
members (likeprivate Node node
), rather thanNodePath
members, and then thoseNode
members could just be assigned in the generated code.
Yep, I understand the suggestion, it just isn't possible to make all the current features work with it.
Ah, I don't think I'm describing myself very well. Essentially I would expect that if a node path/name is provided, then there would be no need to make a best-guess effort. The guess would only be necessary if no node path information was made available.
I just wasn't expecting the guessing to stop working after any more than one-level-deep nodes. π Getting direct children isn't a particularly common scenario for me.
I assumed the point was to optionally remove the (near-)duplication of the member name in [OnReadyGet("MyNode")] private Node myNode;
so my first thought was to ask about additional common cases.
Just a side note to the _Ready
issue, my workaround was instead of doing an override of _Ready
, I created a ctor for the class and hooked to TreeEntered
to execute my own source gen/node wire up logic.
I ended up using TreeEntered
instead of Ready
because _Ready
is called before the event Ready
is fired and I wanted to make sure that if I had custom logic in _Ready
that the nodes would be gathered already.
Well, well! I never considered generating a constructor. From there, the EnterTree or Ready signal makes sense. I don't actually have to pick EnterTree to preserve GodotOnReady functionality, because creating your own _Ready
method was already forbidden while using GodotOnReady on a node script.
(I personally prefer Ready, because descendant node Ready methods/receivers might add additional nodes to the tree or cause other edge cases like that. EnterTree certainly works as its own thing, but as far as keeping GodotOnReady the same as it was in 3.x, it could cause unexpected behavior for some people.)
Thanks, I'll give that a try. I'm hoping I can end up creating one NuGet package that picks the strategy based on the version of Godot it's being used in.
Oh, never mind: I forgot that _Ready
is only one part of the problem.
I just tried it out in 4.0 and there's a more fundamental problem: GodotOnReady generates
[Export]
members, and there's now a Godot source generator that detects exported members. So, the generated members aren't included and don't show up in the inspector.
What's the status of updating to Godot 4? I'd love to make use of this as I currently have several variables being initialized in my _Ready() functions
What's the status of updating to Godot 4?
No change, here's a summary:
[Export] public Node n;
, so I have no personal motivation to find a way to push this through or make compromises on a more limited GodotOnReady API that would work under 4.x's limitations. This is hardly the extent of what GodotOnReady is capable of in 3.x, but it was the part of the library I ended up actually using 99% of the time myself. (I prefer to assign node references in the editor rather than in scripts using e.g. hard-coded strings.)
I believe Godot 4.0 now lets you
[Export] public Node n;
. Providing[OnReadyGet] public Node n;
is the main reason I wrote this library, so I'm thinking it might now be obsolete.I've still got to get my hands on the 4.0 beta to confirm that works how I expect, and see if there's other feature gaps I'm forgetting that I want to use GodotOnReady to plug. GodotOnReady has evolved to support more cases (like defining a default NodePath in the attribute that can be overridden in the editor) but personally I'm not sure if those would justify using the library in my own projects.
Now that Godot has built-in source generators and analyzers, ideally any new features can be added to Godot directly through a proposal rather than a library like GodotOnReady.
Opening this issue for comments in case anyone else has some thoughts. π