App does not receive DeviceMotion updates in background (WatchOS 6) - swift

I'm developing an application for Apple Watch that acquires accelerometer, gyroscope data through DeviceMotion, and heart rate through HKWorkout.
Until the application stays in the foreground, all works well. However, when it enters the inactive state (when the user lowers the wrist), for some time it works, but at a certain point it stops. Furthermore, when the app goes in background, the app stops to collect new DeviceMotion updates, but I think that the observer of the heart rate continues to work since the green led still remains on.
The code I use for DeviceMotion is:
if self.motion.isDeviceMotionAvailable {
self.motion.deviceMotionUpdateInterval = 1.0 / 100.0
self.motion.showsDeviceMovementDisplay = true
self.motion.startDeviceMotionUpdates(using: .xMagneticNorthZVertical, to: OperationQueue.main, withHandler: { (data, error) in
if let d = data {
//collecting data
}
}
}
The code I use for heart rate:
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .crossTraining
workoutConfiguration.locationType = .indoor
do {
if workoutSession == nil {
workoutSession = try HKWorkoutSession(healthStore: HealthDataManager.sharedInstance.healthStore!, configuration: workoutConfiguration)
workoutSession?.startActivity(with: Date())
}
} catch {
print("Error starting workout session: \(error.localizedDescription)")
}
HealthDataManager.sharedInstance.observeHeartRateSamples { (heartRate) -> (Void) in
print("heart rate sample: \(heartRate)")
self.lastHearRateSample = heartRate
}
Since Apple Documentation states (https://developer.apple.com/documentation/healthkit/workouts_and_activity_rings)
While a workout session is active, your app can continue to run in the background. This lets your app monitor the user and gather data throughout the activity. Additionally, it ensures that your app appears whenever the user checks their watch.
I have thought that there was no need to manage the behavior of the app in these cases. However, it seems that something must be done. What's the best way to collect new DeviceMotion data also in the background state (and in the inactive one)?

It turned out that the application went in the background because of the health kit workout. However, I was using too many resources and the system suspended the app.

Related

Game Center Turn Timeout for Multiplayer GAmes

I have created a turn based multiplayer board game using Swift and Game Center that works pretty well. One of the last items I would like to add is a way to keep a player from abandoning a game near the end if they know they are going to lose. It seems like the turnTimeout portion of the endTurn function is built in especially for this purpose, but I cannot get it to work. My endTurn function is below:
func endTurn(_ model: GameModel, completion: #escaping CompletionBlock) {
guard let match = currentMatch else {
completion(GameCenterHelperError.matchNotFound)
return
}
do {
let currenParticipantIndex: Int = (match.participants.firstIndex(of: match.currentParticipant!))!
let nextPerson = [match.participants[(currenParticipantIndex+1) % match.participants.count]]
print("end turn, next participant \(String(describing: nextPerson[0].player?.alias))")
match.endTurn(
withNextParticipants: nextPerson,
turnTimeout: 15,
match: try JSONEncoder().encode(model),
completionHandler: completion
)
} catch {completion(error)}
}
This function takes into account the advice from Anton in the comment of the answer to this question:
Trying to set a time limit on my Game Center game
to update the array of nextParticipant players so that the end of the array is never reached. I've also tried to account for this in my testing by having both player 1 and player 2 delay the end of their turn to see if it would fire (The game is a 2 player game only)
This should also answer this question:
Game Center turn timeouts
The documentation says:
timeoutDate: The time at which the player must act before forfeiting a turn. Your game decides what happens when a turn is forfeited. For some games, a forfeited turn might end the match. For other games, you might choose a reasonable set of default actions for the player, or simply do nothing.
https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/GameKit_Guide/ImplementingaTurn-BasedMatch/ImplementingaTurn-BasedMatch.html
Unfortunately I am unable to get the turnTimeout function to fire at all. I have done a fair amount of research and I found no definitive answer to what function is actually called when it fires (i.e. the player takes longer than the allotted time to take their turn). I would expect that the same function is called for a timeout as a regular call of the endTurn function and the below player listener is called:
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool) {
if let vc = currentMatchmakerVC {
currentMatchmakerVC = nil
vc.dismiss(animated: true)
}
print("received turn event")
if !didBecomeActive {
print("\n\n\n player listener did become active")
print("match did change")
NotificationCenter.default.post(name: .matchDidChange, object: match)
} else if didBecomeActive {
print("present game")
NotificationCenter.default.post(name: .presentGame, object: match)
}
}
I am able to get the player listener (received turn event) to fire when endTurn is specifically called from the game, but I do not see anything that is called when the turnTimeout event triggers. If it was the player listener I would see the print statements in the console as well as the notification on the next player's device.
The GKTurnTimeoutDefault is 604,800 and is a Time Interval which I did some research on and arrived at the conclusion that it is in seconds, which is 7 days. I changed it to 0.00001, 15, 2000 and a few values in between but I wasn't able to get it to fire.
I also found the below, but the first has no answer and the second only says the turn timeouts probably warrants its own full answer:
Game Center Turnbased Game turn timeout
How to detect when Game Center turn based match has ended in iOS9?
I am thinking that my mistake is probably that I am unable to find the function that is called when the turn timeout fires, although I might be mistaken on the Time Interval values that I'm putting in there as well.
Thank you for taking the time to review my question :)

