A main menu typically displays a set of buttons that allows players to navigate the game. Selecting a button transfers the game to another screen, such as an options screen or the gameplay itself. Eventually, after the chosen screen terminates, the game transits back to the main menu.
In the demo figure, we symbolize the chosen screens as clickable buttons associated with the user choices. Clicking the button again terminates the screen and returns to the main menu. Our goal is to apply structured reactive techniques in our implementation in Ceu. Let’s discuss it in a top-down approach, starting with the main application:
-- enumeration with the possible menu choices type Menu = <Story=(), Editor=(), ...> -- task signatures for the menu and buttons task main_menu: () -> Menu 👈 (next posts) -- returns the chosen screen to navigate task menu_button: [pos:Point, lbl:String] -> () 👈 (next posts) -- receives a position and label to show -- spawns the game code spawn { -- the outer loop 1️⃣ loop { -- main menu var opt = await spawn main_menu () 2️⃣ -- chosen screen var lbl = ifs { opt ? Story { "Story" } opt ? Editor { "Editor" } ... -- other options } await spawn menu_button [[0,0], lbl] 2️⃣ -- loops back to menu after screen terminates } } -- enters the SDL engine loop call pico_loop ()
The relevant structured mechanism in the code is the outer loop (1️⃣), which
alternates between the main menu and the chosen screen.
These screens can be arbitrarily complex and are handled with the spawn-await
combination (2️⃣), which resembles standard function calls: spawn the task and
await its termination.
We will discuss the main_menu
and menu_button
implementations in future
posts.
While the main task waits in the loop, the current screen executes in a
separate task, possibly reacting to events and spawning auxiliary tasks.
In the meantime, the language keeps the loop context alive (i.e., locals and
program counter) similarly to coroutines.
This direct style with spawn-await
contrasts with the arguably more
intricate continuation passing style (CPS), which is one of the control-flow
patterns described in the previous post:
The original implementation in C++ uses CPS and pushes the screen navigation to occur inside the button click callback, thus textually far away from the main game function:
MainMenu::MainMenu () {
...
story_but = gui->create<MenuButton>(...)
... // other menu buttons
}
void MainMenu::on_click (MenuButton* button) {
if (button == story_but) {
...
Screen::push_screen(worldmap); // screen navigation
...
} else ... { // other buttons actions
...
}
}
The implementation in C++ also relies on an explicit stack to alternate between the main menu and the chosen screen: it pushes a new screen on top of the main menu, which must be explicitly popped when terminating:
void Worldmap::update (...) {
...
if (exit_worldmap) {
Screen::pop_screen();
}
...
}
void WorldmapCloseButton::on_click() {
Screen::pop_screen();
}
Not only this approach requires the called screen to be aware of its parent navigation track, but also relies on a data structure to simulate a control-flow mechanism.
Comment on @_fsantanna.