-- our coroutine-with-mailbox thing. badly needs renaming. declare("current_threadinfo") active_threads = {} function CreateThread(threadfunc, ...) local fsm = coroutine.create(threadfunc) local threadinfo = {fsm=fsm, queued_triggers={}} local prev_threadinfo = current_threadinfo current_threadinfo = threadinfo local ok, err = coroutine.resume(fsm, threadinfo, unpack(arg)) -- start the fsm current_threadinfo = prev_threadinfo if not ok then log.error("CreateThread(): "..err) return nil, err end if coroutine.status(fsm) ~= "dead" then active_threads[threadinfo] = true end return threadinfo end function trigger_fsm(threadinfo,eventname, topts) if threadinfo == nil then log.error("nil threadinfo.") return end local fsm = threadinfo.fsm if fsm == nil or type(fsm) ~= "thread" then log.error("trigger_fsm(...,"..eventname..","..StrTable(topts).. ") on non-thread?!\nthreadinfo="..StrTable(threadinfo)) return end if coroutine.status(fsm) == "running" then log.error('trigger_fsm: trying to resume already running coroutine!') return end if coroutine.status(fsm) ~= "suspended" then return end local prev_threadinfo = current_threadinfo current_threadinfo = threadinfo local success, err = coroutine.resume(fsm, eventname, topts) current_threadinfo = prev_threadinfo if coroutine.status(fsm) == "dead" then active_threads[threadinfo] = nil end if not success then log.error("trigger_fsm("..tostring(eventname).."): "..err) if threadinfo.abort then threadinfo:abort() end return end return err end function get_queued_triggers(threadinfo) if table.getn(threadinfo.queued_triggers) > 0 then local t = table.remove(threadinfo.queued_triggers, 1) return t[1], t[2] end end function check_for_queued_triggers(threadinfo, triggers) local k,v, k2, v2 for k,v in ipairs(threadinfo.queued_triggers) do for k2,v2 in ipairs(triggers) do if v[1] == v2 then table.remove(threadinfo.queued_triggers, k) return v[1], v[2] end end end end function check_for_queued_trigger(threadinfo, trigger) local k,v for k,v in ipairs(threadinfo.queued_triggers) do if v[1] == trigger then table.remove(threadinfo.queued_triggers, k) return v[1], v[2] end end end -- returns the trigger options function WaitForSingleTrigger(threadinfo, trigger) -- log.print("Waiting for '"..trigger.."' trigger.") if not threadinfo then log.error('nil thread info') return end local incomingtrigger, topts = check_for_queued_trigger(threadinfo, trigger) if incomingtrigger then return topts end while true do incomingtrigger, topts = coroutine.yield() if incomingtrigger == trigger then break end log.print("WaitForSingleTrigger NOTE: queuing "..incomingtrigger..", waiting for "..trigger) -- if incomingtrigger == "LogOff" then -- log.error('XXX ERROR: queueing a logoff while waiting for a '..trigger) -- end table.insert(threadinfo.queued_triggers, {incomingtrigger, topts} ) end return topts end -- returns which trigger and its options function WaitForMultipleTriggers(threadinfo, triggers) -- log.print("Waiting for multiple triggers: {") -- for k,v in pairs(triggers) do log.print(" '"..v.."'") end -- log.print(" }") local incomingtrigger, topts = check_for_queued_triggers(threadinfo, triggers) if incomingtrigger then return incomingtrigger, topts end while true do incomingtrigger, topts = coroutine.yield() for k,v in pairs(triggers) do if v == incomingtrigger then return incomingtrigger, topts end end log.print("WaitForMultipleTriggers NOTE: queuing "..incomingtrigger..", waiting for "..StrTable(triggers)) -- if incomingtrigger == "LogOff" then -- printtable(triggers) -- log.error(debug.traceback('XXX ERROR: queueing a logoff while waiting for above triggers')) -- end table.insert(threadinfo.queued_triggers, {incomingtrigger, topts} ) end return incomingtrigger, topts end -- returns which trigger and its options function WaitForAnyTrigger(threadinfo) -- log.print("Waiting for any trigger.") local incomingtrigger, topts = get_queued_triggers(threadinfo) if not incomingtrigger then incomingtrigger, topts = coroutine.yield() end -- log.print("WaitForAnyTrigger: Got "..incomingtrigger) return incomingtrigger, topts end local condition_waits = {} function cond_wait(cond) condition_waits[cond] = condition_waits[cond] or {} table.insert(condition_waits[cond], current_threadinfo) WaitForSingleTrigger(current_threadinfo, cond) end function cond_signal(cond) local t = condition_waits[cond] condition_waits[cond] = nil if not t then return end for _,fsm in ipairs(t) do trigger_fsm(fsm, cond) end end