godotengine / godot

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

Class (instead of file) based script assignment in editor #7402

Closed Xydium closed 4 years ago

Xydium commented 7 years ago

Following some discussion with Karroffel and getting neikeq's GodotSharp module functional, I had an idea to make script/class organization easier, by having Godot automatically parse the project files for all classes/scripts and having a popup that lists them (with search bar and filters) to select a class rather than pointing a given node to a file directly. For example, on a Node2D selecting a subclass would make this window appear:

untitled3

For GDScript/VisualScript, the class path could either be the file path in dot notation, or to further reduce the dependency on the file system's organization, GD/VS could have namespaces added or simply always appear top-level with no namespaces.

XCode operates similarly where the selection menu for a custom subclass on a view/component will list all of the classes in the project that inherit from the required type.

bojidar-bg commented 7 years ago

I partly agree -- especially for C#/DLScript where the classes are known beforehand, it would be nifty to have this; but for languages as GDScript and VScript where every file is a class, and there is no global "polling" of all files, it makes no sense at all (even less with the scenes idea, maybe you wanted to have just a "dotified" path there?)

Xydium commented 7 years ago

Scenes.MainScene doesn't refer to the main scene in the project. It would be equivalent to a folder structure set as res://Scenes/MainScene/TestScript.gd. The Dot notation is to be graphically consistent with namespaces even if it is technically a filepath. It is mostly for C# though where one file can contain multiple classes.

neikeq commented 7 years ago

Two possible issues:

This is one of the solutions I had in mind for solving the namespace detection issue with C#: neikeq/GodotSharp#9, but I soon discarded the idea for the reasons above (we are just going to use a simple parser to find the namespace).

Xydium commented 7 years ago

Fair concerns, ideally there would be no path association with script loading, so to assign a script you would simply point it to a script with the correct inheritance for the node it is being attached to, and if the file were moved it would update the reference.

Maybe a better alternative is to be able to sort scripts by what they extend, such as having an option to search res:// for scripts that extend KinematicBody2D, with the option to include those that indirectly subclass, such as with extends "MyScript.gd".

karroffel commented 7 years ago

@neikeq if you're using a parser to find the namespace, does it mean F# can't be used anymore? I liked that the code just had to live in the assembly and be CLI compliant, with this you kick out all the CLI compliant languages that run on Mono, don't you?

neikeq commented 7 years ago

@karroffel The current behaviour is that it iterates through the typedef table until it finds a class that derives from GodotEngine.Object and matches the name of the script file. The problem with this is that you can't have NamespaceOne.Player and NamespaceTwo.Player, making namespaces pointless. This will be the default behaviour for any language that has no parser, including F# (in other words, it will be the same as now). Alternatively you can write a parser for it (see 4613cb7).

touilleMan commented 7 years ago

On my godot python binding, I've deal with this problem following the zen of python: "Explicit is better than implicit"

So to be available from Godot, a Python class should have been explicitly decorated as exposed. Obviously only one exposed class is allowed per file so it feels just like GDScript.

from godot import exposed

@exposed
class Player(Node):
    def _ready(self):
        pass

While it works great with dynamic language where a lot can be done at load time, I'm not sure such a thing could be achieved on C#...

@neikeq I don't know much about C#, is there a __file__ attribute (like the C one which the preprocessor replaces by the file path at compile time) ? If so you could implement this in the decorator to end up with some classes flagged as exportable and with there associate file path. From there it would be trivial to run your scan between the compiled class and the source file.

neikeq commented 7 years ago

@touilleMan I tried that already. I was close to achieving this by using the CallerFilePath attribute. This was an attempt: https://gist.github.com/neikeq/db685eda63e558d207abf12dececa51b The problem was that the path provided by CallerFilePath is absolute, and considering it's at compile time... I thought about saving the path to the project somewhere and then using it to fix the script paths when running the game, but I don't think anyone would like the idea of exporting their dev directory path with the game.

touilleMan commented 7 years ago

@neikeq at least you made me puke my tea with your commit message 😆

I guess the simplest way that remains is to force user too keep namespace and file path the same for the exposed classes. Potentially providing a configuration field the user could overwrite to specify by hand a custom path for the class.

NathanWarden commented 6 years ago

