Skip to content

C++, the Godot WayΒ #7248

Open
Open
@greenfox1505

Description

I believe there is a gap in the documentation for C++ development. As I work on my own module, I'm finding types and conventions that seem to not be well documented other than the example that is the entire engine. That helps you figure out how to use various Godot types, but it doesn't tell you what types to use or why use one over another.

The intended audience for this document would be intermediate to advanced C++ programmers who want to contribute to the engine, write C++ modules, or write C++ GDExtentions. The goal is not to teach C++ itself, or to help readers compile the engine. The goal is to help direct readers to the right tools within the engine to solve common problems and match Godot's architecture. When should I use a List<T> or an Array<T>? What types should I inherit from? What is a Variant and how do I use it? Some of these problems already have great GDScript/C# documentation, but many do not have GDScript equivalents.

I have started to write that document. I would like to know if I should continue working on this. There are gaps in my knowledge and I would also like help filling out some of these sections, for my own education and to better the documentation on these features.

People who are very familiar with something tend to have trouble understanding whats confusing about it. People who are very new at something tend to have trouble understanding where to start. At the moment, I am directly in the middle of these people and this is the result of the notes I've taken and what I've learned along this path.

Writing C++ for Godot Modules, GDNative/GDExtension, and Engine Development

If you're reading this, you're probably REALLY familiar with GDScript and you want to push further into lower level stuff.

This document will not teach you how to compile Godot, how to compile a GDNative/GDExtension DLL, etc. It will not teach you how to write C++.

This document is intended to teach how to write Godot C++. How to use Godot's types and how to convert to and from foreign types where applicable. What common macros exist and how to use them (and when NOT to use them).

Types

Overall Type Guidelines

Overall, it is recommended that you use Godot's types over standard library types. [todo: add reasons, I don't actually know]. If you are pulling in an external library foreign to Godot, it's not recommended that you convert the entire library to use Godot types. However, at the interface layer (your glue code), you should try to convert that library's types to Godot types (except where there are significant performance implications).

That means:

  • Instead of std::list, use List.
  • Instead of std::vector, use Array.
  • Instead of std::map, use Dictionary.

If a standard library gives you one of these or other Godot-incompatible type and it is not prohibitively performant to convert them into Godot types, do so before handing them off to the user (GDScript programmer). If you want to preserve the original object or you it would be prohibitively performance expensive to convert, it makes sense to write GDScript-friendly interfaces.

Examples:

  • SteamIDs are, under the hood, 64bit ints. It's very easy to expose those to Godot's ints. This allowed your GDScript to pass SteamIDs with out building a whole new class just for handling them.
  • SteamIDs are, under the hood, 64bit ints. However, it doesn't make sense to treat them as an int as you well never do math with them. Functions that expect a SteamID, don't expect just any number, they expect a valid account. A class dedicated to this will help use type safty to keep you from putting junk data into a function.

There are often no right answers here. Do what makes sense for your project.

Primitives

int, int64, unsigned int
float, float64
strings

String

When interfacing with external libraries, you'll often be handed C-style strings (a char pointer or array that points to a sequence of data that is \0 terminated). When interfacing with Godot libraries, they will expect and return Godot Strings.

[Todo] write a conversion table for converting a Godot String to and from each of these:

  • C String (null terminated pointer)
  • std::string
  • Pointer+Size

Container Types

These types are containers for storing other objects. They are typically templated <T> types.

List<>

Godot's List<> type is a Linked List (this is like C/C++'s std::list, but it is NOT like C#'s List<T> which is actually an array). Adding elements to the start or end of this structure is constant cost. Accessing the Nth value costs N*time.

Use a List<> when:

  • You have a First In, First Out style queue where you add elements to one end, and remove them from the other end.
  • You First In, Last Out style stack where you add elements to one end, and remove them from the same end.
  • You strictly need to iterate through sequentially.

Do NOT use a List<> when:
- You often need to indices in the middle.

[todo] document for loop iterators, DO NOT USE index based accessors!

Array<>

Godot's Array<> type is a GDScript frinedly Array. It's very similar to C/C++ std::vector. Accessing the Nth value costs constant time. Adding and removing elements costs more time for longer arrays.

Use an Array<> when:

  • You often need to indices in the middle.

Do NOT use an Array<> when:
- You often need to add elements to very large Arrays. Adding or removing

Variant

[TODO]

Ref, RefCount

[Todo]

Dictionary

