Context leak detected, msgtracer returned -1 (Swift 3) - swift

Running Xcode 8.2 on a Swift Playground. It's also making Xcode not respond. This is the error I'm getting.
Context leak detected, msgtracer returned -1
I've traced the problem to these lines of code
while (true){
let rotate = SKAction.rotate(toAngle: angle - CGFloat(M_PI_2), duration: 1)
crank.run(rotate)
}
When I comment out the while loop and leave just the inside it works fine.

The while (true){ part will make that loop execute forever and your code probably will not proceed beyond that point. If you want an action to repeat forever there is an SKAction method for that - take a look at repeat.
If you remove the while (true){ condition and instead use a repeat action, I believe your code should run fine.

#Fahim is 100% correct, by the code posted you are stuck in an endless loop. So the question for you is how to get out of it?
The question for you is - how to make this loop do what you need. I'll guess that you want to change the condition to something like while (someVar == true) and put in a condition inside your loop to change someVar to false. But only you know what that condition is. The syntax would be:
let someVar = true
while (someVar == true){
let rotate = SKAction.rotate(toAngle: angle - CGFloat(M_PI_2), duration: 1)
crank.run(rotate)
if someCondition == isMet { // THIS IS FOR YOU TO DECIDE!
someVar = false
}
}

I ended up rewriting it completely and using SKAction.repeatForever to run an SKAction (to rotate) and that terminated only if the condition was false.
crank.run(
SKAction.repeatForever (
SKAction.sequence([
SKAction.wait(forDuration: 0.1),
SKAction.run({
let angle = atan2(self.point.y - crank.position.y , self.point.x - crank.position.x)
let rotate = SKAction.rotate(toAngle: angle - CGFloat(M_PI_2), duration: 0.25)
crank.run(rotate)
if(!(crank.frame.contains(self.point))) {
self.removeAction(forKey: "New Thread")
}
})
])
),
withKey: "New Thread"
)

I think the existing answers are missing the point here. You have two "threads" you are dealing with: the active thread running the routine (which will keep pumping new rotate actions onto the actions list for the node as fast as it can) and the action processing thread (which is really not a "thread" per se but can be imagined as one for the purpose of this question) which is where the rotate actions are actually executed.
Essentially, in a given timeslice, the "main" execution thread might pump 100k "rotate" actions onto your node's action list, and the SpriteKit engine will process all of them at the same time as efficiently as it can (I believe by removing duplicate actions but I'm not positive on that).
The effect is that you are doing a lot more work than you have to, and burning CPU in both your spin loop and in the SKAction processing code trying to recover from the millions of actions added to the node every time it comes up to process.
Replace it with a repeat action, as Fahim suggested, and you'll be good. Then you just need a trigger of some sort to stop the action (give it a name to end just the one action, or stop all actions on this node when the trigger fires).

I solve this problem using more singletong. For example SKView() inside the loop in singletong.

Related

Adding SKShapeNodes - using while loop blanks UI

I have a game which is running well but as soon as I introduce a while loop my entire UI goes blank.
I have some code which generates a sprite from an array and moves it down the screen
func addALetter() {
let randomX = CGFloat.random(in: 50...size.width - 50)
let shrink = SKAction.scale(to: 0.1, duration: 0.01)
let grow = SKAction.scale(to: 1, duration: 0.5)
let wait = SKAction.wait(forDuration: 0.7)
let spawn = SKAction.move(to: CGPoint(x: randomX, y: size.height - tileSize), duration: 0.001)
let move = SKAction.moveTo(y: -500, duration: 7)
let sequence = SKAction.sequence([shrink, spawn, grow, move, wait])
// scroll through the lettersArray
if activeLetter < lettersArray.count - 1 {
bubbles[activeLetter].removeAllActions()
bubbles[activeLetter].run(sequence)
activeLetter += 1
} else {
// go back to first in letter array
activeLetter = 0
bubbles[activeLetter].removeAllActions()
bubbles[activeLetter].run(sequence)
}
}
It is working fine triggered using an SKAction in my didMove to view run(SKAction.repeatForever(SKAction.sequence([SKAction.run(addALetter), SKAction.wait(forDuration: spawnTime)])))
but I have problems with that as I get to the end of the array because the action repeats too frequently making sprites disappear before I want them too.
So I tried using a while loop instead...
while gameOn == true {
addALetter()
}
to repeat the action. But then I get a completely blank UI - I assume because it's then stuck in the loop there's no time to update the UI?
Looking for a solid way to repeat the function that I can vary the frequency as the array gets to low numbers
It seems likely that the problem is that spawnTime is small enough that you're wrapping around through the letters too quickly and start doing bubbles[activeLetter].removeAllActions() before the bubble's previous action sequence has finished.
I think the most best way to deal with this sort of situation is to coordinate through completion blocks. I.e., use the run method with a closure to be called after the action finishes, https://developer.apple.com/documentation/spritekit/sknode/1483103-run. That way you don't wind up trying to adjust delays explicitly in an effort to keep actions from being prematurely cancelled. The completion blocks will update state that you can use to coordinate, or they can run other actions directly.
It's not clear from your question what behavior you want. But, e.g., you might have addALetter set a flag for the bubble when it starts the action sequence and include a completion block for the sequence to clear the flag. Then before addALetter restarts the sequence for a bubble it can make sure that the flag is clear; if it's not (the bubble is still running the previous sequence), just return without doing anything.

Sprite Kit: Waiting to call a function until condition X is met

I'm working on a game (top-down shooter) and have run into a bit of a snag. Up to this point, I've spawned enemies with functions that just work with delays:
Wave One Function - Delay 3, spawn enemies //
Wave Two Function - Delay 6, spawn enemies
I do this because I haven't found a way to wait for all actions in a given function to complete before calling the next - as things stand, functionWaveOne calls functionWaveTwo, which calls functionWaveThree (etc).
This has worked until now. What's changed is I want two enemies to remain on-screen and until they're dead, I don't want the next wave to come. My initial solution to this was to have a running count of how many enemies died in wave four:
Detect collision -> Apply Damage -> Check if dead -> If yes, deadWaveFourEnemies++
Here's my problem: I have a primary weapon that's two parallel lasers, so they have potential to collide with an enemy at exactly the same time. This results in false positives, making the dead enemy count go higher than it should. I even tried adding an "am I alive" function to the enemy class, but to no avail.
Have any of you got a recommendation on how to either call functions in a better way or to get around these false positives? Thanks!
In case it helps:
if([enemySprite.name isEqual: #"shooter"]){
enemyShooter *enemyClass = (enemyShooter *)enemySprite;
itDied = [enemyClass doDamageWithAmount:secondaryWeaponDamage];
scoreIncrease = [enemyClass getScoreIncrease];
stillAlive = [enemyClass amIAlive];
}
[weaponSprite removeFromParent];
if(itDied){
[self increasePlayerScoreBy:scoreIncrease];
if(inWaveFour == 1 && stillAlive == 1){
waveFourKilled++;
NSLog(#"Seconday / Number killed: %i", waveFourKilled);
}
}

How to make a delay in a loop in SpriteKit?

I have made some search here and there but I didn't find (or understand) how to make a delay in a loop in SpriteKit.
This is the idea : I have some SKSpriteNodes sorted in an array and I want to display them on the screen, one every second. The problem is... I simply don't manage to do it (sorry, I'm quite new to this).
let sprite1 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
sprite1.position = CGPoint(x: 100, y: 100)
let sprite2 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
sprite2.position = CGPoint(x: 100, y: 300)
let sprite3 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
sprite3.position = CGPoint(x: 300, y: 100)
let sprite4 = SKSpriteNode(color: .red, size: CGSize(width: 20, height: 20))
sprite4.position = CGPoint(x: 300, y: 300)
let allSprites : [SKSpriteNode] = [sprite1, sprite2, sprite3, sprite4]
let delay = 1.0
var i = 0
while i <= 3
{
let oneSprite = allSprites[i]
self.addChild(oneSprite)
DispatchQueue.main.asyncAfter(deadline : .now() + delay) {
i = i + 1
}
}
If you're asking : this doesn't work at all. It seems that what is inside the DispatchQueue.main.asyncAfter isn't read.
So, if you can help me to understand why, that would be great. I'm not picky, I can't take the answer with a "for" loop.
Regards,
This is a common beginner misunderstanding of event-driven / run-loop-based / GUI programming systems. Some key points to help get on the right track:
SpriteKit is trying to render the scene 60 (or so) times per second.
That means SpriteKit internally has a loop where, once per frame, it calls your code asking what's new (update), then runs its own code to draw the changes (render).
Setup code, or things that happen in response to events (clicks/taps/buttons) are external to this loop, but feed into it: events change state, which the render loop reacts to.
So, if you have a loop in an update method, an event handler, or initial setup code, everything that happens in that loop will happen before SpriteKit gets a chance to draw any of it. That is, if we ignore for a moment the "delay" part of your question, a loop like this...
var i = 0
while i <= 3 {
let oneSprite = allSprites[i]
self.addChild(oneSprite)
}
... will result in none of the sprites being visible right before the loop starts, and all of the sprites being visible after it completes. No amount of introducing delays within the loop will change that, because SpriteKit doesn't get its first chance to draw until after your loop is finished.
Fixing the problem
The best way to do animations and delays in a system like SpriteKit is to make use of the tools it gives you for treating animation declaratively. That is, you tell SpriteKit what you want to have happen over the next several (hundred) frames, and SpriteKit makes that happen — each time it goes through that update/render loop, SpriteKit determines what changes need to be made in the scene to accomplish your animation. For example, if you're running at 60 fps, and you ask for a fade-out animation on some sprite lasting one second, then each frame it'll reduce that sprite's opacity by 1/60.
SpriteKit's tool for declarative animations is SKAction. There are actions for most of the things you'd want to animate, and actions for composing other actions into groups and sequences. In your case, you probably want some combination of the wait and unhide and run(_:onChildWithName) actions:
Give each of your sprites a unique name:
let sprite1 = // ...
sprite1.name = "sprite1"
// etc
Add all the nodes to your scene, but keep the ones you don't want visible yet hidden:
let allSprites : [SKSpriteNode] = [sprite1, sprite2, sprite3, sprite4]
for sprite in allSprites {
sprite.isHidden = true
self.addChild(sprite)
}
Create a sequence action that alternates delays with telling each node to unhide, and run that action on your scene:
let action = SKAction.sequence([
run(.unhide(), onChildNodeWithName: "sprite1"),
wait(forDuration: 1.0),
run(.unhide(), onChildNodeWithName: "sprite2"),
wait(forDuration: 1.0),
// etc
])
self.run(action)
That should accomplish what you're looking for. (Disclaimer: code written in web browser, may require tweaking.) Read on if you want to better understand the situation...
Other alternatives
If you really want to get your head around how the update/render loop works, you could participate directly in that loop. I wouldn't recommend it in general, because it's a lot more code and bookkeeping, but it's useful to know.
Keep a queue (array) of nodes that you want to add or unhide.
In you scene's update method, keep track of how much time has passed.
If it's been at least 1.0 second (or whatever delay you're after) since you last showed a node, add the first one in the array, and remove it from the array.
When the array is empty, you're done.
About DispatchQueue.asyncAfter...
This isn't essential to completing your task, but helpful to understand so you don't get into similar problem later: the word "async" in the call you were using to try delaying things is short for "asynchronous". Asynchronous means that the code you're writing doesn't execute in the order you write it in.
A simplified explanation: Remember that modern CPUs have multiple cores? When CPU 0 is executing your while loop, it gets to the asyncAfter call and passes a note to CPU 1 saying to wait one second and then execute the closure body attached to that asyncAfter call. As soon as it passes that note, CPU 0 continues merrily on its way — it doesn't wait for CPU 1 to receive the node and complete the work that note specifies.
Just from reading the code, I'm not 100% clear on how this approach fails, but fail it certainly does. Either you have an infinite loop, because the while condition never changes, because the work to update i is happening elsewhere and not propagating back to local scope. Or the the changes to i do propagate back, but only after the loop spins an indeterminate number of times waiting for the four asyncAfter calls to finish waiting and execute.

Spawn nodes at random times combing waitForDuration:withRange and runBlock: in an SKAction sequence

I am making a game with SpriteKit where I have nodes spawning at the top of the screen and falling. However, I want these nodes to spawn at a random time interval between 0.1 and 3 seconds. For example, the first node spawns in 1.3 seconds, the next in 1.8, then 2.5, then 0.8, and so forth forever. I'm not sure how to utilize the waitForDuration function to do this. The code that I currently have is:
let wait = SKAction.waitForDuration(3, withRange: 2)
let spawn = SKAction.runBlock { addTears()
}
let sequence = SKAction.sequence([wait, spawn])
self.runAction(SKAction.repeatActionForever(spawn))
This code freezes my game when I try to run it. I removed the addTears() and put a log, and there was an infinite loop on the log. I need to know how to get rid of this.
The code for my addTears() func is:
func addTears() {
let Tears = SKSpriteNode (imageNamed: "Tear")
Tears.position = CGPointMake(Drake1.position.x, Drake1.position.y - 2)
Tears.zPosition = 3
addChild(Tears)
//gravity
Tears.physicsBody = SKPhysicsBody (circleOfRadius: 150)
Tears.physicsBody?.affectedByGravity = true
//contact
Tears.physicsBody = SKPhysicsBody (circleOfRadius: Tears.size.width/150)
Tears.physicsBody!.categoryBitMask = contactType.Tear.rawValue
Tears.physicsBody!.contactTestBitMask = contactType.Bucket.rawValue
}
If I remember well, the waitForDuration:withRange: method works like so :
if you put a duration of 3 (seconds) and a range of 1 seconds the random value that you get will be between 2 and 4 seconds. That said, you should use those value for what you described : let wait = SKAction.waitForDuration(1.55, withRange: 1.45)
For the freeze problem, if you pasted your code correctly here the problem come with this line self.runAction(SKAction.repeatActionForever(spawn)) where you should instead be calling your sequence like this : self.runAction(SKAction.repeatActionForever(sequence)).
PS : At a certain point you still might want to control the amount of tears on screen at the same time.
Let me know if it helped.

Create custom transition in Corona SDK

I went through the docs and couldn't find anything.
Is it possible to create a custom transition?
I have to simulate a throw with custom easing etc.
In cocos2d I was able to just create a custom action, with corona I'm puzzled.
The docs are quite silent on the topic. However, if you do some trying out, you will find out that a custom easing function takes four parameters (only nil from the fifth parameter and up). Some playing around reveals that a custom easing function looks like this:
function easer(currentTime, duration, startValue, targetDelta)
...
end
Explanation
currentTime / duration: duration is self-explanatory, and currentTime is simply [0..duration] during the course of the transition.
startValue: Snapshot of the value at the beginning of the transition.
targetDelta: This is the value of the target-value, minus the startValue, i.e. the distance the easing-function "has to walk"
Annotated Example
Say you have the following code:
foo.frob = 1
transition.to(foo, {time=1001, frob=0.25})
I.e., you want a transition of foo.frob of [1..0.25]. Then:
function easer(currentTime, duration, startValue, targetDelta)
-- currentTime: [0..1001]
-- duration: 1001
-- startValue: 1
-- targetDelta: to-from = 0.25-1 = -0.75
return startValue + (currentTime/duration) * targetDelta -- a linear interpolator
end
Return Value
The return value should be startValue in the beginning, and startValue+targetDelta at the end. It is perfectly allowed to leave that range; however, once the transition stops, it automatically becomes startValue+targetDelta, so make it startValue and startValue+targetDelta at the beginning and end.
But you can be creative between the start and end, and let the function bounce back and forth, for example, like some of the included easing functions already do.
Well the answer is in the docs.
One of the params to transition.to is easing, which can be a custom function.