prevent carry-over conditions
[god.git] / lib / god / timer.rb
bloba46d4dd8d625d3a6a2949e8d94388a831b5747e4
1 module God
2   
3   class TimerEvent
4     attr_accessor :condition, :at, :phase
5     
6     # Instantiate a new TimerEvent that will be triggered after the specified delay
7     #   +condition+ is the Condition
8     #   +delay+ is the number of seconds from now at which to trigger
9     #
10     # Returns TimerEvent
11     def initialize(condition, delay)
12       self.condition = condition
13       self.phase = condition.watch.phase
14       
15       now = (Time.now.to_f * 4).round / 4.0
16       self.at = now + delay
17     end
18   end
19   
20   class Timer
21     INTERVAL = 0.25
22     
23     attr_reader :events, :timer
24     
25     @@timer = nil
26     
27     # Get the singleton Timer
28     #
29     # Returns Timer
30     def self.get
31       @@timer ||= Timer.new
32     end
33     
34     # Reset the singleton Timer so the next call to Timer.get will
35     # create a new one
36     #
37     # Returns nothing
38     def self.reset
39       @@timer = nil
40     end
41     
42     # Instantiate a new Timer and start the scheduler loop to handle events
43     #
44     # Returns Timer
45     def initialize
46       @events = []
47       @conditions = []
48       @mutex = Monitor.new
49       
50       @timer = Thread.new do
51         loop do
52           begin
53             # get the current time
54             t = Time.now.to_i
55             
56             # iterate over each event and trigger any that are due
57             @mutex.synchronize do
58               triggered = []
59               
60               @events.each do |event|
61                 if t >= event.at
62                   # trigger the event and mark it for removal
63                   self.trigger(event)
64                   triggered << event
65                 else
66                   # events are ordered, so we can bail on first miss
67                   break
68                 end
69               end
70               
71               # remove all triggered events
72               triggered.each do |event|
73                 @conditions.delete(event.condition)
74                 @events.delete(event)
75               end
76             end
77           rescue Exception => e
78             message = format("Unhandled exception (%s): %s\n%s",
79                              e.class, e.message, e.backtrace.join("\n"))
80             applog(nil, :fatal, message)
81           ensure
82             # sleep until next check
83             sleep INTERVAL
84           end
85         end
86       end
87     end
88     
89     # Create and register a new TimerEvent
90     #   +condition+ is the Condition
91     #   +delay+ is the number of seconds to delay (default: interval defined in condition)
92     #
93     # Returns nothing
94     def schedule(condition, delay = condition.interval)
95       @mutex.synchronize do
96         unless @conditions.include?(condition)
97           @events << TimerEvent.new(condition, delay)
98           @conditions << condition
99           @events.sort! { |x, y| x.at <=> y.at }
100         end
101       end
102     end
103     
104     # Remove any TimerEvents for the given condition
105     #   +condition+ is the Condition
106     #
107     # Returns nothing
108     def unschedule(condition)
109       @mutex.synchronize do
110         @conditions.delete(condition)
111         @events.reject! { |x| x.condition == condition }
112       end
113     end
114     
115     # Trigger the event's condition to be evaluated
116     #   +event+ is the TimerEvent to trigger
117     #
118     # Returns nothing
119     def trigger(event)
120       Hub.trigger(event.condition, event.phase)
121     end
122     
123     # Join the timer thread
124     #
125     # Returns nothing
126     def join
127       @timer.join
128     end
129   end
130