Conflict between SKScene and dismissViewControllerAnimated - modalviewcontroller

I have 4 VC's in my app- Main -> Game -> End and Main ->Settings.
When I go from Game to Main, or from End to Main, the SKScene in main is appeared as paused, though it isn't (confirmed by an NSLog)- it appears as in a middle of one of the actions that the SKScene usually executes.
When I go from Settings to Main, the SKScene in main works!
What is this kind of sorcery?
Thanks!

Related

ARKit: pausing session does not stop the scene to be called

I'm using ARKit with SpriteKit. My AR feature is not a core feature in my app, users may optionally navigate to a viewController with an ARSKView where I configure and set an SKScene in the ARsession.
I pause the session when the user navigates back:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
then the viewController is pop from the navigationController where it was pushed. But I'm seeing that the func update(_ currentTime: TimeInterval) in my SKScene subclass keeps being called... which is the appropriate way to also stop that? I thought that pausing the ARSession would also stop the scene.
EDIT: when I navigate back from the view controller that holds the ARSKView and it is deinitialized, I see that the SKScene is still in memory and a new one is created every time I navigate again to the AR related view controller. So, if I navigate there N times, I see I have N SKScenes in memory. How could I appropiately remove the scene when navigating back? The scene property of the ARsession is read only.
that sounds like a memory retain issue. Make sure that any node in you SKScene subclass is properly removed and deinitialized.
Set your scene delegate to nil, set any node or array of nodes referenced in your VC to nil.
I ran a simple test with 2 VC embedded in a navigation controller, the first one has a view with a start button, the second one is the ARVC.
I run the session, set my custom scene delegate to ARVC, when I hit the back button everything stops by itself and my ARVC is being popped. Nothing in memory.
So make sure that you manually remove all your nodes and their animations, constraints, actions and anything that relates to you custom SKScene subclass.
Also don't be afraid to call sceneView.presentScene(nil) if that can help.
I couldn't find any official documentation about this, apparently there is not proper method to stop a session yet. What I found that was similar to your scenario is this work around.
It suggests you create ARSKView as disposable and before leaving your controller, pause the session, remove the view from superview and assign it to nil.

Heavy SceneKit scene in UINavigationViewController takes too long to dealloc

My app uses standard UINavigationViewController.
At some point, user has navigated to a ViewController which shows a SceneKit Scene.
This scene is very heavy in memory, since it has around 6000 geometries and each geometry has its own Material (this needs to be this way).
When user clicks the back button at the top bar in order to return to the previous viewController, the app correctly pops the current View Controller and shows the one below.
However, for around 4 seconds, the UI of the new ViewController shown freezes.
I have used Instruments Time Profiler, and I can see that big part of those 4 seconds are taken by these SceneKit methods:
-[NSConcreteMapTable countByEnumeratingWithState:objects:count:]
-[SCNMetalResourceManager _geometryWillDie:]
-[SCNMetalResourceManager _materialWillDie:]
It makes sense because there are so many geometries and materials.
How can I solve this situation, so that UI is not blocked while the heavy Scene is being released, either from a SceneKit perspective (making deallocation quicker) or from a UINavigationViewController perspective (maybe forcing deallocation of the Scene to happen in a separate thread?).
My understanding is that SceneKit uses it's own background thread to do work, so it shouldn't be blocking the main thread.
It is possible that the blocking of the main thread has something to do with UINavigationController animating the content away. I've had issues when trying to animate SCNViews/SCNScenes and it looks like SceneKit and CoreAnimation do not collaborate very well.
I don't have the full context here, but I can suggest couple of things for you to test:
You can try setting the scene propery of the SCNView with the heavy scene scnView.scene = [SCNScene scene]; to a new empty scene just before the View Controller with the scene is popped off, so that the heavy scene is not involved in the animation, and SceneKit properly deallocates it in it's background threat.
You can try keeping a strong reference to the heavy scene in the View Controller that prepares the problematic View Controller with the heavy scene.
Then when the latter is popped off and you return to the one below it, the popped off View Controller and all of its content should have deallocated, except the heavy scene and you'll have a reference to it.
Here you can try setting to nil (after the animation has completed) and possibly it will deallocate properly on SceneKit background thread.
If not, you can first remove all heavyScene.rootNode child nodes, actions, etc and then deallocate.
If this still does not work, you can first try using a recursive function to traverse all your nodes one by one and nil their geometry/material first and then deallocate the scene itself.
Using the rendering loop -renderer:updateAtTime: method to remove the nodes over several frames (less than a second). Then you can be sure that SceneKit will deal with them the way it is designed to.
You will have to detect when the user presses UINavigationController "Back button" and the VC with the heavy scene is being popped off. Then over, say 20 frames (1/3 of a second at 60fps), remove 5% of nodes at each frame, so there will still be a short delay but 1/3 of a second and hopefully unnoticeable.
The detection of the VC being popped off can happen by overriding its didMoveToParentViewController: method and checking if nil is passed as the parent parameter.
E.g.
- (void)didMoveToParentViewController:(UIViewController *)parent {
if (!parent) {
// work that should be done when VC is being removed from parent
// maybe set a counter in the VC being popped off to
// count down from 20 to 0, -1 at each -update call
}
}

