2

My SpriteKit game has three scenes for now; Menu.m, LevelSelect.m and Level.m. When I start the app, the memory usage is 35MB. Upon transitioning from the main menu to the level selection scene it pretty much stays around 35MB. Moving from the level select scene to the level itself it shoots up to around 150MB. Granted, there are more sprites and classes involved in creating the level. However, when I reload the level via an overlay menu that I have as a sprite the memory usage continues to rise by around 2MB for each reload. Could it be a retain cycle issue? The same is true if I switch back to the level select scene and then re-enter the level itself, the memory continues to climb and climb. I am concerned about my implementation of my state machine. I'll post some skeletal code below:

LevelScene.m

-(void)didMoveToView:(SKView *)view {
    ...
    [self initGameStateMachine];
    ...
}

...
//other methods for setting up the level present
...

-(void) initGameStateMachine {
    LevelSceneActiveState *levelSceneActiveState = [[LevelSceneActiveState alloc] initLevelScene:self];
    LevelSceneConfigureState *levelSceneConfigureState = [[LevelSceneConfigureState alloc] initLevelScene:self];
    LevelSceneFailState *levelSceneFailState = [[LevelSceneFailState alloc] initLevelScene:self];
    LevelSceneSuccessState *levelSceneSuccessState = [[LevelSceneSuccessState alloc] initLevelScene:self];

    NSMutableDictionary *states = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
                                   levelSceneActiveState, @"LevelSceneActiveState",
                                   levelSceneConfigureState, @"LevelSceneConfigureState",
                                   levelSceneFailState, @"LevelSceneFailState",
                                   levelSceneSuccessState, @"LevelSceneSuccessState",
                                   nil];

    _gameStateMachine = [[StateMachine alloc] initWithStates:states];
    [_gameStateMachine enterState:levelSceneActiveState];
}


-(void)update:(CFTimeInterval)currentTime {
    //update the on screen keyboard with notes that are being played

    [_gameStateMachine updateWithDeltaTime:[NSNumber numberWithDouble:currentTime]];
}


-(void) willMoveFromView:(SKView *)view {
    [self removeAllChildren];
}

StateMachine.m

#import "StateMachine.h"
#import "GameState.h"

@interface StateMachine()

@property NSMutableDictionary *states;

@end

@implementation StateMachine

-(instancetype)initWithStates:(NSMutableDictionary*)states {
    if (self = [super init]) {
        for (id key in [states allValues]) {
            if (![key conformsToProtocol:@protocol(GameState)]) {
                NSLog(@"%@ does not conform to @protocol(GameState)", key);
                return nil;
            }
        }
        _states = states;
    }
    return self;
}

//this method will be used to start the state machine process
-(bool)enterState:(id)nextState {
    if (!_currentState) {
        _currentState = [_states objectForKey:[nextState className]];
        [[NSNotificationCenter defaultCenter] addObserver:_currentState selector:@selector(keyPressed:) name:@"KeyPressedNotificationKey" object:nil];
        return YES;
    }
    else if ([_currentState isValidNextState:nextState]) {
        [_currentState performSelector:@selector(willLeaveState)];
        _currentState = [_states objectForKey:[nextState className]];
        [[NSNotificationCenter defaultCenter] addObserver:_currentState selector:@selector(keyPressed:) name:@"KeyPressedNotificationKey" object:nil];
        return YES;
    }
    return NO;
}

-(void)updateWithDeltaTime:(NSNumber*)currentTime {
    [_currentState performSelector:@selector(updateWithDeltaTime:) withObject:currentTime];
}

@end

LevelSceneActiveState //this is one of the 4 states

#import "LevelSceneActiveState.h"
#import "LevelSceneConfigureState.h"
#import "LevelSceneSuccessState.h"
#import "LevelSceneFailState.h"
#import "StateMachine.h"
#import "LevelScene.h"
#import "SSBitmapFontLabelNode.h"


@interface LevelSceneActiveState()

@property LevelScene *levelScene;

@end

@implementation LevelSceneActiveState

-(instancetype)initLevelScene:(LevelScene *)levelScene {
    if (self = [super init])
        _levelScene = levelScene;
    return self;
}

