Skip to content

Commit d3758f0

Browse files
committed
Move rate and RateKeeper2 back to rate_control (and remove MISC from with_notebook)
1 parent 3e04b2c commit d3758f0

File tree

3 files changed

+81
-80
lines changed

3 files changed

+81
-80
lines changed

vpython/rate_control.py

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
from __future__ import division, print_function
22
import time
33
import platform
4+
import queue
5+
import json
46

57
from ._notebook_helpers import _isnotebook
68

79
if _isnotebook:
810
import IPython
11+
import ipykernel
12+
ws_queue = queue.Queue()
913

1014
# Unresolved bug: rate(X) yields only about 0.8X iterations per second.
1115

@@ -31,15 +35,15 @@
3135

3236
##Possible way to get one-millisecond accuracy in sleep on Windows:
3337
##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
4347
##in the system makes to a Win32 function with a timeout.
4448

4549
def _sleep(dt):
@@ -58,18 +62,18 @@ def _sleep(dt):
5862
tend = _clock()+dt
5963
while _clock() < tend:
6064
pass
61-
65+
6266
class simulateDelay:
6367
"""
64-
Simulate rendering/compute times.. with an average value of delayAvg with
68+
Simulate rendering/compute times.. with an average value of delayAvg with
6569
a variance of something like delaySigma**2.
6670
"""
6771

6872
def __init__(self, delayAvg=0.001, delaySigma=0.0001):
6973
self.delayAvg=delayAvg
7074
self.delaySigma=delaySigma
7175
self.callTimes = []
72-
76+
7377
def __call__(self):
7478
self.callTimes.append(_clock())
7579

@@ -94,9 +98,9 @@ def initialize(self):
9498
self.whenToRender = []
9599
for i in range(MAX_RENDERS+2):
96100
self.whenToRender.append(0)
97-
self.renderIndex = 0
101+
self.renderIndex = 0
98102
self.rateCount = 0 # counts calls to rate in a 1-second cycle (reset to 0 every second)
99-
103+
100104
def callInteract(self):
101105
t = _clock()
102106
self.interactFunc()
@@ -148,7 +152,7 @@ def buildStrategy(self, rate):
148152

149153
# Prepare the self.renderIndex array of indices for when to do renders:
150154
self.distributeRenders(M, N)
151-
155+
152156
# M = self.rateCalls = number of calls to rate/second
153157
# N = number of renders/second
154158
# callTime = time spent in rate function (very small)
@@ -160,20 +164,20 @@ def buildStrategy(self, rate):
160164
self.delay = (1.0 - N*R - self.renderWaits*self.interactionPeriod)/M - self.callTime - U
161165
## print("%1.4f %i %i %i %1.6f %1.6f %1.6f %1.6f" % (_clock(), M, N, self.renderWaits,
162166
## self.userTime, self.callTime, self.delay, self.renderTime))
163-
167+
164168
def __call__(self, maxRate=100):
165169
#td.add('-------------------------')
166170
if not self.initialized:
167171
self.initialize()
168172
self.initialized = True
169-
calledTime = _clock()
173+
calledTime = _clock()
170174
if maxRate < 1: raise ValueError("rate value must be greater than or equal to 1")
171175
self.count += 1
172176
if self.count == 1: # first time rate has been called
173177
self.callInteract()
174178
self.lastEndRate = _clock()
175179
return
176-
180+
177181
dt = calledTime - self.lastEndRate # time spent in user code
178182
nr = self.whenToRender[self.renderIndex]
179183
if self.count == 2 or (self.count == self.lastCount + self.rateCalls):
@@ -187,7 +191,7 @@ def __call__(self, maxRate=100):
187191
self.lastSleep = _clock()
188192
elif dt < 0.2: # don't count long delays due to menu or similar operations
189193
self.userTime = 0.95*self.userTime + 0.05*dt
190-
194+
191195
dt = _clock() - calledTime # approximate amount of time spent in this function
192196
if self.callTime == 0.0: self.callTime = dt
193197
elif dt < 0.2: # don't count long delays due to menu or similar operations
@@ -216,11 +220,23 @@ def __call__(self, maxRate=100):
216220
self.lastEndRate = _clock()
217221

