ZDoom Variables

From ZDoom Wiki
Jump to navigation Jump to search

This is a guide on some of the core variable types present in ZDoom when working with the engine. Note that this is not for ZScript variables, but for the engine itself. If ZScript variables are desired, see here. This outline will focus on standards for primitives, data structures that are preferred, and what types correspond to the external ZScript types. For primitive types in particular these guidelines should be strictly adhered to to prevent any possible technical issues that could occur.

Starting Notes

Some types may be prefixed with either an F or D. D generally stands for double while F (originally for float) is largely used for anything else and is a carryover from Unreal Engine, an inspiration for ZDoom back when it was first being created. Classes and structs that begin with D or F are usually not templated. While not required, it's considered good convention to use T for classes and structs that are templated and F for standard classes and structs (or D if it's meant to represent doubles).

In general C++'s stock types should be used unless specified otherwise. GZDoom currently targets C++17.

Pointer Types

C++'s built-in pointer types are preferred (e.g. std::unique_ptr), but ZDoom comes with its own reimplementation of std::shared_ptr that offers greater consistency. RefCountedBase is a class that can be inherited from to define a type that should be shared while RefCountedPtr<T> is the actual pointer class that manages those types.

class FMyClass : public RefCountedBase
{
    // ...
}

// variable type
RefCountedPtr<FMyClass> myVar;

For storing pointer fields to DObjects, TObjPtr<T*> should be used. This has built-in safety read barriers which ensure that the object you're trying to access is valid and not about to be garbage collected. Note that this isn't needed if simply passing around the object pointer to other functions i.e. a function does not need a TObjPtr<T*> parameter but can use T* directly.

Data Structures

ZDoom has some of its own data structures present. Some are made for handling specialized tasks while others may have been implemented before standardized versions were widely available. Even if a version exists in the std library these should still be used for consistency reasons.

  • TArray<T>
This is the class that should be used for handling all dynamic arrays. Avoid using manually managed memory as often as possible in order to prevent potential memory leaks that could occur from failing to free the memory properly. This should be used in place of std::vector.
  • TIterator<T>
A class for iterating through TArrays. TArray comes with methods for both standard and immutable versions.
  • TMap<K, V>
This is the class that should be used for maps. It acts similar to std::unordered_map and should be used over the std variant as TMap provides a significant performance boost for integer (and by extension FName) keys.
  • TMapIterator<K, V>
The iterator class for TMaps. TMapConstIterator<K, V> is the class for working with immutable TMaps. It uses the following syntax:
TMap<type_a, type_b>::Pair* pair;
TMapIterator<type_a, type_b> iteratorName(myTMap);

while (iteratorName.NextPair(pair))
    // ...
  • VSMatrix
A 4x4 matrix class commonly used within the renderer. As such its values are stored as 32-bit floats.

Primitive Types

The following is a guideline on what primitive types to use and when. Caution must be used in particular with ints as incorrectly narrowing them can cause architectural issues in the future.

Integers

  • bool
  • (u)int8_t
These should be used in place of (unsigned) char when dealing with numeric values.
  • (u)int16_t
These should be used in place of (unsigned) short.
  • (u)int32_t
These should only be used in places where a strict 32-bit integer size needs to be expected e.g. as a field in a memory-efficient struct. Otherwise using regular (unsigned) int is preferred.
  • (u)int64_t
These should be used for all 64-bit integers
  • (unsigned) int
These should be used whenever possible.
  • size_t
When dealing with indices in data structures, this is the preferred type as it can track incredibly large index values.

Floating-Point

  • float
This should be used when working within the renderer or within structs where memory efficiency is important.
  • double
This should be used when working with ZDoom's playsim code. A float cannot represent the entire range of Doom's 32-bit fixed point numbers and using it can lead to precision errors.

Strings

Both FString and char* should be used in place of std::string, but FString is strongly preferred. Remember to pass FStrings by reference when possible and avoid returning copies (prefer to pass a reference that the result is put in).

Special ZScript Types

Below is a list of ZScript's special types as how they're represented internally. Read as internal -> ZScript:

  • FString -> string
  • FName -> Name
  • FSoundID -> Sound
  • FTextureID -> TextureID
  • PalEntry -> Color
  • FBaseCVar -> CVar
  • DVector[2/3/4] -> Vector[2/3/4]
  • DQuaternion -> Quat

Special Types

Below is a list of other data types that can and should be used when doing special operations:

  • TAngle
A struct made for storing an angle. This should be used over plain floating-point types for the purposes of clarity, built-in functionality for handling common angle operations, and greater coverage across different angle types (e.g. radians, BAM).
  • TRotator
A struct made for storing a yaw, pitch, and roll, all as TAngle types. This should be used over TVector3 for the purposes of clarity and enhanced functionality for dealing with its angles.