Skip to content
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

Implement namespaces in GDScript #1566

Open
me2beats opened this issue Sep 25, 2020 · 25 comments
Open

Implement namespaces in GDScript #1566

me2beats opened this issue Sep 25, 2020 · 25 comments

Comments

@me2beats
Copy link

Describe the project you are working on:
GDScript plugins

Describe the problem or limitation you are having in your project:
class_name is a great feature and I use it often when making plugins.

But there are at least 3 problems which can occur when using class_name:

  1. If you use a custom class named say Util when creating a plugin, there is absolutely no guarantee that the user is not using a custom type with the same name.
    So you will get

    Unique global class "Utils" already exists

  2. As the number of your custom classes gets large (imagine a room with several hundred items on the floor), you will most likely want to structure them by dividing them into logical parts (arrange all items on their shelves) for easier access. But in this case, you will face various challenges, and it seems that none of the solutions will be truly convenient
    especially if you want to keep having each class separately (i.e. don't use nested classes)

  3. Littering the global namespace as a whole.
    all custom classes are visible in the global namespace. The user might mistakenly try to use your class.
    also a large number of custom classes clogs up the autocomplete.

thus, having a large number of custom classes is incompatible with ease of development, which, as the code base of the project grows, will force the developer to abandon their use or resort to hackу workarounds.

However, this deprives the user of the benefits of using class_name.
Perhaps custom classes are the most convenient way to have classes as separate files that need global access.
It's just convenient. For example, I have many utility classes with static methods as custom classes.

The problem is especially acute when creating plugins.

Describe the feature / enhancement and how it helps to overcome the problem or limitation:
Implementing namespaces system could solve the ploblem.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
I imagine it as a new keyword - namespace
For example
a.gd:

namespace X
class_name A

b.gd:

namespace X
class_name B

in this case, access to classes A and B could be done as follows:
new_script.gd:

func _ready():
  print (X.A)
  print (X.B)

when trying to get class A from the global namespace, an error will appear:
new_script.gd:

func _ready():
  print (A)

Error: The identifier "A" isn't declared in the current scope

As you can see, several scripts can use one namespace, which is one of the main advantages of namespaces over custom classes

using the same class inside the same namespace should lead to error:
a.gd:

namespace X
class_name A

a_new.gd:

namespace X
class_name A

Unique global class "A" already exists in namespace: X

Another important advantage of namespaces is the ability to use nested namespaces.

c.gd:

namespace X.Y
class_name C

new_script.gd:

func _ready():
  print (X.Y.C)

If namespace is not specified (keyword namespace is not used), then just the global namespace is used (as usual).
That is, when creating a script d.gd with class_name D, class D will be visible in any script.
d.gd:

# namespace not specified, use the usual scenario
class_name D

new_script.gd:

func _ready():
  print (D)

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

Is there a reason why this should be core and not an add-on in the asset library?:
Implementing this feature not at the core level would hardly be a good solution.

@Xrayez
Copy link
Contributor

Xrayez commented Sep 25, 2020

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

especially if you want to keep having each class separately (i.e. don't use nested classes)

On the project level, it's kind of possible, since you can create a single global class to load other classes (not necessarily inner classes), see concrete example in WAT/namespace.gd.

# utils.gd

# Note: no class_name here!
extends Node

static func say_hello():
	print("Hello!")
# namespace.gd
class_name Game

const Utils = preload("res://utils.gd")
# main.gd
extends Node2D

func _ready():
	Game.Utils.say_hello()

Not sure how efficient such solution would be compared to just using class_name at run-time, since it takes an extra "namespace resolution" with such approach.

Avoiding using too many global classes can also speed up editor performance in Godot 3.2+: godotengine/godot#27333.

But yeah namespace issues in general are must have to resolve if we aim for proper plugins support, and we have similar issues with C++ namespaces when it comes to exposing core classes to scripting, with some solutions: godotengine/godot#26990.

@Calinou
Copy link
Member

Calinou commented Sep 25, 2020

As a workaround, you can add a prefix to your class names if you expect your class names to conflict with other plugins (or user code). Even a single letter will do, be creative 🙂

That said, if the class name isn't a single proper noun, it makes conflicts much less likely already. So far, I've mostly seen this be an issue in utility classes, not so much advanced nodes where you're unlikely to use several add-ons implementing the same feature.

@AlexDarigan
Copy link

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

especially if you want to keep having each class separately (i.e. don't use nested classes)

On the project level, it's kind of possible, since you can create a single global class to load other classes (not necessarily inner classes), see concrete example in WAT/namespace.gd.

@Xrayez To an extent you can do that but 1) Cyclic References, so you can't be comprehensive and 2) This can still conflict with inner classes of the classes in that namespace, such as this state class conflict issue.

Namespaces are essentially mandatory for plugin creators.

@Xrayez
Copy link
Contributor

Xrayez commented Sep 28, 2020

Namespace collisions with already mentioned testing frameworks: bitwes/Gut#215. 😃

@Spartan322
Copy link

Spartan322 commented Sep 29, 2020

Also makes it more comparable to C# and increases organization of projects without cluttering the global space, tho at this point wouldn't it be better to stop relying upon files as classes? Its already a chore that if you need to keep using a bunch of classes, every single one has to have a class_name or preload (which tbh ends up requiring load most of the time because cyclical dependency issues) so with any moderately sized project you not only start running into global bloat in code that becomes difficult to manage, (especially without a dedicated IDE for GDScript) but massive amount of boilerplate that seems counterproductive. If I was to use GDScript, I would really hate to use "C-style namespaces", seems a bit poor of a solution especially in the modern environment where we're emulating python an awful lot.

@Xrayez
Copy link
Contributor

Xrayez commented Oct 6, 2020

See previous related discussion in godotengine/godot#21215.

@Calinou Calinou changed the title Implement namespaces Implement namespaces to avoid collisions in third-party add-ons Oct 6, 2020
@FrederickDesimpel
Copy link

FrederickDesimpel commented Oct 9, 2020

Really would like to see this happening.

I'm staying away from class_name (name_global ?) because of this, and also find the cyclic dependencys are a headache.

Why can't I type a member in a class to itself ( say to have a ref to a parent of the same type ), if only for typing/autocomplete. As long as you don't instance it i don't see why that would give a cyclic dependency. I also have it sometimes when i want to type a return value.

I tried all kinds of things... autoload with nodes seems to work well, but it always breaks at the last step. haven't tried one global class_name as the namespace yet. I don't think you can do nested namespace (name_space ?) with that workaround either ?

Tried a const preloaded .tres too, since that is loaded only once... but tres is not the expected type,... or something.

As a workaround, you can add a prefix to your class names

A workaround indeed. With proper namespace support you can have something like author or studio initials and plugin_name or whatever as the first part, instead of on all the scripts.

@FrederickDesimpel
Copy link

FrederickDesimpel commented Oct 9, 2020

Also, when considering name_spaces, it would be nice to be able to have 'imports' with shorthands, like in

import My_Stuff.Nested.NameSpace.Class as MyClass

SomeObject : Myclass
Myclass.whatever()

which would basically be like working with the consts now.

could also be keyword uses or using, since it's not really an import.

@PranavSK
Copy link

We could have a namespace approach if we can implement this #1709 where the folder structure could be determined at compile time and use that as namespaces. This also works well with Godot’s approach of organising assets by scene and logic.

@dueddel
Copy link

dueddel commented Dec 31, 2020

I appreciate the idea of introducing namespaces. I come from other languages having namespaces and already wondered if this is possible in GDScript. Found out it isn't. That being said, I upvote your request, @me2beats .

For now I go for the workaround of prefixing my classnames as it has been done 10 years ago in PHP, for instance.

So, I'd have something like class_name MyDomain_Util_WhateverThisClassIsCalled. Not very beautiful, but at least avoiding name collisions.

The workaround as suggested by @Xrayez is somehow neat and odd at the same time. "Neat" because I like how the classes can be used, like MyDomain.Util.WhateverThisClassIsCalled.new() which looks by far better than using underscores. But "odd" because of writing the extra classes to achieve these "namespaces" is kinda intricate.

As of writing this I am not sure what feels better in the end. 🤔

Just make namespaces happen! 😉

Also, I agree to @FrederickDesimpel. If namespaces are introduced to GDScript, some import feature is needed as well.

@quantumcode-martin
Copy link

Anything new about namespaces ?

@Calinou
Copy link
Member

Calinou commented Jun 2, 2022

Anything new about namespaces ?

GDScript in 4.0 is in feature freeze as the goal is now to stabilize it. Therefore, no new features will be added to it until 4.1 at least.

@me2beats me2beats changed the title Implement namespaces to avoid collisions in third-party add-ons Implement namespaces in gdscript Jul 19, 2022
@me2beats me2beats changed the title Implement namespaces in gdscript Implement namespaces to avoid collisions in third-party scripts Jul 19, 2022
@me2beats me2beats changed the title Implement namespaces to avoid collisions in third-party scripts Implement namespaces in GDScript Jul 19, 2022
@me2beats
Copy link
Author

me2beats commented Jul 19, 2022

Changed to Implement namespaces (removed to avoid collisions in third-party add-ons)
because add-ons is not the only reason to have the feature.

For example you have many classes and you want to group them.
Refactorings scripts so that they will use classes as namespaces may be a so-so idea, because you may not want to have big scripts

@AlexDarigan
Copy link

godotengine/godot#67714 has effectively solved this issue by allowing users to create a named class of preloaded consts to other classes. There are a few elements that might cause a problem, like a global State class will conflict with a non-global State variable but overall the PR has added significant ability for namespacing.

@red1939
Copy link

red1939 commented Nov 30, 2022

@AlexDarigan Does is mean casting to such const X = preload() type will work now?

@AlexDarigan
Copy link

@AlexDarigan Does is mean casting to such const X = preload() type will work now?

Seems like it.

@Pyxus
Copy link

Pyxus commented Jan 4, 2023

To add to the discussion. Using class names and preloads to fake namespaces is the approach I've taken in my addon, and it works well enough. However, in 4.0 class names seem to be required to take advantage of the new custom resource exports, and the doc comment's class referencing. I suppose that would be resolved if they were changed to not require class names but namespaces in GDScript would still be ideal.

@AlexDarigan
Copy link

Yeah. I've come across the issue with documentation comments. I don't think it is an unfair ask for plugin developers have some way to prevent polluting the global namespace.

@derkork
Copy link

derkork commented Jul 3, 2023

You also need to have a class_name if your want your custom nodes to be showing in the node tree in the "Add node" dialog.

@derkork
Copy link

derkork commented Jul 3, 2023

I suppose that would be resolved if they were changed to not require class names but namespaces in GDScript would still be ideal.

It would still be a problem if users of the addon would need to use the class name in their scripts. E.g. if you have a class Flubbermator in your addon which is a node and the users would want to use this in their script like this:

@onready var flubbermator:Flubbermator = %Flubbermator

then they need a proper class name unless they also do this preload thing which I personally find hard to maintain. When using class_name it doesn't matter where my file resides, but using preload or load requires me to hardcode the path which makes every move of a script a breaking change. Ultimately I think namespaces are the only real solution to this. A workaround is prefixing all addon classes with something which doesn't help readability.

@nice-shot
Copy link

nice-shot commented Oct 3, 2023

Adding to what @Pyxus wrote, it's very possible to effectively have namespaces by using the @preload keyword but the fact that custom resource exports isn't available means that it's extremely limited for creating things like component systems.

A simple use case for me will be:

const HealthComponent = preload("./health/health_component.gd")

@export var health: HealthComponent

However, in the editor this will not let me select the relevant node. Only by using class_name will the node be selectable.

ExportCustomComponent

Is it possible to allow this method to work without adding the more complex namespace concept to gdscript?

@adamscott
Copy link
Member

Adding to what @Pyxus wrote, it's very possible to effectively have namespaces by using the @preload keyword but the fact that custom resource exports isn't available means that it's extremely limited for creating things like component systems.

A simple use case for me will be:

const HealthComponent = preload("./health/health_component.gd")

@export var health: HealthComponent

However, in the editor this will not let me select the relevant node. Only by using class_name will the node be selectable.

ExportCustomComponent

Is it possible to allow this method to work without adding the more complex namespace concept to gdscript?

Could you try again? I tried in 4.2 and it works flawlessly.

image

MRP: home-namespaces.zip

@nice-shot
Copy link

Could you try again? I tried in 4.2 and it works flawlessly.

This does not work. I looked at your MRP and you've added class_name to health_component.gd so it only works because of that.

@hayahane
Copy link

hayahane commented Jun 26, 2024

It seems like @export doesn't allow non-global class to be exported in 4.3beta2. This makes namespace a needed feature.
image
image

@AThousandShips
Copy link
Member

See:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests