1
1
from __future__ import division , print_function
2
2
import time
3
3
import platform
4
+ import queue
5
+ import json
4
6
5
7
from ._notebook_helpers import _isnotebook
6
8
7
9
if _isnotebook :
8
10
import IPython
11
+ import ipykernel
12
+ ws_queue = queue .Queue ()
9
13
10
14
# Unresolved bug: rate(X) yields only about 0.8X iterations per second.
11
15
31
35
32
36
##Possible way to get one-millisecond accuracy in sleep on Windows:
33
37
##http://msdn.microsoft.com/en-us/library/windows/desktop/ms686298(v=vs.85).aspx
34
- ##When your program starts, the Windows system's timer resolution has a
35
- ##seemingly random value that depends on which programs are running
36
- ##(and apparently, which programs were run and then exited).
37
- ##Common values for the resolution are 15 ms and 1 ms, but a range
38
- ##of values is possible (use timeGetDevCaps to determine this range).
39
- ##AFAICT, calling timeBeginPeriod() changes the system timer resolution
40
- ##for every call you make to a Win32 function with a timeout
41
- ##(e.g., MsgWaitForMultipleObjects() works exactly the same as Sleep()
42
- ##with respect to the timeout) and every call that every other application
38
+ ##When your program starts, the Windows system's timer resolution has a
39
+ ##seemingly random value that depends on which programs are running
40
+ ##(and apparently, which programs were run and then exited).
41
+ ##Common values for the resolution are 15 ms and 1 ms, but a range
42
+ ##of values is possible (use timeGetDevCaps to determine this range).
43
+ ##AFAICT, calling timeBeginPeriod() changes the system timer resolution
44
+ ##for every call you make to a Win32 function with a timeout
45
+ ##(e.g., MsgWaitForMultipleObjects() works exactly the same as Sleep()
46
+ ##with respect to the timeout) and every call that every other application
43
47
##in the system makes to a Win32 function with a timeout.
44
48
45
49
def _sleep (dt ):
@@ -58,18 +62,18 @@ def _sleep(dt):
58
62
tend = _clock ()+ dt
59
63
while _clock () < tend :
60
64
pass
61
-
65
+
62
66
class simulateDelay :
63
67
"""
64
- Simulate rendering/compute times.. with an average value of delayAvg with
68
+ Simulate rendering/compute times.. with an average value of delayAvg with
65
69
a variance of something like delaySigma**2.
66
70
"""
67
71
68
72
def __init__ (self , delayAvg = 0.001 , delaySigma = 0.0001 ):
69
73
self .delayAvg = delayAvg
70
74
self .delaySigma = delaySigma
71
75
self .callTimes = []
72
-
76
+
73
77
def __call__ (self ):
74
78
self .callTimes .append (_clock ())
75
79
@@ -94,9 +98,9 @@ def initialize(self):
94
98
self .whenToRender = []
95
99
for i in range (MAX_RENDERS + 2 ):
96
100
self .whenToRender .append (0 )
97
- self .renderIndex = 0
101
+ self .renderIndex = 0
98
102
self .rateCount = 0 # counts calls to rate in a 1-second cycle (reset to 0 every second)
99
-
103
+
100
104
def callInteract (self ):
101
105
t = _clock ()
102
106
self .interactFunc ()
@@ -148,7 +152,7 @@ def buildStrategy(self, rate):
148
152
149
153
# Prepare the self.renderIndex array of indices for when to do renders:
150
154
self .distributeRenders (M , N )
151
-
155
+
152
156
# M = self.rateCalls = number of calls to rate/second
153
157
# N = number of renders/second
154
158
# callTime = time spent in rate function (very small)
@@ -160,20 +164,20 @@ def buildStrategy(self, rate):
160
164
self .delay = (1.0 - N * R - self .renderWaits * self .interactionPeriod )/ M - self .callTime - U
161
165
## print("%1.4f %i %i %i %1.6f %1.6f %1.6f %1.6f" % (_clock(), M, N, self.renderWaits,
162
166
## self.userTime, self.callTime, self.delay, self.renderTime))
163
-
167
+
164
168
def __call__ (self , maxRate = 100 ):
165
169
#td.add('-------------------------')
166
170
if not self .initialized :
167
171
self .initialize ()
168
172
self .initialized = True
169
- calledTime = _clock ()
173
+ calledTime = _clock ()
170
174
if maxRate < 1 : raise ValueError ("rate value must be greater than or equal to 1" )
171
175
self .count += 1
172
176
if self .count == 1 : # first time rate has been called
173
177
self .callInteract ()
174
178
self .lastEndRate = _clock ()
175
179
return
176
-
180
+
177
181
dt = calledTime - self .lastEndRate # time spent in user code
178
182
nr = self .whenToRender [self .renderIndex ]
179
183
if self .count == 2 or (self .count == self .lastCount + self .rateCalls ):
@@ -187,7 +191,7 @@ def __call__(self, maxRate=100):
187
191
self .lastSleep = _clock ()
188
192
elif dt < 0.2 : # don't count long delays due to menu or similar operations
189
193
self .userTime = 0.95 * self .userTime + 0.05 * dt
190
-
194
+
191
195
dt = _clock () - calledTime # approximate amount of time spent in this function
192
196
if self .callTime == 0.0 : self .callTime = dt
193
197
elif dt < 0.2 : # don't count long delays due to menu or similar operations
@@ -216,11 +220,23 @@ def __call__(self, maxRate=100):
216
220
self .lastEndRate = _clock ()
217
221
218
222
219
- class _RateKeeper2 (RateKeeper ):
223
+ def message_send_wrapper ():
224
+ """
225
+ The only purpose of this function is delay import of baseObj to eliminate
226
+ what would otherwise be a circular import. __init__ imports rate, and vpython imports rate,
227
+ and this cannot also import vpython at the same time.
228
+ """
229
+ from .vpython import baseObj
230
+ def message_sender (msg ):
231
+ baseObj .glow .handle_msg (msg )
232
+ return message_sender
233
+
220
234
235
+ class _RateKeeper2 (RateKeeper ):
221
236
def __init__ (self , interactPeriod = INTERACT_PERIOD , interactFunc = simulateDelay ):
222
237
self .rval = 30
223
238
self .tocall = None
239
+ self ._sender = None
224
240
super (_RateKeeper2 , self ).__init__ (interactPeriod = interactPeriod , interactFunc = self .sendtofrontend )
225
241
226
242
def sendtofrontend (self ):
@@ -229,7 +245,19 @@ def sendtofrontend(self):
229
245
230
246
# Check if events to process from front end
231
247
if _isnotebook :
232
- if IPython .__version__ >= '3.0.0' :
248
+ if not self ._sender :
249
+ self ._sender = message_send_wrapper ()
250
+ if ((IPython .__version__ >= '7.0.0' ) or
251
+ (ipykernel .__version__ >= '5.0.0' )):
252
+ while ws_queue .qsize () > 0 :
253
+ data = ws_queue .get ()
254
+ d = json .loads (data ) # update_canvas info
255
+ for m in d :
256
+ # Must send events one at a time to GW.handle_msg because bound events need the loop code:
257
+ msg = {'content' :{'data' :[m ]}} # message format used by notebook
258
+ self ._sender (msg )
259
+
260
+ elif IPython .__version__ >= '3.0.0' :
233
261
kernel = IPython .get_ipython ().kernel
234
262
parent = kernel ._parent_header
235
263
ident = kernel ._parent_ident
@@ -241,5 +269,6 @@ def __call__(self, N): # rate(N) calls this function
241
269
if self .rval < 1 : raise ValueError ("rate value must be greater than or equal to 1" )
242
270
super (_RateKeeper2 , self ).__call__ (self .rval ) ## calls __call__ in rate_control.py
243
271
272
+
244
273
# The rate function:
245
274
rate = _RateKeeper2 (interactFunc = simulateDelay (delayAvg = 0.001 ))
0 commit comments