godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
91.55k stars 21.26k forks source link

Add support for editing Scripts via code. #15581

Closed willnationsdev closed 6 years ago

willnationsdev commented 6 years ago

Currently, if I wished to programmatically create a scripted type, my only two options are to create write GDScript code to a file, and then load that file as a script (so read, parse, and compile it) or to create a VisualScript object and call methods on it (despite the fact that VisualScript's have all sorts of unrelated functionality / dependencies having to do with the VisualScript editor.

It would be much more convenient if scripts of this sort could be made with an additional Script type that has no explicit editor outside of code. They would be completely exposed to the scripting API and you could therefore make them using any other type of script.

For discussion's sake, we might call this CodeScript.

I already have two use-case in mind:

I wouldn't mind implementing it on my own, but I'd like to hear people's thoughts first.

willnationsdev commented 6 years ago

After further examination of VisualScript, it seems as though most of the concepts involved would actually be necessary for a "CodeScript" anyway. For example, you have to define execution flow in case of an if statement for if/else/finish, etc. One could simply make port connections from code in order to accomplish this. So this entire concept may already be fulfilled by the existing VisualScript implementation.

willnationsdev commented 6 years ago

In fact, I'm thinking that most of the needs of this could be accomplished by just creating a VisualScript class that essentially isn't bound to the VisualScript module.

LikeLakers2 commented 6 years ago

+1 to this.

Another possible use case for this, at least in my mind: Developers could easily call upon a interface for this and show it to the user, and allow them to make code for things like game mods or custom enemies, or what-have-you. (Although at that point, might the developer already be considering Lua support for their game? Either way, an in-game code editor would be really nice, if you ask me.)

erodozer commented 6 years ago

IMO I feel like this might not be suitable for Godot/GDScript, as GDScript up to this point hasn't been a general purpose programming language, and it might be best to keep it that way. Something like this would be better suited to be exposed through an additional module that exposes an embedded lua interpreter for your game, then create behavioral scripts that allow executing code strings with the interpreter.

An in-game code editor with syntax highlighting or anything of that nature might also be better as something in the asset store instead of in the engine, as it sounds like too specific of a use-case to warrant being in core.

vnen commented 6 years ago

Currently, if I wished to programmatically create a scripted type, my only two options are to create write GDScript code to a file, and then load that file as a script (so read, parse, and compile it)

You say this like it's a bad thing or even avoidable. How can you run a code that didn't made these steps? You don't really need an extra file for this, you can simply instantiate a new GDScript and set its source code, then attach to where you want (if you want).


In any case, I fail to understand what this is about. You start by stating your issue (which is good, but I don't know if it really is an issue, hence my previous paragraph). Then you mention properties of your proposed solution (explicit editor outside of code and completely exposed to the scripting API). Then you give a name to it. Finally you mention two specific use-cases. You just forgot to describe it.

I don't mean to be harsh, I genuinely didn't understand the proposal. Please illustrate what CodeScript is. How will it run? Which "script" will be put into it? For instance, NativeScript uses a native library as script, PluginScript is a bridge for other scripting languages, that is handled by a GDNative library. GDScript and VisualScript are handled by their respective modules.

Or do you want to make a parser/interpreter in GDScript? That would be quite a task, especially if you want to handle a kind of script that does anything (essentially creating a ScriptLanguage in GDScript, maybe doable, not worth it IMO). In the case of a custom type you want to parse/interpret for specific cases (like a game console), you don't need the engine help for it.

I have the impression that you are overthinking and trying to abstract something that doesn't need abstraction. Ever heard of the Inner-platform effect? This request smells like it (for the amount I understood).

vnen commented 6 years ago

For instance, here's an example of making a GDScript programmatically, no IO required:

extends Node

func _ready():
    var my_script = GDScript.new()

    my_script.set_source_code(
"""
# This is the generated source code
extends Reference

func _init():
    print("I'm RUNNING!")
""")

    my_script.reload() # Necessary step update the source code
    var instance = my_script.new() # Will call the constructor, which prints a message
willnationsdev commented 6 years ago

@nhydock

Something like this would be better suited to be exposed through an additional module

An in-game code editor with syntax highlighting or anything of that nature might also be better as something in the asset store instead of in the engine

I agree, which is why I was hoping to create both of my suggested use cases as plugins for the engine. However, in order to implement these things, I was looking for ways in which I could incrementally build a type and use it to evaluate expressions in a context. So that, if a user decides to define a variable, I can do exactly that. And if they later decide to evaluate an expression using that variable, I can do that.

@vnen

here's an example of making a GDScript programmatically, no IO required

Setting the source code in a GDScript object directly like that is a really good idea that hadn't occurred to me for some reason. It pretty much solves all of the problems one way or another, depending on how and when you append things to the text. It would be kind of annoying to have to keep track of what line a given function is currently at (if users swap between appending content to multiple functions, you'll have to keep track of the ending line for each function to append things properly). But that could easily be managed with some wrapper code.

Another slightly weird tidbit would be that if you wanted to eval a GDScript expression (given the state of the script context up to that point in the interactive tool), you'd have to keep a dedicated function around just for evaluating the content.

func eval():
    return __EXPRESSION__

Do a search-and-replace on "__EXPRESSION__" in this script with an if statement the user gives you, and then append that code to your dynamic Script, reload it while keeping the state, call the function, get the result, then remove that content and reload the script again in preparation for the next potential if statement the user provides.

^ Would've been easier if the state could be tracked by code and just call an eval function on an object directly, but that may be too much work for what it would be worth...

willnationsdev commented 6 years ago

@vnen

Ever heard of the Inner-platform effect? This request smells like it (for the amount I understood).

Yeah, I was kinda hoping to not have to resort to a whole new Script type, but I was feeling like I was running out of options outside of just going with VisualScript and forcing an unrelated dependency on it on my tools. That's why I like the GDScript.set_source_code suggestion. If possible, I would prefer to leverage the power of the existing content and make it easier to control the state of a script's execution more precisely.

mhilbrunner commented 6 years ago

@vnen: With your example, is there a way to

That'd be great for mods/plugins.

Besides that, I think this mostly covers this issue, IMO?

erodozer commented 6 years ago

typically you're still going to want to have an interpreter and a sandbox instead of just arbitrarily calling code for something like this, especially for modding/scripting, just so then you can limit the scope of what the scripts can and can't do. You give a user too much power and they'll easily break the game.

Adding exec/eval functions to GDScript/VisualScript is hacky and a bit unsafe, as discussed in #8003. Such functionality is best suited as an extension/module to prevent adding something to core that's convoluted and easily abused. I am against using GDScript for modding, as it can give the user too much power since its API/standard lib is designed around Godot's feature set. Instead exposing another embeddable language and interpreting code in strings against that runtime's scope is much safer.

willnationsdev commented 6 years ago

@nhydock Well, both of my use cases are actually meant to be extensions to the Editor, not necessarily for use in a particular game. But I definitely get the concern there.

vnen commented 6 years ago

@mhilbrunner

With your example, is there a way to

  • check on the scrupt object if it defines a certain method?

Not unless you instance it. One solution for this is exposing a few Script functions so they can be called from GDScript.

  • call that custom method if available?

Yes, the same way you can call on any GDScript: from the Script object if it's static or from an instance if it's not.


Of course, this generated code is not sandboxed and can affect the global state, so it's not safe to trust user-created scripts. But it can be useful for game developers and for creating editor tools.

mhilbrunner commented 6 years ago

@vnen: I don't think the missing sandbox would always be a showstopper. See Minecraft modding. Also, editor plugins.

One solution for this is exposing a few Script functions so they can be called from GDScript.

Should we open this as a seperate issue? Suggestion for a title? Would be very nice to have, IMO.

willnationsdev commented 6 years ago

I think this is pretty much closed at this point. Thanks for the help in discussing this.