Is there a limit on how long the update callback can run for? [EDIT: yes, 10 seconds]

I'm developing in C on Windows, building a pdx for the device, and uploading the pdx to the device with the Windows Simulator.

My app isn't a game, it runs some diagnostic tests and logs results with logToConsole. All of the execution happens the first time my update callback is invoked. It seems that if the update takes more than about 10 seconds then my program is forcibly halted and the device is reset. Is this a deliberate failsafe built into the operating system to terminate "stuck" programs? Is there a way to disable this behaviour?

Normally I'd never want my update function to run for 10 or more seconds, but in this case I'm doing some memory benchmarking in a small utility program, this isn't something that will ever go to an end user.

EDIT: I see mention in some other posts of "run loop stalled", is this what I'm hitting and can I do anything about it?

Thanks for any light you can shed on the situation!

For reference, here's what I see in the simulator's console window when this happens:

(output from my program, followed by ...)
22:05:40: ReadFile failed (995)
22:05:40: PlaydateSerialRead failed
22:05:40: SerialReadThread OnExit
22:05:40: Device Disconnected: COM4
22:05:40: PlaydateSerialClose (0x00000DC8)
22:05:41: Playdate Found: COM4
22:05:41: Device Connected: COM4
22:05:41: PlaydateSerialOpen (COM4)
22:05:41: Serial port opened (0x00000DC8)
22:05:41: SetCommState OK (0), BaudRate: 115200, fDtrControl: 2
22:05:41: SerialReadThread::Entry
echo off
~version:
target=DVT1
build=59185ded27c7-1.12.3-release.140884-buildbot-20220811_165705
boot_build=59185ded27c7-1.12.3-release.140884-buildbot
SDK=1.12.3
pdxversion=11200
serial#=PDU1-Y010994
cc=9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]
crashed=1
time and date set
2 Likes

I vaguely remember that the limit is 10 seconds indeed.

Note that the update runs in a 'coroutine'

You might be able to defeat the timeout by ensuring your code calls 'coroutine.yield' every 5 seconds or so. No other code modifications are necessary. Your code will almost immediately continue executing right where you called this function.

There might be weird side effects like button state and other timers not being updated not being

Definitely seems like there's a 10 second limit. This also applies to eventHandler(), not just the update callback, so I was unable to run my tests on kEventInit. I don't see anything in the C API about coroutines, is there documentation elsewhere that I'm missing?

My tests involve a lot of repeated loops (I'm benchmarking memory accesses, and repeating them enough times to get accurate timing information, not to mention running a whole bunch of tests one after another). I'll revise my test framework so I can spread the work over multiple updates.

Coroutines is a Lua thing--it won't apply to C. The update callback is given 10 seconds as a concession, but it should never-ever-ever take that long: it's just there to make sure your program isn't prematurely terminated for an abnormally long task that will eventually finish.

If you need to perform work in excess of 10 seconds, you'll need to introduce some mechanism to perform this work in chunks of < 10s at a time.

Yes, you have to pass control back to the run loop within 10 seconds or the system assumes your code is stuck. That's maybe a clumsy way to do it, but in practical usage if you're blocking the run loop for even 1/2 a second your game is going to feel pretty janky. (though I understand this isn't a game you're talking about..)

I've never used it and can't vouch for it, but here's a coroutine library for the Playdate C API, if reworking your code so it runs in chunks and returns out of the update function regularly isn't feasible: [C/C++] Coroutines Library for Playdate

2 Likes

Thanks @dave! The coroutine library looks fancy, but instead I've refactored my test runner to run one test per update, and I'll just make sure that each test takes less than 10 seconds.

Should have some results to share soon. :slight_smile:

3 Likes

I have a similar issue to this, saving my game can take a long time, over the 10s limit - this is because there can be a lot to save and the JSON can get to be > 1 MB.

For explicit saves or auto-saves I can work around this. I display a "SAVING" modal and save a little bit of content each frame, until it is all streamed out.

But I also want to be able to auto-save on case kEventTerminate: case kEventLock: case kEventLowPower: and here I cannot use this trick, the save has to be all in one go...

I cannot think of anything else I could do here, so it might be safest overall to not attempt to save on exit and have a message on the pause window to tell the player to save before quitting?

Happy to hear any suggestions of alternates!

Are you using datastore? It sounds like you might want to think about writing your own save data format into string data and using the playdate.file API instead of datastore's JSON.

Or, could you split up your save data into many files and only write out the stuff that's changed?

Thanks @AlexMay for the thoughts,

I'm using the PlayDate JSON C-API, and I have already split the game into different "worlds" to help manage the amount of data saved (also limited by the system RAM!). But an individual world can still get to the size where I can't save it on the case kEventTerminate: callback.

The gamestate is also correlated over the whole world, and the things which get saved are all the things which are all changing on a frame by frame basis, so I don't think I can shrink things there.

Short of coming up with my own binary format instead of using the JSON C-API, I am expecting that I am going to be stuck here.

(And I really don't want to be supporting my own binary format here, it took long enough as it is to debug the JSON serialisation! :slight_smile: )

2 Likes