35 def realtime(self): |
37 def realtime(self): |
36 if self._realtime is None: |
38 if self._realtime is None: |
37 self._realtime=time.time() |
39 self._realtime=time.time() |
38 return self._realtime |
40 return self._realtime |
39 |
41 |
40 def dreamtime(self): |
42 def dreamtime_sleeping(self): |
41 if self._dreamtime is None: |
43 if self._dreamtime is None: |
42 if _dreamtime_monitor: |
44 if _dreamtime_monitor: |
43 self._dreamtime=_dreamtime_monitor.dreamtime(snapshot=self) |
45 self._dreamtime, self._sleeping=_dreamtime_monitor.dreamtime_sleeping(snapshot=self) |
44 else: |
46 else: |
45 self._dreamtime=self.monotonic() |
47 self._dreamtime, self._sleeping=self.monotonic(), False |
46 return self._dreamtime |
48 return self._dreamtime, self._sleeping |
|
49 |
|
50 def dreamtime(self): |
|
51 time, _=self.dreamtime_sleeping() |
|
52 return time |
|
53 |
|
54 def sleeping(self): |
|
55 _, sleeping=self.dreamtime_sleeping() |
|
56 return sleeping |
47 |
57 |
48 # The main Time class, for time advancing in various paces |
58 # The main Time class, for time advancing in various paces |
49 class Time: |
59 class Time: |
50 def __init__(self, when): |
60 def __init__(self, when): |
51 self._value=when |
61 self._value=when |
82 return cls(other._value+seconds) |
92 return cls(other._value+seconds) |
83 else: |
93 else: |
84 return cls.from_monotonic(other._monotonic(snapshot)+seconds, |
94 return cls.from_monotonic(other._monotonic(snapshot)+seconds, |
85 snapshot) |
95 snapshot) |
86 |
96 |
87 def datetime(self): |
97 def datetime(self, snapshot=Snapshot()): |
88 return datetime.datetime.fromtimestamp(self.realtime()) |
98 return datetime.datetime.fromtimestamp(self._realtime(snapshot)) |
89 |
99 |
90 def seconds_to(self): |
100 def seconds_to(self, snapshot=Snapshot()): |
91 return self._value-self._now(Snapshot()) |
101 return self._value-self._now(snapshot) |
92 |
102 |
93 def isoformat(self): |
103 def isoformat(self): |
94 return self.datetime().isoformat() |
104 return self.datetime().isoformat() |
95 |
105 |
96 def realtime(self): |
106 def realtime(self, snapshot=Snapshot()): |
97 return self._realtime(Snapshot()) |
107 return self._realtime(snapshot) |
98 |
108 |
99 def monotonic(self): |
109 def monotonic(self, snapshot=Snapshot()): |
100 return self._monotonic(Snapshot()) |
110 return self._monotonic(snapshot) |
|
111 |
|
112 # Counted from the monotonic epoch, how far is this event? Usually should |
|
113 # equal self.monotonic(), but Dreamtime can be stopped by system sleep, |
|
114 # and will return ∞ (math.inf). |
|
115 def horizon(self, snapshot=Snapshot()): |
|
116 return self._monotonic(snapshot) |
101 |
117 |
102 def __compare(self, other, fn): |
118 def __compare(self, other, fn): |
103 if isinstance(other, self.__class__): |
119 if isinstance(other, self.__class__): |
104 return fn(self._value, other._value) |
120 return fn(self._value, other._value) |
105 else: |
121 else: |
141 |
157 |
142 @staticmethod |
158 @staticmethod |
143 def _now(snapshot): |
159 def _now(snapshot): |
144 return snapshot.monotonic() |
160 return snapshot.monotonic() |
145 |
161 |
|
162 # class Infinity(Time): |
|
163 # def __init__(self): |
|
164 # super().__init__(math.inf) |
|
165 # |
|
166 # def _realtime(self, snapshot): |
|
167 # return math.inf |
|
168 # |
|
169 # def _monotonic(self, snapshot): |
|
170 # return math.inf |
|
171 # |
|
172 # @staticmethod |
|
173 # def _now(snapshot): |
|
174 # return 0 |
|
175 |
146 class DreamTime(Time): |
176 class DreamTime(Time): |
147 def _realtime(self, snapshot): |
177 def _realtime(self, snapshot): |
148 return self._value+(snapshot.realtime()-snapshot.dreamtime()) |
178 return self._value+(snapshot.realtime()-snapshot.dreamtime()) |
149 |
179 |
150 # Important: monotonic is "static" within a wakeup period |
180 # Important: monotonic is "static" within a wakeup period |
151 # and does not need to call time.monotonic(), as it gets compared |
181 # and does not need to call time.monotonic(), as it gets compared |
152 # to a specific time.monotonic() realisation |
182 # to a specific time.monotonic() realisation |
153 def _monotonic(self, snapshot): |
183 def _monotonic(self, snapshot): |
154 return self._value+(snapshot.monotonic()-snapshot.dreamtime()) |
184 return self._value+(snapshot.monotonic()-snapshot.dreamtime()) |
155 |
185 |
|
186 def horizon(self, snapshot=Snapshot()): |
|
187 if snapshot.sleeping(): |
|
188 return math.inf |
|
189 else: |
|
190 return self._monotonic(snapshot) |
|
191 |
156 @staticmethod |
192 @staticmethod |
157 def _now(snapshot): |
193 def _now(snapshot): |
158 return snapshot.dreamtime() |
194 return snapshot.dreamtime() |
159 |
195 |
160 |
196 |
161 if platform.system()=='Darwin': |
197 if platform.system()=='Darwin': |
162 |
198 |
163 import Foundation |
199 import Foundation |
164 import AppKit |
200 import AppKit |
|
201 |
|
202 def do_callbacks(callbacks, woke): |
|
203 for callback in callbacks.values(): |
|
204 try: |
|
205 callback(woke) |
|
206 except Exception: |
|
207 logger.exception("Error in sleep/wake notification callback") |
165 |
208 |
166 # |
209 # |
167 # Wake up / sleep handling |
210 # Wake up / sleep handling |
168 # |
211 # |
169 |
212 |
179 |
222 |
180 return self |
223 return self |
181 |
224 |
182 def handleSleepNotification_(self, aNotification): |
225 def handleSleepNotification_(self, aNotification): |
183 logger.info("System going to sleep") |
226 logger.info("System going to sleep") |
184 now=time.monotonic() |
227 try: |
185 with self.__lock: |
228 now=time.monotonic() |
186 self.__sleeptime=now |
229 with self.__lock: |
|
230 self.__sleeptime=now |
|
231 callbacks=self.__callbacks.copy() |
|
232 do_callbacks(callbacks, False) |
|
233 except: |
|
234 logger.exception("Bug in sleep handler") |
187 |
235 |
188 def handleWakeNotification_(self, aNotification): |
236 def handleWakeNotification_(self, aNotification): |
189 logger.info("System waking up from sleep") |
237 logger.info("System waking up from sleep") |
190 try: |
238 try: |
191 now=time.monotonic() |
239 now=time.monotonic() |
194 slept=max(0, now-self.__sleeptime) |
242 slept=max(0, now-self.__sleeptime) |
195 logger.info("Slept %f seconds" % slept) |
243 logger.info("Slept %f seconds" % slept) |
196 self.__slept=self.__slept+slept |
244 self.__slept=self.__slept+slept |
197 self.__sleeptime=None |
245 self.__sleeptime=None |
198 callbacks=self.__callbacks.copy() |
246 callbacks=self.__callbacks.copy() |
|
247 do_callbacks(callbacks, True) |
199 except: |
248 except: |
200 logger.exception("Bug in wakeup handler") |
249 logger.exception("Bug in wakeup handler") |
201 |
250 |
202 for callback in callbacks.values(): |
|
203 try: |
|
204 callback() |
|
205 except Exception: |
|
206 logger.exception("Error in wake notification callback") |
|
207 |
|
208 # Return dreamtime |
251 # Return dreamtime |
209 def dreamtime(self, snapshot=Snapshot()): |
252 def dreamtime_sleeping(self, snapshot=Snapshot()): |
|
253 sleeping=False |
210 with self.__lock: |
254 with self.__lock: |
211 # macOS "sleep" signals / status are complete bollocks: at least |
255 # macOS "sleep" signals / status are complete bollocks: at least |
212 # when plugged in, the system is actually sometimes running all |
256 # when plugged in, the system is actually sometimes running all |
213 # night with the lid closed and sleepNotification delivered. |
257 # night with the lid closed and sleepNotification delivered. |
214 # Therefore, we need our timers to work in a sane manner when |
258 # Therefore, we need our timers to work in a sane manner when |
215 # we should be sleeping! |
259 # we should be sleeping! |
216 if self.__sleeptime is not None: |
260 if self.__sleeptime is not None: |
217 now_monotonic=self.__sleeptime |
261 now_monotonic=self.__sleeptime |
|
262 sleeping=True |
218 else: |
263 else: |
219 now_monotonic=snapshot.monotonic() |
264 now_monotonic=snapshot.monotonic() |
220 now_dreamtime=max(0, now_monotonic-self.__epoch-self.__slept) |
265 now_dreamtime=max(0, now_monotonic-self.__epoch-self.__slept) |
221 return now_dreamtime |
266 return now_dreamtime, sleeping |
222 |
267 |
223 # Weirdo (Py)ObjC naming to stop it form choking up |
268 # Weirdo (Py)ObjC naming to stop it form choking up |
224 def addForObj_aCallback_(self, obj, callback): |
269 def addForObj_aCallback_(self, obj, callback): |
225 with self.__lock: |
270 with self.__lock: |
226 self.__callbacks[obj]=callback |
271 self.__callbacks[obj]=callback |