-(void)updateWithDeltaTime:(NSNumber*)currentTime {
    //game variables created here
    ....

    //state machine needs to be set here...if set in init, it does not have a value in the LevelScene yet
    if (_gameStateMachine == nil)
        _gameStateMachine = _levelScene.gameStateMachine;

    //game logic performed here
    ...

    //check for timer finishing
    if (!_levelScene.timer.isValid) {
        //success
        if (_levelScene.score >= 7) {
            [_gameStateMachine enterState:LevelSceneSuccessState.self];
        }
        else { //failure
            [_gameStateMachine enterState:LevelSceneFailState.self];
        }
    }

}

//another class is used to trigger notifications of key presses
-(void) keyPressed:(NSNotification*)notification {
    NSNumber *keyCodeObject = notification.userInfo[@"keyCode"];
    NSInteger keyCode = keyCodeObject.integerValue;

    if (keyCode == 53)
        [self escapePressed];
}

-(void) escapePressed {
    [_gameStateMachine enterState:LevelSceneConfigureState.self];
    [_levelScene childNodeWithName:@"timer"].paused = YES;
}

-(bool)isValidNextState:(id)nextState {
    if ([[nextState className] isEqualToString:@"LevelSceneConfigureState"])
        return YES;
    ...

    return NO;
}

//this makes sure that we're not notifying an object that may not exist
-(void) willLeaveState {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:@"KeyPressedNotificationKey"
                                                  object:nil];
}

@end

When switching between states I do not want the LevelScene scene to go away. I understand that this is hard to diagnose especially when you're not sitting in front of the full project. What can I do to self-diagnose this myself? Any helpful tips/tricks would be great.

[UPDATE] I tried the Product->Profile->Instruments->Allocation thing, but I have no idea what to do with it. The memory is indicating that it continues to rise though.

02fentym
  • 1,762
  • 2
  • 16
  • 29

1 Answers1

3

Instrument Tutorial Article

For people that are as clueless as me when it comes to using Xcode's Instruments, here is an amazing article from Ray Wenderlich that really dumbs it down!

I found so many issues in my project that I didn't even think were issues because of that article. I'm not surprised that no one posted an answer regarding this question because when you have these types of issues they are very personal to the project that you are working on and quite hard to debug.

My Issue + Solution

My issue is a common one. I was loading a set of resources, in this case a .sf2 (sound font) file, over and over and over again when my scene reloaded. I honestly thought I had gotten rid of that issue with all of my sprites.

Here's how I found it using Instruments:

  • In Xcode go to Product->Profile then select Allocations

This window should pop up: Allocations Instrument in Xcode

  • Click on the red circle button at the top left (this will start your app)
  • Perform the operations in your app that seem to cause issues then press Mark Generation
  • Repeat the operations that cause the issues and continue to press Mark Generation
    Note: Mark Generation takes a snapshot of the app at that time so that you can see the changes in memory

Pressing the <code>mark generation</code> button



  • Stop the app from running and then dive into one of the generations (I chose Generation C, because that's when the differences in memory usage became constant) After pressing the <code>mark generation</code> button several times
  • My sampler (an AVAudioUnitSampler object) shown as SamplerState is allocating a bunch of memory Sampler object

  • I clicked on the small arrow to the right of SamplerState and it brought me into this view (shows a ton of items that are allocated memory) Sampler items

  • Clicking on one of them plus clicking on the extended details button will allow you to see the stack trace for this item Extended details info

  • I double clicked on the method that I thought would be the issue enter image description here

  • After double clicking on the method, it brings up another view with your code in it along with percentages of what lines of code allocate the most memory (extremely helpful!)
    Note: The culprit, in my case, was allocating roughly 1.6MB every time I reloaded the level! Ignore the commented out code, I took the screen shot of a saved session after I fixed the issue. enter image description here

  • After fixing my code there is no longer any major memory allocations..although I still have some things to clean up! Only 33KB between level reloads, which is much better! enter image description here

02fentym
  • 1,762
  • 2
  • 16
  • 29