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
testWireis to produce aSessionthat will statefully return time deltas. The waySessionspreserve state is encapsulated in their type:Here, a
Sessionlies within a Monad (usuallyIO) and every time it is evaluated, returns a "time state" value of typesand a newSession. Usually, any useful statescan be written as aTimedvalue 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 aSessionwhose 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
bthat does the following:amesBy 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 inputdoes exactly what we said earlier: It takes awireand passes it the currenttimestateandinputfrom the previous wire. Then, in the underlying Monadm, it either produces a value ofRight bor 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
SessionandWire, 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 thestepWirefunction. 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
Statemonad. I useStateand notReaderin order to handle key debounces properly (so that clicking on UI doesn't also click on something underneath UI). The state is set in mymainfunction 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 wiresrightandleftfor 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, ifrightinhibits, it'll just move on to the next possible wire.a <|> bwill inhibit only if bothaandbinhibit.You could also write your code to take advantage of netwire's
Eventsystem, but you'd have to make your own wires that return anEventusingControl.Wire.Unsafe.Event. That being said, I have yet to find this abstraction more useful than simple inhibition.