lua.wire
local wire=require("wire")
We use wire as the local name of this library. So we can, uhh, wire things together?
Wire provides multiple long lived lua states managed via standard c11 threads and mutexs with simple fifo message handling between threads.
May try and change to atomic fifos in the future to reduce the possibility of deadlocks but for now I think mutexs are the right choice. Partially due to the lua overhead and partially because this is not intended for sending millions of packets per second between tightly coupled threads.
A small compatibility library is needed for building for windows using mingw. Native windows builds might work if your compiler supports the C11 standard.
We are not doing anything clever, so beware of the following:
Only data is passed and that data must fit in messagepack, eg json like but binary strings are allowed.
Do not use lua tables that are both arrays and objects. Pick one or the other it will probably work but do not do it in case I have to break that in the future.
No userdata, no functions.
When in doubt, send a string, then unpack it in the thread. Binary strings will sometimes need to be escaped into lua code, eg \0 for null etc but this only happens to globals passed into a thread. Data is usually passed in message pack streams.
Any lua libs required in a thread must be thread safe or they will break in strange and uncomfortable ways. So check that any library you are using has luaopen_ functions do not try and initialize anything twice if opened in another thread.
This library does not reuse handles ( as this is intended for long lived threads ) and the number of handles is a hard limit set in the C code. Defaults to 4096 threads and 4096 fifos. Bump it at compile time if you need more.
You can reuse handles, but it must be self managed and must be done explicitly.
EG: You might want to shut down threads, clear out any pending data and then restart game logic threads using the same handles and names. This is possible but it is up to you to do it right. For instance the thread must be written to cooperate with the shut down.
lua.wire.active
active = wire.active(handle)
active = wire.active(thread)
If handle is nil then use wire.thread_handle
Check if this handle is an active thread, Eg the thread is running and should be dealing with memos.
returns false if not a thread or thread has halted or thread has been asked to halt.
returns true if thread exists and is running.
This function should be used in a running thread to check if that thread has been asked to halt.
lua.wire.data_to_table
table = wire.data_to_table( data )
Convert a data (string) into a lua table.
We currently use cmsgpack but this may change to another system and can not be relied upon. It is even possible that data may not be a string in future versions.
This is a local function for local people. It should not be used by you.
lua.wire.do_start
Wrapper code used in a new thread to set things up and handle errors etc.
lua.wire.fifo
fifo = wire.fifo(name)
fifo = wire.fifo(handle)
fifo = wire.fifo(opts)
Create or get a fifo. Fifos are not GC'd they must be created and destroyed explicitly.
When called with a name (string) we return a previously created fifo with that name or create a new one ( via house so can take some time ) This is the preferred may to safely create a named fifo.
When called with a handle (number) we return a previously created fifo with that handle ( via house so can take some time ).
We will raise an error if no fifo is found with that handle, so if this function returns you will always have a fifo.
When called with opts (table) we do the following based on the contents.
opts.handle=nil
The handle to use in creation ( so we may reuse an old handle ). If nil we will assign a new one.
opts.name="namedfifo"
The name to use in fifo creation. Usually the only value needed but the creation happens on this thread rather than house so this is a dangerous thing to do.
lua.wire.fifo.peek
bool = fifo:peek()
bool = thread:peek()
Peek and see if there is a memo to pull but do not pull it.
Note that a pull, even immediately after a successful peek has no guarantee to work.
If you wish to actually look at the contents of the memo then pull a memo and then push it back into the fifo. This will of course change the order of memos in the fifo.
lua.wire.fifo.pull
memo = fifo:pull()
memo = thread:pull()
Pull memo out of this fifo.
If this is a memo for us to deal with memo.state will be set to "got"
If this is a result we will return the sent memo with memo.result set to the reply data and the memo state will be changed to "result".
memo may be ignored for now and dealt with later by finding it in wire.memos.
This is useful if you are waiting for a specific reply and want to keep pulling until you find it.
Returns nil if there is no memo available.
May raise an error if we get a reply with an id that we do not recognize.
lua.wire.fifo.push
fifo:push(memo)
thread:push(memo)
Push memo into this fifo.
lua.wire.fifo.wait
fifo:wait(secs)
thread:wait(secs)
Wait upto the given number of seconds for a new memo to arrive in this fifo.
lua.wire.house_code
The message loop code we run in the house thread (-2) to synchronize process wide task names and handles.
lua.wire.memo
memo = wire.memo()
memo = wire.memo( { fifo=handle , data={} , on_result=callback } )
Create a memo and return it, if you pass in a new table then that is the table we will modify and return.
So you can either get a fresh memo and then fill it or pass in a new table with values already set and have that tables meta adjusted to make it a memo.
memo.fifo must be provided as it is the destination. May be a fifo/thread (table) or a handle (number).
memo.data is the data to send and must be a table.
memo.result must be nil and will be set to the result when we receive it.
memo.sender can be provided or it will be set to our handle ( which is wire.thread.us.handle ) It must be a handle and it is where replies are sent.
memo.id can be provided or it will be generated. When receiving a memo, we must use the sent id or will not be able to reply. This will be handled by fifo.pull so best to consider it an internal value and not mess with it.
The memo will also be placed in wire.memos for later processing by wire, it will remain there until wire.update deals with result/fail callbacks and removes it. You may keep your own memo reference or rely on the callbacks to remind you. If you create a memo that does not get sent and replied to then you will have to remove it manually.
memo.state will be set to "setup"
if memo.on_result is provided then it will be called during wire.update after we get a reply. Note that a result may be a fail if result.fail is set. on_result will be called like so:
memo.on_result(memo)
With or without a callback, the memos that had results will then be removed by wire.update as there is nothing else for wire to do.
FYI HAX TBH : memo.id is set to a light userdata of the memo tables pointer, this should be unique across multiple lua states and threads in the same process, for as long as the table stays on the stack. A future lua garbage collection system could break this by moving tables around in system memory but I believe this is currently "safe" as of 2026 in currently available versions of lua/luajit and if that changes I will fix it. There exists some reqritw of lua from c to java etc, these probably work but you might want to double check.
lua.wire.memo.remove
memo:remove()
Remove a memo from wire.memos.
memo.state will be changed to "removed" and it can no longer be found in wire.memos
This means it is no longer a live memo as far as wire is concerned.
You may of course keep this memo around if you need it.
lua.wire.memo.resolve
result = memo:resolve()
result = wire.memo(...):resolve()
If memo.state is "setup" then we memo:send() the memo. "setup" is the state a newly created memo will be in.
Wait for a result on our thread fifo ( wire.threads.us ) before returning.
Other memos may have been pulled and ignored for now. These memos can be found later in wire.memos
result is returned and can also be found in memo.result this should always be a table containing the result of the memo provided by the thread.
If a result was returned with result.fail set then the thread replied with a hard fail. A hard fail means the thread could not deal with your memo, so lua code broke, memo was invalid etc. This is a hard fail and will raise an error rather than return a result.
If the thread does not reply (probably a broken or missing task) then this will lock waiting on a result.
If the currently running thread is asked to halt then this will raise an error rather than return.
Note that this function ignores callbacks which are only called during wire.result
lua.wire.memo.send
memo:reply()
Reply to a memo, this uses the senders handle as a fifo without it needing to be wrapped in a full fifo/thread.
memo.state will be changed to "reply"
lua.wire.memo.status
memo:status(state)
Change status of memo, moving it between wire.memos[ memo.state ] caches.
This is a local function for local people. It should not be used by you.
lua.wire.prepare_start
start = wire.prepare_start( { start="..." , globals={} } )
opts.start=nil
opts.globals={}
opts.header="..."
opts.footer="..."
See wire.thread for documentation on these opts, we just pass the opts table down into this function to prepare the start string.
lua.wire.reference
it = wire.reference(name)
it = wire.reference(handle)
Return a fifo or thread if it exists, this is a way of wrapping a handle/name that you hope already exists but you are not 100% sure.
Will return nil if the name or handle does not exist.
We may talk to the house thread to check for the existence of name or handle, so this can block for a while before returning.
The returned value may be a fifo or a thread, it.is will be "thread" or "fifo" for the different results.
lua.wire.serialize
luastr = wire.serialize(tab)
luastr = wire.serialize(tab,{ indent="\t" , newline="\n" , errors=true })
Turn a table into a valid lua string that will recreate the table.
Optionally pass in an opts table that we will use during serialization so must be unique.
Possible options are.
indent=" "
Indention per level. set to "" for compact output.
newline="\n"
Line ending, set to "" for overly compact output.
errors=nil
Set this to true and we will raise errors rather than simply adding comments to the output. EG unknown types or recursion.
lua.wire.sleep
wire.sleep(secs)
Take a nap for at least the given amount of seconds, probably a little bit longer.
lua.wire.table_to_data
data = wire.table_to_data( table )
Convert a lua table into a data (string).
We currently use cmsgpack but this may change to another system and can not be relied upon. It is even possible that data may not be a string in future versions.
This is a local function for local people. It should not be used by you.
lua.wire.tasks
wire.tasks(name,count,code)
wire.tasks(name,0)
Start or halt the named tasks, we plan to be running count number of named tasks after calling this.
name is the type of task, eg "http". A fifo of that name will be created when creating tasks ( if one does not already exist ) for sending mwmo work request to and all tasks created will have their names prefixed with this name.
count is the number of tasks we want, call with 0 and we will halt all named tasks.
code is the string of lua code to run in each task, eg for http tasks it would be "require('wiretasks').http_code()" to run the wire.http_code function in each task. If count zero, this may be skipped.
We will try and reuse old tasks, if stopping and starting, but really all you should need to do is call at startup and then you are good to go until the process shuts down.
lua.wire.thread
thread = wire.thread(handle)
thread = wire.thread(name)
thread = wire.thread(opts)
Create or get a thread table. Threads are not GC'd they must be created and destroyed explicitly.
When called with a handle (number) we return a previously created thread with that handle.
When called with a name (string) we return a previously created thread with that name.
We will assert if no thread is found with that handle or name, so no need to check returns.
When called with opts (table) we do the following based on the contents.
opts.handle=nil
The handle to wrap or use in creation. If a valid handle we will simply wrap it with a thread table and return that new table.
opts.start=nil
Run this string as lua code in a new thread. Usually just a require and a call to a function in that module. eg "require('modname').funcname()"
opts.globals={}
Optional globals for the new thread. Will be serialized and become part of the start string. So binary data strings are not ideal but will work.
opts.preloadlibs=wire.preloadlibs
Must be a cfunction to be run in new thread. Use false to not preload any custom libs. When running under gamecake this is automatically filled in and makes all the internal modules available. If you want to use this module outside of gamecake ( possible but not overly tested ) then you may need to provide your own.
opts.header=...
opts.footer=...
internal setup so best not to change this. It is used by wire.prepare_start to wrap your start code string with helpful error handlers etc.
lua.wire.time
secs = wire.time()
Get the current TIME_UTC via a timespec_get and converted into a double.
lua.wire.timeres
secs = wire.timeres()
Removed since mingw does not like...
Get the TIME_UTC resolution via a timespec_getres and converted into a double.
lua.wire.update
wire.update()
Fetch and process memos, should be called at least once a second to deal with memo replies or callbacks will not fire in a reasonable time.
memo callbacks on_result will only be launched from this function. Note that a result may be a fail if result.fail is set.
After calling this all memos that got a reply will have been removed from the wire cache as we no longer need to deal with them.
lua.wire.wait
wire.wait(secs)
Take a nap for upto the given amount of seconds. We will wakeup whenever a new msg is sent to any fifo or when this time has passed.
lua.wire.wrap
it = wire.wrap(name,handle)
We know that this handle exists. So either return a previously cached fifo/thread or create a new one with the given name and return that.
The returned value may be a fifo or a thread, it.is will be "thread" or "fifo" for the different results.
Always returns a valid table.