Quantcast
Channel: FlexMonkey
Viewing all articles
Browse latest Browse all 257

Threads in Swift with NSOperation: Gray Scott Reaction Diffusion

$
0
0


When I first discovered ActionScript Workers, one of the first things I did was use them to implement a model of the Gray Scott reaction diffusion system. Now that I've started playing with Swift, I thought I'd do something similar with NSOperation.

Before we start, a big caveat: the best place to run big cellular automata like reaction diffusion systems is, of source, the GPU. This blog post is really about my experiments with Swift's threading. I'm looking forward to tinkering with Metal soon, then we'll have some proper reaction diffusion going on. 

As much as I loved AS Workers, they are a bit tricky to get along with: there's a lot of boilerplate code and all the data passed between workers has to be serialised. Classes that extend NSOperation can pass any type of data back and forth to the main UI thread and to execute them, they simply need to be added to a queue and their completionBlock closure is invoked when they're finished:


        let queue = NSOperationQueue();
        [...]


        solver = GrayScottSolver();
    
        solver.setGrayScott(grayScottData);
        solver.setParameterValues(f: f, k: k, dU: dU, dV: dV)
        solver.threadPriority = 0;
        solver.completionBlock = {self.didSolve(self.solver.getGrayScott())};
  

        queue.addOperation(solver);

However, as we'll see later, I'm not using the completionBlock.

I've created two operation classes, GrayScottSolver and GrayScottRenderer

The solver class accepts a one dimensional representation of a two dimensional grid. I did start with a two dimensional array (of the form array[x][y]), but found switching back to one dimensional a lot faster. It's main() function loops over each item and creates a temporary array using the following formula:



Originally, I was updating each element in the array, but found appending items to a temporary array a lot quicker. 

The renderer class accepts a similar array but creates a UIImage instance based on the data. For each pixel, I draw a 1 x 1 rectangle onto a graphics context, then call UIGraphicsGetImageFromCurrentImageContext() to populate the UIImage. No serialisation required here - the main UI thread can happily access the background thread's properties with no conversion. 

The 'u' species of the system is mapped to red and green and the 'v' species to blue.

I mentioned above that the completionBlock is executed when the operations are finished. However, that completion block doesn't execute in the main UI thread. The upshot is, that any UI changes aren't reflected. I'm trying to set the image property of an UIImageView when the renderer is completed and using completionBlock doesn't work.

After a lot of head scratching, I've come up with what feels like a slightly hacky solution: I have a timer firing twenty times a second looking at the finished property of each operation. If the solver is finished, it gets the Gray Scott data array, starts the render operation and restarts the solver operation. If the renderer operation is finished, if sets the UIImageView's image property the the image generated by the renderer.

At anytime, both operations can be running in parallel.

To add some ad-hoc functionality to both CGFloat and Int types, I've made use of Swift's Extensions. Also known as retroactive modelling, extensions allow developers to add new methods to existing classes. For example, to clamp CGFloat values between zero and one,  my extension class is as simple as:


    func clip() -> CGFloat
    {
        ifself<0
        {
            return0.0;
        }
        elseifself> 1.0
        {
            return1.0;
        }
        else
        {
            returnself;
        }

    }

And now, any instance of a CGFloat, can be clamped like so:


      let foo : CGFloat = 1234.5678;

      foo.clip()

I've added some controls to set the parameter values and these show that, although each step takes each solver around a third of a second, because they're happening in a separate thread, the user interface remains fully responsive. 

Just for fun, if this was running on the GPU, here's a screen recording of this project really, really speeded up:




The project is available here in my GitHub repository. If you can see any ways to speed things up, I'd love to hear them!


Viewing all articles
Browse latest Browse all 257

Trending Articles