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

Const arrays and dictionaries are static variables in diguise #61274

Closed
Trioct opened this issue May 22, 2022 · 3 comments · Fixed by #71051
Closed

Const arrays and dictionaries are static variables in diguise #61274

Trioct opened this issue May 22, 2022 · 3 comments · Fixed by #71051

Comments

@Trioct
Copy link
Contributor

Trioct commented May 22, 2022

Godot version

4.0.dev (4173a47)

System information

5.17.5-gentoo (Linux)

Issue description

Any array or dictionary like const x := [3,2,1] acts like a static variable. If edited anywhere from any scope, function, or instance, x is permanently changed across every other use of it. This is because the compiler wants to substitute in constants for its identifier in code, where the constant is actually an address to a constants vector.

Steps to reproduce

Example 1:

for i in range(4):
    const x := []
    x.append(i)
    print(x)

Expected: [0] [1] [2] [3]
Actual: [0] [0, 1] [0, 1, 2] [0, 1, 2, 3]

Example 2:

func test():
    const x := []
    x.append(1)
    print(x)

func _ready():
    test()
    test()

Expected: [1] [1]
Actual: [1] [1, 1]

Example 3:

class Test extends Object:
    const x := []
    # var x := [] also works here

func _ready():
    var test1 := Test.new()
    var test2 := Test.new()
    test1.x.append(1)
    print(test2.x)
    test1.free()
    test2.free()

    var test3 := Test.new()
    print(test3.x)
    test3.free()

Expected: [] []
Actual: [1] [1]

Minimal reproduction project

No response

@ExplodingImplosion
Copy link

ngl this is something i been dealing wit for multiple months and just thought this was intentional LMFAO

@and3rson
Copy link
Contributor

and3rson commented Oct 1, 2022

I found it very convenient to use const dictionaries to share data between instances of the script, like a node-bound global variable, or a "static" variable.

I really hope this is intended and won't be removed in the future since it offers awesome features:

  • It eliminates the need of having some sort of Global singleton.
  • It nicely encapsulates node-specific "global" variables without polluting the actual global scope.

For example, consider this code for a debouncing AudioStreamPlayer2D which prevents multiple sounds from playing simultaneously and creating a loud sound:

extends AudioStreamPlayer2D
class_name AdvancedAudioStreamPlayer2D
tool

export var debounce = 0.1

func play(from_position: float = 0.0):
    var name = self.stream.resource_path
    var key = 'debouncer:%s:last_used' % name
    var last_used = Shared.get_value(key, -INF)  # <- by trying to "avoid global variables in Godot",
                                                 # we had to commit to a greater evil:
                                                 # depend on a singleton which is just one messy
                                                 # amalgamated global variable with few extra steps.
    if Util.since(last_used) > debounce:
        .play(from_position)
        Shared.set_value(key, Util.time())

And here's how it looks like with shared const dict:

extends AudioStreamPlayer2D
class_name AdvancedAudioStreamPlayer2D
tool

export var debounce = 0.1

const sounds_last_used = {}

func play(from_position: float = 0.0):
    var name = self.stream.resource_path
    var last_used = sounds_last_used.get(name, -INF)  # Isn't this cleaner than a global singleton?
    if Util.since(last_used) > debounce:
        .play(from_position)
        sounds_last_used[name] = Util.time()

The second option doesn't depend on the global Shared singleton and nicely contains its own shared state in sounds_last_used. Won't you agree that it's better?

The only thing that grinds my gears is the fact that I'm mutating a const value.

@gokiburikin
Copy link

I just happened upon this behaviour and find it useful. Since it's marked as a bug, if I exploit this should I expect to have to rewrite whatever systems use it since afaik there is no static member variable implementation to replace it?

Similar to the above, I'm being a menace and using a const array to keep track of all instances of a custom class and a static function to be able to access the array from the outside (since accessing LineTrailer._line_trailers returns an empty array, furthering the case that this is indeed a bug):

class_name LineTrailer extends Object

const _line_trailers:Array[LineTrailer] = []

func _init():
	_line_trailers.append(self)

static func all():
	return _line_trailers

I'm likely breaking a bunch of conventions but it sure is convenient.

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

Successfully merging a pull request may close this issue.

5 participants