[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Scripts with pause
- From: Thomas Wrensch <twrensch@...>
- Date: Thu, 4 Aug 2005 03:03:01 +0000 (UTC)
On Thu, 4 Aug 2005, Russell Y. Webb wrote:
Yes, I was thinking on use coroutines to do this, but
perhaps there is some way of implement a script that
has sequential tasks, and some taks take a little to
complete.
I can't think of a way to it without coroutines that don't require
rewriting the script code in an unnatural way. Any suggestions?
Coroutines are VERY useful for a lot of different purposes, but they
are not obvious to use. To help the original poster of the thread I put
together a quick tutorial on building a scheduler using coroutines.
If they (and maybe others?) give me feedback I can clean it up and put it
on the Wiki.
Tutorial is attached.
- Tom
twrensch@sdf.lonestar.org
SDF Public Access UNIX System - http://sdf.lonestar.org
-- A Quick Coroutine/Scheduling "recipe"
--
-- By Tom Wrensch (twrensch@freeshell.org)
--
-- Coroutines are indeed the answer to this particular problem,
-- but using coroutines is non-trivial. This bit of text is
-- intended to both get you up to speed and solve the problem
-- you gave in your E-mail (I hope).
-- I'm going to keep things as dirt simple as possible
-- so no fancy object system and the minimal support needed to
-- both illustrate the technique and solve your problem.
-- In this example we are going to use the idea of Game Time
-- to keep track of when everything happens. We will use a
-- function waitfor(time) to wait for some number of game
-- clock 'ticks'. We'll start the clock at 1:
GameTime = 1.
-- Next we need to build some actors to play around in the
-- world. Actors in the world will have names, will be able to
-- walk, sleep, and shoot.
Actor = {}
function Actor:walk(dist)
print(GameTime, self.name, "has started walking")
waitfor(dist*1) -- Assume 1 time unit per distance unit
print(GameTime, self.name, "has finished walking")
end
function Actor:sleep(time)
print(GameTime, self.name, "Has gone to sleep")
waitfor(time)
print(GameTime, self.name, "has finished sleeping")
end
function Actor:shoot(what)
print(GameTime, self.name, "has shot "..what.name)
end
function newActor(name)
local newobj = {}
newobj.name = name
setmetatable(newobj,{__index=Actor})
return newobj
end
-- The key to making the sleep and walk methods above work is
-- the waitfor(time) function. This function will assume it is
-- running in a coroutine and use yield to stop running and
-- pass necessary information along about when it should be
-- woken up again.
function waitfor(time)
coroutine.yield(time)
end
-- Now for the hard part. We have to create a central scheduler
-- whose job it is to make sure everyone runs when they should.
-- This is a lot easier than it sounds, but still is not simple.
Scheduler = {}
-- The scheduler will keep a list of coroutines that are waiting
-- for particular clock ticks. We will initialized this list with
-- an init() method and make a method that lets us add new
-- coroutines to the list. We will also add a field to keep track
-- of the largest tick in the ticktable, so we know when to stop.
function Scheduler:init()
self.ticktable = {}
self.lasttick = 0
end
function Scheduler:add(fun)
self:resumeAtTick(coroutine.create(fun),GameTime)
end
-- Now for the guts of the scheduler. We need a method to add a coroutine
-- to the schedule to be executed at a particular tick (in fact we assumed
-- such a method existed in the add(fun) method above).
function Scheduler:resumeAtTick(corout, tick)
local ticklist = self.ticktable[tick]
if ticklist == nil then -- If no list for this tick, add one
ticklist = {}
self.ticktable[tick] = ticklist
end
table.insert(ticklist, corout)
self.lasttick = math.max(self.lasttick, tick) -- update largest tick
end
-- And we need a function to actually run through the tick list. I will
-- break this up into two functions. The main one, run(), will pick the
-- next coroutine to run. The second, runOne(corout) will take that
-- coroutine, run it, and handle any returned values from a yield that
-- might cause it to be rescheduled.
function Scheduler:runOne(corout)
local flag,count = coroutine.resume(corout)
if flag and count then -- no errors and wants to restart
self:resumeAtTick(corout,GameTime+count)
end
end
function Scheduler:run()
while GameTime <= self.lasttick do
local ticklist = self.ticktable[GameTime]
if ticklist then -- ticklist is not nil
while table.getn(ticklist) > 0 do
self:runOne( table.remove(ticklist, 1) )
end
end
GameTime = GameTime + 1
end
print("Game Over!")
end
-- Nearly there now. We have to create a test using our new scheduler
-- and game system. The design will require that each actor have their
-- own script.
Mike = newActor("Mike")
Betty = newActor("Betty")
function MikeScript()
Mike:walk(3)
Mike:shoot(Betty)
Mike:sleep(3)
end
function BettyScript()
Betty:sleep(3)
Betty:walk(2)
for i=1,5 do
Betty:shoot(Mike)
end
end
-- Finally we start things up and see what happens.
Scheduler:init()
Scheduler:add(MikeScript)
Scheduler:add(BettyScript)
Scheduler:run()
-- It isn't hard to change things so the yield returns more information
-- that can be used by the scheduler. One trick is to return a function
-- that must evaluate to true to cause the coroutine to be rescheduled.
-- That can easily be used to implement the game time technique shown
-- here, but can also be used to handle more complicated situations
-- (like actors waking up when they are shot).
-- Good luck.