Latent Functions - pret/pokeemerald GitHub Wiki

A latent function is a function whose execution is distributed over multiple frames, instead of running start-to-finish in one shot. Sometimes, this is done because a function or process must wait on events occurring elsewhere, such as multiplayer link messages. Other times, this is because the function itself may trigger events that interrupt its own activity, such as UI pop-ups.

The general pattern is this: the latent function is built as a switch-case statement, switching on a "state" variable of some sort. The variable is typically a counter, being incremented at the end of each case, though that may be different when branching execution is needed. The latent function is called once per frame until its work completes. If there's some event that the latent function needs to wait on, it'll do so by just checking whether the event is still happening: if so, the latent function will just try again on the next frame. Within the latent function, any variables that are needed across multiple stages of computation (including the "state" variable itself) can't be local (because they'll be lost at the end of each frame) and must be stored elsewhere (e.g. global or static variables).

As for how a function gets itself to be called once per frame, there are three common cases:

  • Some processes are implemented as frame callback handlers (e.g. the functions whose names are prefixed with CB2).
  • The task system exists as a way to help set up latent functions. Tasks get processed on every frame.
  • For battle scripts, the script instruction pointer doesn't advance automatically; battle script commands must advance it manually. If they don't advance the instruction pointer, then they'll just run again on the next frame.

Basic examples

  • There are several functions that set up the initial state for battles, depending on the battle type (Link Battle, Multi Battle, etc.). These split their work over multiple frames and generally use gBattleCommunication[MULTIUSE_STATE] as their state variable.
  • The getexp battle script command is run when a Pokemon faints, to award EXP to all eligible Pokemon in the battle. The script command may trigger UI popups for EXP being awarded and for a Pokemon leveling up, and when a UI popup triggers, the command has to wait for the user to dismiss it; this means the command has to be latent. The command uses gBattleScripting.getexpState as its state variable, and uses gBattleStruct->expValue and gBattleStruct->expGetterBattlerId to track data that gets used across multiple stages of computation.
  • The trygivecaughtmonnick battle script also has to wait on GUI events and interactions. It uses gBattleCommunication[MULTIUSE_STATE] as its state variable, and relies on gBattleStruct->caughtMonNick to hold the player's chosen nickname for a caught Wild Pokemon.
  • The overworld has a handful of tasks which poll to check when the player moves. One of these tasks makes you slide down muddy slopes if you're not riding the Mach Bike.

Latent functions and the task system

The task system exists as a framework to simplify latent functions. As mentioned above, latent functions have to store their state externally, so tasks allow you to pair a latent function with a small storage area (32 bytes). (Latent functions that need more storage space can use the game's memory heap.) Tasks also serve as a way to contain latent functions within a particular part of the game and make sure they don't accidentally "leak out" and run for longer than they should: there's a ResetTasks function that mass-deletes all tasks, and Game Freak calls it frequently.

Game Freak's code typically resets all tasks when opening and closing menus, though there are some exceptions (e.g. any menu that can be open during a multiplayer link, since tasks also coordinate link communications; and a limited set of nested menus, where interrupting the parent menu would cause severe problems). In general, it's best to think of tasks as being scoped to screens and menus.

APIs and flexibility

The following APIs are worth knowing about for tasks, if only to understand the usage patterns involved.

  • CreateTask(id) and DestroyTask(id): Self-explanatory. One thing worth keeping in mind is that when a task destroys itself, its call stack isn't interrupted; and so a task can do things (e.g. spawn its own replacement) in the moments after it has destroyed itself.
  • ResetTasks(): Destroys all extant tasks.
  • FuncIsActiveTask(fp): Check whether there exists a running task whose handler function is the function pointer fp.
  • FindTaskIdByFunc(fp): Search for any running task whose handler function is the function pointer fp; return the ID of the found task, if any, or TASK_NONE otherwise.
  • SetTaskFuncWithFollowupFunc and SwitchTaskToFollowupFunc: These are convenience functions that co-opt some of a task's data buffer to store a function pointer. They exist to allow more generic swapping of a task's handler function: "Do X, and then do Y afterward," where "X" doesn't have to know or care what the follow-up function "Y" is.

Using tasks to coordinate latent functions

Many of the game features that use tasks involve multi-step processes: run one latent function, and then another, and then another. Some game features require running multiple latent functions side-by-side. How does Game Freak use tasks to coordinate these functions?

Handler swapping

It's common for Game Freak to create a task which initially has one handler function, but which replaces its handler function during its operation. There are two patterns:

  • A series of sequential operations which must be performed over time (e.g. the game startup cutscene). Each step in the sequence is given its own handler function, and upon completion of a given step, the task switches to the next step's handler. At the end of the sequence, the task will generally destroy itself, relying on CB2 to move onto whatever comes next.

  • A single task used as the main loop for a menu or similar system, where, when the system is idle, the task's handler function waits for input. When input is received, the handler function dispatches to functions which react to those inputs by switching the task's handler. This has the effect of allowing the menu to respond to player inputs over time (e.g. by playing sound effects, animating the menu, et cetera) while blocking input until the menu has finished responding. Once the menu has finished responding, the task's handler is switched back to the function that waits for input.

Subordinate tasks

It's common for Game Freak to use separate tasks for minor effects, such as individual animations in the game's startup cutscene, or minor animations within menus (e.g. scroll indicators bobbing up and down). It's less common for Game Freak to have one subordinate task spawn its own subordinate task.

Timed subordinates

In some cases, subordinate tasks are designed to run for a finite length of time and then destroy themselves, with the timing arranged to ensure that they're dead by the time the primary task needs to move on. This is the case for the game's startup cutscene, where subordinate tasks used for graphics always destroy themselves, and always in time for their graphics to be unloaded and replaced when prepping for the next part of the cutscene.

Communication with subordinates

In other cases, subordinate tasks are puppeteered by the primary task, which remembers the IDs of the subordinate tasks in order to access the subordinate tasks' buffers and read or write data. Since this requires keeping track of the subordinate task's ID, a parent or ancestor task will generally use that ID to terminate the subordinate task via DestroyTask as well.

Self-terminating subordinates

In still other cases, a subordinate task may check whether its parent task is still running, and destroy itself if not. One known example involves graphics-related tasks in the evolution cutscene, where the primary task may look up and terminate all subordinate tasks, or a nested-subordinate task may self-terminate if its parent has stopped running on its own.

This pattern is easier to use when dealing with tasks that never swap out their handlers, as you can then use FuncIsActiveTask and FindTaskIdByFunc instead of having to store task IDs. In essence, the creation of the subordinate task becomes fire-and-forget: the parent task doesn't have to keep track of the subordinate task and explicitly terminate it, because the child task can just terminate itself when it sees that its parent is gone. If the top-level task ever needs to instantly terminate all child and descendant tasks (i.e. when the top-level task itself is exiting), it can look them up by their functions and then call DestroyTask (and this, too, happens in the evolution cutscene).