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
, useList
. - Instead of
std::vector
, useArray
. - Instead of
std::map
, useDictionary
.
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
int
s. It's very easy to expose those to Godot'sint
s. This allowed your GDScript to pass SteamIDs with out building a whole new class just for handling them. - SteamIDs are, under the hood, 64bit
int
s. However, it doesn't make sense to treat them as anint
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 String
s.
[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 N
th 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 N
th 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
.
Object
s 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.
Node
s 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.
RefCounted
s 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 Resource
s.
Resource
s 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 areturn;
. Used for errors that should just your current function without continuing. - ...
FAIL_V(m_retval)
adds areturn m_retval;
- ...
CONTINUE
adds acontinue;
to your error. Skips just this iteration of a loop. - ...
BREAK
ads abreak;
to the end of your error. Skips the reset of the loop.
- ...
MSG(m_msg)
adds am_msg
to the error message.COND(m_cond)
adds aif (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 dumpm_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. ImpliesPRINT
.ONCE
is used inWARN_
andERR_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
orEDMSG
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 nonED
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.