What I think would be a better generalized solution for all resources, would be for each resource whether script or mesh to simply have a unique id that would be stored in an import file. So, each resource would have the unique ID instead of a file path. The unique ID could then be associated with each asset. So if a script of any type, or a material, texture, audio file, etc would no longer be dependent on a specific path.

This would require Godot to scan the project when you load it and create a Map of each asset ID and it's path, and then when a script slot has UniqueID in it, then it will internally know that ID X belongs to path X. If an asset is moved, then it's import file is also moved and the Map is updated with the new path.

This would allow all resources to be moved around and literally nothing would ever break.

edit: I think I'll add this as a feature request.

NathanWarden commented 6 years ago

Now, as far as having to type in the fully qualified script name, I think this is a bad idea that sounds very good on the surface. For instance, what happens when you decide a script that you've used in several places in your project has been poorly named?

You have to either: A) Go to each and every place where you've used it and correct the name. or B) Just leave it as it is.

With my solution in the previous post you: A) Rename the file and B) Rename the class in the file

and then everything is auto-magically updated for you since the Unique ID didn't change.

Xydium commented 6 years ago

@NathanWarden That's actually closer to what I had in mind: a system that eliminates the dependence on specific files and allows some degree of automatic refactoring. My original post was more about filtering down the options for script files when selecting them, but in general the class/file system Godot uses is tedious to use in cases like accessing another scripts enums due to the lack of static variables, etc.

NathanWarden commented 6 years ago

@Xydium Ah, I think I understand, like having a search feature that would help narrow it down instead of digging through directories or something like that? I think that would be a great idea!!!

I opened a feature requiest for using unique IDs... https://github.com/godotengine/godot/issues/15673

karroffel commented 6 years ago

I would still very much like a system like this.

It would be important to not break old code, so what about a system like this?


Registry

A script registry would scan the project for all detected script files and queries the appropriate ScriptingLanguage for any "exported" classes. An "exported" class would be identifyable by name.

There could be a "low level" API for querying and even procedurally inserting exposed classes.

By accessing the ScriptRegistry singleton it would be possible to get Scripts by name for example.

Namespaces

Since "exported" classes would need to have a unique name namespaces might be optionally needed, using a default "empty" namespace would work too.

GDScript integration

With this design it wouldn't be required to be limited by the file-based architecture that's currently implemented in Godot.

To not break old code, the ScriptRegistry integration should be optional and old code would still behave like it does.

What could be done with this registry in place, is that multiple classes (or in the case of GDScript - inner classes) could be put into one file.

example of usage

fruits.gd


# no "extends" here, will default to Reference then

export class Fruit:
    var name
    func _init():
        name = "some fruit, has not been given a name yet"

export class Apple extends Fruit:
    func _init():
        name = "Apple"

export class Pear extends Fruit:
    func _init():
        name = "Pear"

export class FruitBox:
    var fruit

    func set_fruit(f):
        fruit = f

    func get_fruit():
        return fruit

# for namespaces this could be used
# 
# export("fruits") class ...
# or this for nested namespaces maybe?
# export("fruits", "tasty")

some_other_file.gd

extends KinematicBody2D # maybe a Player controller?

func on_hit_by_object(obj):
    if not obj.is_in_group("has_fruit_attachement"):
        return

    if obj.get_fruit() is Pear: # inheritance check via name instead of preload()
        show_dialog_bubble("Why are you throwing pears? Their shape doesn't even hurt me on impact")
    else:
        show_dialog_bubble("Ohhh you hit me with a ", obj.get_fruit().name)

func on_fire_button_pressed():
    var fruit_box = FruitBox.new() # creates class by name, not preload()
    fruit_box.set_fruit(Apple.new())
    shoot_box(fruit_box)

GDScript would check if an identifier exists in the registry, like this ScriptRegistry.get_class("SomeClass", ["fruits", "tasty"]) and if it exists it can be used. This is similar to how GDScriptNativeClass works, it checks if an identifier names a class known by ClassDB, Autoloads work the same way.


TL;DR:

WDYT?

Xydium commented 6 years ago

@karroffel That would be much more convenient, especially if it works with autocompletion. Static variables would be a nice inclusion too, especially for enums.

willnationsdev commented 6 years ago

Just discovered that I've been working on a similar project. Hopefully somebody else hasn't already been working on this too. XD Gotta figure out how we're gonna coordinate on this mess, cause I'm trying to create one branch that handles this Issue and another.

