packages feed

mischief-ecs-0.1.0.0: src/Mischief/ECS/Tutorial/Startup.hs

{-# OPTIONS_GHC -Wno-unused-imports #-}

-- |
-- Module: Components Tutorial
-- Description: Introductory Tutorial
--
-- This module walks the user through setting up Mischief and creating a simple app.
--
-- [Next Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")
--
-- [Main Page]("Mischief.ECS")
module Mischief.ECS.Tutorial.Startup
  ( -- * Learn You an ECS for Great Mischief! - 1. Startup Guide

    -- * What do I need to know?
    -- $know

    -- * Setup
    -- $setup

    -- * The App
    -- $app

    -- * The ECS
    -- $ecs

    -- * Your First System
    -- $firstSystem

    -- * Your First Component
    -- $firstComp

    -- * Your First Query
    -- $firstQuery

    -- * Your First Mutation
    -- $firstMut

    -- * Your First Resource
    -- $res

    -- * Your First Relationship
    -- $rel

    -- * Your First Transitive Query
    -- $trans

    -- * What's Next?
    -- $next

    -- * [Next Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")
  )
where

import Control.Monad (when)
import Data.Foldable (for_)
import Data.Traversable (for)
import Mischief.ECS

-- $know
-- This book doesn't assume any knowledge of other game engines or programming paradigms, but it does expect some Haskell knowledge.
--
-- While it is possible to read this book and get a pretty good idea of what Mischief is and how it works, you'll have a much better time
-- if you have at least a very basic understanding of Haskell syntax.

-- $setup
-- In order to use Mischief, you'll first need to install @GHC@ and @cabal@. You can follow [this](https://www.haskell.org/cabal/) quick-start guide in order to do that.
--
-- After you have a new project set up, you need to install @mischief-ecs@:
--
-- @
-- cabal add mischief-ecs
-- @
--
-- We recommend using @GHC2024@ as the language standard (set in your @.cabal@ file).
--
-- == Language Extensions
-- We generally recommend using the following language extensions in a Mischief project:
--
-- @
-- DeriveAnyClass
-- DuplicateRecordFields
-- NoFieldSelectors
-- DerivingStrategies
-- OverloadedRecordDot
-- OverloadedStrings
-- QuasiQuotes
-- RequiredTypeArguments
-- TypeFamilyDependencies
-- @
--
-- @QuasiQuotes@ and @OverloadedStrings@ are especially important because some Mischief features are not available without them (namely quasi-queries and logging).
-- The rest of the extensions are highly optional.
--
-- You can paste these extensions in the @default-extensions@ field of your @.cabal@ file.

-- $app
-- A Mischief program usually starts with an app and a plugin.
--
-- @
-- import "Mischief.ECS.Prelude"
--
-- main :: 'IO' ()
-- main = do
--   app <- 'newApp' MyPlugin
--   'runApp' app
--
-- data MyPlugin = MyPlugin deriving ('Eq', 'Plugin')
-- @
--
-- If you copy this code into your project and run it using @cabal run@, your app will start! Although we haven't told it to do anything yet.
--
-- The @App@ is a wrapper around our @World@, which is the structure containing all data stored by the ECS. It allows us to add
-- initializition instructions and to plug additional logic into our game through @Plugins@.

-- $ecs
-- Mischief's ECS logic is designed to be very approachable and simple to write.
--
-- @Components@ are just types deriving the @Component@ typeclass.
--
-- @
-- data Position = Position {x :: 'Float', y :: 'Float'} deriving ('Component')
-- @
--
-- @Systems@ are functions in the @System@ monad.
--
-- @
-- printPositions :: 'System' ()
-- printPositions = do
--   'info' . 'text' =<< 'query' ('C' \@Position)
-- @
--
-- @Entities@ are ids used to represent and manipulate data.
--
-- @
-- data Entity = Entity 'Int'
-- @

-- $firstSystem
-- Paste the following function into your module:
--
-- @
-- helloWorld :: 'System' ()
-- helloWorld = 'info' "Hello World!"
-- @
--
-- This will be our first system. The only remaining step is to schedule it to run!
--
-- @
-- import "Mischief.ECS.Systems" qualified as [Systems]("Mischief.ECS.Systems")
-- @
--
-- @
-- instance 'Plugin' MyPlugin where
--
--   'Mischief.ECS.App.Plugins.init' :: MyPlugin -> 'System' ()
--   'Mischief.ECS.App.Plugins.init' _ = do
--     [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' helloWorld
-- @
--
-- The @Systems.add@ function adds the system to your App's update schedule, making it run once per frame. If you run your app again,
-- you will see \"Hello World!\" printed to your terminal many, many times.
--
-- As you may have noticed, the @init@ we give to the Plugin is, in itself, a system! There's nothing differentiating
-- the logic you write here from the logic ran at any point in your app's runtime. @init@ is just a convenient way of
-- adding some initialization that happens before anything else, but we'll get into that later.

-- $firstComp
-- Let's do a little more than greeting the whole world, let's greet some individual people!
--
-- In ECS, you would generally model people as entities with a set of components that define them. Let's start with a simple @Person@ component:
--
-- @
-- data Person = Person deriving ('Component')
-- @
--
-- So how can we give people names? In a more traditional design you could just add a @name :: String@ field to @Person@. But the ECS makes you think of it differently!
-- A @Name@ is just a piece of data that can be attached to anything. A dog could also have a name. So why not just make a @Name@ component?
--
-- @
-- data Name = Name 'String' deriving ('Component')
-- @
--
-- No need to define it though, since this exact @Name@ is already defined internally by Mischief, you can just use it directly.
--
-- Now that we can represent people with names, let's make a system that spawns some:
--
-- @
-- addPeople :: 'System' ()
-- addPeople = do
--   kim <- 'spawn' (Person, Name \"Kimberly\")
--   nick <- 'spawn' (Person, Name \"Nicholas\")
--   flo <- 'spawn' (Person, Name \"Florian\")
--   'pure' ()
-- @
--
-- You can register it to run on the app's startup like this:
--
-- @
-- 'Mischief.ECS.App.Plugins.init' _ = do
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' helloWorld
-- @

-- $firstQuery
-- If you run your app, the people will be spawned but we aren't doing anything with them yet! Let's make a system that greets them:
--
-- @
-- greetPeople :: 'System' ()
-- greetPeople = do
--   people <- 'query' ('C' \@Name, 'C' \@Person)
--   'for_' people $ \(name, _) ->
--     'info' $ \"Hello \" <> 'text' name
-- @
--
-- The above query will grab the @Name@ and @Person@ of every entity. Then it iterates over them  in order to greet them.
--
-- The @Person@ component however, is only queried to ensure we are querying the right entities. We don't care about its value at all! So we can instead write it
-- as a filter to limit the types of entities selected by the query and save us the trouble of carrying an extra variable around.
--
-- @
-- greetPeople :: 'System' ()
-- greetPeople = do
--   people <- 'query'' ('C' \@Name) ('With' ('C' \@Person))
--   'for_' people $ \name ->
--     'info' $ \"Hello \" <> 'text' name
-- @
--
-- Do note the use of @query'@ here instad of @query@. The former is a variant of the same function but which also expects a filter.
--
-- Mischief has two equivalent ways of writing queries. The normal way that you've seen above, and the quasi way:
--
-- @
-- people <- ['q'|Name / With Person|]
-- @
--
-- Query-queries are macros meant to simplify writing queries. They'll become especially helpful once we start dealing
-- with relationships and transitive queries.
--
-- Now we can schedule this system to also run:
--
-- @
-- 'Mischief.ECS.App.Plugins.init' _ = do
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (helloWorld, greetPeople)
-- @
--
-- Running our app will result in the following output:
--
-- @
-- [INFO] Hello World!
-- [INFO] Hello Kimberly
-- [INFO] Hello Nicholas
-- [INFO] Hello Florian
-- @
--
-- Note that \"Hello World\" might show above or beneath the other people, since systems in the same schedule can run in any order unless they are explicitly ordered.

-- $firstMut
-- If we want to change the name of some people, we can apply a mutation to a value obtained from the query:
--
-- @
-- updateFlo :: 'System'
-- updateFlo = do
--   people <- ['q'|Name / With Person|]
--   'for_' people $ \name -> do
--     'when' (name == Name \"Florian\") $
--       'set' name (Name \"Florianne\")
-- @
--
-- Although.. that feels awfully imperative doesn't it? We can also write the same system as:
--
-- @
-- updateFlo = do
--   florians <- ['q'|Name / With Person, Check (== Name \"Florian\")|]
--   'for_' florians $ '`set`' Name \"Florianne\"
-- @
--
-- The above query can also be written like this, in non-quasi notation:
--
-- @
-- florians <- 'query'' ('C' \@Name) ('With' ('C' \@Person), 'Check' (== Name \"Florian\"))
-- @
--
-- Let's add the new system to a schedule:
--
-- @
-- 'Mischief.ECS.App.Plugins.init' _ = do
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (helloWorld, greetPeople)
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' $ updateFlo '`before`' greetPeople
-- @
--
-- Note that we have explicitly ordered @updateFlo@ to happen before @greetPeople@. We want to only greet Flo after their name has changed!

-- $res
-- Resources are a great way to store global information that can be easily written to and read in any system.
--
-- Let's say we want to have a custom greeting that we can change at runtime. We could store that in a resource:
--
-- @
-- data Greeting = Greeting 'String' deriving ('Component')
-- @
--
-- Yes, resources are just normal components! Any component can be stored and retrieved as a resource by using @insertRes@ and @res@.
--
-- Let's insert a greeting from our init system:
--
-- @
-- 'Mischief.ECS.App.Plugins.init' _ = do
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (helloWorld, greetPeople)
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' $ updateFlo '`before`' greetPeople
--
--   'insertRes' (Greeting \"Hey\")
-- @
--
-- And let's modify @greetPeople@ so that it uses the current greeting from the resource:
--
-- @
-- greetPeople :: 'System' ()
-- greetPeople = do
--   'Just' (Greeting greeting) <- 'res' \@Greeting
--
--   people <- ['q'|Name / With Person]
--   'for_' people $ \name ->
--     'info' $ 'text' greeting <> \" \" <> 'text' name
-- @
--
-- You should now see this when running the app:
--
-- @
-- [INFO] Hello World!
-- [INFO] Hey Kimberly
-- [INFO] Hey Nicholas
-- [INFO] Hey Florianne
-- @

-- $rel
-- Relationships in Mischief are pairs made up of a Component and an Entity. Let's implement a simple relationship between our entities that says which like which.
--
-- We'll start by defining a component:
--
-- @
-- data Likes = Likes deriving ('Component')
-- @
--
-- Let's now modify our spawning system to also insert relationships between our three entities. We can insert a relationship using the @Rel@ keyword.
--
-- @
-- addPeople :: 'System' ()
-- addPeople = do
--   kim <- 'spawn' (Person, Name \"Kimberly\")
--   nick <- 'spawn' (Person, Name \"Nicholas\")
--   flo <- 'spawn' (Person, Name \"Florian\")
--
--   'insert' ('Rel' Likes kim) flo
--   'insert' ('Rel' Likes nick, 'Rel' Likes flo) kim
-- @
--
-- We've now made @flo@ like @kim@, and we've made @kim@ like both @nick@ and @flo@!.

-- $trans
-- We now have relationships but we aren't doing much with them. What about having a system that displays the name of each entity, along with the name of all entities they like?
--
-- There are a few different ways to get the names of entities that a given entity likes, for instance we could do:
--
-- @
-- people <- 'query' ('C' \@Name, 'R' \@Likes Any)
--
-- 'for_' people $ \(name, likes) -> do
--   names \<- 'for' likes $ \\l -\> 'get' ('C' \@Name) l.target
--   'info' $ 'text' name <> " likes " <>  'text' names
-- @
--
-- We get the name of each entity, along with all their @Likes@ relationships (using the @R@ marker). Then, for each entity, we iterate over all their relationships and get the names of the targets.
-- (@get@ is just like @query@ but it queries the components of a specific entity).
--
-- But we'll have a way easier time getting there by just using @transitive queries@!
-- Rather than querying for the relationships themselves, Mischief allows us to query for the components of the relationship targets from within the same query:
--
-- @
-- people <- 'query' ('C' \@Name, 'R' \@Likes ('Q' ('C' \@Name)))
--
-- 'for_' people $ \(name, names) -> do
--   'info' $ 'text' name <> " likes " <> 'text' names
-- @
--
-- There's no need for a second query to grab the names. The equivalent quasi-query looks like this:
--
-- @
-- people \<- ['q'|Name, Likes -\> (Name)|]
-- @
--
-- Pretty nice, huh?
--
-- Let's now put this logic in a proper system and schedule it to run:
--
-- @
-- showLikes :: 'System' ()
-- showLikes = do
--   people \<- ['q'|Name, Likes -\> (Name)|]
--
--   'for_' people $ \(name, names) -> do
--     'info' $ 'text' name <> " likes " <> 'text' names
-- @
--
-- @
-- 'Mischief.ECS.App.Plugins.init' _ = do
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (helloWorld, greetPeople, showLikes)
--   [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' $ updateFlo '`before`' greetPeople
--
--   'insertRes' (Greeting \"Hey\")
-- @
--
-- We should now see these additional likes printed to the terminal:
--
-- @
-- [INFO] \"Florianne\" likes [\"Kimberly\"]
-- [INFO] \"Kimberly\"  likes [\"Nicholas\", \"Florianne\"]
-- @

-- $next
-- What you learn next is up to you.
--
-- The next chapter will have you working on a little dungeon game in the terminal and introduce you to more notions. If you prefer to learn by example it's recommended to
-- go check that out.
--
-- After that, the next chapters go into detail on various topics (Components, Queries, Systems, etc.), so you may choose to just read those directly, and maybe come
-- back to the game later.