Waiting: suspending calls and handles
Some work takes time: reading a file, saving to storage, waiting for a person to answer. Your app waits for it in one of two ways: a call that pauses your code until the result is ready, or a handle you check, wait on, or give a callback.
Calls that pause
A suspending call pauses the code that called it until its result is
ready, then goes on, and the result comes back as any return value does.
The reference marks each one with a "suspends" pill. Reading a file's
bytes, counting a table's rows, datumhue.time.sleep and
datumhue.screen.screenshot are suspending calls.
While your code is paused, the rest of your app waits with it, but nothing else does. DatumHue keeps showing your app's last frame, and other apps keep running. Work on this workstation usually finishes within a frame; a slow query or a dialog someone has to answer can take much longer. Treat a suspending call as one that can pause, and keep it out of loops that must be quick.
Handles that wait
Saving to storage, writing a file and reading a shared document start their work the moment you call them, and return a handle at once, before the result exists. Images, sounds, fonts and shaders load the same way, in the background. Since the work starts at the call, several operations run side by side: start them all, then wait once.
| To | Write | It |
|---|---|---|
| wait for one handle | Op:ready | pauses, then returns the same handle; raises if the work failed |
| wait for several | datumhue.ready | pauses until every handle in the list is ready; raises on the first failure |
| look without waiting | Op.state | reads where the work is; never pauses or raises |
| hear when it ends | Op:on_done | calls your function once the work is done or has failed |
| not care | nothing | the work still happens |
Reading a result off a handle that is not ready raises, so wait first. This program saves two values, then reads both back together:
local store = datumhue.storage.open()
store:set("alpha", datumhue.bytes("first")):ready()
store:set("beta", datumhue.bytes("second")):ready()
local a = store:get("alpha")
local b = store:get("beta")
datumhue.ready({ a, b })
print(a.value:text(), b.value:text())
In this example: datumhue.storage.open Storage:set datumhue.bytes Op:ready Storage:get datumhue.ready BytesFetch.value Bytes:text
:ready()on eachsetwaits for the write before going on.- The two
getcalls start both reads at once, anddatumhue.readywaits for both. a.valueis safe to read only after the wait.
Reacting without pausing
on_done lets your app go on at once and react when the work ends. Its
callback runs once, as a callback of its own, and receives the handle, so
:ready() inside it returns or raises straight away:
local store = datumhue.storage.open()
local status = datumhue.ui.root:label({ text = "Saving..." })
store:set("note", datumhue.bytes("hello")):on_done(function(op)
local ok, err = pcall(op.ready, op)
status.text = ok and "Saved" or tostring(err)
end)
In this example: datumhue.storage.open datumhue.ui.root UiElement:label Storage:set datumhue.bytes Op:on_done UiElement.text
- The label shows "Saving..." while the write runs.
pcallturns a failed write into a message instead of an error.
A chart's scale domain and its data-space conversions settle as a frame
is drawn. Until then they read nil: read them again on a later frame
rather than waiting for them.
Waiting inside callbacks
Every callback may make suspending calls, the per-frame handler and UI handlers included. Your app runs one callback at a time, so a flow of several suspending calls in one callback runs from start to finish with nothing else of yours in between. That makes one callback the best home for a long flow. The cost is that the rest of your app's callbacks wait behind it:
| While a callback is paused | What happens |
|---|---|
| The same button is clicked again | the second run waits, and starts when the first ends |
| A repeating timer comes due | its fires due during the pause wait as one, which runs after the callback ends; its dt covers them all |
| New frames start | at most one per-frame run waits; the time of the frames it missed adds to its dt |
| Other apps | keep running |
Guard a flow that must not run twice with a flag. A coroutine of your own does not change any of this: a suspending call inside it pauses your whole app, not just the coroutine.
No timeouts
A suspending call waits until its result arrives, however long that is.
datumhue.ready on an image that never arrives waits for good, and
datumhue.app.request_permission waits for as long as the app above
yours takes to answer, which may mean waiting for a person. There is no
timeout to set, and pcall does not shorten a wait: waiting is not an
error.
To give up on slow work, do not pause for it. Keep its handle, check
state from a timer, and move on when you have waited long enough. A
paused app is still an ordinary app: it can be ended at any time, and it
never holds up other apps or the workstation.
See also
Reference used on this page: Op:ready datumhue.ready Op.state Op:on_done datumhue.storage.open Storage:set datumhue.bytes Storage:get BytesFetch.value Bytes:text datumhue.ui.root UiElement:label UiElement.text datumhue.time.sleep datumhue.screen.screenshot datumhue.app.request_permission