reduz commented 6 years ago

I think gdscript needs no changes for this. Entire system does not need go become more complex for differences with other systems, or an apparent ease of use that is not.

On Mar 9, 2018 19:35, "Will Nations" notifications@github.com wrote:

Just discovered that I've been working on a similar project. Hopefully somebody else hasn't already been working on this too. XD Gotta figure out how we're gonna coordinate on this mess, cause I'm trying to create one branch that handles this Issue and another https://github.com/godotengine/godot/issues/6067.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/7402#issuecomment-371964960, or mute the thread https://github.com/notifications/unsubscribe-auth/AF-Z20W_kL-5IcDwgujmNV3BuESUIPn9ks5tcwOzgaJpZM4LYlq8 .

willnationsdev commented 6 years ago

@reduz So then, if we wanted a feature of this sort, it would have to be a feature that is exclusive to and part of the /modules/gdscript/ module? Or you just don't want GDScript to support this at all because of the added complexity?

reduz commented 6 years ago

Also selecting class from a global list is imho a terrible idea. You still know better where your file is than where your class is on a large project.

For C#, I still think the best that can be done is marking the main class in the file with some tag, I fail to see why doing this is not possible.

reduz commented 6 years ago

@willnationsdev I see this pointless in GDScript too. I don't think it's needed.

reduz commented 6 years ago

In other words, going from script oriented to class oriented is a definitive no from me.

mysticfall commented 6 years ago

@reduz

Also selecting class from a global list is imho a terrible idea. You still know better where your file is than where your class is on a large project. ... In other words, going from script oriented to class oriented is a definitive no from me. I beg to differ on this one, at least when it involves C# support.

I can understand how GDScript might not need any such changes, as it is a special purpose language which means anything written in that language would presuppose a Godot specific workflow.

But we can't assume the same with C#, or potentially other similar general purpose languages that are not used as scripts. Such a language like C# is never meant to be used by referencing the sources.

It was simply an unfortunate side effect of introducing the C# binding after GDScript - which is more suitable for such a script oriented approach - rather than being a conspicuous choice to provide an optimal workflow for C# users as well.

C# already has a well established convention and mechanism to resolve and manage dependencies which is all assembly/class based rather than script based, and it's the way what most of its tools and libraries presupposes and what the developers are accustomed to.

And unlike GDScript, there exist a vast amount of third party libraries that can be referenced from a C# project in their binary format. Godot currently supports referencing those dlls from the main *.csproj file, but when it comes to addons we still need to find a way to make it possible for a C# project to reference C# addons with its own binary dependencies both within Godot and external IDEs.

If we are to adhere to a workflow that is alien to a typical C# development process, it would make the development set up more difficult and common practices like building your addon from a popular IDE and distributing it as a NuGet package would provide only a limited usage in Godot, because any classes contained in such an assembly cannot be referecend within the editor.

And I disagree that providing a global class list to be a terrible idea. It's the way most of the IDEs supports a similar functionality (i.e. selecting a target class for a unit test), and with auto completion or namespace support, it's pretty easy to find a wanted class that way.

Actually, in most cases, it's much easier than referencing them by their source locations. Developing a C# project can involve dealing with multiple libraries and classes can be located in a deep namespace hierarchy. So, doing a quick search by their names is often much more convenient than having to navigate between different file system locations.

And it's also how Unity supports its C# binding as well, so I think it'd be better if we also support such a workflow that is more common to typical C# development projects including those involving Unity, rather than shoehorning a binary based language into a workflow designed for a scripting language.

eon-s commented 6 years ago

With optional typing, something like syntatic sugar but for classes can be useful to make better code.

Writing

func a_function(load("res://some_directory/sub_directory/character_weapon.gd"): weapon)
  var load("res://some_directory/sub_directory/character.gd"):character = load("res://some_directory/sub_directory/character.gd").new()
...

may look ugly, probably :thinking:

willnationsdev commented 6 years ago

Idk if the static typing was meant to include scripted types (not privy to any of that debate). If it does though, I'm guessing that in that case we'd have to use something without identifiers of any kind, like...

const MyType = preload("res://dir/dir/another_dir/MyType.gd")

func my_func(p_obj: MyType):
    # do something with p_obj where (p_obj is MyType) == true  
eon-s commented 6 years ago

