Search code examples
androidmultithreadingscalaactorgame-loop

Actor/message-passing within threaded Game "event loop"


I am building an Android application (using Scala 2.9) and am using a Thread that renders to a SurfaceView; this is for a game so it should update as often as it can. I imagine this issue is similar to other game "event loops" where the input comes from a different thread.

Here is a gross approximation of the current approach that relies on synchronization. It "works well enough", but I have general misgivings about having to use explicit synchronization and "tying up" the View/input thread.

View, "UI thread":

def View.onTouchEvent(e) { // on UI thread
  Game.handleInput(e)
}

Game, "Game Thread":

def Game.handleInput(e) = S synchronized { // on UI thread
  alterStateBasedOnInput
}
def Game.run () { // on Game thread 
  while (running) {
    S synchronized { 
      doGameStuff
    }
    View.post(someStateRelayedViaRunnable)
    yield
  }
}

Instead of explicitly using synchronization, I'd like to have something like this:

def View.onTouchEvent(e) { // on UI thread
   Game.sendMessage(e)
}
def Game.run () { // on Game thread 
  while (running) {
    processMessage
    doGameStuff
    View.sendMessage(someState) // hopefully same as Game.sendMessage
    yield
  }
}

Now, this is relatively easy to implement manually using a ConcurrentLinkedQueue or similar, but I would really not like to reinvent the wheel here. In addition, it would be nice to use such an actor/queue to post-back to the UI as well - right now I am using Android's support for posting an (asynchronous) Runnable to the UI thread.

I've briefly looked at several different actor implementations (mainly standard Scala and the Scalaz) and some different Java "message passing" libraries such as Jetlang, but most seem to use implicit threads or a thread executor service. But, in my case I wish to [run the actor and] process messages at a specific time on a specific thread. For View.sendMessage the messages should also be processed on the UI thread, but timing is not as important and can piggyback off of the Runnable execution noted above.

Then, I guess my question is, given the above:

What would be a good - as in "efficient" and idiomatic - approach to feed data between these two threads?

(I am also willing to entertain the suggestion that I completely fail to understand Scala actors and/or Scalaz actors and/or other message passing libraries; Scalaz seems like it might be able to work as I envision, but is hard for me to follow.)


Solution

  • Well, while I would still like to know of a generic/reusable approach to the above, practicality calls. This can also be done by running a Looper on the game thread and then putting the game "event loop stuff" inside the IdleHandler, but I did not like that inversion ..

    Here is how I have currently implemented it:

    Game/Thread class:

    var handler: Handler = _ // handler created on View thread
    
    // Send Message to Looper that exists on View thread
    // (Created implicitly for all UI threads.)
    def publishEvent(event: OutputEvent) {
      handler.obtainMessage(0, event).sendToTarget
    }
    
    protected val queue = new ConcurrentLinkedQueue[InputEvent]
    
    def queueEvent(event: InputEvent) { // on UI thread
      queue.add(event)
    }
    
    def processQueuedEvents() { // on game Thread
      @tailrec
      def processNextEvent {
        val event = queue.poll
        if (event ne null) {
          doStuffWithInputEvent(event)
          processNextEvent
        }
      }    
      processNextEvent
    }
    
    override def run() { // on game Thread
      while (running) {
        processQueuedEvents
        doOtherGameStuff ~ call_publishEvent ~ etc
      }
    }
    

    View class:

    // Created on UI thread, passed to Game instance
    // (The Looper will dispatch Messages to Handlers.)
    val handler = new Handler {
      override def handleMessage(m: Message) {
        val event = m.obj
        doStuffWithOutputEvent(event)
      }
    }
    
    // on UI thread
    override def onTouch(v: View, ev: MotionEvent): Boolean = {
       // safely queued, will be processed at the start of each game loop
       game.queueEvent(InputEvent(..))
    }