Francisco Sant'Anna

A self-reacting button

@_fsantanna

In the previous posts, we discussed the outermost code to alternate between screens and the main menu buttons:

-- enumeration with the possible menu choices
type Menu = <Story=(), Editor=(), ...>

task menu_button: [pos:Point, lbl:String] -> ()  👈 (this post)
    -- receives a position and label to show

task main_menu: () -> Menu {
    par {
        await spawn menu_button [[-125,35], "Story"]  3️⃣
        return Menu.Story
    } with {
        await spawn menu_button [[ 125,35], "Editor"] 3️⃣
        return Menu.Editor
    } with {
        ... -- other options
    }
}

-- spawns the game code
spawn {                                     1️⃣
    -- the outer loop
    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]

        -- loops back to menu after screen terminates
    }
}

-- enters the SDL engine loop
call pico_loop ()

In this post, we complete the structured main menu with the menu_button implementation. The outermost code (1️⃣) spawns the main_menu task (2️⃣), which spawns several menu_button tasks in parallel (3️⃣). In the code below, each button receives a position and label to show, and is responsible for redrawing (1️⃣) and terminating itself on a mouse click (2️⃣):

task menu_button: [pos:Point, tit:String] -> () {
    var size: Size
    output Get.Size.Image [/size, "data/images/menuitem.png"]

    spawn {
        every evt?Draw {    1️⃣
            output Draw.Image [arg.pos, "data/images/menuitem.png"]
            output Set.Font   ["data/fonts/film-cryptic/Filmcryptic.ttf",45]
            output Draw.Text  [arg.pos, arg.tit]
        }
    }

    await and [             2️⃣
        evt?Mouse?Button?Down
        isPointVsRect [evt!Mouse!Button!Down.pos, [arg.pos,size]]
    ]
}

The menu_button task spawns a dedicated task to redraw itself on each occurrence of evt?Draw (1️⃣), which is an engine event to signal that the screen must be updated. While the redrawing task executes in the background, the menu_button also waits for an evt?Mouse?Button?Down event (2️⃣). If the click occurs inside the button, the menu_button task awakes and terminates. Its termination awakes the main_menu task, which in turn returns the chosen screen to the outermost code.

The relevant structured mechanism in this code is how deep nested tasks, such as menu_button, can react to engine events directly (1️⃣ and 2️⃣), bypassing the task hierarchy entirely. This self-dispatching mechanism is one of the control-flow patterns in the previous post:


The original implementation in C++ needs to dispatch the events explicitly through its containers hierarchy, which is split in multiple files. Starting at the engine, the draw method is dispatched through the path ScreenManager -> GUIScreen -> GroupComponent -> SurfaceButton, which ultimately draws the button on the screen:

// screen_manager.cpp:
// https://github.com/Pingus/pingus/blob/master/src/engine/screen/screen_manager.cpp#L200
void ScreenManager::update (...) {
    ...
    get_current_screen()->draw(...);
    ...
}

// gui_screen.cpp
// https://github.com/Pingus/pingus/blob/master/src/engine/screen/gui_screen.cpp#L40
void GUIScreen::draw (...) {
    ...
    gui_manager->draw(...);
    ...
}

// group_component.cpp
// https://github.com/Pingus/pingus/blob/master/src/engine/gui/group_component.cpp#L47
void GroupComponent::draw (...) {
    ...
    for (Components::iterator i...) {
        ...
        (*i)->draw(...);
    }
    ...
}

// surface_button.cpp
// https://github.com/Pingus/pingus/blob/master/src/engine/gui/surface_button.cpp#L52
void SurfaceButton::draw (...) {
    ...
    gc.draw(...);
    ...
}

Understanding the method dispatch requires examining at least 4 files, not including the class hierarchy. For instance, we started examining the PingusMenu implementing the main menu, which extends the GUIScreen in the dispatching path. This all only for redrawing, as the update dispatch goes through a similar hierarchy.


An important detail is that self dispatching in Ceu relies on two properties of its synchronous execution model:


Comment on @_fsantanna.