I've recorded a mouse movement (the movement file is here) in the form of (x,y,delay_in_ms) array and I'm trying to replay it back like this:
for movement in movements {
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(Int(movement.time))) {
let point = CGPoint(x: Int(movement.x), y: Int(movement.y))
let moveEvent = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: point, mouseButton: .left);
moveEvent!.post(tap: CGEventTapLocation.cghidEventTap)
}
}
And it kind of works, but it's very laggy. I only see a couple of points from the shape. On the other hand, when I replay the same file with robotjs:
const path = await fs.readJSON("/Users/zzzz/path.txt");
await Promise.all(path.map((move: {x: number, y: number, time: number}) => {
return new Promise<void>((resolve) => {
setTimeout(async () => {
const x = move.x;
const y = move.y;
robot.moveMouse(x, y);
resolve();
}, move.time);
})
}));
The movement is silky smooth, exactly the way I recorded it.
I've tried replacing the DispatchQueue in Swift with a bunch of usleep calls. Then I could see all the points in the shape, but it was just slow, maybe 4x slower than intended.
Is there anything wrong with my usage of DispatchQueue?
#Rob makes some good points here. You 100% need to be posting UI events on the main thread (or queue). If you need tight control over timing, I would suggest using the "User Interactive" Quality of Service class. (https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html)
JavaScript is a single threaded runtime, that largely pre-supposes that you're working in a web-browser (and thus is always user-interactive) context. With that in mind, it's not surprising that it's smoother than default-class GCD blocks.
A few thoughts:
Consider using the main queue, rather than a global queue. You don’t really want to be initiating UI actions from a background thread.
I would not expect it to kick in quite at this point, but be aware that scheduling multiple timers in the future can get caught up in “timer coalescing”, a power saving feature whereby events will be bunched together to reduce battery consumption. The general rule is that if two independently scheduled timers are with 10% of each other, they can be coalesced.
Try GCD timers (rather than asyncAfter) with the .strict option. Or rather than scheduling all up front, schedule the first one and have it schedule the next one, recursively.
I notice that many of the points are ~7.5msec apart. That's faster than 120 events per second (“eps”). The event handler might not be able to handle them that quickly and you might see points dropped. I'm not sure where the precise cutoff is, but in my test, it had no problem with 60eps, but at 120eps I would see the occasional dropped event, and I'm sure that at faster than 120 eps, you may see even more dropped.
Related
My program has many parallel processes each on their own queue. I'd like to be able to visualize/measure the back pressure of the queues.
One approach is to count every block that enters and exits, but I'm sure GCD has this information already. Is there a better approach to measuring back pressure?
There is no API for querying the number of pending blocks in a GCD queue. That said, since you're asking about a queue that you "own", you can wrap it in a way that lets you keep track of that. For instance, you could make your own wrappers around dispatch_[a]sync that would increment a counter whenever you enqueued a block, and also wrapped the block to decrement the counter when the block completes. This really wouldn't be that hard to implement. (dispatch_queue_get_specific would likely be a good place to start...)
FWIW, a few years back, I would've suggested you use NSOperationQueue, but even NSOperationQueue has deprecated its operationCount property, so it's probably fair to say that Apple is unlikely to provide this functionality going forward, so implementing it yourself is probably your best option, if you really need this functionality.
This is probably not immediately helpful to you, but if you need to measure "back pressure" on GCD queues, you're probably not using them in the right way. If you're enqueuing work items that block during execution, that's bad, and will eventually lead to thread starvation. If you're enqueuing tons of work items that you might want to cancel, say if the user changed screens or something, then you should work on a pattern for cancellable work items (or use NSOperation). I'm struggling to think of a use case that would demand that you be able to measure "back pressure" that couldn't be solved with your own code.
If you want to visualize what is going on, you can use OSLog, post .begin and .end events and watch it in Instruments’ “Points of Interest”. (See WWDC 2019 Getting Started with Instruments.)
So, import os.log:
import os.log
Then create a log:
private let pointsOfInterestLog = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: .pointsOfInterest)
Enqueue 100 tasks:
for i in 0..<100 {
enqueueTask(i) {
print("done \(i)")
}
}
Where the routine posts a .begin event to the points of interest log before dispatching the task, and posts a .end event when the task actually starts, e.g.
func enqueueTask(_ index: Int, completion: #escaping () -> Void) {
let id = OSSignpostID(log: pointsOfInterestLog)
os_signpost(.begin, log: pointsOfInterestLog, name: "backlog", signpostID: id, "queued %d", index)
queue.async {
os_signpost(.end, log: pointsOfInterestLog, name: "backlog", signpostID: id, "started %d", index)
...
completion()
}
}
Then profile the app (with command+i or “Product” » “Profile”) and choose, for example, “Time Profiler” (which includes the “Points of Interest” tool). Start a recording an you will see a visual representation of your backlog:
(I expanded the “Points of Interest” to be big enough to show all 100 backlogged tasks.)
This is one way to visualize your backlog. I must confess that I generally use “Points of Interest” not to show the backlog, but rather to show when these tasks are actually running (i.e. .begin when the dispatched task actually starts running and .end when the dispatched task finishes). Or I use the “thread” view in Instruments to see how my worker threads are being used. Or I use the “CPU” view in Instruments to see how my CPUs are being used.
Regardless, as you can see, Instruments can be used to visualize whatever time ranges you want, in this case, the time between the adding of the task to the queue and when it starts running.
I'm building a turn based game with SpriteKit. It's a human player versus 1+ CPU opponents.
During the CPUs' turns I'm using an SKAction.sequence in order to show what the CPU is doing. This usually consists of a wait action followed by a custom SKAction.run block.
Now part of the problem I have is that the CPU could perform multiple attack actions per turn, but after each attack task I recalculate if there should be anymore attacks done, as conquering a country might open up new avenues of attack. The trouble I've got here is that I then have no way of knowing how many attacks should take place upfront, and as the initial attack itself is in a block, it doesn't know if it will generate more attacks. Thus I don't know how to add additional attack steps to my sequence.
Does anyone have any ideas how I could go about this? One thought was that I actually perform all the attacks upfront without displaying them and subsequently then just replay them for the benefit of the player. But I'm worried it will seem like the game is hanging in this instance.
Is there anyway I could append more SKActions midway through a running sequence?
let sequence:[SKAction] = []
let attackTasks = actions.getAttackTasks()
if attackTasks.count > 0 {
sequence.append(SKAction.wait(forDuration:0.5))
sequence.append(SKAction.run {
//PerformAttackTask Here calls another recursive function which calculates and generates more attacks
self.performAttackTask(attackTask: attackTasks.first!)
self.combatFinished = false
})
}
let sequenceAction = SKAction.sequence(sequence)
node.run(sequenceAction)
For those that are interested I ended up changing my approach for this moving the animations down a level to the performAttackTask method and recursively calling itself.
I then make use of a callback that only fires when all combat is complete to exit this loop.
This also makes use of SKAction.run with a completion handler to then move onto the next set of actions
I am curious what to use for my game. A timer:
let goodFPS = SKAction.wait(forDuration: 0.01666)
let mainGameRunner = SKAction.run {
//my code is here
}
let toRepeat = SKAction.repeatForever(SKAction.sequence([goodFPS,mainGameRunner]))
inGameHighScore.run(toRepeat,withKey:"mainGame")
or the update function:
override func update(_ currentTime: TimeInterval){
//my code is here
}
Which provides faster more consistent updates?
note: my frame rate is in the range of 45 to 58
First I think you are taking the FPS problem the wrong way around. You cannot "force" a faster frame rate than the device can give you. If you are basing the movements in your game on the assumption that every frame will be consistent, you are doing it wrong. It's actually how they did in the early days because CPUs were so slow and the difference from one generation to the new one wasn't too bad at first. But running an old DOS game on younger hardware will be tricky because the framerate is so high that the whole game mechanic becomes unstable or simply too fast to be playable.
The concept here is to think "over time" and to scale down any action in relation with the time elapsed between two frames.
The update() method gives you that opportunity by providing the current system clock state every frame. By keeping track of the time on the last frame, you can calculate the time difference with the current frame and use that difference to scale down what you are doing.
Using a timer to get the update on a consistent frame rate is not recommended nor practical. You may be calling the update closure at a given time interval, but the code inside that closure is taking time to execute on its own, and depending on your game logic, it might even have different execution times. So maybe the pause timing is consistent, but the code running before and after that pause might not be consistent. Then what happens if you run your game on a slower CPU? The code speed will change even more, making your timing inaccurate.
Another point against using an SKAction for your game loop is simply what they are. An action is an object in memory, meany to be reused by multiple objects. If you are making a "jump" action, for example, it is recommended to store that action somewhere and to reuse the same object every time you need something that "jumps", no matter what node it is. Your game loop is meant to be executed every frame, but not by different objects. Actions are also disposable. Meaning that you can kill an action even while it's running. If you put your game loop in an action, it will probably be run by the SKScene. If you use another action on your scene it becomes a puzzle right away because there are only two ways of removing an action besides letting it come to term: removing all actions or creating the action with an identifier key and use that key to remove any action with that key. If you don't see it already, it then forces you to put identifiers on every action that will be run by the scene and remove them one by one. And them again it leave a door open for a mistake that will get rid of your game loop because, keep it in mind, actions are DISPOSABLE! Then there is also no guarantee that your action will get executed first every single frame.
Why use the update() method? Simply because it is built IN your scene. No matter what, every frame, update() gets called first. THEN, the actions get executed. You cannot flush the update() method accidentally like you can with an action. You don't have to be careful about strong/weak relationships causing memory leaks because you are referring to objects from inside a closure like you do with an action.
Suggested reads:
SKAction API reference
SKScene API reference : read about the frame processing in SpriteKit. It will help you understand how they put everything together at every frame.
I hope it makes things clearer.
I'm pretty sure that SKAction's timing facilities are based on the same game loop that is calling update.
The advantage of SKAction is that it's fire and forget for you, while using update would get awkward with setting and checking a bunch of timer variables.
I don't have a ton of experience with SpriteKit but I do have some experience making games and animations in iOS.
There is a class called CADisplayLink that fires a call to a delegate every time the screen is refreshed, this is a great way to update the screen, either in a game or in an animation, because you can know it will be called every frame and no more.
I'm not sure if SpriteKit uses that class to fire the update method, but I'm sure it uses something similar. This is usually called the run loop.
SKActions run on top of this run loop.
By creating your own run loop using a wait action, not only you're not gaining any benefits, you could be introducing inconsistencies in the way your logic is run.
Imagine that you have your wait action set to 0.01 seconds (I rounded it down for simplicity). If the screen is refreshing faster than you expect, and the action is updated every 0.009 seconds, the first time it's checked, it won't fire because there's a remaining 0.001 second on the wait command, so another 0.009 seconds will pass, and your code will be executed after 0.018 seconds, instead of your desired 0.01. Not only that, but two frames will have passed between the execution.
If you use the update function, you can know that your logic will be executed once every screen refresh, and no more.
I spent some time looking into various approaches for queuing and even read this http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1 which was helpful, but I feel as the more I read into GCD, the more confused and unsure I become of myself about the right approach. Could someone give me a recommendation?
My goal is this: I'm making a game where a player can lose health due to some game event. The player has a health bar (hearts) at the top, and each time they get hit, I animate a heart to fly upwards over a .1 time interval. The reason i'm exploring a queue of some sort is the player can lose health so rapidly in some cases that the events fire faster than the animation. As a result, it sometimes "skips" hearts because it can't keep up with the health loss. This is only on some levels where the damage taken is higher than usual. Even without the animation it still happens occasionally although I think a tad bit less.
So I was thinking what I could do is queue up each health loss event, this way it will force the events to go in order, but I wasn't successful (it still went super fast and skipped hearts, removing the 10th heart, the 8th heart, and the 5th, then it said game over. I want to slow it down and remove the hearts in order so you see them vanishing).
This was the code I attempted:
dispatch_async(dispatch_get_main_queue(), {
heartToRemove!.runAction(
SKAction.sequence([SKAction.moveByX(0, y:50, duration:0.1),
SKAction.runBlock
{
heartToRemove!.removeFromParent()
self.hearts.removeLast()
}]))
})
So the idea is to run 3 actions on a queue:
first, move the heart up. next, remove visually from the parent (so it vanishes off the game not just positioned above), and finally remove it from the array of SKNodes (the hearts) so that the next time a heart is removed, it goes to the next one in line not the same one if that makes sense.
I also tried dispatch_sync but that just froze my game so I assume that is wrong. Any other recommendations?
Thank you so much for your time!
You could use the number of items in your hearths array to offset the animation times
something like :
let wait = SKAction.waitForDuration(0.1 * NSTimeInterval(hearts.count-1))
let move = SKAction.moveByX(0, y:50, duration:0.1)
let remove = SKAction.runBlock()
{
heartToRemove!.removeFromParent()
self.hearts.removeLast()
}
heartToRemove!.runAction(SKAction.sequence([ wait, move, remove ]))
Then you won't have to worry about queueing the actions.
In my game I spawn many enemies with random coordinates, but they share the same internal data.
Currently I have a function SpawnEnemy() who looks like this:
func spawnRedPawn(timer: NSTimer) {
var redPawn = Ship(entityNamed: ESHIP_REDPAWN)
let action = SKAction.moveToX(0, duration: timer.userInfo as! NSTimeInterval)
let actionDone = SKAction.removeFromParent()
redPawn.position = CGPoint(x: self.size.width, y: CGFloat(arc4random_uniform(UInt32(self.size.height - redPawn.size.height))))
redPawn.setupPhysicsBody(PC.enemy)
redPawn.setupContactTestBitmask([PC.projectile, PC.player])
redPawn.addEmitter("propulsionParticle", offset: CGPointMake(redPawn.size.width / 2 + 5, 0))
redPawn.runAction(SKAction.sequence([action, actionDone]))
self.addChild(redPawn)
}
PS: the function format (called by NSTimer Selector()) will probably be changed
I am new to SpriteKit but I feel that this setup process is quite resource intensive right?
My question is: is it possible/would it be better to run this fonction only once, and then only duplicate the created enemy?
Here are some basics that you should know:
What you can do (especially if you are experiencing performance problems) is to:
use texture atlases. This way you are reducing number of cpu draw calls required to render a sprites.
cache different resources like emitters (or sounds). This way you are keep them in memory and you don't need to read them from disk.
Search Apple's Adventure game to see how you can cache the resources. Basically, you preload everything before gameplay using the completion handler.
One way you could go is to pre-create a bunch of nodes and use them until game is finished. This way you won't re-create the SKSpriteNodes many times (which sometimes can cause noticeable lag). Here you can read about that.
Important:
Don't take simulator results into account, because those are not realistic. If you are interested in real information about performance you should test on device.
Hope this helps a bit.