Non-blocking connections and binary stream operations.
Asynchronous TCP client
demi.network.tcp connects to raw TCP services without blocking gameplay. A shared libuv event loop handles connection setup, DNS and socket readiness. TCP is an unencrypted byte stream, not a message protocol. Use HTTPS for REST services and do not send credentials over raw TCP on an untrusted network.
local Tcp = require("demi.network.tcp")
local pending, connection, stage
function on_start()
local err
pending, err = Tcp.connect("127.0.0.1", 9000, {timeout_ms = 5000})
assert(pending, err)
stage = "connect"
end
function on_update(dt)
if not pending or not pending:done() then return end
local response = pending:response()
pending = nil
if not response.ok then
if connection then connection:close() end
error(response.error)
end
if stage == "connect" then
connection = response.connection
pending = connection:write("hello\n", {timeout_ms = 5000})
stage = "write"
elseif stage == "write" then
pending = connection:read(4096, {timeout_ms = 5000})
stage = "read"
else
print(response.data) -- One available chunk, not necessarily a whole message.
connection:close()
end
end
Connect/read/write return operations with done(), response(), wait() and cancel(). Poll them from main-thread callbacks such as on_update. In a Task.fork worker, operation:wait(timeout_ms) waits on a native completion notification and returns the same response fields as response(), followed by a nil error. Omit the timeout to wait indefinitely; zero checks without blocking. Explicit timeouts must be integers from 0 through 2,147,483,647 milliseconds. Main VM waits are rejected, including calls from its coroutines and on completed operations. A wait timeout returns nil, "timeout"; worker cancellation returns nil, "cancelled". Neither cancels the operation. Native failures still return a response table; inspect ok and error. Responses contain ok, binary-safe data, error_code, error, and a connection for a successful connect. Create handles and connections in their owning VM; TCP userdata cannot be transferred between VMs. Closing a connection terminates its pending operations; open() reports its current state.
A read returns the bytes currently available up to the requested count. It does not promise a whole message or exactly that count. Buffer and decode your own framing. Only one read can be pending on a connection. Read cancellation/timeouts preserve unread buffered data. A write timeout closes the connection because some bytes may already have reached the peer.
Timeouts use milliseconds; zero disables the deadline. Native handles may remain readable after the service shuts down. OS DNS cancellation is best-effort, so shutdown can still wait for a system resolver call.
Android builds need the Internet permission for network access. Keep credentials and private data off unencrypted connections.