Messages, events and shared state
Send messages between apps, react to what happens on the workstation, and keep values that several apps read and watch.
Apps work together in three ways:
| You want to | Use |
|---|---|
| tell other apps something as it happens | messages, datumhue.messaging |
| hear about windows, apps and input | system events, datumhue.events |
| keep values other apps can read, change and watch | documents, datumhue.documents |
Send messages
messaging.publish sends a payload on a topic, and messaging.subscribe
runs a callback for every message on that topic, with the payload in
msg.payload. A payload is a plain Lua value: a table, a string, a
number or a boolean. Each subscriber gets its own copy, in which a table
the payload holds twice is two tables; a table that holds itself raises.
A message reaches the apps on this workstation that subscribe to its
topic, but never the app that sent it, even when it subscribes too.
Every app shares the same topic names, so give a private channel a name no other app would pick, such as one starting with your package's name.
This program starts a dice service, a second app with no window, and asks it for a roll five times a second:
local messaging = datumhue.messaging
-- A service app, with no window, that answers every roll
-- request with a number from 1 to 6.
local roller = [[
local messaging = datumhue.messaging
messaging.subscribe({
topic = "dice/roll",
callback = function()
messaging.publish({
topic = "dice/result",
payload = { value = math.random(1, 6) },
})
end,
})
]]
datumhue.app.spawn({ name = "roller", code = roller, service = true })
-- This app shows every result it hears.
local card = datumhue.ui.root:panel({ padding = 24, gap = 12 })
card:label({ text = "Dice", font_size = 24 })
local results = card:label({ text = "Rolling..." })
local rolls = {}
messaging.subscribe({
topic = "dice/result",
callback = function(msg)
table.insert(rolls, msg.payload.value)
results.text = "Rolled " .. table.concat(rolls, " ")
end,
})
-- Ask for a roll every 0.2 seconds.
datumhue.time.every(0.2, function()
messaging.publish({ topic = "dice/roll", payload = true })
end)
In this example: datumhue.messaging datumhue.app.spawn datumhue.ui.root UiElement:panel UiElement:label datumhue.messaging.subscribe UiElement.text datumhue.time.every datumhue.messaging.publish
The roller keeps running while its subscription is live, and ends with the app that started it. Launch other apps covers starting apps.
With scope = "network", a message reaches the apps of your @scope
whose packages came from where yours did, on this workstation and on the
others connected to the same deployment, including apps your app starts
from Lua source, unless it starts them with storage = "own". Both
sides need the network permission. An app under
another @scope, or a copy of yours run from a folder, reaches a
different topic under the same name, and messaging.presence keeps its
groups apart the same way. To talk with the apps of other @scopes,
both sides set shared = true: a shared topic, or presence group, is
one for every app that names it with shared.
A network topic keeps the apps of other @scopes from mixing their
messages by accident; it keeps out no one: anyone who can reach the
deployment and runs a modified workstation can hear and send on any
network topic. To pass something confidential to another creator's
app, put it in a document from documents.create and send that app a
grant over a shared topic.
Hear system events
events.on runs a callback each time a named event happens: the window
resizes, an app starts, ends or takes focus, an app asks for a
permission. An event about an app reaches only the apps that manage it:
itself, the app that started it, and the apps above that. Its payload
names the app it is about.
Some events report a value you would otherwise read every frame. They fire once as you subscribe, with the current value, then again whenever it changes:
| Event | Follows |
|---|---|
input_source | input.last_source, the device last used |
fullscreen | screen.is_fullscreen and screen.fullscreen_available |
focus | ui.focused, the widget holding focus |
density | theme.density, the size of controls |
theme | theme.current(), the theme in effect |
connectivity fires once as you subscribe, with whether this session
is connected.
local hint = datumhue.ui.root:label({ text = "" })
datumhue.events.on("input_source", function(e)
hint.text = e.source == "gamepad" and "Press A" or "Click"
end)
events.on returns a Subscription; call Subscription:unsubscribe
when you no longer want the event.
Share values in a document
A document holds keys and values that several apps read, change and
watch. documents.open_named opens one by name, and it lasts until the
workstation quits. Document:set writes a key, Document:get reads
one, and Document:subscribe runs a callback for each change under a
key prefix.
Every app of your @scope on this workstation that opens the same name
gets the same document, including apps your app starts from Lua source,
unless it starts them with storage = "own".
An app under another @scope, or a copy of yours run from a folder,
gets a different document under the same name. With shared = true,
every app that opens the name with shared gets one document, whatever
its @scope.
This program opens a board, listens for notes, and starts a second app that writes one:
local documents = datumhue.documents
-- Every app of this @scope on this workstation that opens
-- "board" gets the same document, until the workstation quits.
local board = documents.open_named({ name = "board" })
board:subscribe("notes/", function(key, value)
print(key .. " = " .. tostring(value))
end)
-- Another app writes to the board; this one hears it.
datumhue.app.spawn({
name = "writer",
service = true,
code = [[
local board = datumhue.documents.open_named({ name = "board" })
board:set("notes/greeting", "hello from the writer"):ready()
]],
})
In this example: datumhue.documents datumhue.documents.open_named Document:subscribe datumhue.app.spawn
It prints notes/greeting = hello from the writer. Opening a document
and reading a key wait for their result; Waiting: suspending calls and
handles explains how. For data only your app
keeps from one run to the next, use
Save data between runs instead.
Hand a document to another app
documents.create makes a document with no name, which other apps can
open only through a grant. Document:grant makes a DocumentGrant for
any document, to read or to write as well. DocumentGrant:encode turns
it into a string that travels over any channel, a message included, and
documents.open opens the document from it.
With scope = "network", a document is shared with other workstations
connected to the same deployment, and needs the network permission.
A workstation syncs it while a handle or a subscription on it is open
there, and an app's handles close when the app ends.
When two of them write the same key, the newest write wins. Each
workstation turns away a write stamped more than ten minutes ahead of
its own clock, so a workstation whose clock is off by more than that
loses writes; keep clocks set automatically. Keys are /-separated
paths. Writing a key replaces what its author wrote under it, and
Document:delete_prefix removes what its author wrote under the
prefix; keys other authors wrote there stay.
A document opened by name keeps the apps of other @scopes from
mixing their data by accident; it keeps out no one: anyone who can
reach the deployment and runs a modified workstation can read and
change it, under any author. Keep anything private in a document from
documents.create, shared through grants; to give one to another
creator's app, send it the grant over a shared topic.
Each signed-in person also has a communal document, opened with
documents.communal: their own section is always theirs to write, and
reading another person's section takes a grant from them.
A grant to a network document can cover one key prefix, and lasts for
the days valid_days sets. It reads the document's history and the
changes stamped until it runs out, never one stamped later; once it
runs out, DatumHue starts no new sync of the document to its holder. A
grant cannot be taken back: whoever holds it keeps that access until it
runs out. Whoever holds a grant can DocumentGrant:delegate a narrower
one, but never a wider one.
A write grant's holder can add writes stamped from an hour before the grant was made until it runs out, and can still add such writes after it runs out; they lose to any newer write of the same key.
See also
- Launch other apps
- Write a window manager or launcher
- Save data between runs
- Waiting: suspending calls and handles
Reference used on this page: datumhue.messaging datumhue.events datumhue.documents datumhue.app.spawn datumhue.ui.root UiElement:panel UiElement:label datumhue.messaging.subscribe UiElement.text datumhue.time.every datumhue.messaging.publish datumhue.input.last_source datumhue.screen.is_fullscreen datumhue.screen.fullscreen_available datumhue.ui.focused datumhue.theme.density datumhue.theme.current datumhue.events.on datumhue.documents.open_named Document:subscribe datumhue.messaging.presence datumhue.documents.create Subscription:unsubscribe Document:set Document:get Document:grant DocumentGrant:encode datumhue.documents.open Document:delete_prefix datumhue.documents.communal DocumentGrant:delegate