Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Threads in Swift with NSOperation: Gray Scott Reaction Diffusion

DZone's Guide to

Threads in Swift with NSOperation: Gray Scott Reaction Diffusion

· Java Zone
Free Resource

Learn how our document data model can map directly to how you program your app, and native database features like secondary indexes, geospatial and text search give you full access to your data. Brought to you in partnership with MongoDB.

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
    {
        if self < 0
        {
            return 0.0;
        }
        else if self > 1.0
        {
            return 1.0;
        }
        else
        {
            return self;
        }
   }
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!

Discover when your data grows or your application performance demands increase, MongoDB Atlas allows you to scale out your deployment with an automated sharding process that ensures zero application downtime. Brought to you in partnership with MongoDB.

Topics:

Published at DZone with permission of Simon Gladman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}