[Todo] I'm going need some help with this one. I believe there are certain rules on what can and cannot be used as a key/value and I'm not sure I fully understand this yet.

Base Classes

These are types that your classes will want to inherit from. Inheriting from these types gives your types Godot engine features, like reference counting or compatibility with higher level scripting tools. However, all of these types will cost some overhead and you shouldn't use them unless the features are worth the overhead.

Object

Object is the base class for any type that can interface with GDScript. You should inherit off Object only if no other type makes sense. The default inheritance in GDScript is not Object, it's Resource.

Objects are not reference counted and must be explicated deleted when no longer needed.

Node : Object

Node is the base class for all elements of the Godot scene tree. It contains functionality for scene traversal as well as a number of other features. If you have used GDScript extensively, you are probably very familiar with Node. If your class needs to exist inside the scene tree, Node or something that inherits from it is probably the right base class.

Nodes are not reference counted and must be explicated deleted when no longer needed.

RefCounted

[Todo] get help figuring out when the best time to use RefCounted vs Resource vs RefCount is.

RefCounteds are reference counted and will delete themselves when no longer needed. However, they can still result in a memory leak in the case of a cycle (A has a reference B and B has a reference A, or A has a reference to itself).

Resource : RefCounted

Resource is used for data that multiple objects need a shared reference to. Textures, models, etc are typical examples. But Resources.

Resources are reference counted and will delete themselves when no longer needed. However, they can still result in a memory leak in the case of a cycle (A has a reference B and B has a reference A, or A has a reference to itself).

RefCount

[todo] no idea when and where to use this

Macros

Godot has a LOT of macros that make writing Godot C++ easer. I'm not going to talk about all of them. But here are a few that are useful and some "gotcha"s that you should look out for.

error_macros.h

The error macros is Godot are pretty powerful for reporting events and warning within Godot at runtime. There are so many of them, that I don't think it makes sense to go through them all. We will have go over the various types.

  • ERR_ denotes an error. You will see red text in the editor console.
    • ...FAIL adds a return;. Used for errors that should just your current function without continuing.
    • ...FAIL_V(m_retval) adds a return m_retval;
    • ...CONTINUE adds a continue; to your error. Skips just this iteration of a loop.
    • ...BREAK ads a break; to the end of your error. Skips the reset of the loop.
  • MSG(m_msg) adds a m_msg to the error message.
  • COND(m_cond) adds a if (m_cond == true) to the error checker. This error will only happen if this condition is true. This is mostly what you'll use.
  • PRINT(m_msg) denotes a function that does NOT affect control flow (return, break, or continue), but will still dump m_msg as an error message.
  • WARN(m_msg) denotes a warning. You will see yellow text in the editor console and the function will continue. Implies PRINT.
  • ONCE is used in WARN_ and ERR_PRINT_ to denote an error/warning message that should only print once. You will only see an error/warning the first time this line hits. The line will still run, but you won't create additional messages.
  • ED or EDMSG denotes when an error creates a notification, which pops up in the lower right of the editor. Outside of the editor, this acts just like its non ED counterpart.

The error macros in error_macros.h are built of several of these tags at once. For example this example, we can replace a null check, an error message, and a return value with one line that does all three in a way that is very readable for anyone that needs to review this later.

Error MyClass::do_something(){
    if(this->something==NULL){
        print_error("Something was not initialized properly!");
        return ERR_DOES_NOT_EXIST;
    }
    //...do something!
}
Error MyClass::do_something_better(){
    ERR_FAIL_COND_V_MSG(something==null, ERR_DOES_NOT_EXIST, "Something was not initialized properly!")
    //...do something!
}

Review error_macros.h to get a better sense of what's available. Most autocomplete in IDEs should give you a good sense of what's available if you try to combine some of these tags.

GDCLASS(m_class, m_inherits)

GDCLASS(m_class, m_inherits) is used to register your class with Godot's ClassDB.

[todo] expand that various _bind_method types. enums? enum class?

.clang-format

.clang-format can be copied directly from the Godot repo into your C++ project and will allow compatible linters to help you autoformat your code to match Godot's style guides. It won't cover every formatting case, but it will do a good job at first pass. You might not like the formatting rules (tabs vs spaces, newlines at open brace, etc ), as is often the case, consistency is more valuable than correctness.

Metadata

Assignees

No one assigned

    Labels

    area:contributingIssues and PRs related to the Contributing/Development section of the documentationcontent:new pageIssues and PRs related to creation of new documentation pages for new or undocumented featuresenhancementtopic:gdextension

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions