synthnostate My main complaints are the GUI system, 3D/physics performance, and instability in every sense of the word.

What exactly do you mean with instability? Instability of the editor application or of the game build?

    One thing I don't think Godot's documentation makes obvious enough for newbies:

    Strings are often not just Strings! Often times you will come across examples of functions being called via String, when really what the function takes is a StringName or a NodePath

    StringName is used commonly in places like Input where you want to compare strings.

    var did_jump = Input.is_action_pressed("jump")

    This code is actually using the String "jump" to construct a StringName. Each StringName points to the same location, so two StringNames can be compared for equality far faster than normal Strings can. However, creating a StringName from a String is itself kinda slow. A good practice is to cache your StringNames so you only construct them once. Here's one way:

    @export var jump_str := StringName("jump")
    #later...
    var did_jump = Input.is_action_pressed(jump_str)

    This way all the engine is essentially doing is checking an int comparison!

    NodePath is used by functions like get_node.

    Calling get_node with a String will construct a NodePath with the String. To make things quicker and less prone to error, one thing you can do is exporting the NodePath.

    @export var path_to_some_node:NodePath

    This will appear in the inspector as a field you can drag and drop another node into, and if that Node's name or location changes, the NodePath will update automatically.

    Resources in the filesystem don't have their own unique kind of String, but you can still get similar automatic behavior using the export alternatives @export_file and @export_dir. When the resources are moved within Godot, the exported Strings will be updated accordingly. If something goes wrong, say for instance the files get moved in Git but the references weren't updated, Godot can try to automatically repair the broken paths.

      One final thing though, as to the question

      "How refactor-friendly is Godot really?"

      Less friendly than Unity. GDScript doesn't have great refactoring support. C# and C++ are much better in that regard. Certainly with Strings, sometimes references can break, even if you're careful, and if you have a large project it can be difficult to refactor all the uses of a particular string. Personally I like to overcome this by keeping my strings in one place and then referencing those variables. For example, my Config class contains StringName variables for every InputAction I have named, and I can access those from anywhere.

      award This code is actually using the String "jump" to construct a StringName. Each StringName points to the same location, so two StringNames can be compared for equality far faster than normal Strings can. However, creating a StringName from a String is itself kinda slow. A good practice is to cache your StringNames so you only construct them once. Here's one way:

      @export var jump_str := StringName("jump")
      #later...
      var did_jump = Input.is_action_pressed(jump_str)

      This way all the engine is essentially doing is checking an int comparison!

      Yup, compilers and interpreters use this method heavily, they usually call them "interned strings".

      There's a stringname literal syntax (at least in 4.x), you just put a & in front of the string. Should be just as efficient.

      var did_jump = Input.is_action_pressed(&"jump")

        MeisterDerMagie What exactly do you mean with instability? Instability of the editor application or of the game build?

        Every sense of the word.
        Editor crashes.
        Scene corruption.
        API changes in every minor release.
        Renaming nodes when you reimport a scene after upgrading, because they changed the way Godot handles disallowed characters (replacing them with underscores and such) because they didn't get it right the first time, because they rush out releases without thinking about that stuff or giving beta testers a chance to notice these sorts of issues that don't crop up immediately.

        synthnostate var did_jump = Input.is_action_pressed(&"jump")

        Wouldn't this still construct the StringName anew each is_action_pressed call? Or does GDScript do some smart constifying?

        • xyz replied to this.

          award synthnostate var did_jump = Input.is_action_pressed(&"jump")

          Wouldn't this still construct the StringName anew each is_action_pressed call? Or does GDScript do some smart constifying?

          Very likely the latter. Otherwise it would nullify the benefits of using immutable strings in the first place. GDSScript does the same with resource preloading. It makes them into "const" references to resource objects prior to runtime.

            xyz Otherwise it would nullify the benefits of using immutable strings in the first place.

            Well, it would nullify the benefits of using immutable strings as variables initialized within a function. You'd get the benefit as long as you cache the StringName in a member variable. I want to find out for sure, because if you're right then obviously there is no need for me to continue caching them.

            • xyz replied to this.

              award If you construct and initialize a string variable, there's no benefit. Every time your code hits a var declaration, a variable will be constucted. However, a literal without var is a compile-time constant and it's not constructed at runtime.

              When you initialize a string it's totally irrelevant whether you use a regular string literal or a string name. They're both immutable string constants, sitting all the time somewhere in memory. So the following are exactly the same in terms of what gets constructed. A single variable gets constructed and is initialized from an immutable constant:

              var s: String = "text"
              var s: String = &"text" 
              const s: String = "text"
              var s2: String = s

              The only real performance benefit of string names you get is when comparing two string names, or doing some kind of hashing with them.

                xyz No, &"text" is a pointer (a 64 bit integer)

                When the engine loads .gd scripts it compiles them to some sort of bytecode. Whenever it sees a StringName literal, it looks it up in the string table (like a Dictionary), adds it if not found, and outputs a pointer (the memory address of the string in the string table). This all happens ahead of time, not when the script is running.

                Compiled languages like C do that with all strings in the code. I assume GDScript doesn't for a reason, otherwise this would all be pointless.

                • xyz replied to this.

                  synthnostate No, &"text" is a pointer (a 64 bit integer)

                  So is a regular string literal. When you assign either to a string variable, the pointer gets dereferenced and data it points to is copied to the variable. It makes no difference when used to initialize a variable, that's what I meant.

                  There is also no performance gains when you compare a regular string variable and a string name. The string name again has to be dereferenced and the data it points to is compared to data in the string variable, byte by byte. Just like comparing two regular strings.

                  Only string name vs string name comparison can bring benefits, as it can/will be reduced down to pointer vs pointer comparison.

                    xyz Only string name vs string name comparison can bring benefits, as it can/will be reduced down to pointer vs pointer comparison.

                    Right. And you can write stuff like if s == &"text": ... all over your code, and it's just as efficient as if x == 123: ... with integers.

                    • xyz replied to this.

                      synthnostate Right. And you can write stuff like if s == &"text": ... all over your code, and it's just as efficient as if x == 123: ... with integers.

                      Yes, but again, only if s is a string name. In my above example I declared s as a regular string. In that case the s == &"text" is efficiency-wise equivalent to s == "text"

                        15 days later

                        xyz Yes, it's pointless unless you're comparing StringNames to StringNames. That's their only purpose.