2 # 1. Timer::Timer ---> Timer
3 # 2. timer id is now the object_id of the action
4 # 3. Timer resolution removed, we're always arbitrary precision now
5 # 4. I don't see any obvious races [not that i did see any in old impl, though]
6 # 5. We're tickless now, so no need to jerk start/stop
7 # 6. We should be pretty fast now, wrt old impl
8 # 7. reschedule/remove/block now accept nil as an action id (meaning "current")
9 # 8. repeatability is ignored for 0-period repeatable timers
10 # 9. configure() method superceeds reschedule() [the latter stays as compat]
15 # Timer handler, manage multiple Action objects, calling them when required.
16 # When the Timer is constructed, a new Thread is created to manage timed
17 # delays and run Actions.
19 # XXX: there is no way to stop the timer currently. I'm keeping it this way
20 # to weed out old Timer implementation legacy in rbot code. -jsn.
23 # class representing individual timed action
26 # Time when the Action should be called next
30 # start:: Time when the Action should be run for the first time.
31 # Repeatable Actions will be repeated after that, see
32 # :period. One-time Actions will not (obviously)
33 # Default: Time.now + :period
34 # period:: How often repeatable Action should be run, in seconds.
36 # blocked:: if true, Action starts as blocked (i.e. will stay dormant
38 # args:: Arguments to pass to the Action callback. Default: []
39 # repeat:: Should the Action be called repeatedly? Default: false
40 # code:: You can specify the Action body using &block, *or* using
43 def initialize(options = {}, &block)
52 debug("adding timer #{self} :period => #{opts[:period]}, :repeat => #{opts[:repeat].inspect}")
53 self.configure(opts, &block)
54 debug("added #{self}")
57 # Provides for on-the-fly reconfiguration of the Actions
58 # Accept the same arguments as the constructor
59 def configure(opts = {}, &block)
60 @period = opts[:period] if opts.include? :period
61 @blocked = opts[:blocked] if opts.include? :blocked
62 @repeat = opts[:repeat] if opts.include? :repeat
70 raise 'huh?? blockless action?' unless @block
71 if opts.include? :args
72 @args = Array === opts[:args] ? opts[:args] : [opts[:args]]
75 if opts[:start] and (Time === opts[:start])
76 self.next = opts[:start]
78 self.next = Time.now + (opts[:start] || @period)
82 # modify the Action period
83 def reschedule(period, &block)
84 self.configure(:period => period, &block)
87 # blocks an Action, so it won't be run
92 # unblocks a blocked Action
101 # calls the Action callback, resets .next to the Time of the next call,
102 # if the Action is repeatable.
103 def run(now = Time.now)
104 raise 'inappropriate time to run()' unless self.next && self.next <= now
108 rescue Exception => e
109 error "Timer action #{self.inspect}: block #{@block.inspect} failed!"
110 error e.pretty_inspect
111 debug e.backtrace.join("\n")
114 if @repeat && @period > 0
115 self.next = now + @period
122 # creates a new Timer and starts it.
124 self.extend(MonitorMixin)
125 @tick = self.new_cond
132 # creates and installs a new Action, repeatable by default.
133 # period:: Action period
134 # opts:: options for Action#new, see there
135 # block:: Action callback code
136 # returns the id of the created Action
137 def add(period, opts = {}, &block)
138 a = Action.new({:repeat => true, :period => period}.merge(opts), &block)
140 @actions[a.object_id] = a
146 # creates and installs a new Action, one-time by default.
147 # period:: Action delay
148 # opts:: options for Action#new, see there
149 # block:: Action callback code
150 # returns the id of the created Action
151 def add_once(period, opts = {}, &block)
152 self.add(period, {:repeat => false}.merge(opts), &block)
155 # blocks an existing Action
156 # aid:: Action id, obtained previously from add() or add_once()
158 debug "blocking #{aid}"
159 self.synchronize { self[aid].block }
162 # unblocks an existing blocked Action
163 # aid:: Action id, obtained previously from add() or add_once()
165 debug "unblocking #{aid}"
172 # removes an existing blocked Action
173 # aid:: Action id, obtained previously from add() or add_once()
176 @actions.delete(aid) # or raise "nonexistent action #{aid}"
180 alias :delete :remove
182 # Provides for on-the-fly reconfiguration of Actions
183 # aid:: Action id, obtained previously from add() or add_once()
184 # opts:: see Action#new
185 # block:: (optional) new Action callback code
186 def configure(aid, opts = {}, &block)
188 self[aid].configure(opts, &block)
193 # changes Action period
195 # period:: new period
196 # block:: (optional) new Action callback code
197 def reschedule(aid, period, &block)
198 self.configure(aid, :period => period, &block)
202 raise 'already started' if @thread
204 debug "starting timer #{self}"
205 @thread = Thread.new do
207 tmout = self.run_actions
208 break if tmout and tmout < 0
209 self.synchronize { @tick.wait(tmout) }
215 raise 'already stopped' unless @thread
216 debug "stopping timer #{self}..."
218 self.synchronize { @tick.signal }
219 @thread.join(60) or @thread.kill
220 debug "timer #{self} stopped"
228 raise "no current action" unless aid
229 raise "nonexistent action #{aid}" unless @actions.include? aid
233 def run_actions(now = Time.now)
235 @actions.keys.each do |k|
236 return -1 if @stopping
238 next if (!a) or a.blocked?
256 nxt = v if v and ((!nxt) or (v < nxt))
261 delta = 0 if delta < 0