HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Multithreading in a 2D Mining Game

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
multithreadinggamemining

Problem

I have implemented some basic multithreading in my 2D mining game that I asked about in a previous question. The first part is working out great (creating the initial object instances in the background while displaying a loading screen rather than just having a black screen while they load). However, implementing multithreading for code that runs during the actual game is not giving me the performance gains that I was hoping for. That being said, the code does run and I do gain a small amount of performance.

This might just be a limitation of the software because for games in iOS, all of the rendering must take place on the main thread. I have tried to cheat and create sprites on another thread and then add them to the scene on the main thread, but doing it this way reduces performance rather than increases it. Based on my testing and profiling of the time used by the methods, almost all of the time is used by rendering the actual scene.

Mostly I want a review of my actual implementation of multithreading (using Grand Central Dispatch here), but any other performance enhancing tips would be appreciated.

Here is the first part (the loading screen):

```
-(void) didMoveToView:(SKView *)view {
self->_draw_queue = dispatch_queue_create("scene queue", nil);

_initialScreenSize = self.size;
_screenBufferX = _initialScreenSize.width*kBufferFactor;
_screenBufferY = _initialScreenSize.height*kBufferFactor;

self.backgroundColor = [SKColor colorWithRed:0.10 green:0.10 blue:0.2 alpha:1.0];

_world = [[SKNode alloc]init];
[self addChild:_world];

[_world addChild:[self loadingSprite]];

_gameSpeed = 0.15;

dispatch_async(self -> _draw_queue, ^{
_game = [[DWGame alloc]init];
dispatch_async(dispatch_get_main_queue(), ^{
SKNode *spriteToRemove = [_world childNodeWithName:@"loadingSprite"];
[spriteToRemove removeAllChildren];
[spriteToRemove removeFromParent];

_gameRender

Solution

Personally, I don't really care for Grand Central Dispatch. I prefer NSOperationQueue. Although, if I'm honest, I'm not sure what the performance difference (if any) there exists between the two. To me, NSOperationQueue just looks cleaner.

But with that said, I do see some problems.

For example, it is considered best practice to use the reverse domain name look up scheme to naming your threads. And also... as with every where else, we should be using named constants rather than literal strings.

NSString * const kQUEUE_GameUpdate = @"com.yourdomain.gameUpdate";

// ....

self->_draw_queue = dispatch_queue_create(kQUEUE_GameUpdate, nil);


dispatch_async(self -> _draw_queue, ^{
    _game = [[DWGame alloc]init];
    dispatch_async(dispatch_get_main_queue(), ^{


We're doing very little on the background queue here. Unless the init method is really intensive, I'm curious as to how much is actually gained here.

Can we put some more on the background here?

-(void) didMoveToView:(SKView *)view {
    self->_draw_queue = dispatch_queue_create("scene queue", nil);

    _initialScreenSize = self.size;
    _screenBufferX = _initialScreenSize.width*kBufferFactor;
    _screenBufferY = _initialScreenSize.height*kBufferFactor;

    self.backgroundColor = [SKColor colorWithRed:0.10 green:0.10 blue:0.2 alpha:1.0];

    _world = [[SKNode alloc]init];
    [self addChild:_world];

    [_world addChild:[self loadingSprite]];

    _gameSpeed = 0.15;

    dispatch_async(self -> _draw_queue, ^{
        _game = [[DWGame alloc]init];
        dispatch_async(dispatch_get_main_queue(), ^{
            SKNode *spriteToRemove = [_world childNodeWithName:@"loadingSprite"];
            [spriteToRemove removeAllChildren];
            [spriteToRemove removeFromParent];

            _gameRender = [[DWGameRender alloc]initWithGame:_game];
            [_world addChild:_gameRender.gameSprites];

            self.backgroundColor = [SKColor colorWithRed:0 green:0 blue:0 alpha:1.0];

            //Set up the gesture recognizers
            _panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePanFrom:)];
            [[self view] addGestureRecognizer:_panRecognizer];
            _pinchRecognizer = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
            [[self view] addGestureRecognizer:_pinchRecognizer];
        });
    });
}


Could we rewrite this into something more like the following?

- (void)didMovetoView:(SKView *)view {
    self->_draw_queue = dispatch_queue_create(kQueueScene,nil);

    dispatch_async(self->_draw_queue, ^{
        _initialScreenSize = self.size;
        _screenBufferX = _initialScreenSize.width*kBufferFactor;
        _screenBufferY = _initialScreenSize.height*kBufferFactor;

        _world = [[SKNode alloc]init];
        [self addChild:_world];

        [_world addChild:[self loadingSprite]];

        _gameSpeed = 0.15;

        _game = [[DWGame alloc]init];

        SKNode *spriteToRemove = [_world childNodeWithName:@"loadingSprite"];

        _gameRender = [[DWGameRender alloc]initWithGame:_game];
        [_world addChild:_gameRender.gameSprites];

        _panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePanFrom:)];
        _pinchRecognizer = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];

        dispatch_async(dispatch_get_main_queue(), ^{
            // all the code that *must* be done on the main queue
        });
    });
}


Essentially, the gist is, if you're going to multithread, just remember that anything that touches the UI must be on the main thread. After that, you should move everything else into the background if you're going to bother multithreading at all.

And as a disclaimer, I'm not familiar with Sprite Kit at all, so some of the stuff I left in the background may actually be UI code that must be in the foreground.

Code Snippets

NSString * const kQUEUE_GameUpdate = @"com.yourdomain.gameUpdate";

// ....

self->_draw_queue = dispatch_queue_create(kQUEUE_GameUpdate, nil);
dispatch_async(self -> _draw_queue, ^{
    _game = [[DWGame alloc]init];
    dispatch_async(dispatch_get_main_queue(), ^{
-(void) didMoveToView:(SKView *)view {
    self->_draw_queue = dispatch_queue_create("scene queue", nil);

    _initialScreenSize = self.size;
    _screenBufferX = _initialScreenSize.width*kBufferFactor;
    _screenBufferY = _initialScreenSize.height*kBufferFactor;

    self.backgroundColor = [SKColor colorWithRed:0.10 green:0.10 blue:0.2 alpha:1.0];

    _world = [[SKNode alloc]init];
    [self addChild:_world];

    [_world addChild:[self loadingSprite]];

    _gameSpeed = 0.15;

    dispatch_async(self -> _draw_queue, ^{
        _game = [[DWGame alloc]init];
        dispatch_async(dispatch_get_main_queue(), ^{
            SKNode *spriteToRemove = [_world childNodeWithName:@"loadingSprite"];
            [spriteToRemove removeAllChildren];
            [spriteToRemove removeFromParent];

            _gameRender = [[DWGameRender alloc]initWithGame:_game];
            [_world addChild:_gameRender.gameSprites];

            self.backgroundColor = [SKColor colorWithRed:0 green:0 blue:0 alpha:1.0];

            //Set up the gesture recognizers
            _panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePanFrom:)];
            [[self view] addGestureRecognizer:_panRecognizer];
            _pinchRecognizer = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];
            [[self view] addGestureRecognizer:_pinchRecognizer];
        });
    });
}
- (void)didMovetoView:(SKView *)view {
    self->_draw_queue = dispatch_queue_create(kQueueScene,nil);

    dispatch_async(self->_draw_queue, ^{
        _initialScreenSize = self.size;
        _screenBufferX = _initialScreenSize.width*kBufferFactor;
        _screenBufferY = _initialScreenSize.height*kBufferFactor;

        _world = [[SKNode alloc]init];
        [self addChild:_world];

        [_world addChild:[self loadingSprite]];

        _gameSpeed = 0.15;

        _game = [[DWGame alloc]init];

        SKNode *spriteToRemove = [_world childNodeWithName:@"loadingSprite"];

        _gameRender = [[DWGameRender alloc]initWithGame:_game];
        [_world addChild:_gameRender.gameSprites];

        _panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePanFrom:)];
        _pinchRecognizer = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(handlePinch:)];

        dispatch_async(dispatch_get_main_queue(), ^{
            // all the code that *must* be done on the main queue
        });
    });
}

Context

StackExchange Code Review Q#66807, answer score: 3

Revisions (0)

No revisions yet.