----------------------------------------------------------- -- -- Agent-Focused Scheduler for Lua -- -- A beginning at a system that allows you to do simple -- scripting of agents in a way that is easy and works -- the way you expect it to (I hope). -- -- The approach is to keep a list of action/condition -- pairs, where each condition decides if a script should -- get some time to run, and the action is the script -- itself in the form of a coroutine. -- -- Whenever the condition part of one of these pairs is -- true, the scheduler removes the pair from the list -- and executes the script. If the script does not -- re-register itself, it will not be given any more time. -- -- When the script is ready to stop executing (say to wait -- for some event such as a period of time elapsed) -- then it should yield and pass a new condition function -- as the argument to the yield. Note that this action -- can be done inside some function designed for that -- purpose. (See the 'wait' function in the example below). -- If the script ends without yielding or yields with no -- argument, it will not be added back to the scheduling -- system, and so will not continue executing. -- -- Advantages -- -- You don't have to think in a event-driven manner to -- write scripts for this system. Just write the script, -- certain functions will cause the script to yield and -- be rescheduled at an appropriate time. -- -- Disadvantages -- -- Still have the 'C-boundry' crossing problem. In many -- situations this will not matter, as primitive calls -- should be atomic. Yields should only happen in places -- where you've defined functions that have them. You -- may still have problems with callbacks and metamethods -- though. ----------------------------------------------------------- local public,private = {},{} ----------------------------------------------------------- -- The approach here is to keep a list of condition/action -- pairs, where the condition decides if a script should -- run for a while and an action is the script packaged -- in a coroutine object. -- -- Of course we need somewhere to put the list of pairs: -- private.actions = {} ---------------------------------------------------------- -- asScript (private) -- Scripts should be coroutines. This function turns -- the object passed in the script, presumably a -- coroutine or function into a coroutine object. You -- may want to modify the last rules for tables and -- the default rule (last two options here). -- function private.asScript(obj) if type(obj) == "thread" then return obj elseif type(obj) == "function" then return coroutine.create(obj) elseif type(obj) == "table" then return coroutine.create(function() obj() end) else return coroutine.create(function() end) end end ---------------------------------------------------------- -- register (public) -- Place a condition/script pair into the action -- table. Probably you'll often want to use start -- and start the script with an appropriate -- execution condition. -- -- Note that conditions can be either a function or the -- value true. -- function public.register(condition, script) table.insert( private.actions, {cond=condition, script=private.asScript(script)} ) end ---------------------------------------------------------- -- start (public) -- Insert script into to system so it runs in the -- next available slot. (Shortcut for register). -- function public.start(script) public.register(true,script) end ---------------------------------------------------------- -- activate (private) -- Activate a script. Uses the return values from -- the resume function to decide if the script should -- be put back into the action list. -- function private.activate(script) local status,nextCond = coroutine.resume(script) if status then if nextCond ~= nil then public.register(nextCond, script) end end end ---------------------------------------------------------- -- step (private) -- Pick a script to do and execute it. Return true if a -- script was executed and false if it wasn't. -- function private.step() for i, rule in ipairs(private.actions) do if rule.cond==true or rule.cond() then table.remove(private.actions,i) private.activate(rule.script) return true end end return false end ---------------------------------------------------------- -- run (public) -- Begins executing the various scripts. Stops when there -- are no more scripts waiting to execute. This is a good -- place to add some rules to handle 'dead' scripts or -- to do some other background processing. You may also -- choose to expose the step function above and use that -- to give you finer control over script execution. -- function public.run() while table.getn(private.actions) > 0 do private.step() end end ---------------------------------------------------------- -- Expose the public functions -- simsch = public ---------------------------------------------------------- -- TEST CODE ---------------------------------------------------------- -- Wait is a function that tells the scheduler to -- wait for some number of seconds before restarting -- the script. function wait(seconds, start) local t = (start or os.clock()) + seconds coroutine.yield( function() return os.clock() >= t end) end -- Two pretty much identical functions that differ -- only in the label they print and the amount of -- time they wait between prints. simsch.start( function () for i=1,10 do wait(1) print("One ", i) end end) simsch.start( function () for i=1,10 do wait(1.6) print("Two ", i) end end) -- Start it up