Choice. The Problem Is Choice.

Continuing the theme of “things I should have learned sooner”, starting with finding out that If/Switch Statements are expensive, I also finally started learning about Delegates. I had heard of them, and I knew what Function Pointers were from C++ way back in 1999. But I can’t believe I hadn’t looked into Delegates sooner, or that none of the tutorials or articles I ever read had forced me to learn them until now.

The timing worked out okay though, because Delegates are useful in writing Branchless code to avoid If Statements. By setting up a Delegate to point to the proper Function beforehand, you can avoid using an If to decide which Function to call every GameLoop.

Here I’ve created a Structure to easily set up a Choice between two or more Functions, using an Array of Actions. An Action is a generic Delegate that points to a Sub (or a Void in C#). In other words a Function with no Parameters and no Return value. I haven’t even started using this technique yet, other than verifying that this code works.

Public Structure Choice
    Public Branch() As Action

    Public Sub SetupBoolean(_True As Action, _False As Action)
        ReDim Branch(1)
        Branch(0) = _False
        Branch(1) = _True
    End Sub
    Public Sub New(_True As Action)
        SetupBoolean(_True, Nothing)
    End Sub
    Public Sub New(_True As Action, _False As Action)
        SetupBoolean(_True, _False)
    End Sub
    Public Sub New(Actions() as Action)
        Branch = Actions
    End Sub
    '// Use [Brackets] for names that are already keywords.
    Public Sub [If](Comparison As Boolean)
        Branch(-Comparison)?.Invoke()
    End Sub
    Public Sub [Call](Index As Integer)
        Branch(Index)?.Invoke()
    End Sub

    Public Property Yes As Action
        Get
            Return Branch(1)
        End Get
        Set(value As Action)
            Branch(1) = value
        End Set
    End Property
    Public Property No As Action
        Get
            Return Branch(0)
        End Get
        Set(value As Action)
            Branch(0) = value
        End Set
    End Property
End Structure

And here is a very basic example of these Actions in action:

Dim Question as New Choice(AddressOf ToBe, AddressOf NotToBe)
Question.InvokeIf(Nobler > Whatever)

There are three New() declarations to Instantiate a Choice. If you only specify the AddressOf for the True Action, then InvokeIf(False) will do Nothing. New() also takes an Array of Actions, but I’ll get to that later.

The most notable line of code in the Choice Structure is only line in the If() Function, Branch(-Comparison)?.Invoke(), and in particular the “?.” or “Null-Conditional Operator”. Before I get to that, remember that -Comparison becomes 0 or 1, which “chooses” from the first two Actions in the Branch Array.

Then the “?.“, or as I call it the “Hello Operator”, will “ask” the variable if it has been instantiated as New, or is still set to Nothing, AKA Null in C#. If it gets no response, the Statement will “short-circuit” and just move on, the Delegate’s Function (the Action’s Sub in this case) is not Invoked.

This is what allows you to avoid checking If Branch(-Comparison) IsNot Nothing before Invoking the Delegate. Even a simple If Statement like that is expensive in a GameLoop. There might also be a small cost to set up a Delegate or Action, so it’s not something you want to change every Frame.

Dim Question as New Choice({AddressOf ToBe, AddressOf NotToBe, AddressOf ToDie,
                            AddressOf ToSleepNoMore, AddressOf PerchanceToDream,
                            AddressOf TheNub})

Here’s the example of initializing a Choice with an Array of Action Delegates. Then you can use Call(#) to call each Function. I’ll be using this to replace times where I use a Switch Statement to choose a Function based on an ID number, such as the Genus of a Nub, or the Type of a jeOS Interface Zone.

I’m still pretty surprised I hadn’t learned this sooner, but I’m also kinda surprised it’s not built-in. It’s pretty easy to implement yourself though, and you might want to do things differently. For instance, you could add Functions specific to your game that use an Enumerator to reference which Branch to Invoke.

    Public Function IfGenus(_Genus as Genus)
        Branch(_Genus)?.Invoke()
    End Function

This way, when you go to call IfGenus(, the list of items in the Genus Enumerator would show up in AutoComplete as soon as you hit “(“.

Leave a Reply