TabBarController with SKScene - delay on presenting

I have a tab bar controller that contains different ViewControllers.
One of them has a SKView and is creating a new SKScene in viewDidLoad
First time i navigate to the tab with the SKScene, it will display immediately, but every next time it will take about 2 seconds!
The strangest part is, the SKScene is totally empty! There is no nodes in it.
Every other view controller displays immediately.
What could be causing this issue?

What's causing this memory leak?

The Problem
I'm currently building an iPad game using SpriteKit. The gameplay is driven by sound provided by EZAudio. After running the Instrumentation tools to profile my app, I noticed that whenever the GameViewController is shown memory allocation jumps up. When I repeatedly show the screen (5+ times) it crashes the app. My project uses ARC.
Navigation
The navigation consists of 4 ViewControllers:
MenuViewController: This shows the menu
CharacterSelectionViewController: It lets you a pick a character to use
GameViewController: This lets you play a game with the player chosen
ScoreViewController: It shows you the score you achieved in the game
1 - MenuViewController
From here you can navigate to CharacterSelectionViewController via a Show (e.g. Push) segue.
2 - CharacterSelectionViewController
You can navigate to GameViewController via a Show (e.g. Push) segue. There is also a back button that goes back to MenuViewController with the following code:
[self.navigationController popViewControllerAnimated:YES];
3 - GameViewController
It first shows a 5 second countdown (using NSTimer)
The game starts with the character chosen in CharacterSelectionViewController
The game can be paused, allowing you to quit and go back to MenuViewController via a manual Show Detail (e.g. Replace) segue.
When the game ends, a manual Show (e.g. Push) segue is called that navigates to the ScoreViewController.
It's view hierarchy consists of three sets of view - one for the countdown, one for the pause menu and one for the game. These are subsequently shown/hidden. See the view hierarchy below:
4 - ScoreViewController
This allows you to quit or restart the game. When quit it pressed it performs a Show Detail (e.g. Replace) segue to MenuViewController. If restart is pressed it performs an unwind to CharacterSelectionViewController.
Responses
Please provide answers regarding:
How this kind of leak could occur
Any observations you have from the allocation and leaks screenshots
Allocation
Below, you can see the increasing allocations as I cycle through the apps screens to repeatedly show the GameViewController. I used Mark Generation to be able to show the increase in memory allocations.
Leaks
Here you can see the memory leak that occurred. This is ordered by size.
Ignore the leaks for the moment; fix the generational accretion first.
Is that generation snapshot representative of what is left after a typical snapshot? Typically, you'd want to show view controller, take snapshot, hide then show view controller, take snapshot, etc... as many times as you can without crashing (or 10 times if it doesn't crash).
Then look at generation 3 or 4 as that'll be the most stable representation of per-generation accretion.
If it is representative, it looks like you are leaking everything that the view controller would normally allocate. Ultimately, you are looking for the "root" of your object graph that is keeping everything around. Fix the reason why the root is sticking around and the rest'll likely go away.
I wrote a weblog post about this. It is a bit outdated, but the analysis workflow remains the same.
http://www.friday.com/bbum/2010/10/17/when-is-a-leak-not-a-leak-using-heapshot-analysis-to-find-undesirable-memory-growth/
How are you unwinding from your various view controllers? I note that you mention that when the game ends you're pushing another VC onto the stack, but I presume this VC chain will at some point unwind back to your initial menu? (In essence, I wonder if you're just looping around, hence adding new VCs to the stack everytime you play a game.)
To create an un-wind segue, simply create an empty method in the destination VC (i.e.: your main menu) as such:
- (IBAction)unwindToMainMenu:(UIStoryboardSegue*)sender
{
// Intentional NOP
}
(N.B.: Make sure it's also listed in the header.)
You can then call this as you would any other segue in your storyboard by dragging from the source object in question to the exit option at the top of the VC that contains the source object in the storyboard. This will present you with a list of segues to choose from. (You can verify that the segue is setup correctly by selecting the source object in the storyboard - the Connections inspector should list the unwind segue within the Triggered Segues section.)

How to get from SKScene to UIViewController?

I am new to programming and I am trying to build a simple game using Swift and SpriteKit. It looks good now but I have a little problem to solve. How do I get from a SKScene (Game) to a UIViewController in the Storyboard?
My game looks like this. In the Storyboard i have 3 views (Main menu, About and Game Over) and 1 SKScene (Game). When the player clicks on the play button in the main menu he gets to the game SKScene. When the player fails I want to get to a new UIViewController where I can link 2 buttons (back to main menu and restart).
I don't know how to do this. I will also accept Objective-C if you don't know in Swift.
For others encountering this problem down the road:
self.view?.window?.rootViewController
will return a UIViewController object when called in a SKScene.

Resources