@willnationsdev I know, also the whole thing an be made by a plugin that registers every new GD file in a singleton (writing consts in the script) for global access, but something integrated may be better.

willnationsdev commented 6 years ago

@eon-s I thought of that too. It's basically a hack, but it'd be MUCH better than nothing. I already set it up so that EditorPlugins can add/remove singletons. Now, if I can submit a PR for GDScript so that it can extend from a Script object instance and not just a string literal filepath, then that'd be something. The GDScript module would have to be able to see into the Editor somehow to look them up though (or have them added to the global map).

Xydium commented 6 years ago

@reduz I agree that my original idea at the top of this thread is mostly pointless, and only a convenience feature to avoid clicking through folders. However, with the suggestion from @karroffel, the discussion shifted to file-versus-class as the basis for GDScript. Since his recommendation was an entirely optional feature, a 'definitive no' is slightly heavy-handed. One of the main issues I've had with GDScript is the way scripts are handled as loaded files, and thus require explicit load statements at the top of other scripts for all use cases. Changing the location of a script file means changing all the scripts that reference its path. While that is a similar issue as with import statements in Java, most Java IDEs offer a refactor tool that automatically updates such references, and an auto-import tool that removes the need to remember class locations. As far as I know, Godot's file-mover does not modify paths written in scripts, and while autocomplete does provide script paths, the suggestions aren't filtered down to a specific class name. Loading and storing countless different scripts to do instance-of checks or instantiation is far less elegant than an import namespace.* statement. GDScript is already quick and easy to learn, and an optional feature that resembles other popular languages would assist those coming from a Java/C# background.

Static-typed GDScript classes could instead be a different 'script type', like C# Script, NativeScript, and VisualScript. GDScript (.gd) would be for the existing file-based system, and GDClass (.gc) could be for a registered class with a namespace. The format for such a file might then resemble:

namespace enemies.ranged
import enemies.*

class Tank extends RangedEnemy:
    const MOVE_SPEED = 100 #All consts are static by default

    static var count = 0 #Static vars would also be a nice feature

    var name

    func _init(name):
        count += 1
        self.name = name

    func _process(delta):
        position.x += 100 * delta

If necessary, file scripts could still reference a class script either with import statements or by loading the file, and class scripts could reference file scripts by loading them as well. This ability likely would not be commonly used though, as most developers would select and stick with one paradigm for a project.

reduz commented 6 years ago

First, It's not heavy handed, this is about making a game engine, so the priority is that it's simple and performant.

Second, the use case is rare at most. It's only for assigning scripts to nodes. I'v worked on pretty large projects and only did this once.. twice maybe? 99% of the time you create new scripts, rarely assign them.

So, bloat for something rarely used is difficult to justify for me.

reduz commented 6 years ago

Sorry, did not mean to close.

mysticfall commented 6 years ago

If we are going to separate the 'file vs class' aspect from this issue and treat it purely as a usability problem for GDScript, maybe we should reopen #15661 and continue the discussion there?

While I might agree that it could be less of a problem for GDScript, I believe it's not simply a matter of convenience for C# projects though.

reduz commented 6 years ago

I don't really see it as a problem for C# either, the rationale is the same.

1) 99% of the time you create scripts, not assign them. 2) Class based makes 1) more complex

I just don't see any justification for the use case.

Xydium commented 6 years ago

@reduz I don't believe that @karroffel's idea would sabotage simplicity or performance. Godot now has four scripting languages, and a binding API for almost any other language. Contrast that with Unity's choice to drop support for anything but C#. Giving users options may increase the length of a drop-down menu, but allowing users to stick with what they already know makes everything else simpler.

However, unofficial languages will never be as convenient or well-suited to Godot as GDScript. Even the well-implemented C#/Mono binding has additional implementation and compilation challenges. Providing more ways to use GDScript, instead of bailing to another language, would simplify the user experience.

The issue is no longer just about assigning scripts from paths versus from qualified names. It's about writing scripts too. With my background in Java, const Class = preload("res://script.gd") is annoying, especially if I need to load multiple scripts:

const enemy_types_path = "res://scripts/enemies"
const enemy_type_names = ["tank", "soldier", "aircraft"]
var enemy_types = load_enemy_types()

func load_enemy_types():
    var types = []
    for name in enemy_type_names:
        types.append(load(enemy_types_path + name + ".gd"))
    return types