GKLeaderboard's localPlayerScore delay after saving a GKScore

I'm trying to get a user's new leaderboard rank after scoring a high score and I found it takes about 5 seconds for the GKLeaderboard scores to update. I've tested the code against a Release build (from Xcode) and the delay is still there.
let score = GKScore(leaderboardIdentifier: leaderboardId)
score.value = Int64(highScore)
GKScore.report([score]) { _ in
// Adding a 5 second delay here solves the problem.
let leaderboard = GKLeaderboard()
leaderboard.identifier = leaderboardId
leaderboard.loadScores { _, _ in
// leaderboard.localPlayerScore shows data from before saving the new score.
}
}
Is there a way around this? The 5-second delay seems flaky. Maybe once the app is in the App Store this delay is no longer there?
I could probably create a workaround by storing the scores before the user plays the game and locally calculate the new rank but the shared code should avoid the need for that, right?

Is this possible to execute a test regularly in a controller?

With my application I take the coordinates of my user when he is in movement in order to display him the traject he took. But my problem is to know when the user stops in order to stop the current traject and display him this trajectory on a map.
My code to know when the user starts his trajectory works perfectly well, I do it with didUpdateLocations with a distanceFilter of 100.
My problem is I can't know when user stop.
I want to know if for example there is a solution to know when I will no get any update locations anymore and so this will mean that the traject is over.
I tried with the locationManagerDidPauseLocationUpdates, but my program seems to never enter to this function.
//Here is my code that gets the update of the user location
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let mostRecentLocation = locations.last else {
return
}
if !self.isTrajectStarted {
if self.locations.count == 0 {
print("First coordinate")
} else if (self.locations.count == 1) {
if self.locations[0].distance(from: mostRecentLocation)>150 {
print("traject begin")
}
}
} else {
print(" New location is %#", mostRecentLocation)
}
}
Write a new logic to detect the stop.
locationManagerDidPauseLocationUpdates, won't get called immediately after a stop. It is usually called after 5 minutes (not documented, this number is what I have seen in my apps).
You need to write a logic by first categorizing the mode of travel. If your app records user's movement in a vehicle, you would know that whenever speed drops to below 4-5 mph, user is either on a traffic signal or has reached on the destination. If you don't get any new update for next 5 minutes or so, conclude it to be a stop, or else continue recording.
Try taking advantage of the motion sensor as well by appending it to your logic, where it could tell you different activity types such as walking, driving, stationary etc.

How can i synchronize AVCaptureDevice setFocusModeLockedWithLensPosition call

I want to synchronize setFocusModeLockedWithLensPosition, setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains and setExposureModeCustomWithDuration calls.
Is there a logic order to call thoses functions ?
What i want to do is to start Running session when i am sure that focus, balance and exposure are properly set (i want to set values, not in automatic)
I have tried to lock the configuration, then call the 3 functions, then unlock, then startRunning on session. I put nil in the 3 completion handler parameters.
What i see in this case is that my image preview is not pretty (kind of blue filter). I have to wait before having a good image quality. What i want is to display the image only when it is good. I want do be notified.
So i tried to cascade my 3 calls with completion handler. in some cases, the completion handler is not called. I suppose this is when i want to put my lens position to 0.4 and the current lens position is 0.4.
So i don't know which is the best method.
Thanks
You can set your camera options in completion handler like this. It will wait till focus has been set to set exposure and the same principle will work with white balance and exposure. You can read more about camera setting here.
var AVCGains:AVCaptureWhiteBalanceGains = AVCaptureWhiteBalanceGains()
AVCGains.redGain = 1.0;
AVCGains.greenGain = 1.0;
AVCGains.blueGain = 1.0;
self.camera?.focusMode = .locked
self.camera?.exposureMode = .locked
self.camera?.whiteBalanceMode = .locked
self.camera?.setFocusModeLockedWithLensPosition(focus_point, completionHandler: {(timestamp:CMTime) -> Void in
print("Focus applied")
self.camera?.setExposureModeCustomWithDuration(CMTimeMake(1,10), iso: 100, completionHandler: {(timestamp:CMTime) -> Void in
print("Exposure applied")
self.camera?.setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains(AVCGains, completionHandler: {(timestamp:CMTime) -> Void in
print("White Balance applied")
// All settings have been applied, start running session
})
})
})

