I’m writing a game where the Scene
has a similar role to the role of Page
in a typical SPA app. My model looks like this (I’m not using Browser.application
):
type alias Model =
{ scene : Scene
, gameResults : GameResult.RemoteGameResults
}
type Scene
= ChooseVariant
| Game Scene.Game.Model
| GameWon Scene.GameWon.Model
type Msg
= InitializeGame Game.Variant.Identifier
| GameSceneMsg Scene.Game.Msg
| GameWonSceneMsg Scene.GameWon.Msg
| GameResultsReceived Decode.Value
gameResults
is outside of the Scene
type because multiple scenes can show game results.
There’s a great thread about different approaches to “parent-child” state, I’m using pretty much what @MarkHamburg described:
So, we now handle ancestral state either by passing it down to functions like
view
andsubscriptions
which have no power to modify state anyway and broadcasting it to descendants when it changes for those cases where children want to respond in their own state to changes in ancestral state. I’m less thrilled about the latter and am looking at ways to eliminate it but the data structures and algorithms often get more complicated in the absence of the ability to react to parental state changes. But that’s its own topic.
Well, let’s get to the less thrilling part.
In the main update
method, I have a separate branch for intercepting a message when the results are updated (the message is sent through a JS port defined in Main):
case ( model.scene, msg ) of
( _, GameResultsReceived rawGameResults ) ->
There I update the shared piece of model, however, when game results are received, I have a scene which would like to send a command that focuses on a certain input field rendere by that scene.
Right now in order to handle this scenario, I do case model.scene of
and when it matches that certain scene, I craft an internal message of this scene by hand and call its update function.
case ( model.scene, msg ) of
( _, GameResultsReceived rawGameResults ) ->
let
gameResults =
-- Decoding game results here.
( newSceneModel, sceneCmds ) =
-- For certain scenes, we need to fire some commands after the results get
-- loaded.
case model.scene of
GameWon sceneModel ->
Scene.GameWon.update (Scene.GameWon.GameResultsReceived gameResults) sceneModel
|> Tuple.mapFirst GameWon
|> Tuple.mapSecond (Cmd.map GameWonSceneMsg)
_ ->
( model.scene, Cmd.none )
in
( { model | gameResults = gameResults, scene = newSceneModel }, sceneCmds )
I think the Main
module here knows way to much about that scene’s internals than it should, it also needs to know which scenes are interested in knowing about those changes rather than the scenes handling this fact.
What are the other solutions to this problem? I suppose there’s no escape from defining something for each scene which in for most of the scenes will act as noop but a single scenes is going to emit a command?