Skip to content

Methods, Macros and Subroutines

ItsDeltin edited this page Feb 28, 2023 · 5 revisions

Methods, macros, and subroutines

Methods, macros, and subroutines can be declared to create reusuable pieces of code. They can all be defined at the rule level or inside classes.

They can all take parameters and return a value. Macros do not generate any workshop actions, so unlike methods they can be used in conditions.

Table of contents

Inline methods

The term 'inline' refers to methods that are inserted into the rule's action list when called.

// Void methods return nothing
void method_name() {
}

// A method that returns a type
type_name method_name() {
    return expr;
}

// With parameters
type_name method_name(type_name param, type_name typeParam) {
    return expr;
}

// With return type
type_name method_name()
{
    return expr;
}

// With accessor (class/struct only)
public type_name method_name() {
    return expr;
}

// Recursive
recursive type_name method_name() {
    return expr;
}

Any of the above definitions can be converted to subroutines by adding the name of the subroutine after the parameter list. More information can be found in the subroutines section.

Macros

Macros do not create any actions. This makes macros suitable to be used in conditions and reevaluation. Macros cannot be recursive.

Macro declaration
type_name macro_name(): expr;

// With parameters
type_name macro_name(type_name param): expr;

// Variable
type_name macro_name: expr;

// Shorter way of getting the normal of a raycast
Vector Normal(Vector start, Vector end): RayCastHitNormal(start, end, null, null, true);

// This method will work exactly like the above macro. Macros just enforce the no-action rule.
Vector Normal(in Vector start, in Vector end) {
    return RayCastHitNormal(start, end, null, null, true);
}

Parameters

Constant types

Parameters can have workshop enum types (HudTextRev, Effect, etc.). Parameters with these types cannot be set to, but they can be used in method, macros, or constructors that use them.

void MakeHelpText(Icon icon, in String text)
{
    CreateHudText(
        VisibleTo: AllPlayers(),
        Header   : IconString(icon),
        Text     : text,
        TextColor: Color.SkyBlue
    );
}

in

When exporting to the workshop, methods will assign a workshop variable for each parameter. For basic methods this can be quite wasteful. You can use the in attribute to use the input value directly. When declaring a method, it's a good idea to tag parameters with in by default.

The expression used with in parameters are used directly. This means that in parameters can be reevaluated.

// 'in example'
Any create_effect(in Vector position) {
    return CreateEffect(AllPlayers(), Position: position);
}

Parameters in macros and subroutines cannot be tagged with in. Macro parameters are always in. Constant types are also in by default.

(wiki todo: make page describing constant types)

ref

Variables passed to the method can be modified using the ref constraint. Values passed to ref parameters must be variables. These parameters can be chased, appended to, or modified in any way and the changes will reflect the given variable.

ref example
void modify_value(ref Number variable, in Number destination = 100, in Number rate = 1) {
    variable += 3;

    ChaseVariableAtRate(variable, destination, rate, RateChaseReevaluation.DestinationAndRate);
}

globalvar Number global_variable;
globalvar Number another_global_variable;

rule: 'ref example' {
    modify_value(global_variable);
    modify_value(another_global_variable, destination: 50);
}

Like in, only methods can be tagged with ref. Macros cannot use them because they can't have actions and thus can't modify variables. Subroutines can't use them either.

Recursion

Methods with the recursive attribute can be called more than once in a stack.

recursion example
// This will output the following when a player presses interact:
// forward 0
// forward 1
// forward 2
// forward 3
// forward 4
// forward 5
// forward 6
// forward 7
// forward 8
// forward 9
// forward 10
// back 10
// back 9
// back 8
// back 7
// back 6
// back 5
// back 4
// back 3
// back 2
// back 1
// back 0

rule: "Recursion!"
Event.OngoingPlayer
if (IsButtonHeld(EventPlayer(), Button.Interact))
{
    CountUpThenDown(0);
}

recursive void CountUpThenDown(define num)
{
    SmallMessage(AllPlayers(), <"forward <0>", num>);
    MinWait();

    if (num < 10)
    {
        CountUpThenDown(num + 1);
    }

    SmallMessage(AllPlayers(), <"back <0>", num>);
    MinWait();
}

Recursive subroutine

Subroutines do not require the recursive attribute in order to call itself, but variable stacks will not be handled, potentially causing problems with parameters and variables defined in the subroutine. Add the recursive attribute to your subroutine in order to handle the variable stacks.

Inline recursive

'inline-recursive' are recursive functions that are not stored in subroutines. The function continuation is handled directly in the action-set. This assigns an additional variable to store data used to continue execution at the correct spot in the action-set. If you are worried about element count, consider making the function a subroutine.

Variable stack

Recursive functions store the parameter values as a stack, so it isn't a good idea to have recursive function's parameters in the extended collection. This additionally applies to variables defined inside the recursive function.

Recursive virtual

Virtual, recursive functions assigns an additional variable in order to store the object stack data. This applies to both inline-recursive and subroutine-recursive.

Subroutines

Typical methods are inserted into the action list of the rule that called it. Subroutines only have one instance, meaning no matter how many times the subroutine is called it will only be written into the output once.

Subroutines in OSTW supports parameters and returning values.

Pros:

  • Only one instance of the function will exist, meaning less elements used in most cases.
  • Recursive subroutines require one less stack than recursive inline methods, resulting in one less variable used in the output.

Cons:

  • The ref and in attributes do not work with parameters.
  • Constant types such as Effect, EffectRev, HudTextRev, or constant lambdas do not work with parameters.
  • Race conditions if there are waits.

To make a subroutine, add a string to the end of the function. The string will be the name of the subroutine's rule.

void Subroutine() "My subroutine!"
{
}

The parameters and variables defined in subroutines will be global by default. To change it to be player variables, add the playervar keyword before the subroutine rule name.

void Subroutine() playervar "My Subroutine!"
{
}

Subroutines can be called asynchronously.

async MySubroutine(); // If the subroutine is already executing in the current global or player context, it will be restarted.
async! MySubroutine(); // Adding ! will do nothing if it is already executing.