658e4538aed7cbdfd3d8a33e19290b51eb39dddf
[god.git] / lib / god / timer.rb
blob658e4538aed7cbdfd3d8a33e19290b51eb39dddf
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, :pending_events, :conditions, :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       @pending_events = []
48       @conditions = []
49       @pending_mutex = Mutex.new
50       
51       @timer = Thread.new do
52         loop do
53           # applog(nil, :debug, "timer main loop, #{@events.size} events pending")
54           
55           begin
56             # pull in pending events
57             @pending_mutex.synchronize do
58               @pending_events.each { |e| @events << e }
59               @pending_events.clear
60             end
61             
62             @events.sort! { |x, y| x.at <=> y.at }
63             
64             # get the current time
65             t = Time.now.to_i
66             
67             # iterate over each event and trigger any that are due
68             triggered = []
69             
70             @events.each do |event|
71               if t >= event.at
72                 # trigger the event and mark it for removal
73                 self.trigger(event)
74                 triggered << event
75               else
76                 # events are ordered, so we can bail on first miss
77                 break
78               end
79             end
80             
81             # remove all triggered events
82             triggered.each do |event|
83               @conditions.delete(event.condition)
84               @events.delete(event)
85             end
86           rescue Exception => e
87             message = format("Unhandled exception (%s): %s\n%s",
88                              e.class, e.message, e.backtrace.join("\n"))
89             applog(nil, :fatal, message)
90           ensure
91             # sleep until next check
92             sleep INTERVAL
93           end
94         end
95       end
96     end
97     
98     # Create and register a new TimerEvent
99     #   +condition+ is the Condition
100     #   +delay+ is the number of seconds to delay (default: interval defined in condition)
101     #
102     # Returns nothing
103     def schedule(condition, delay = condition.interval)
104       applog(nil, :debug, "timer schedule #{condition} in #{delay} seconds")
105       unless @conditions.include?(condition)
106         @pending_mutex.synchronize do
107           @pending_events << TimerEvent.new(condition, delay)
108         end
109         @conditions << condition
110       end
111     end
112     
113     # Remove any TimerEvents for the given condition
114     #   +condition+ is the Condition
115     #
116     # Returns nothing
117     def unschedule(condition)
118       applog(nil, :debug, "timer unschedule #{condition}")
119       @conditions.delete(condition)
120     end
121     
122     # Trigger the event's condition to be evaluated
123     #   +event+ is the TimerEvent to trigger
124     #
125     # Returns nothing
126     def trigger(event)
127       applog(nil, :debug, "timer trigger #{event}")
128       Hub.trigger(event.condition, event.phase)
129     end
130     
131     # Join the timer thread
132     #
133     # Returns nothing
134     def join
135       @timer.join
136     end
137   end
138