4

I am trying to write a basic "game loop" using Observables in F#. Basically I conceptualize the fundamental input stream of events as two streams merged together: the key presses of the user (game uses just keyboard to begin with), and the regular ticks of the game (say, 60 times per second).

My problem seems to stem from the fact that one of the observed sequences, i.e. the ticks, is also the loop that calls DispatchEvents() on the Window allowing it to process its inputs and fire key pressed events, so one stream of events is actually driven by the other, if that makes sense. Here is the code:

open System;
open System.IO
open SFML.Window
open SFML.Graphics
open System.Reactive
open System.Reactive.Linq
open System.Diagnostics
type InputEvent =
| Tick of TimeSpan
| KeyPressed of Keyboard.Key
[<EntryPoint;STAThread>]
let main _ = 
 use window = new RenderWindow(VideoMode(640u, 480u), "GameWindow")
 window.SetVerticalSyncEnabled(true)
 let displayStream = 
 Observable.Create(
 fun (observer:IObserver<TimeSpan>) -> 
 let sw = Stopwatch.StartNew()
 while (window.IsOpen()) do
 window.DispatchEvents() // this calls the KeyPressed event synchronously
 window.Display() // this blocks until the next vertical sync
 window.Clear()
 observer.OnNext sw.Elapsed
 sw.Restart()
 observer.OnCompleted();
 { new IDisposable with member this.Dispose() = ()}) 
 let onDisplay elapsedTime = 
 // draw game: code elided
 let inputEvents = Observable.merge 
 (window.KeyPressed |> Observable.map (fun key -> KeyPressed(key.Code)))
 (displayStream |> Observable.map (fun t -> Tick(t)))
 use subscription = 
 inputEvents.Subscribe(fun inputEvent -> match inputEvent with
 | Tick(t) -> onDisplay(t)
 | KeyPressed(key) -> printfn "%A" key)
 0

This works, however, if I change the order of parameters in Observable.merge:

 let inputEvents = Observable.merge 
 (displayStream |> Observable.map (fun t -> Tick(t)))
 (window.KeyPressed |> Observable.map (fun key -> KeyPressed(key.Code)))

Then the game renders (onDisplay is called), but I don't see KeyPressed events printed to the console. Why is that?

(If you're wondering what is SFML, here's the link).

asked Sep 3, 2014 at 1:06
0

1 Answer 1

6

In pseudo-code, what merge does is:

firstStream.Subscribe(...);
secondStream.Subscribe(...);

The subscribe function you pass to Observable.create is synchronous and never yields control back to the caller. This means that merge itself is blocked from trying to subscribe to any streams that come after displayStream. When you reorder the streams so that displayStream is first, you prevent it from ever subscribing to your KeyPressed stream. This is why you are seeing the behavior you see.

In some respects, your displayStream is behaving badly. Subscribe methods should not block.

So, either make sure displayStream is the last item in your list, or do some refactoring of your code. You could just use a Subject for displayStream. Then subscribe to everything and finally start the "display loop", where you execute the loop that is currently in your displayStream definition and each time through the loop, just call OnNext on the subject.

answered Sep 8, 2014 at 7:12
Sign up to request clarification or add additional context in comments.

1 Comment

+1 - great observation - I did not notice the blocking loop in there :(

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.