218222

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+
220234

235+
class _RateKeeper2(RateKeeper):
221236
def __init__(self, interactPeriod=INTERACT_PERIOD, interactFunc=simulateDelay):
222237
self.rval = 30
223238
self.tocall = None
239+
self._sender = None
224240
super(_RateKeeper2, self).__init__(interactPeriod=interactPeriod, interactFunc=self.sendtofrontend)
225241

226242
def sendtofrontend(self):
@@ -229,7 +245,19 @@ def sendtofrontend(self):
229245

230246
# Check if events to process from front end
231247
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':
233261
kernel = IPython.get_ipython().kernel
234262
parent = kernel._parent_header
235263
ident = kernel._parent_ident
@@ -241,5 +269,6 @@ def __call__(self, N): # rate(N) calls this function
241269
if self.rval < 1: raise ValueError("rate value must be greater than or equal to 1")
242270
super(_RateKeeper2, self).__call__(self.rval) ## calls __call__ in rate_control.py
243271

272+
244273
# The rate function:
245274
rate = _RateKeeper2(interactFunc = simulateDelay(delayAvg = 0.001))

vpython/vpython.py

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
from time import clock
1111
import time
1212
import sys
13-
import queue
14-
import json
1513
from . import __version__, __gs_version__
1614
from ._notebook_helpers import _isnotebook
17-
from ._vector_import_helper import vector, mag, norm, dot, adjust_up, adjust_axis, object_rotate
15+
from ._vector_import_helper import (vector, mag, norm, dot, adjust_up,
16+
adjust_axis, object_rotate)
1817

1918
# List of names that will be imported form this file with import *
2019
__all__ = ['Camera', 'GlowWidget', 'GSversion', 'Mouse', 'arrow', 'attach_arrow',
@@ -28,7 +27,6 @@
2827
'standardAttributes', 'text', 'textures', 'triangle', 'vertex',
2928
'wtext']
3029
vec = vector # synonyms in GlowScript
31-
ws_queue = queue.Queue()
3230

3331
def __checkisnotebook(): # returns True if running in Jupyter notebook
3432
try:
@@ -210,45 +208,9 @@ def _encode_attr(D, ismethods): # ismethods is True if a list of method operatio
210208
out.append(s)
211209
return out
212210

213-
class _RateKeeper2(RateKeeper):
214-
215-
def __init__(self, interactPeriod=INTERACT_PERIOD, interactFunc=simulateDelay):
216-
self.rval = 30
217-
self.tocall = None
218-
super(_RateKeeper2, self).__init__(interactPeriod=interactPeriod, interactFunc=self.sendtofrontend)
219-
220-
def sendtofrontend(self):
221-
# This is called by the rate() function, through rate_control _RateKeeper callInteract().
222-
# See the function commsend() for details of how the browser is updated.
223-
224-
# Check if events to process from front end
225-
if _isnotebook:
226-
if (IPython.__version__ >= '7.0.0') or (ipykernel.__version__ >= '5.0.0'):
227-
while ws_queue.qsize() > 0:
228-
data = ws_queue.get()
229-
d = json.loads(data) # update_canvas info
230-
for m in d:
231-
# Must send events one at a time to GW.handle_msg because bound events need the loop code:
232-
msg = {'content':{'data':[m]}} # message format used by notebook
233-
baseObj.glow.handle_msg(msg)
234-
235-
elif IPython.__version__ >= '3.0.0' :
236-
kernel = get_ipython().kernel
237-
parent = kernel._parent_header
238-
ident = kernel._parent_ident
239-
kernel.do_one_iteration()
240-
kernel.set_parent(ident, parent)
241-
242-
def __call__(self, N): # rate(N) calls this function
243-
self.rval = N
244-
if self.rval < 1: raise ValueError("rate value must be greater than or equal to 1")
245-
super(_RateKeeper2, self).__call__(self.rval) ## calls __call__ in rate_control.py
246-
247211
if sys.version > '3':
248212
long = int
249213