Versus:

import enemies.*

Registered classes can still behave like script objects, in the sense of being able to store them as variables and in arrays:

namespace spawners

import enemies.*

export class EnemySpawner extends Spawner:
    var enemy_types = [Tank, Soldier, Aircraft]

    func _ready():
        var random_enemy = enemy_types[randi() % 3].new()

@mysticfall, yes this issue has now seperated from #15661.

mysticfall commented 6 years ago

@reduz Let me elaborate then. Let's assume I have MyProject which references 3rdPartyAddon both of which are C# projects.

In a typical C# development, all I need to do is adding the reference of 3rdPartyAddon to MyProject within an IDE, preferrably by using something like NuGet.

However, such a use case is currently impossible with Godot, if it is needed to reference a class from 3rdPartyAddon within the editor. So, I also need the source bundle of the 3rdPartyAddon project and extract it under addons and reference them by source files.

But not only that, I also need to edit MyProject.csproj to include every source files from 3rdPartyAddon to make it compile within the IDE, and also find any external assembly references 3rdPartyAddon.csproj it may contain and manually add them to the main project as well.

This is far from how a typical C# developer would want to manage their projects. It's inconvenient enough for a single addon, but it'd be quite a problem when you have to use large number of addons and have to keep the source/library references in MyProject.csproj every time you add/remove/update them.

And I strongly disagree that people would very rarely assign scripts to existing nodes. Godot currently don't have tons of 3rd party addons like Unity has, but I believe (and hope) we will get there eventually.

And when I used Unity, I've used many framework or template type assets which provide scripts that you can attach to your own nodes. Even in Godot, I assign different classes to a node all the time, at least while I'm developing. So I believe it's not something we can assume to be true for all developers and all their projects.

If we ever become popular and have a growing ecosystem like Unity current has with their Asset Store, it'd be much more common to see projects that rely on many different types of addons and in large numbers.

In that case, navigating into a few different deep hierarchies (i.e. Addons/3rdPartyAddon/Scripts/Com/Acme/Controller/PlayerControl.cs) every time you assign a node can quickly become cumbersome.

At the very least, I believe it is best to support a workflow that is well established and optimized for each individual bindings, rather than simply assuming every binding languges for Godot would work like GDScript does when forcing them when they don't.

reduz commented 6 years ago

@Xydium Again, I apologize, but will not add more complexity to GDScript to make it work like Java. It may bother you but this is still not a common case scenario and, for most users, GDScript is much simpler than Java.

I think there are more chances to get Java to work in Godot rather than doing this. Also If you are doing this in C#, I don't quite get why it wouldn't work.

@mysticfall This is clearly a problem for the case of a third party add-on that comes compiled, but i can imagine much simpler work arounds to this that do not require changing how Godot works. Also again, no matter how it works, that add-on will most likely register the custom types for you, so you may not need to assign them anyway.

reduz commented 6 years ago

Again, I would like to discuss regular use cases that are not corner cases. I don't want to discuss implementation complexity.

To me, the way it currently works, is fine in 99% of use cases, and usability is better than using classes. Rare use cases like the ones described should be workaroundable. Throwing away the system that has the best usability for something not often used really does not make sense to me. This is why I'm completely against it.

Xydium commented 6 years ago

@reduz That's fair, though I'm not looking for GDScript to become like Java, nor would I find Java preferable in any way to GDScript.

I consider this proposed feature more like the Built-In Script option. Built-In Script is an additional setting, thus, adds complexity, however it also simplifies things greatly by eliminating the requirement for a separate file when only implementing a handful of basic functions. There are projects which may never make use of it, but the feature exists for those that need or prefer it.

Similarly, class-based scripting can significantly simplify writing scripts (as shown above). Instantiating objects from scripts is not a corner case. It's fundamental to object-oriented programming. The above example of loading multiple scripts and storing them in an array has appeared in almost every project I have worked on in Godot, from platformers to space-shooters to real-time-strategy games. There are more situations than just that where classes would simplify script-writing. It should not replace the current file-based system. The two can coexist, and users can choose whichever they find more convenient for the use cases they encounter.

To further prevent any confusion for end-users, the class system could be disabled by default, and require explicit enabling from the project settings.

willnationsdev commented 6 years ago

