more diagnostics
[god.git] / lib / god / timer.rb
blob51d8e69d71bd5d3d62127c922fa39174d5585ec7
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             # GC.start
93             # BleakHouseDiagnostic.snapshot
94             # p BleakHouseDiagnostic.logger.mem_usage
95             sleep INTERVAL
96           end
97         end
98       end
99     end
100     
101     # Create and register a new TimerEvent
102     #   +condition+ is the Condition
103     #   +delay+ is the number of seconds to delay (default: interval defined in condition)
104     #
105     # Returns nothing
106     def schedule(condition, delay = condition.interval)
107       applog(nil, :debug, "timer schedule #{condition} in #{delay} seconds")
108       unless @conditions.include?(condition)
109         @pending_mutex.synchronize do
110           @pending_events << TimerEvent.new(condition, delay)
111         end
112         @conditions << condition
113       end
114     end
115     
116     # Remove any TimerEvents for the given condition
117     #   +condition+ is the Condition
118     #
119     # Returns nothing
120     def unschedule(condition)
121       applog(nil, :debug, "timer unschedule #{condition}")
122       @conditions.delete(condition)
123     end
124     
125     # Trigger the event's condition to be evaluated
126     #   +event+ is the TimerEvent to trigger
127     #
128     # Returns nothing
129     def trigger(event)
130       applog(nil, :debug, "timer trigger #{event}")
131       Hub.trigger(event.condition, event.phase)
132     end
133     
134     # Join the timer thread
135     #
136     # Returns nothing
137     def join
138       @timer.join
139     end
140   end
141