-
-
Notifications
You must be signed in to change notification settings - Fork 155
Model View Update for Games via MMCC
Nu's high-level programmability API is based off a program structuring technique knows as Model-View-Update (or MVU
).
Nu has it's own implementation of MVU called Model-Message-Command-Content (or MMCC
).
MMCC
is a generalization of MVU that affords it three additional capabilities important for game programming -
-
It expands the scope of applicability from just UI programming to general simulation programming, additionally providing users an in-the-box means of defining their own stateful simulants.
-
It allows users to conveniently separate effectful operations (such as those that do IO or transform the World) from functional operations (those that transform a Model).
-
It allows the MVU pattern to be used recursively in that each simulant (Game, Screen, Group, or Entity) can contain its own isolated internal MMCC structure.
But for those unfamiliar with MVU to understand MMCC, let's first understand MVU.
Model-View-Update (MVU) is a design pattern used in user interface development. It provides a structured way to organize the components of a graphical user interface (GUI) and manage the state and behavior of the application. MVU is particularly popular in functional programming languages and frameworks.
Let's break down the components of MVU:
- Model: The Model represents the application state, which includes all the data that the application needs to work with. It is like a snapshot of the current state of the user interface. The Model is typically an immutable data structure, such as a record or a data class, and it contains all the relevant information required to render the user interface and respond to user interactions.
For example, in a simple to-do list application, the Model might contain an array or list of tasks, each with a description and a status (completed or not).
- View: The View is responsible for rendering the user interface based on the data in the Model. It doesn't contain any application logic; it simply displays the information provided by the Model. The View is a pure function that takes the current state (Model) as input and returns the graphical representation of the user interface.
In our to-do list example, the View would take the list of tasks from the Model and display them on the screen, showing their descriptions and status.
- Update: The Update is the core of the MVU pattern. It represents the actions or events that can occur in the application that lead to changes in the Model. These actions are typically triggered by user interactions, such as button clicks or input changes. The Update is a pure function that takes the current Model and an action as input and returns a new updated Model.
In the to-do list application, an Update function could handle actions like adding a new task, marking a task as completed, or deleting a task. When the user interacts with the application, an action is generated, and the Update function is called to produce a new Model reflecting the changes.
The MVU pattern follows a unidirectional flow of data:
View -> (User Interaction) -> Update -> Model -> View
- The View displays the current state of the Model to the user.
- When the user interacts with the View, an action is triggered.
- The Update function processes the action and produces a new updated Model.
- The updated Model is used to re-render the View, and the cycle continues.
This unidirectional data flow makes it easier to reason about the application's behavior and manage its state, as there are no direct side effects or mutable state. Overall, MVU is a simple and predictable pattern for building user interfaces, especially in functional programming environments, as it relies on pure functions and immutable data structures.
If you need further clarification on MVU, you can explore the following introductory material to via Elm MVU as found here - https://guide.elm-lang.org/ or as used with other popular F# projects.
The Model
part of MVU is exactly the same in MMCC.
The Update
part of MVU is split into two parts in MMCC; a Message
function and Command
function, each with their own algebraic types to specify the messages or commands each is to handle.
The View
part of MVU is similar to the Content
function in MMCC. Unlike MVU Views
, MMCC uses a more generalized Content
function to represent all the active simulants in a game, not just the UI or things that appear on screen.
Each type of simulant (Game
, Screen
, Group
, Entity
) has its own super-type of stateless dispatcher (GameDispatcher
, ScreenDispatcher
, GroupDispatcher
, EntityDispatcher
). MMCC behavior is defined by inheriting from some sort of existing dispatcher then overriding the relevant methods, such as those available from a GameDispatcher<'model, 'message, 'command>
-
/// The game's own MMCC definitions.
abstract Definitions : 'model * Game -> DefinitionContent list
/// The message handler of the MMCC programming model.
abstract Message : 'model * 'message * Game * World -> Signal list * 'model
/// The command handler of the MMCC programming model.
abstract Command : 'model * 'command * Game * World -> Signal list * World
/// The content specifier of the MMCC programming model.
abstract Content : 'model * Game -> ScreenContent list
/// Render the game using the given model.
abstract Render : 'model * RenderPass * Game * World -> unit
/// Implements additional editing behavior for a game via the ImGui API.
abstract Edit : 'model * EditOperation * Game * World -> Signal list * 'model
Additionally, there are other dispatcher overloads available from the non-generic, non-MMCC GameDispatcher
type. But to get started with MMCC in Nu, these are the ones you'll need to be aware of.
So what is the difference between a Message
and a Command
? A Message
is a subtype of Signal
that can result in a transformed model value. It allows users to specify functions that take a simulant's model value and produce a different model value for said simulant. A Command
is another subtype of Signal
, but rather than resulting in a transformed model value, it allows a transformation of the entire world. For the most part, most of your signals will be messages since they only result in model changes. But when you need to do something more effectful - such as having a transformative effect on the world value, you'll issue a command instead.