gore-and-ash 1.2.1.0 → 1.2.2.0
raw patch · 5 files changed
+506/−1 lines, 5 files
Files
- CHANGELOG.md +4/−0
- README.md +27/−0
- README_netwire.md +437/−0
- gore-and-ash.cabal +6/−1
- stack.yaml +32/−0
+ CHANGELOG.md view
@@ -0,0 +1,4 @@+1.2.2.0+=======++* Added CHANGELOG.md and `extra-source-files` to cabal file.
+ README.md view
@@ -0,0 +1,27 @@+gore-and-ash+============++Core package of game engine called Gore&Ash. The engine has following features:++- based on arrowised FRP ([netwire](https://wiki.haskell.org/Netwire))++- provides high-modularity and reusability. Actually the core can only compose modules that extends engine capabilities.++- actor based style of programming, see [gore-and-ash-actor](https://github.com/Teaspot-Studio/gore-and-ash-actor) module.++- network API over UDP with user controlled reliability, see [gore-and-ash-network](https://github.com/Teaspot-Studio/gore-and-ash-network) module.++- synchronization EDSL that greately simplifies complexity of client-server programming, see [gore-and-ash-sync](https://github.com/Teaspot-Studio/gore-and-ash-sync) module.++- input module via SDL2 library, see [gore-and-ash-sdl](https://github.com/Teaspot-Studio/gore-and-ash-sdl) module.++For complete proof-of-concept, see [gore-and-ash-demo](https://github.com/Teaspot-Studio/gore-and-ash-demo) repo that contains implementation of simple game.++Making your own module+======================++You can generate backbone of core module with `stack`:++```+stack new gore-and-ash-testtemp ./gore-and-ash-module.hsfiles -p module-name:TestTemp -p module-name-lower:testtemp --solver+```
+ README_netwire.md view
@@ -0,0 +1,437 @@+Netwire+=======++Netwire is a functional reactive programming (FRP) library with signal+inhibition. It implements three related concepts, *wires*, *intervals*+and *events*, the most important of which is the *wire*. To work with+wires we will need a few imports:++ import FRP.Netwire+ import Prelude hiding ((.), id)++The `FRP.Netwire` module exports the basic types and helper functions.+It also has some convenience reexports you will pretty much always need+when working with wires, including `Control.Category`. This is why we+need the explicit `Prelude` import.++In general wires are generalized automaton arrows, so you can express+many design patterns using them. The `FRP.Netwire` module provides a+proper FRP framework based on them, which strictly respects continuous+time and discrete event semantics. When developing a framework based on+Netwire, e.g. a GUI library or a game engine, you may want to import+`Control.Wire` instead.+++Introduction+------------++The following type is central to the entire library:++ data Wire s e m a b++Don't worry about the large number of type arguments. They all have+very simple meanings, which will be explained below.++A value of this type is called a *wire* and represents a *reactive*+value of type $b$, that is a value that may change over time. It may+depend on a reactive value of type $a$. In a sense a wire is a function+from a reactive value of type $a$ to a reactive value of type $b$, so+whenever you see something of type `Wire s e m a b` your mind should+draw an arrow from $a$ to $b$. In FRP terminology a reactive value is+called a *behavior*.++A constant reactive value can be constructed using `pure`:++ pure 15++This wire is the reactive value 15. It does not depend on other+reactive values and does not change over time. This suggests that there+is an applicative interface to wires, which is indeed the case:++ liftA2 (+) (pure 15) (pure 17)++This reactive value is the sum of two reactive values, each of which is+just a constant, 15 and 17 respectively. So this is the constant+reactive value 32. Let's spell out its type:++ myWire :: (Monad m, Num b) => Wire s e m a b+ myWire = liftA2 (+) (pure 15) (pure 17)++This indicates that $m$ is some kind of underlying monad. As an+application developer you don't have to concern yourself much about it.+Framework developers can use it to allow wires to access environment+values through a reader monad or to produce something (like a GUI)+through a writer monad.++The wires we have seen so far are rather boring. Let's look at a more+interesting one:++ time :: (HasTime t s) => Wire s e m a t++This wire represents the current local time, which starts at zero when+execution begins. It does not make any assumptions about the time type+other than that it is a numeric type with a `Real` instance. This is+enforced implicitly by the `HasTime` constraint.++The type of this wire gives some insight into the $s$ parameter. Wires+are generally pure and do not have access to the system clock or other+run-time information. The timing information has to come from outside+and is passed to the wire through a value of type $s$, called the *state+delta*. We will learn more about this in the next section about+executing wires.++Since there is an applicative interface you can also apply `fmap` to a+wire to apply a function to its value:++ fmap (2*) time++This reactive value is a clock that is twice as fast as the regular+local time clock. If you use system time as your clock, then the time+type $t$ will most likely be `NominalDiffTime` from `Data.Time.Clock`.+However, you will usually want to have time of type `Double` or some+other floating point type. There is a predefined wire for this:++ timeF :: (Fractional b, HasTime t s, Monad m) => Wire s e m a b+ timeF = fmap realToFrac time++If you think of reactive values as graphs with the horizontal axis+representing time, then the `time` wire is just a straight diagonal line+and constant wires (constructed by `pure`) are just horizontal lines.+You can use the applicative interface to perform arithmetic on them:++ liftA2 (\t c -> c - 2*t) time (pure 60)++This gives you a countdown clock that starts at 60 and runs twice as+fast as the regular clock. So it after two seconds its value will be+56, decreasing by 2 each second.+++Testing wires+-------------++Enough theory, we wanna see some performance now! Let's write a simple+program to test a constant (`pure`) wire:++ import Control.Wire+ import Prelude hiding ((.), id)++ wire :: (Monad m) => Wire s () m a Integer+ wire = pure 15++ main :: IO ()+ main = testWire (pure ()) wire++This should just display the value 15. Abort the program by pressing+Ctrl-C. The `testWire` function is a convenience to examine wires. It+just executes the wire and continuously prints its value to stdout:++ testWire ::+ (MonadIO m, Show b, Show e)+ => Session m s+ -> (forall a. Wire s e Identity a b)+ -> m c++The type signatures in Netwire are known to be scary. =) But like most+of the library the underlying meaning is actually very simple.+Conceptually the wire is run continuously step by step, at each step+increasing its local time slightly. This process is traditionally+called *stepping*.++As an FRP developer you assume a continuous time model, so you don't+observe this stepping process from the point of view of your reactive+application, but it can be useful to know that wire execution is+actually a discrete process.++The first argument of `testWire` needs some explanation. It is a recipe+for state deltas. In the above example we have just used `pure ()`,+meaning that we don't use anything stateful from the outside world,+particularly we don't use a clock. From the type signature it is also+clear that this sets `s = ()`.++The second argument is the wire to run. The input type is quantified+meaning that it needs to be polymorphic in its input type. In other+words it means that the wire does not depend on any other reactive+value. The underlying monad is `Identity` with the obvious meaning that+this wire cannot have any monadic effects.++The following application just displays the number of seconds passed+since program start (with some subsecond precision):++ wire :: (HasTime t s) => Wire s () m a t+ wire = time++ main :: IO ()+ main = testWire clockSession_ wire++Since this time the wire actually needs a clock we use `clockSession_`+as the second argument:++ clockSession_ ::+ (Applicative m, MonadIO m)+ => Session m (Timed NominalDiffTime ())++It will instantiate $s$ to be `Timed NominalDiffTime ()`. This type+indeed has a `HasTime` instance with $t$ being `NominalDiffTime`. In+simpler words it provides a clock to the wire. At first it may seem+weird to use `NominalDiffTime` instead of something like `UTCTime`, but+this is reasonable, because time is relative to the wire's start time.+Also later in the section about switching we will see that a wire does+not necessarily start when the program starts.+++Constructing wires+------------------++Now that we know how to test wires we can start constructing more+complicated wires. First of all it is handy that there are many+convenience instances, including `Num`. Instead of `pure 15` we can+simply write `15`. Also instead of++ liftA2 (+) time (pure 17)++we can simply write:++ time + 17++This clock starts at 17 instead of zero. Let's make it run twice as+fast:++ 2*time + 17++If you have trouble wrapping your head around such an expression it may+help to read `a*b + c` mathematically as $a(t) b(t) + c(t)$ and read+`time` as simply $t$.++So far we have seen wires that ignore their input. The following wire+uses its input:++ integral 5++It literally integrates its input value with respect to time. Its+argument is the integration constant, i.e. the start value. To supply+an input simply compose it:++ integral 5 . 3++Remember that `3` really means `pure 3`, a constant wire. The integral+of the constant 3 is $3 t + c$ and here $c = 5$. Here is another+example:++ integral 5 . time++Since `time` denotes $t$ the integral will be $\frac{1}{2} t^2 + c$,+again with $c = 5$. This may sound like a complicated, sophisticated+wire, but it's really not. Surprisingly there is no crazy algebra or+complicated numerical algorithm going on under the hood. Integrating+over time requires one addition and one division each frame. So there+is nothing wrong with using it extensively to animate a scene or to move+objects in a game.++Sometimes categorical composition and the applicative interface can be+inconvenient, in which case you may choose to use the arrow interface.+The above integration can be expressed the following way:++ proc _ -> do+ t <- time -< ()+ integral 5 -< t++Since `time` ignores its input signal, we just give it a constant signal+with value `()`. We name time's value $t$ and pass it as the input+signal to `integral`.+++Intervals+---------++Wires may choose to produce a signal only for a limited amount of time.+We refer to those wires as intervals. When a wire does not produce,+then it *inhibits*. Example:++ for 3++This wire acts like the identity wire in that it passes its input signal+through unchanged:++ for 3 . "yes"++The signal of this wire will be "yes", but after three seconds it will+stop to act like the identity wire and will inhibit forever.++When you use `testWire` inhibition will be displayed as "I:" followed by+a value, the *inhibition value*. This is what the $e$ parameter to+`Wire` is. It's called the *inhibition monoid*:++ for :: (HasTime t s, Monoid e) => t -> Wire s e m a a++As you can see the input and output types are the same and fully+polymorphic, hinting at the identity-like behavior. All predefined+intervals inhibit with the `mempty` value. When the wire inhibits, you+don't get a signal of type $a$, but rather an inhibition value of type+$e$. Netwire does not interpret this value in any way and in most cases+you would simply use `e = ()`.++Intervals give you a very elegant way to combine wires:++ for 3 . "yes" <|> "no"++This wire produces "yes" for three seconds. Then the wire to the left+of `<|>` will stop producing, so `<|>` will use the wire to its right+instead. You can read the operator as a left-biased "or". The signal+of the wire `w1 <|> w2` will be the signal of the leftmost component+wire that actually produced a signal. There are a number of predefined+interval wires. The above signal can be written equivalently as:++ after 3 . "no" <|> "yes"++The left wire will inhibit for the first three seconds, so during that+interval the right wire is chosen. After that, as suggested by its+name, the `after` wire starts acting like the identity wire, so the left+side takes precedence. Once the time period has passed the `after` wire+will produce forever, leaving the "yes" wire never to be reached again.+However, you can easily combine intervals:++ after 5 . for 6 . "Blip!" <|> "Look at me..."++The left wire will produce after five seconds from the beginning for six+seconds from the beginning, so effectively it will produce for one+second. When you animate this wire, you will see the string "Look at+me..." for five seconds, then you will see "Blip!" for one second, then+finally it will go back to "Look at me..." and display that one forever.+++Events+------++Events are things that happen at certain points in time. Examples+include button presses, network packets or even just reaching a certain+point in time. As such they can be thought of as lists of values+together with their occurrence times. Events are actually first class+signals of the `Event` type:++ data Event a++For example the predefined `never` event is the event that never occurs:++ never :: Wire s e m a (Event b)++As suggested by the type events contain a value. Netwire does not+export the constructors of the `Event` type by default. If you are a+framework developer you can import the `Control.Wire.Unsafe.Event`+module to implement your own events. A game engine may include events+for key presses or certain things happening in the scene. However, as+an application developer you should view this type as being opaque.+This is necessary in order to protect continuous time semantics. You+cannot access event values directly.++There are a number of ways to respond to an event. The primary way to+do this in Netwire is to turn events into intervals. There are a number+of predefined wires for that purpose, for example `asSoonAs`:++ asSoonAs :: (Monoid e) => Wire s e m (Event a) a++This wire takes an event signal as its input. Initially it inhibits,+but as soon as the event occurs for the first time, it produces the+event's last value forever. The `at` event will occur only once after+the given time period has passed:++ at :: (HasTime t s) => t -> Wire s e m a (Event a)++Example:++ at 3 . "blubb"++This event will occur after three seconds, and the event's value will be+"blubb". Using `asSoonAs` we can turn this into an interval:++ asSoonAs . at 3 . "blubb"++This wire will inhibit for three seconds and then start producing. It+will produce the value "blubb" forever. That's the event's last value+after three seconds, and it will never change, because the event does+not occur ever again. Here is an example that may be more+representative of that property:++ asSoonAs . at 3 . time++This wire inhibits for three seconds, then it produces the value 3 (or a+value close to it) forever. Notice that this is not a clock. It does+not produce the current time, but the `time` at the point in time when+the event occurred.++To combine multiple events there are a number of options. In principle+you should think of event values to form a semigroup (of your choice),+because events can occur simultaneously. However, in many cases the+actual value of the event is not that interesting, so there is an easy+way to get a left- or right-biased combination:++ (at 2 <& at 3) . time++This event occurs two times, namely once after two seconds and once+after three seconds. In each case the event value will be the+occurrence time. Here is an interesting case:++ at 2 . "blah" <& at 2 . "blubb"++These events will occur simultaneously. The value will be "blah",+because `<&` means left-biased combination. There is also `&>` for+right-biased combination. If event values actually form a semigroup,+then you can just use monoidal composition:++ at 2 . "blah" <> at 2 . "blubb"++Again these events occur at the same time, but this time the event value+will be "blahblubb". Note that you are using two Monoid instances and+one Semigroup instance here. If the signals of two wires form a monoid,+then wires themselves form a monoid:++ w1 <> w2 = liftA2 (<>) w1 w2++There are many predefined event-wires and many combinators for+manipulating events in the `Control.Wire.Event` module. A common events+is the `now` event:++ now :: Wire s e m a (Event a)++This event occurs once at the beginning.+++Switching+---------++We still lack a meaningful way to respond to events. This is where+*switching* comes in, sometimes also called *dynamic switching*. The+most important combinator for switching is `-->`:++ w1 --> w2++The idea is really straightforward: This wire acts like `w1` as long as+it produces. As soon as it stops producing it is discarded and `w2`+takes its place. Example:++ for 3 . "yes" --> "no"++In this case the behavior will be the same as in the *intervals*+section, but with two major differences: Firstly when the first+interval ends, it is completely discarded and garbage-collected, never+to be seen again. Secondly and more importantly the point in time of+switching will be the beginning for the new wire. Example:++ for 3 . time --> time++This wire will show a clock counting to three seconds, then it will+start over from zero. This is why we usually refer to time as *local+time*.++Recursion is fully supported. Here is a fun example:++ netwireIsCool =+ for 2 . "Once upon a time..." -->+ for 3 . "... games were completely imperative..." -->+ for 2 . "... but then..." -->+ for 10 . ("Netwire 5! " <> anim) -->+ netwireIsCool++ where+ anim =+ holdFor 0.5 . periodic 1 . "Hoo..." <|>+ "...ray!"
gore-and-ash.cabal view
@@ -1,5 +1,5 @@ name: gore-and-ash-version: 1.2.1.0+version: 1.2.2.0 synopsis: Core of FRP game engine called Gore&Ash description: Please see README.md homepage: https://github.com/Teaspot-Studio/gore-and-ash@@ -13,6 +13,11 @@ category: Game build-type: Simple cabal-version: >=1.10+extra-source-files:+ README.md+ README_netwire.md+ CHANGELOG.md+ stack.yaml library hs-source-dirs: src
+ stack.yaml view
@@ -0,0 +1,32 @@+# For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md++# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)+resolver: lts-7.9++# Local packages, usually specified by relative directory name+packages:+- '.'++# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)+extra-deps: []++# Override default flag values for local packages and extra-deps+flags: {}++# Extra package databases containing global packages+extra-package-dbs: []++# Control whether we use the GHC we find on the path+# system-ghc: true++# Require a specific version of stack, using version ranges+# require-stack-version: -any # Default+# require-stack-version: >= 0.1.4.0++# Override the architecture used by stack, especially useful on Windows+# arch: i386+# arch: x86_64++# Extra directories used by stack for building+# extra-include-dirs: [/path/to/dir]+# extra-lib-dirs: [/path/to/dir]