ZScript functions
Note: This feature is for ZScript only. |
Functions are created similarly to how they are made in many different programming languages.
int MyFunction(int MyNumber, int Another)
{
return MyNumber + Another;
}
PERFORMANCE NOTE: Functions are useful, and should be used for clean code, especially code that is redundant as this will overall help performance and readability. If you do encounter slowdown, worry about getting the correct results first before optimizing. In very old versions of GZDoom, function calls could seriously harm performance, but with the addition of the JIT and the overhaul of the ZScript calling convention, this is not the case anymore. If you're having issues with performance, the problem will almost always lie in the code, rather than being due to function call overhead.
Using the console command
stat vm
is helpful for seeing performance/benchmarking of ZScript in general.
Multiple Returns
Functions are capable of returning multiple variables at once. All it needs is the return types defined with a comma between each, and for the return statement to also contain the ideal variables like such:
// Returns two ints and a bool.
int, int, bool MyFunction(int MyNumber, int Another)
{
return MyNumber, Another, true;
}
The call to the function does not require all assignments to be filled, only up to the necessary one on the right. Note that an actual non-constant variable of the appropriate type is required for each.
// If only the second return is desired (where 'b' is), the previous (left) variables need a temporary assignment. Anything after does not.
int a, b;
[a, b] = MyFunction(1, 2);
The let keyword can also be used with multiple returns:
let [num1, num2, mybool] = MyFunction(1, 2); //types for return values will be set automatically, even if they're different types
Referencing
As described with ZScript special words, 'in' and/or 'out' can be used to specify sending in a variable for direct modification. This is especially useful if passing structs around, which can alleviate the problem of having so many parameters, and modifying variables of things that otherwise cannot be returned (such as structs themselves). A simple struct must be defined though:
Struct MyStruct
{
int a, b;
}
With the struct defined, simply pass it in like this:
void ChangeStructNumbers (in out MyStruct strct)
{
strct.a = 1;
strct.b = 2;
}
Function types
Static
The static
keyword marks a function as not being tied to any particular instance of a class or struct. They can be called directly by using the class/struct name followed by the name of the function. Be warned that static functions will not have access to any fields within that class/struct. If field access is required, consider making it non-static or passing a reference to an object that has those fields.
class MyClass
{
static void MyStaticFunction() {}
}
MyClass.MyStaticFunction(); // how to invoke the method
A common example of a static function in ZScript is Spawn(): when called outside of an Actor class, it must be called as Actor.Spawn
.
Action
The action
modifier is specifically for non-static Actor functions that are callable from PSprites. Since PSprites are not Actors, the way they pass information to a State's function is different. This is done by passing the State's owner as the invoker
while the player themselves is passed as the self
pointer. A stateInfo
pointer is also passed that contains information about the State the function was called from. For regular Actors this modifier does nothing and shouldn't be used.
action void MyWeaponFunction() {}
For all intents and purposes, this modifier is only useful for classes that inherit from StateProvider (namely, Weapon and CustomInventory), specifically for functions that are called directly in a state, much like the generic Weapon functions such as A_WeaponReady, A_FireBullets and others.
At the same time, not all weapon functions are action functions. For example, DepleteAmmo is never called in a state directly, and is not an action function, so, when called from a weapon state or from another action function, it must be called as invoker.DepleteAmmo
.
Virtual
The virtual
keyword allows child classes to override a non-static function defined in a parent class. This can allow for custom behavior of an object even if only knowing its base type. The override
keyword is used in place of virtual
when overriding it from a child. The function's signature must match one-to-one with the parent's function excluding parameter names. While technically parameters with default arguments can be left out in the override, this is done for future compatibility should a virtual function gain more parameters. Any parameters left out will be inaccessible to the override. Default parameters cannot be defined in an override. A child's function will completely override the function of its parents. To avoid this behavior, the super
keyword can be used.
class A
{
virtual void MyFunction(int arg1, int arg2 = 0) // arg2 has a default argument
{
DoThing(arg1, arg2);
}
}
class B : A
{
override void MyFunction(int arg1, int arg2) // arg2 doesn't have a default in the override
{
super.MyFunction(arg1, arg2); // make sure to still call A's function
DoMoreThings(arg1, arg2);
}
}
ZScript classes come with a large number of virtual functions, see ZScript virtual functions.
Limits
While in practice this should never be an issue, functions in ZScript do have a maximum number of resources available for use, and this must be kept in mind when designing them:
- 32767 integer constants
- 32767 string constants
- 32767 float constants
- 32767 address constants (this includes all function addresses, RNGs or other native resources being referenced.)
- 256 integer registers
- 256 floating point registers
- 256 string registers
- 256 address registers