Background threads
On Android, the Godot part of Nari stops when the app goes to the background, but the audio (written in Rust) keeps playing. Threads run next to the audio, so they keep working with the screen off: scrobbling, webhooks, streaming and sign-in servers.
local worker = nari.thread("worker.lua")worker:on_message(function(message) nari.log("worker says " .. message.text) end)worker:send({ text = "hi" })nari.on("message", function(message) nari.send({ text = message.text .. " back" })end)The handle has send(value), on_message(handler), on_error(handler) (called with the error if the thread
crashes) and stop(). Threads also stop when the plugin is turned off.
Threads are event driven: the script runs once to set up its handlers, then the thread sleeps until an event, a
message or a timer arrives. A slow nari.http only blocks its own thread.
Inside a thread
Section titled “Inside a thread”| Function or event | |
|---|---|
nari.on("message", handler) |
A value sent with handle:send. |
nari.on("track", handler) |
The current song changed (nil when the queue ends). A thread that starts mid-song gets it right away. |
nari.on("state", handler) |
{ playing, position } when playback starts or pauses. |
nari.on("ask", handler) |
A question from another thread: handler(value, reply). |
nari.send(value) |
Sends a value to main.lua, delivered when the main thread is running again. |
nari.after(seconds, handler) |
A timer. |
nari.http(request) |
Blocking: returns response, problem directly. Add stream = function(chunk) end to get the body piece by piece (return false to stop); streamed requests have no time limit. |
nari.tcp(spec) |
Blocking connection to a TCP server. See Sockets. |
nari.serve(handler, options) |
A local web server, answered even while Android keeps the app in the background. |
nari.ask(name, value, timeout) |
Blocking question to another thread. See Threads asking threads. |
nari.resolver(scheme, handler), nari.source(scheme, handler) |
Stream songs yourself. See Streaming. |
nari.analyze(path, options) |
Blocking audio analysis. |
nari.get, nari.set, nari.log, nari.warn, nari.plugin, JSON, Base64, hashes, nari.cipher, nari.modpow, nari.random_bytes |
Same as on the main thread. |
require |
Loads Lua modules from the plugin folder. |
Sockets
Section titled “Sockets”local socket, problem = nari.tcp({ host = "example.com", port = 4070 })socket:send(hello)local header = socket:receive(4, 10) -- exactly 4 bytes, waiting up to 10 secondssocket:on_data(function(bytes) end) -- from now on, bytes arrive as eventssocket:on_close(function(problem) end)| Key | |
|---|---|
host, port |
Required. |
tls |
true for an encrypted connection, checked against the usual certificate authorities. |
timeout |
Seconds to wait for the connection, 20 by default. |
nari.tcpreturnsnil, problemwhen it can’t connect, andsocket:send(bytes)returnstrueornil, problem.socket:receive(count, timeout)blocks untilcountbytes arrived (any amount when left out). It returnsnil, "timeout"ornil, "closed"when it can’t; bytes that arrived before a timeout wait for the next call.socket:on_data(handler)hands every piece tohandlerbetween the thread’s other events, so the thread keeps answering messages while the connection is open. After it,receivecan’t be used.socket:close()closes it. Sockets also close when the thread stops. Only threads have sockets.
Threads asking threads
Section titled “Threads asking threads”A thread started with a name answers questions from the plugin’s other threads. One thread can own a connection or a
login and serve everyone else, even while Android keeps main.lua paused:
nari.thread("session.lua", { name = "session" })nari.thread("audio.lua")nari.on("ask", function(question, reply) if question.cached then return cache[question.cached] -- answer right away end request_key(question.file, function(key) -- or later, from a socket or a timer reply({ key = key }) end)end)local answer, problem = nari.ask("session", { file = id }, 10)nari.ask(name, value, timeout) waits for the answer (20 seconds by default) and returns it, or nil, problem when
the thread doesn’t exist, fails, doesn’t answer in time ("timeout") or calls reply(nil, problem). Only the
first answer counts, and a thread can’t ask itself.