For GDScript at least, one way that this could be faked a bit is by declaring all of the scripts as constants in a singleton. The only thing you would need in order to make it work is allow people to inherit from a Script object instance declared in a Singleton's context.

reduz commented 6 years ago

@Xydium Actually, I did my best to avoid built-in from being used, because it generates some problems that need workarounds. It exists "for free" due to how Godot works, there is zero extra code to make it happen. Originally it was not possible to create a built-in script , but the option was added because users still really wanted it and it was pretty much a line of code (in the new script dialog). If they have problems with it, it's up to them.

I really don't see how it can simplify writing scripts, I only see a LOT more code complexity required for something that will definitely not be used often. I just can't see it being worth it.

mysticfall commented 6 years ago

@reduz Adding custom types would be nice, but I'd much like it to be a feature rather than something required only for C# (or other similar non-scripting languages) when GDScript doesn't suffer the same limitation.

And even if we can let users to use custom types as a workaround, there still remains an issue of forcing users to manually merge and maintain source lists for every addons referenced in MyProject.csproj.

Unity does this by automatically parsing and generating .csproj file whenever something is changed in the project. I don't think this to be the best way to go as it's error prone and not really the idiomatic way of doing things in C#, and I'm not sure it'd be much easier either.

reduz commented 6 years ago

@willnationsdev if you want a script as a global class in GDScript, this is really easy to do and I thought about it many times, but it would be GDScript-only. I'm sure it would aid in usability for more complex projects.

reduz commented 6 years ago

@mysticfall it would work exactly as GDScript, the custom types are a C++ Node/Resource + Script, custom nodes/resources don't magically become part of the engine in GDScript either. This is on purpose.

In fact, it's easier to do this in C# than in GDScript at the moment.

Xydium commented 6 years ago

@reduz So there is a precedent of popular support overriding simplicity?

Here's full details on the use case I explained above:

I need a node to spawn different types of enemies. These enemies exist as scripts, and are a subclass of some generic Enemy which inherits from Node2D. In order to instantiate a script, I need a reference of some kind to it. In the current file-only system, I need to explicitly load every enemy subtype from each file path, and store it in an array. Each enemy file path consists of the directory, the enemy name, and the file extension. Consider the following hypothetical directory tree:

res://
    enemies
        ranged
            trebuchet.gd
            soldier.gd
            tank.gd
        melee
            mech.gd
            swordsman.gd
        support
            medic.gd
            transport.gd

As stated, I need to load each of these scripts from their paths. This is made worse by the fact that I organized enemies in folders by type, to keep things simple.

So, I could just write out explicit constants for each, and then put them in an array. Each time I make a new enemy type, I need another constant, and another load statement:

const TREBUCHET = preload("res://enemies/ranged/trebuchet.gd")
const SOLDIER = preload("res://enemies/ranged/soldier.gd")
#I was going to type these all out, but the fact that I don't want to proves my point
var enemy_types = [TREBUCHET, SOLDIER, ...]

To be clever, and to make adding new enemies slightly simpler, I can store the different directories and enemy types as strings in arrays, and then load them with a loop.

const ENEMY_TYPES = [
    "ranged", ["trebuchet, soldier, tank"],
    "melee", ["mech", "swordsman"],
    "support", ["medic", "transport"]
]

var enemy_types = load_enemy_types()

func load_enemy_types():
    var enemy_types = []
    for dir in range(0, 6, 2):
        for type in ENEMY_TYPES[dir + 1]:
            enemy_types.append(load("res://enemies/" + ENEMY_TYPES[dir] + "/" + type + ".gd"))
    return enemy_types

Contrast either of those techniques with:

import enemies.ranged.*
import enemies.melee.*
import enemies.support.*

var enemy_types = [Trebuchet, Soldier, Tank, Mech, Swordsman, Medic, Transport]

#Or, if class objects need to be explicit:

var enemy_types = [Trebuchet.class, Soldier.class, Tank.class, ...]

If this class-system existed, I would use it almost exclusively, not just in convenient use cases. I've heard rumors that statically-typed GDScript may also be implemented in the future, which I would use almost-exclusively too. The two together would make a valuable addition to the engine.

mysticfall commented 6 years ago

@reduz Thanks for the explanation. But in that case, probably it's less relevant to the problem that I initially mentioned.