Firebase too slow to load for a tile-based game

I'm building a 2d tile-based game for iOS with swift and Firebase. Because the world is large, I've designed it so that I only subscribe to the tiles that are on screen. That is, instead of adding listeners for all 10,000x10,000 tiles, I add them to just the tiles on screen. As the player moves, I de-register the old listeners and register the new ones. I've added a bit of a buffer zone around the edge of the screen, in the hopes that everything will be sufficiently loaded by the time it moves on screen. Unfortunately, there is often significant enough lag from Firebase that this strategy simply isn't working. On sub-optimal internet connections, it's possible to keep walking into the "unloaded world," taking several seconds at times to load the missing tiles.
Here's the thing, though: other MMO iOS games on the same connection and same device work fine. It's not a terrible connection. Which makes me suspect my implementation, or Firebase itself is at fault.
Fundamentally I'm waiting on the "load once" event for about 20 tiles each time I take a step. A step takes about 1/4 of a second, so each second I'm requesting about 100 items from Firebase. I can't think of a better way, though. Firebase documentation suggests that this should not be a problem, since it's all one socket connection. I could "bucket" the objects into, say, 10x10 blocks which would mean that I'd subscribe to fewer objects, but this would also be more wasteful in terms of total data transfer. If the socket connection is truly optimized, total data transfer should be the only bottleneck, implying this strategy would be wrong.
edit
Here's a video showing how it works. The buffer-size has been reduced to -1, so that you can easily see the edges of the screen and the tiles loading and unloading. Near the end of the video, lag strikes and I wander into the emptiness. I opened up another game and it loaded almost instantly. http://forevermaze.inzania.com/videos/FirebaseLag.mov (n.b., I ended the recording before the screen loaded again. It never fails to load, so it's not as if the code is failing to work. It's pure lag.)
Here is the code I'm using to load the tiles. It's called once for each tile. As I said, this means that this code is called about 20 times per step, in parallel. All other apps are running at a fine speed with no lag. I'm on a MiFi with LTE connectivity in Tokyo, so it's a solid connection.
/**
* Given a path to a firebase object, get the snapshot with a timeout.
*/
static func loadSnapshot(firebasePath: String!) -> Promise<FDataSnapshot?> {
let (promise, fulfill, _) = Promise<FDataSnapshot?>.pendingPromise()
let connection = Firebase(url: Config.firebaseUrl + firebasePath)
connection.observeSingleEventOfType(.Value, withBlock: { snapshot in
if !promise.resolved {
fulfill(snapshot)
}
})
after(Config.timeout).then { () -> Void in
if !promise.resolved {
DDLogWarn("[TIMEOUT] [FIREBASE-READ] \(firebasePath)")
fulfill(nil)
//reject(Errors.network)
}
}
return promise
}
The tiles reside at [ROOT]/tiles/[X]x[Y]. Most tiles contain very little data, but if there are objects on that tile (i.e., other players) those are stored. Here's a screenshot from Firebase:
edit2
Per request, I've recreated this issue very simply. Here is a 100-line XCTestCase class: http://forevermaze.com/code/LagTests.swift
Usage:
Drop the file into your Swift project (it should be stand-alone, requiring only Firebase)
Change the value of firebaseUrl to your root URL (i.e., https://MyProject.firebaseio.com)
Run the testSetupDatabase() function test once to setup the initial state of the database
Run the testWalking() function to test the lag. This is the main test. It will fail if any tile takes longer than 2 seconds to load.
I've tried this test on several different connections. A top-notch office connection passes with no problem, but even a high-end LTE or MiFi connection fails. 2 seconds is already a very long timeout, since it implies that I need to have a 10 tile buffer zone (0.2 seconds * 10 tiles = 2 seconds). Here's some output when I'm connected to a LTE connection, showing that it took nearly 10 seconds (!!) to load a tile:
error: -[ForeverMazeTests.LagTests testWalking] : XCTAssertTrue failed - Tile 2x20 took 9.50058007240295
I ran a few tests and the loading completes in 15-20 seconds when I test over a 3G connection. Over my regular connection it takes 1-2 seconds, so the difference is likely purely based on bandwidth.
I rewrote your test case into a JavaScript version, because I had a hard time figuring out what's going on. Find mine here: http://jsbin.com/dijiba/edit?js,console
var ref = new Firebase(URL);
var tilesPerStep = 20;
var stepsToTake = 100;
function testWalking() {
var startTime = Date.now();
var promises = [];
for (var x=0; x < stepsToTake; x++) {
promises.push(testStep(x));
}
Promise.all(promises).then(function() {
console.log('All '+promises.length+' steps done in '+(Date.now()-startTime)+'ms');
});
}
function testStep(x) {
var result = new Promise(function(resolve, reject){
var tiles = ref.child("/tiles_test");
var loading = 0;
var startTime = Date.now();
console.log('Start loading step '+x);
for (var y=0; y < tilesPerStep; y++) {
loading ++;
tiles.child(x+'x'+y).once('value', function(snapshot) {
var time = Date.now() - startTime;
loading--;
if (loading === 0) {
console.log('Step '+x+' took '+(Date.now()-startTime)+'ms');
resolve(Date.now() - startTime);
}
});
}
});
return result;
}
testWalking();
The biggest difference is that I don't delay starting any of the loading and I don't fail for a specific tile. I think that last bit is why your tests are failing.
All loading from Firebase happens asynchronously, but all requests are going through the same connection. When you start loading, you are queueing up a lot of requests. This timing is skewed by "preceding requests that haven't been fulfilled yet".
This is a sample of a test run with just 10 steps:
"Start loading step 0"
"Start loading step 1"
"Start loading step 2"
"Start loading step 3"
"Start loading step 4"
"Start loading step 5"
"Start loading step 6"
"Start loading step 7"
"Start loading step 8"
"Start loading step 9"
"Step 0 took 7930ms"
"Step 1 took 7929ms"
"Step 2 took 7948ms"
"Step 3 took 8594ms"
"Step 4 took 8669ms"
"Step 5 took 9141ms"
"Step 6 took 9851ms"
"Step 7 took 10365ms"
"Step 8 took 10425ms"
"Step 9 took 11520ms"
"All 10 steps done in 11579ms"
You'll probably note that the time taken for each step does not add up to the time taken for all steps combined. Essentially you are starting each request while there are still requests in the pipeline. This is the most efficient way of loading these items, but it does mean that you'll need to measure performance differently.
Essentially all steps start at almost the same time. Then you're waiting for the first response (which is in the above case includes establishing a WebSocket connection from the client to the correct Firebase server) and after that the responses come in reasonable intervals (given that there are 20 requests per step).
All of this is very interesting, but it doesn't solve your problem of course. I would recommend that you model your data into screen-sized buckets. So instead of having each tile separate, store every 10x10 tiles in a "bucket". You'll reduce the overhead of each separate request and only need to request at most one bucket for every 10 steps.
Update
I'm pretty sure we're just debugging multiple artifacts of your benchmark approach. If I update the code to this:
func testWalking() {
let expectation = expectationWithDescription("Load tiles")
let maxTime = self.timeLimit + self.stepTime * Double(stepsToTake)
let startTime = NSDate().timeIntervalSince1970
for (var x=0; x<stepsToTake; x++) {
let delay = Double(x) * stepTime
let data = ["x":x, "ex": expectation]
stepsRemaining++
NSTimer.scheduledTimerWithTimeInterval(0, target: self, selector: Selector("testStep:"), userInfo: data, repeats: false)
}
waitForExpectationsWithTimeout(maxTime) { error in
let time = NSDate().timeIntervalSince1970 - startTime
print("Completed loading after \(time)")
if error != nil {
print("Error: \(error!.localizedDescription)")
}
}
}
/**
* Helper function to test a single step (executes `tilesPerStep` number of tile loads)
*/
func testStep(timer : NSTimer) {
let tiles = Firebase(url: firebaseUrl).childByAppendingPath("/tiles_test")
let data = timer.userInfo as! Dictionary<String, AnyObject>
let x = data["x"] as! Int
let expectation = data["ex"] as! XCTestExpectation
var loading = 0
print("Start loading \(x)")
for (var y=0; y<tilesPerStep; y++) {
loading++
tiles.childByAppendingPath("\(x)x\(y)").observeSingleEventOfType(.Value, withBlock: { snapshot in
loading--
if loading == 0 {
print("Done loading \(x)")
self.stepsRemaining--
if self.stepsRemaining == 0 {
expectation.fulfill()
}
}
})
}
}
It completes the entire loading in less than 2 seconds over a high-speed network, over 3G it takes between 15 and 25 seconds.
But my recommendation of modeling at a level of more than each single tile remains.