godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.15k stars 97 forks source link

Add a ScriptScene resource which optionally, but tightly binds a root script to a scene for all languages supporting built-in scripts. #1909

Open willnationsdev opened 3 years ago

willnationsdev commented 3 years ago

Building on proposals #1224 and #1906, courtesy of @Shadowblitz16...

Describe the project you are working on

No active project. Have used UE4 for projects in the past. Supported an alternative structure that I miss, namely Blueprints. The ease of use of Blueprints in Unreal and the myriad of people who enjoy them are the "existing practical use cases" of demonstrating the usefulness of the proposed feature, albeit not a Godot context.

Describe the problem or limitation you are having in your project

In Godot Engine, script-classes make scripts almost a first-party type system. Global names. Static typing. After #22, they become more widespread. After #18, we get data structure / serialization support.

But, while scenes are a declarative extension of classes defined by scripts, it is quite easy to create a script that becomes dependent on its scene in order to function properly. And yet, in those cases, people still have the ability to make the script without the scene. And if you just load the scene, then you don't have enough type information to treat it like a class (cause it isn't a Script). Furthermore scripts and scenes have potentially divergent or even unrelated inheritance hierarchies (wat). Conceptually, that's just plain weird when you want them to be consist (I will grant, there are use cases for the flexibility, so it is needed).

We don't have any utilities to make things easier though. Controlling runtime instantiation of scripts to guarantee they are only ever made inside the proper scene is unreasonably difficult at the Script/Object/ScriptServer level and far too invasive. Querying for PackedScene inheritance hierarchies against a Node, a Script, or another PackedScene is all too complicated (I've written hundreds of lines of code just trying to make sense of it in a ClassType script to abstract everything). Abstracting resource loading so you don't have to worry about whether your loading a Script or a PackedScene is also frequent and frustrating.

TLDR: It isn't good that we tell people scenes help you make classes when they aren't actually classes and we don't help scripts and scenes work together as one, just to ensure we have the flexibility of keeping them separate. We need an optional alternative that provides an opinionated tool for automating the complexity of keeping them in sync where its most needed.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Take Blueprints as an example. The "scenes" are classes in their own right which fulfill all requirements of being a Script. The scene has Script-ed type information, a "built-in" script at the root, and a guarantee that the script will never be instantiated outside of that scene. And yet, derived "scenes" can extend the same "built-in" script safely. Instantiating this type of "script" actually instantiates the scene internally.

I would dub this new type of resource a ScriptScene.

Pros:

Cons:

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

ScriptScene would likely be a resource that extends Script, but which owns a PackedScene and a Script and that delegates most of its responsibilities to the internal Script, yet has the ability to load and save itself like a scene with a built-in root script (that way the Godot Editor just sees it as a scene where you can click on the built-in script at the root). The reason it would need to extend Script would be for the sake of other languages being able to load the file and perceive it like they would a script (either out-of-the-box or by casting or something); by extension this would enable static typing and the like.

Honestly, the biggest complexity I see in supporting something like this is figuring out how you would get script logic to "load" the file as if it were a Script (where calling new() just calls instance() on the scene under-the-hood), but make only the Editor "load" the same file as if it were a PackedScene. And I don't think the TOOLS_ENABLED stuff would cut it here cause different parts of the Editor might also need to be able to recognize it as a Script.

I leave that for discussion below.

Edit: note that, even though it's technically a new kind of Script, it's actually more like it's a new scene file format, as far as the scene editing system is concerned, but where it's perceived as the root built-in script in all other contexts. It's not exactly like it's a new scripting language per se.

If this enhancement will not be used often, can it be worked around with a few lines of script?

I could easily envision people creating entire projects with nothing but ScriptScenes in place of managing both Scripts and PackedScenes for their project, so long as they only ever planned on using GDScript, VisualScript, etc. for their project. So yes, if the feature existed, it very well could be used a lot.

And no, there is no existing simple solution in scripting to do this, as evidenced by my rejection of such solutions here.