All I want to do is a way to write a Godot addon in C#, and allow other people to either extend its functionality by extending some classes to create their own, or assign them to nodes in their projects.

In order to achieve that, I need to tell them something like "Download the source bundle and extract it somewhere, then open MyAddon.csproj to copy all Compile and Reference tags to merge them into your own .csproj file, and you'll need to do it again whenever you upgrade it or you add/remove any other addon you may use", which is obviously not an optimal workflow for anyone.

And if I'm not mistaken, Godot's C# binding already tries to translate file paths into class names before asking Mono to resolve them. Currently, it seems that it's broken for addons because it doesn't use a proper namespace/domain for them.

In other words, we still need to find some way to map file paths to class names properly anyway unless we want all addon writers to follow some weird namespace convention like Addon.MyAddon that exactly matches the file path.

I haven't looked into the C++ side much yet, but if I recall correctly, we already made it each binding's responsibility to resolve types from script references. Then why can't we also ask each bindings to present suitable list or hierarchy of types, so that C# can implement it using Mono's API that queries exposed types in each assembly, while GDScript can simply list all *.gd in the project path?

I don't know if it would be a much more difficult work, compared to writing some parser/generator for *.csproj that detects every file system changes like Unity does.

willnationsdev commented 6 years ago

if you want a script as a global class in GDScript, this is really easy to do and I thought about it many times, but it would be GDScript-only. I'm sure it would aid in usability for more complex projects.

@reduz Wait, really? What I suggested would be okay? I don't see how that would be very different from registering names / namespaces in a HashMap for each language though. The entire concept of a TypeDB that could coalesce names and namespaces for all types of scripts and/or scenes is basically doing the same thing as using singletons, only the singletons route is LESS performant, not more (as far as I'm aware):

  1. The editor creates a singleton node that has the same name as your project name.
  2. If you add a plugin, the editor creates a singleton node with a name for the plugin (defined in the plugin.cfg). All of the generated singleton names are UpperCamelCase.
  3. Any time a script file is saved, a new constant with an UpperCamelCase name is automatically added to the script that represents the singleton node. (would use the singleton that is most relevant, i.e. a script file in a plugin directory would go into that plugin's singleton script, etc.).
  4. Enable GDScript and VisualScript to inherit from a Script object instance directly rather than having to supply a string literal filepath to them.

If you followed those steps, then you'd be able to do something like this:

//project my_project
my_project.godot
    addons
        my_plugin
            plugin_type.gd
project_type.gd
test.gd

# test.gd
extends MyPlugin.PluginType

func _ready():
    var obj = MyProject.ProjectType.new()

This would actually require minimal changes to GDScript, and I'm guessing you'd be able to do something similar to make VisualScript be able to access the singleton nodes by name as well.

So, if something like that would be permissible, how is that better than a centralized type / script database in core? I mean, with this version, any time you made changes to a script, the "namespacing" script would have to be completely reloaded, causing you to interpret the file and recreate all of the different constants, etc. Whereas if it's all in a core db of some kind, it's just a simple HashMap insertion in constant time, which is WAY faster than anything else.

reduz commented 6 years ago

@Xydium I understand your problem perfectly.

So, if I I have to choose between adding a lot of code complexity so your tiny bit of code, that you will not even use often, is snaller.

Or..

Leave as is, and you write a bit more code in this particular case.

Put yourself in my shoes. What do you think I should do?

reduz commented 6 years ago

@mysticfalls I think this is a mono specific problem that would need to be discussed with @neikeq. I personally dont understand why the restriction of same file name as class is in place. I imagine it should be possible to do away with it.

Xydium commented 6 years ago

@willnationsdev TypeDB? Do you mean something like ClassDB? The singleton database of classes that was renamed and made accessible from scripts three days after I opened this issue in 2016 as per commit ce26eb7?

willnationsdev commented 6 years ago

@Xydium It was a reference to this other issue I recently created. #17387

reduz commented 6 years ago

@willnationsdev I had something much simpler in mind, but I have the feeling this thread is kind of a mess, because each of you have a different idea in mind of what you need..

willnationsdev commented 6 years ago

XD right. Well, @Xydium's issue seems pretty similar to mine in the sense of adding namespaces / typenames to GDScript for usability purposes. Oh, but I see he wants to bring into the current namespace a subset of types with a single line statement...gotcha.