| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
Mischief.ECS.Tutorial.App
Description
This module contains a more in-depth look into the Mischief App and Plugins.
It isn't as technically interesting as the other chapters of the tutorial, focusing more on organization and high-level logic.
Synopsis
Learn You an ECS for Great Mischief! - 3. App and Plugins
Introduction
The is a thin wrapper around the App that works as an interface for plugging in
various behavior and features in modular fashion, via WorldPlugins.
A Mischief game / app usually starts by creating an app with a main plugin and then running it.
main ::IO() main = do app <-newAppmyPluginrunAppapp data MainPlugin = MainPlugin deriving (Eq) instancePluginMainPlugin whereinit_ =info"Hello!"
From that initial plugin you can add other plugins, each adding new features to your game.
A Plugin p instance has two optional functions:
- An initialization system that will be ran at the very beginning of the app.
init:: p ->System()
- A collection of plugins that will be added along with this plugin.
plugins:: p ->Plugins
The collection of plugins can be created using plug.
plugins_ =plug(FooPlugin, BarPlugin, BazPlugin)
Modular Features
Mischief is intended to let you cleanly separate and organize your logic.
For instance, you may have a PhysicsPlugin that adds physics to your game, a RenderPlugin which renders objects, a LevelPlugin that spawns your levels.
instancePluginMainPlugin whereplugins_ =plug(PhysicsPlugin, RenderPlugin, LevelPlugin)
Ideally, each of these plugins would add their own independent features. So, if you were to remove physicsPlugin, your entities simply wouldn't
move and collide anymore, but the rest of the app would work just fine.
Note that, internally, PhsyicsPlugin could also be subdivided into its own plugins with separate roles:
data PhysicsPlugin = PhysicsPlugin deriving (Eq) instancePluginPhysicsPlugin whereplugins_ =collect(CollisionPlugin, MovePlugin)
This also lets the various packages of Mischief be modular, and makes it easy for third party library developers to create plugins that you just plug into your app with ease!
Initialization
So what exactly can you do within the init function of a Plugin?
The answer is, pretty much anything.
As the name suggests, it is typically used for initializing some sort of logic or behavior within your app. This is usually done by scheduling systems, inserting some resource, and spawning important entities, such as observers.
init_ = do Systems.add$Updatefoo`after`bar Systems.add$StartupbazinsertRes$ SomeRes 5
Note that the initialization systems are guaranteed to run before everything else in the app, but there is no guarantee of order between them. So, a Plugin should never depend on another Plugin being added before it.
Additionally, everything that can be done in init can be done the same in a normal system. There's nothing making it unique!
Plugin Resolution
Have you noticed that the Plugin typeclass requires the type to also derive Eq? Let's dig into that.
To give a practical example, think of an SDLPlugin which initializes an SDL program, opening
an actual window and allowing for things such as collecting input and rendering stuff.
data SDLPlugin = SDLPlugin deriving (Eq) instancePluginSDLPlugin whereinit_ = createSDLWindow (Resolution (500, 500))
Two separate Plugins exist, an InputPlugin which handles reading input events, and a RenderPlugin which handles processing shaders and rendering.
Both those Plugins need SDLPlugin to function properly.
data InputPlugin = InputPlugin deriving (Eq) instancePluginInputPlugin whereplugins_ =plugSDLPlugin data RenderPlugin = RenderPlugin deriving (Eq) instancePluginRenderPlugin whereplugins_ =plugSDLPlugin
So it's a handy feature to have multiple plugins depend on the same plugin. But what if SDLPlugin looked like this:
data SDLPlugin = SDLPlugin {res :: (Int, Int)} deriving (Eq)
instance Plugin SDLPlugin where
init (SDLPlugin {res}) = createSDLWindow (Resolution res)
SDLPlugin now produces different effects based on its value. This means there is a possibility that the following happens:
data InputPlugin = InputPlugin deriving (Eq) instancePluginInputPlugin whereplugins_ =plug(SDLPlugin (300, 300)) data RenderPlugin = RenderPlugin deriving (Eq) instancePluginRenderPlugin whereplugins_ =plug(SDLPlugin (500, 500))
What if you were to add both InputPlugin and RenderPlugin to your app? Which variant of SDLPlugin would it pick?
This conflict is why the Plugin typeclass requires Eq. When the same plugin is added into the app twice,
their two values will be compared and, if equal, the plugin will be added, otherwise the app will crash.
This ensures a level of control over how plugins do their initialization, without forcing a plugin to only be added from one source.