Now, if some select changes were made to the engine, there is a different viable option that makes it easier to implement a similar idea in script code (https://github.com/godotengine/godot-proposals/issues/1906#issuecomment-735451623), but I ultimately believe a different, more thorough approach will yield stronger benefits since this version naturally has support for more languages without custom language code and better satisfies the requirements of the problem without requiring boilerplate code for every class that is declared.

Is there a reason why this should be core and not an add-on in the asset library?

Technically, you could probably get away with something similar in script code using a mix of PluginScript, some sophisticated resource format loader/saver stuff (maybe with some changes to core?), but PluginScript doesn't have script class support (and #22 actually makes no mention of adding it - not sure how that would be done). And what's more, PluginScript is badly documented with many just not knowing how exactly it works (NativeScript is already complex enough without adding PluginScript on top of that). An add-on is just plagued with all sorts of issues that become far easier to identify, address, and resolve in engine code.

Not to mention, making it a built-in feature would just be a lot more useful to a much wider collection of users, especially considering the percentage of users that exclusively use GDScript and VisualScript for their projects (something like, what, 60+%?). And even to people who also use C# and/or GDNative C++, they can always mix and match what kinds of scene files they use for different parts of their project, so again, still useful to a massive audience of Godot users, especially beginners.

Shadowblitz16 commented 3 years ago

I like this idea but I have issues with it not supporting C# scripts as that's what I am going to once C# support is complete.

Is there a way to support multiple script types?

willnationsdev commented 3 years ago

@Shadowblitz16 With this proposal, it entirely depends on the script information being something that is an embedded resource of some kind (a "built-in" script).

The only way you could potentially work with all scripting languages would be if you took @dalexeev's suggestion and implemented a separate solution for each scripting language to be able to bind a given Script resource to a particular PackedScene.

The only problem I see with that approach, aside from the annoyance of needing so many different language changes, is that you'd also have to add logic in various places to prevent users from being able to create an instance of a scene that splits the inheritance chain.

But, who knows, maybe that approach would be simpler and more flexible in the long run, and my initial worries about it are for nothing. For example, if you added a scene file path to the script class info that can be registered (actually, wouldn't want to require that a scene-bound script is a script class), then you'd effectively have most everything you needed to get started, aside from the instantiation logic and the scene-design checks that the Editor would need to perform.

willnationsdev commented 3 years ago

I guess, actually, in retrospect, I'm thinking that dale's suggestion might be a better way to go provided that we make sure to add tooling that reinforces the constraints it defines for the script - because PackedScene can't do the job itself, and scripts won't be aware of the scene context either, so the Editor has to do all of the heavy-lifting.

willnationsdev commented 3 years ago

I'll leave this open for a little while, just in case others want to see it and give feedback on it, but chances are, we'll end up going down the Dale rabbit hole on this one simply because it doesn't have the "built-in" script limitation. Was really hoping for a one-size-fits-all kind of solution using the resource system, but if a language-specific solution can handle it better in practice, then the work's gotta be done. 🤦 🚶 🏗️ 🏛️ 💯

DriNeo commented 3 years ago

The current system is ok but this idea to associate script with the whole scene should bring simplicity in many ways. So why not go further and removing the existing scripting system by this proposal. "ScriptScene" should simply be "Script". It is generally hard to convince people to drop some flexibility in favor of simplicity, but I will try anyway.

willnationsdev commented 3 years ago

@DriNeo Firstly, you should clarify whether you are saying that every Script should be paired with a PackedScene resource on a mandatory basis, or whether you think the above proposed ScriptScene concept should be the only scripting option available.

If the latter, I would be completely against it, precisely because it forces "built-in" script usage, which not all languages support, and for which you are unable to use third-party text editors that come with far more features than Godot's provided ScriptEditor.

If the former, it's actually even more unreasonable from an architectural view because Script is something that is placed on the Object class, not the Node class. As such, there are many, many types for which mandating a PackedScene is irrelevant. The base Script type shouldn't need to know anything about Node/PackedScene. If we do add info to scripts about an associated scene, as per @dalexeev, then it should be an optional feature that only specific implementations of Script have to be concerned with.

Even if you have an optional scene-binding feature, you still accomplish most of the same benefits/pros that you suggest, but you also keep the flexibility at the same time (except for your last two simplification points).

I agree that scenes are a natural scope for Node-extending scripts, but I also think there is a place for being able to quickly add a bit of custom functionality to a Node type without the burden of having to create an entirely new scene just to do it.

DriNeo commented 3 years ago

@willnationsdev I totally ignored the current Godot classes in my post. It is a concept inspired by Shadowblitz posts and yours : a scene is not only a collection of nodes, a script property is added, like the current node has one. Whatever the script is a GDScript bytecode or a GDnative binary. This idea assumes a flexibility cost, like you said at the end. Not sure how to implement that and how to expose the scene properties in the UI, as the scene is a different concept here (maybe in the script dock ?). I found simplicity beautiful and I can sacrifices a lot to avoid choice paralysis and learning time. But I also understand the flexibility loss can be unacceptable for some users. I hope I helped people to explore this topic by throwing this idea.

willnationsdev commented 3 years ago

@DriNeo Yeah, I'm just glad we've got people discussing the issue(s) in the first place. XD

DriNeo commented 3 years ago

To be honest, the current system with a node extended by a script is beautiful too, but the addition of scene inheritance broke it.

In order to merge the two inheritance hierachies to a single one, the scene should be an object with a script property, the nodes become aggregated data and the scene operates on them. Maybe it is a better explanation for my, possibly stupid, idea :sweat_smile:

willnationsdev commented 3 years ago

In order to merge the two inheritance hierachies to a single one, the scene should be an object with a script property

Scenes are instances of a PackedScene resource which does actually already have its own script property. You could, if you wanted to, add custom features to the PackedScene class and then assign that script to every instance of the PackedScene class you load. You could probably even open a proposal to configure a default PackedScene script in the ProjectSettings which the ResourceLoader would auto-assign to every loaded scene resource, thereby enabling you to customize the features available to scenes (not a bad idea actually).

But, none of that would change how the is keyword fails to support scenes or how scripts and scenes can have divergent inheritance hierarchies.

the nodes become aggregated data and the scene operates on them.

Technically, that's what a scene already kind of does. Ish. In Godot, there is a clear distinction between basic Objects, Nodes, and/or Servers which perform operations on data, and Resources which exist entirely for the purpose of serializing data. Since PackedScene is a resource, it's whole purpose is to "pack" a node tree. There's even a SceneState object which you can gather from a PackedScene resource that contains all of the aggregated information about the nodes in the scene, including a reference to any base scene (if you know where to look). But PackedScene doesn't perform operations on the data. For that, you'd have Editor tools.

Maybe it is a better explanation for my, possibly stupid, idea

Nah, I hardly encounter stupid ideas. It's mostly just ill-informed ideas that don't align with the architecture Godot already has (which won't be changing anytime soon).

The key thing that has to happen is that whatever change is made has to be done with something that implements the Script interface in some way. That would thereby enable it to be treated as a class by all of Godot's systems out-of-the-box without any custom logic needing to be required throughout the rest of the engine/editor. That in turn means you either need a new custom resource that implements the Script class (this proposal) (too invasive/backward-breaking a change to do it to PackedScene) or you need to manually teach how every scripting language how to manually bind itself to a single scene inheritance hierarchy (#1935). There aren't really a lot more options that I can think of.

kubecz3k commented 2 years ago

@willnationsdev I'm not sure where I can find mentioned proposition from @dalexeev, can you provide a link?

dalexeev commented 2 years ago

@kubecz3k https://github.com/godotengine/godot-proposals/issues/1906#issuecomment-735451623. willnationsdev formulated this in #1935.

h0lley commented 2 years ago

I don't quite follow what the ScriptScene resource is required for. we already have the ability to couple scripts with scenes: by adding a built-in script to a scene's root node. unfortunately though, these scripts do not support class_name. if they would, then that would enable treatment of scenes as node classes without any new syntax, UI, annotations or resources.

willnationsdev commented 2 years ago

@h0lley Yeah, this was kind of an old proposal, before thinking of a more effective one with 1935 mentioned just before your comment. And making yet another "global class" system that operates on scene file's (and that works exclusively with built-in scripts) does not seem like a good solution to me.