How to Subclass Nsoperation in Swift to Queue Skaction Objects for Serial Execution

How to subclass NSOperation in Swift to queue SKAction objects for serial execution?

According to the document:

In your custom implementation, you must generate KVO notifications for the isExecuting key path whenever the execution state of your operation object changes.

In your custom implementation, you must generate KVO notifications for the isFinished key path whenever the finished state of your operation object changes.

So I think you have to:

override var executing:Bool {
get { return _executing }
set {
willChangeValueForKey("isExecuting")
_executing = newValue
didChangeValueForKey("isExecuting")
}
}

override var finished:Bool {
get { return _finished }
set {
willChangeValueForKey("isFinished")
_finished = newValue
didChangeValueForKey("isFinished")
}
}

add SKAction to Sprite queue run one after another

Generally, you can make SKActions run concurrently using the group method, and have them run sequentially using the sequence method.

But if you need a queuing system, rather than building your own, use the native operation queue to do this for you. So you can create a serial operation queue and add operations to it. The issue is that you don't want an operation to complete until the SKAction does.

So, you can wrap your SKAction in a concurrent NSOperation subclass that only completes when the SKAction does. Then you can add your operations to a serial NSOperationQueue, and then it will won't start the next one until it finishes the prior one.

So, first, create an ActionOperation (subclassed from NSOperation) that looks like:

// ActionOperation.h

#import <Foundation/Foundation.h>

@class SKNode;
@class SKAction;

@interface ActionOperation : NSOperation

- (instancetype)initWithNode:(SKNode *)node action:(SKAction *)action;

@end

and

// ActionOperation.m

#import "ActionOperation.h"
@import SpriteKit;

@interface ActionOperation ()

@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;

@property (nonatomic, strong) SKNode *node;
@property (nonatomic, strong) SKAction *action;

@end

@implementation ActionOperation

@synthesize finished = _finished;
@synthesize executing = _executing;

- (instancetype)initWithNode:(SKNode *)node action:(SKAction *)action
{
self = [super init];
if (self) {
_node = node;
_action = action;
}
return self;
}

- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}

self.executing = YES;

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.node runAction:self.action completion:^{
self.executing = NO;
self.finished = YES;
}];
}];
}

#pragma mark - NSOperation methods

- (BOOL)isConcurrent
{
return YES;
}

- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}

- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}

@end

You could then, for example, create a serial queue during the initialization process:

self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;

You can then add the operations to it:

SKAction *move1 = [SKAction moveTo:point1 duration:2.0];
[self.queue addOperation:[[ActionOperation alloc] initWithNode:nodeToMove action:move1]];

and you can later add more actions:

SKAction *move2 = [SKAction moveTo:point2 duration:2.0];
[self.queue addOperation:[[ActionOperation alloc] initWithNode:nodeToMove action:move2]];

And because the queue is serial, you know that move2 will not be started until move1 is done.

Swift uninitialized let property

Well, the problem was caused by the way I crate group action:

let gravOperation = ActionOperation(node: piecesLayer, action: SKAction.group(groupActions))

When groupActions array is empty action will never be executed on the node and if we call:

node.run(badGroupAction, completion: { /* never called */ })

the completion will be never called. That's why queue was stuck. I think it's a SpriteKit flaw. I would prefer completion to be called immediately if we try to run empty group or sequence.

Why does action become uninitialized? I don't know, I think it's a bug, probably related to the empty array.



Related Topics



Leave a reply



Submit