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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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.
Have fun experimenting with await in your own mobile application too.
No comments:
Post a Comment