250-
# The rate function:
251-
rate = _RateKeeper2(interactFunc = simulateDelay(delayAvg = 0.001))
252214

253215
def list_to_vec(L):
254216
return vector(L[0], L[1], L[2])

vpython/with_notebook.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import notebook
66
import IPython
77
from IPython.display import display, Javascript
8+
import ipykernel
89

910
from .vpython import GlowWidget, baseObj, canvas
11+
from .rate_control import ws_queue
1012
from . import __version__
1113

1214
import tornado.httpserver
@@ -88,7 +90,7 @@ def find_free_port():
8890
if libready and dataready: break
8991
# Mark with the version number that the files have been transferred successfully:
9092
fd = open(nbdir+'/vpython_version.txt', 'w')
91-
fd.write(__version__)
93+
fd.write(__version__)
9294
fd.close()
9395

9496
display(Javascript("""if (typeof Jupyter !== "undefined") {require.undef("nbextensions/vpython_libraries/glow.min");}else{element.textContent = ' ';}"""))
@@ -101,30 +103,26 @@ def find_free_port():
101103

102104
time.sleep(1) # allow some time for javascript code above to run before attempting to setup Comm Channel
103105

104-
baseObj.glow = GlowWidget() # Setup Comm Channel
105-
106-
baseObj.trigger() # start the trigger ping-pong process
107-
108106
wsConnected = False
109107

110108
class WSHandler(tornado.websocket.WebSocketHandler):
111109
def open(self):
112110
global wsConnected
113111
wsConnected = True
114-
112+
115113
def on_message(self, message):
116114
ws_queue.put(message)
117-
115+
118116
def on_close(self):
119117
self.stopTornado()
120118

121119
def stop_tornado(self):
122120
ioloop = tornado.ioloop.IOLoop.instance()
123121
ioloop.add_callback(ioloop.stop)
124-
122+
125123
def check_origin(self, origin):
126124
return True
127-
125+
128126
def start_server():
129127
asyncio.set_event_loop(asyncio.new_event_loop())
130128
application = tornado.web.Application([(r'/ws', WSHandler),])
@@ -135,6 +133,7 @@ def start_server():
135133
Log.setLevel(level)
136134
tornado.ioloop.IOLoop.instance().start()
137135

136+
138137
if (ipykernel.__version__ >= '5.0.0'):
139138
from threading import Thread
140139
t = Thread(target=start_server, args=())
@@ -144,16 +143,27 @@ def start_server():
144143
time.sleep(0.1) # wait for websocket to connect
145144
else:
146145
baseObj.glow = GlowWidget() # Setup Comm Channel
147-
148-
scene = canvas()
149-
150-
# This must come after creating a canvas
151-
class MISC(baseObj):
152-
def __init__(self):
153-
super(MISC, self).__init__()
154-
155-
def prnt(self, s):
156-
self.addmethod('GSprint', s)
146+
147+
baseObj.trigger() # start the trigger ping-pong process
148+
149+
if IPython.__version__ >= '4.0.0':
150+
if (ipykernel.__version__ >= '5.0.0'):
151+
async def wsperiodic():
152+
while True:
153+
if ws_queue.qsize() > 0:
154+
data = ws_queue.get()
155+
d = json.loads(data)
156+
# Must send events one at a time to GW.handle_msg because
157+
# bound events need the loop code
158+
for m in d:
159+
# message format used by notebook
160+
msg = {'content': {'data': [m]}}
161+
baseObj.glow.handle_msg(msg)
162+
163+
await asyncio.sleep(0)
164+
165+
loop = asyncio.get_event_loop()
166+
loop.create_task(wsperiodic())
157167

158168
# Dummy name to import...
159169
_ = None

0 commit comments

Comments
 (0)