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
137 # Returns the id of the created Action
138 def add(period, opts = {}, &block)
139 a = Action.new({:repeat => true, :period => period}.merge(opts), &block)
141 @actions[a.object_id] = a
147 # Creates and installs a new Action, one-time by default.
148 # _period_:: Action delay
149 # _opts_:: options for Action#new, see there
150 # _block_:: Action callback code
152 # Returns the id of the created Action
153 def add_once(period, opts = {}, &block)
154 self.add(period, {:repeat => false}.merge(opts), &block)
157 # blocks an existing Action
158 # _aid_:: Action id, obtained previously from add() or add_once()
160 debug "blocking #{aid}"
161 self.synchronize { self[aid].block }
164 # unblocks an existing blocked Action
165 # _aid_:: Action id, obtained previously from add() or add_once()
167 debug "unblocking #{aid}"
174 # removes an existing blocked Action
175 # _aid_:: Action id, obtained previously from add() or add_once()
178 @actions.delete(aid) # or raise "nonexistent action #{aid}"
182 alias :delete :remove
184 # Provides for on-the-fly reconfiguration of Actions
185 # _aid_:: Action id, obtained previously from add() or add_once()
186 # _opts_:: see Action#new
187 # _block_:: (optional) new Action callback code
188 def configure(aid, opts = {}, &block)
190 self[aid].configure(opts, &block)
195 # changes Action period
197 # _period_:: new period
198 # _block_:: (optional) new Action callback code
199 def reschedule(aid, period, &block)
200 self.configure(aid, :period => period, &block)
204 raise 'already started' if @thread
206 debug "starting timer #{self}"
207 @thread = Thread.new do
209 tmout = self.run_actions
210 break if tmout and tmout < 0
211 self.synchronize { @tick.wait(tmout) }
217 raise 'already stopped' unless @thread
218 debug "stopping timer #{self}..."
220 self.synchronize { @tick.signal }
221 @thread.join(60) or @thread.kill
222 debug "timer #{self} stopped"
230 raise "no current action" unless aid
231 raise "nonexistent action #{aid}" unless @actions.include? aid
235 def run_actions(now = Time.now)
237 @actions.keys.each do |k|
238 return -1 if @stopping
240 next if (!a) or a.blocked?
258 nxt = v if v and ((!nxt) or (v < nxt))
263 delta = 0 if delta < 0