I'm trying to get into Netwire, I've dug to find documentations, introductions, tutorials and whatnot, but just about every tutorial & existing-code is outdated as to Netwire 5 and uses functions from Netwire 4 that are just no longer with us. The README is kind of helpful, but not everything compiles and still it barely provides enough information to get started.
I am asking for explanation or an example just to get a game-loop running and being able to respond to events, so I seek information so I'll eventually know:
- The basic structure (like how in reactive-banana you actuate network-descriptions that consume handlers, define behaviors and reactimate to events).
- How it ultimately goes in
main
. - How to handle IO events (like a mouse-click, a key-down or a game-loop callback), how do events come in sessions, etc.
And anything else relevant.
I figure that from there I could get something running, and so I could learn the rest by experimentation (as the state of documentation and tutorials for this in the 5th version is horribly non-existent, I hope some will appear very shortly).
Thank you!
Disclaimer: I haven't been able to find any large-scale programs that use Netwire, so everything I'm about to write you should take with a grain of salt, as it's based on my own experiences using Netwire. The examples that I use here are mostly taken from my own library and attempt at writing a game using FRP, and might not be "the right way" to do things.
Question 1: The basic structure (like how in reactive-banana you actuate network-descriptions that consume handlers, define behaviors and reactimate to events).
Sessions: The author of the netwire library gave a really good answer about the basic structure of a netwire program. Since it's a bit old, I will outline some of the points here. Before we look at wires, let's first take a look at how netwire handles time, the underlying driver of FRP. The only way to advance time without using the testing harness
testWire
is to produce aSession
that will statefully return time deltas. The waySessions
preserve state is encapsulated in their type:Here, a
Session
lies within a Monad (usuallyIO
) and every time it is evaluated, returns a "time state" value of types
and a newSession
. Usually, any useful states
can be written as aTimed
value that can return some instance ofReal t
:For example, in games, you usually want a fixed timestep to do your update calls. netwire encodes this notion with:
A
countSession_
takes as input the timestep, in this case a fixed value of typet
, and produces aSession
whose state values are of typeTimed t ()
. This means that they only encode a single value of typet
, and do not carry any additional state with the()
value. After we discuss wires, we will see how this plays a role in evaluating them.Wires: The main type of a 'wire' in Netwire is:
This wire describes a reactive value of type
b
that does the following:a
m
e
s
By the nature of being reactive values, wires can be thought of as time-varying functions. Hence, each wire is encoded as a function of time (or the time state
s
) that produces, at that instant in time, a new value of typeb
, and a new wire with which to evaluate the next input of typea
. By returning a value and a new wire, the function can encompass state by propagating it through the function definitions.Additionally, wires may inhibit or not produce a value. This is useful for when computations are not defined (such as when the mouse is outside of the application window). This allows you to implement things like
switch
, where a wire changes to a different wire to continue execution (such as a player finishing his jump).With these ideas, we can see the main driver of wires in netwire:
stepWire wire timestate input
does exactly what we said earlier: It takes awire
and passes it the currenttimestate
andinput
from the previous wire. Then, in the underlying Monadm
, it either produces a value ofRight b
or inhibits with a value ofLeft e
, and then gives the next wire to use for the computation.Question 2: How it ultimately goes in main.
Armed with values of type
Session
andWire
, we can construct a loop that does two things over and over:Here is an example of a program that alters a fixed counter to count by twos forever:
Question 3: How to handle IO events (like a mouse-click, a key-down or a game-loop callback), how do events come in sessions, etc.
There is somewhat of a debate about how to do this. I think in this situation it's best to take advantage of the underlying Monad
m
, and simply pass a snapshot of the current state to thestepWire
function. In doing so, most of my input wires look something like this:Where the input to the wire is ignored, and the mouse input is read from a
State
monad. I useState
and notReader
in order to handle key debounces properly (so that clicking on UI doesn't also click on something underneath UI). The state is set in mymain
function and passed torunState
, which also does the wire stepping. The inhibition behavior of wires like this can make for some elegant code. For example, suppose you have wiresright
andleft
for your arrow keys that produce a value if the key is pressed and inhibit otherwise. You can create character movement with a wire that looks like this:Since wires are an instance of
Alternative
, ifright
inhibits, it'll just move on to the next possible wire.a <|> b
will inhibit only if botha
andb
inhibit.You could also write your code to take advantage of netwire's
Event
system, but you'd have to make your own wires that return anEvent
usingControl.Wire.Unsafe.Event
. That being said, I have yet to find this abstraction more useful than simple inhibition.