[disclaimer]


This is a personal blog. The opinions expressed here represent my own and not those of any of my employers or customers.

Except if stated otherwise, all the code shared is reusable under a MIT/X11 licence. If a picture is missing a copyright notice, it's probably because I'm owning it.

Thursday, March 14, 2013

Await in the Land of iOS - Collisions in Chipmunk

Note: this blog post follows the ones of Frank Krueger about the alpha release of mono 3.x for Xamarin.iOS bringing .NET 4.5 features to the mobile world: Drag-n-drop and Scripting Users. Read that first, it's worth it.

The old way!

If you're using the Chipmunk bindings, the correct way to handle collisions between shapes is to register 4 (FOUR!) handlers for the different steps: begin, preSolve, postSolve and separate. Your collision handling logic is then spread in 4 different functions. All of that for the same collision.



bool Begin (Arbiter arb)
{
 Console.WriteLine ("began");
 return true;
}

bool PreSolve (Arbiter arb)
{
 Console.WriteLine ("presolved");
 return true;
}

void PostSolve (Arbiter arb)
{
 Console.WriteLine ("postsolved");
}

void Separate (Arbiter arb)
{
 Console.WriteLine ("separated");
}
It would be great, for the sake of simplicity, if we could group the logic altogether.

await to the rescue

C# 5 (in .NET 4.5) allows just that. Procedural code in the form of:

await began;
Console.WriteLine ("BEGAN");
await presolved;
Console.WriteLine ("PRESOLVE");
await postsolved;
Console.WriteLine ("POSTSOLVE");
await separated;
Console.WriteLine ("SEPARATED");

All this, ran into an infinite loop:

async void WaitForCollisions ()
{
 using (var waiter = new AsyncCollisionWaiter (space, WALL, CHARACTER)) {
  for(;;) {
   var began = waiter.GetBeginAsync ();
   var presolved = waiter.GetPreSolveAsync ();
   var postsolved = waiter.GetPostSolveAsync ();
   var separated = waiter.GetSeparateAsync ();

   await began;
   Console.WriteLine ("BEGAN");
   await presolved;
   Console.WriteLine ("PRESOLVE");
   await postsolved;
   Console.WriteLine ("POSTSOLVE");
   await separated;
   Console.WriteLine ("SEPARATED");
  }
 }
}

The magic lies in the AsyncCollisionWaiter. Here's a quick implementation for it
//
// AsyncCollisionWaiter.cs
//
// Author:
// Stephane Delcroix <stephane@delcroix.org>
//
// Copyright (c) 2013 S. Delcroix
//
using System;
using System.Threading.Tasks;
using Chipmunk;
namespace ChipmunkHelper
{
public class AsyncCollisionWaiter : IDisposable
{
Space space;
uint collisionTypeA, collisionTypeB;
public AsyncCollisionWaiter (Space space, uint collisionTypeA, uint collisionTypeB)
{
this.space = space;
this.collisionTypeA = collisionTypeA;
this.collisionTypeB = collisionTypeB;
space.AddCollisionHandler (collisionTypeA, collisionTypeB, Begin, PreSolve, PostSolve, Separate);
}
object syncHandle = new object ();
TaskCompletionSource<Arbiter> beginTcs, presolveTcs, postsolveTcs, separateTcs;
public Task<Arbiter> GetBeginAsync ()
{
lock (syncHandle) {
beginTcs = new TaskCompletionSource<Arbiter> ();
}
return beginTcs.Task;
}
bool Begin (Arbiter arb)
{
lock (syncHandle) {
if (beginTcs != null)
beginTcs.SetResult (arb);
beginTcs = null;
}
return true;
}
public Task<Arbiter> GetPreSolveAsync ()
{
lock (syncHandle) {
presolveTcs = new TaskCompletionSource<Arbiter> ();
}
return presolveTcs.Task;
}
bool PreSolve (Arbiter arb)
{
lock (syncHandle) {
if (presolveTcs != null)
presolveTcs.SetResult (arb);
presolveTcs = null;
}
return true;
}
public Task<Arbiter> GetPostSolveAsync ()
{
lock (syncHandle) {
postsolveTcs = new TaskCompletionSource<Arbiter> ();
}
return postsolveTcs.Task;
}
void PostSolve (Arbiter arb)
{
lock (syncHandle) {
if (postsolveTcs != null)
postsolveTcs.SetResult (arb);
postsolveTcs = null;
}
}
public Task<Arbiter> GetSeparateAsync ()
{
lock (syncHandle) {
separateTcs = new TaskCompletionSource<Arbiter> ();
}
return separateTcs.Task;
}
void Separate (Arbiter arb)
{
lock (syncHandle) {
if (separateTcs != null)
separateTcs.SetResult (arb);
}
separateTcs = null;
}
public void Dispose ()
{
if (space != null)
space.RemoveCollisionHandler (collisionTypeA:collisionTypeA, collisionTypeB:collisionTypeB);
}
}
}

Caveats

There's a 5-10 ms delay between the moment the result for the Task is set and the moment the awaiting function is resumed. That means nothing in most cases, but means A LOT when you're simulating physics. That means the separated event happens before the process resumes after the await began. That's why we have to Get*Async() all the events we're interested into before waiting for the first one.

Due to that delay as well, we loose the ability to decide in a timely manner if we want this collision to happen or not. That's why I unconditionally return true from Begin and PreSolve in the AsyncCollisionWaiter.

So what ?

Frank got me excited with his series of post. I wanted to apply the same process to my own stuffs. Although the code looks nicer, you're paying a high price for that beauty, and all the value of having 4 events is kinda ruined by the delay. In this case, a single collisionHappened would have been enough.
Have fun experimenting with await in your own mobile application too.

No comments:

Post a Comment