borgend/dreamtime.py

changeset 101
3068b0de12ee
parent 100
b141bed9e718
child 106
a7bdc239ef62
equal deleted inserted replaced
100:b141bed9e718 101:3068b0de12ee
8 import time 8 import time
9 import threading 9 import threading
10 import weakref 10 import weakref
11 import datetime 11 import datetime
12 import logging 12 import logging
13 import math
13 14
14 logger=logging.getLogger(__name__) 15 logger=logging.getLogger(__name__)
15 16
16 _dreamtime_monitor=None 17 _dreamtime_monitor=None
17 18
24 class Snapshot: 25 class Snapshot:
25 def __init__(self): 26 def __init__(self):
26 self._monotonic=None 27 self._monotonic=None
27 self._realtime=None 28 self._realtime=None
28 self._dreamtime=None 29 self._dreamtime=None
30 self._sleeping=None
29 31
30 def monotonic(self): 32 def monotonic(self):
31 if self._monotonic is None: 33 if self._monotonic is None:
32 self._monotonic=time.monotonic() 34 self._monotonic=time.monotonic()
33 return self._monotonic 35 return self._monotonic
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

mercurial