Skip to content

Commit 7535f72

Browse files
committed
Improve responsiveness; fix bugs
Changed from 30 renders/s to 60 renders/s. Fixed scene.camera.follow(None). Enabled visible attribute of graph objects. Addressed serious event ordering in the case of waiting for pick or compound.
1 parent 72ce40d commit 7535f72

File tree

5 files changed

+71
-27
lines changed

5 files changed

+71
-27
lines changed

labextension/vpython/src/glowcommlab.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ var lastrange = 1
216216
var lastautoscale = true
217217
var lastsliders = {}
218218
var lastkeysdown = []
219-
var interval = 33 // milliseconds
219+
var interval = 17 // milliseconds
220220

221221
function update_canvas() { // mouse location and other stuff
222222
"use strict";
@@ -321,13 +321,17 @@ function send_pick(cvs, p, seg) {
321321
"use strict";
322322
var evt = {event: 'pick', 'canvas': cvs, 'pick': p, 'segment':seg}
323323
events.push(evt)
324+
if (timer !== null) clearTimeout(timer)
325+
send() // send the info NOW
324326
}
325327

326328
function send_compound(cvs, pos, size, up) {
327329
"use strict";
328330
var evt = {event: '_compound', 'canvas': cvs, 'pos': [pos.x, pos.y, pos.z],
329331
'size': [size.x, size.y, size.z], 'up': [up.x, up.y, up.z]}
330332
events.push(evt)
333+
if (timer !== null) clearTimeout(timer)
334+
send() // send the info NOW
331335
}
332336

333337
var waitfor_canvas = null
@@ -560,6 +564,9 @@ function decode(data) {
560564
}
561565
} else if (attr == 'waitfor' || attr == 'pause' || attr == 'delete') {
562566
val = m[3]
567+
} else if (attr == 'follow') {
568+
if (m[3] == 'None') val = null
569+
else val = Number(m[3])
563570
} else val = Number(m[3])
564571
out = {'idx':idx, 'attr':attr, 'val':val}
565572
if (datatype == 'attr') as.push(out)
@@ -970,7 +977,8 @@ async function handle_methods(dmeth) {
970977
}
971978
obj.unbind(val, process_binding)
972979
} else if (method === "follow") {
973-
obj.camera.follow(glowObjs[val])
980+
if (val === null) obj.camera.follow(null)
981+
else obj.camera.follow(glowObjs[val])
974982
} else if (method === "capture") {
975983
await obj.capture(val)
976984
} else if (method === 'waitfor') {
@@ -987,7 +995,7 @@ async function handle_methods(dmeth) {
987995
}
988996
process_pause()
989997
} else if (method === 'pick') {
990-
var p = glowObjs[val].mouse.pick() // wait for pick render; val is canvas
998+
var p = glowObjs[val].mouse.pick() // wait for pick render; val is canvas
991999
var seg = null
9921000
if (p !== null) {
9931001
if (p instanceof curve) seg = p.segment

vpython/rate_control.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
# Unresolved bug: rate(X) yields only about 0.8X iterations per second.
2020

2121
MIN_RENDERS = 10
22-
MAX_RENDERS = 30
22+
MAX_RENDERS = 60
2323
INTERACT_PERIOD = 1.0/MAX_RENDERS
2424
USER_FRACTION = 0.5
2525

@@ -28,7 +28,7 @@
2828
##if _plat == 'Windows':
2929
## # On Windows, the best timer is supposedly time.clock()
3030
## _clock = time.clock
31-
## _tick = 1/60
31+
## _tick = 1/INTERACT_PERIOD
3232
##elif _plat == 'Macintosh':
3333
## # On platforms other than Windows, the best timer is supposedly time.time()
3434
## _clock = time.time
@@ -239,7 +239,7 @@ def message_sender(msg):
239239

240240
class _RateKeeper2(RateKeeper):
241241
def __init__(self, interactPeriod=INTERACT_PERIOD, interactFunc=simulateDelay):
242-
self.rval = 30
242+
self.rval = MAX_RENDERS
243243
self.tocall = None
244244
self._sender = None
245245
super(_RateKeeper2, self).__init__(interactPeriod=interactPeriod, interactFunc=self.sendtofrontend)

vpython/vpython.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,10 @@ def __del__(self):
328328
# Now there is no threading in Jupyter VPython. Data is sent to the
329329
# browser from the trigger() function, which is called by a
330330
# canvas_update event sent to Python from the browser (glowcomm.js), currently
331-
# every 33 milliseconds. When trigger() is called, it immediately signals
332-
# the browser to set a timeout of 33 ms to send another signal to Python.
331+
# every 17 milliseconds. When trigger() is called, it immediately signals
332+
# the browser to set a timeout of 17 ms to send another signal to Python.
333333
# Note that a typical VPython program starts out by creating objects (constructors) and
334-
# specifying their attributes. The 33 ms signal from the browser is adequate to ensure
334+
# specifying their attributes. The 17 ms signal from the browser is adequate to ensure
335335
# prompt data transmissions to the browser.
336336

337337
# The situation with non-notebook use is similar, but the http server is threaded,
@@ -400,9 +400,10 @@ def handle_close(self, data):
400400
def _wait(cvs): # wait for an event
401401
cvs._waitfor = None
402402
if _isnotebook: baseObj.trigger() # in notebook environment must send methods immediately
403+
t = clock()
403404
while cvs._waitfor is None:
404-
rate(30)
405-
return cvs._waitfor
405+
rate(1000)
406+
if _isnotebook: baseObj.trigger() # restart activity in glowcomm.html
406407

407408
class color(object):
408409
black = vector(0,0,0)
@@ -1999,6 +2000,7 @@ def setup(self, args):
19992000
self._legend = False
20002001
self._interval = -1
20012002
self._graph = None
2003+
self._visible = True
20022004
objName = args['_objName']
20032005
del args['_objName']
20042006
self._constructing = True ## calls are from constructor
@@ -2144,6 +2146,15 @@ def plot(self, *args1, **args2):
21442146
p = self.preresolve2(args2)
21452147
self.addmethod('plot', p)
21462148

2149+
@property
2150+
def visible(self):
2151+
return self._visible
2152+
@visible.setter
2153+
def visible(self,value):
2154+
self._visible = value
2155+
if not self._constructing:
2156+
self.addattr('visible')
2157+
21472158
def delete(self):
21482159
self.addmethod('delete', 'None')
21492160

@@ -2840,7 +2851,8 @@ def __init__(self, **args):
28402851
baseObj._canvas_constructing = False
28412852

28422853
def follow(self, obj): ## should allow a function also
2843-
self.addmethod('follow', obj.idx)
2854+
if obj is None: self.addmethod('follow', 'None')
2855+
else: self.addmethod('follow', obj.idx)
28442856

28452857
def select(self):
28462858
canvas.selected = self

vpython/vpython_libraries/glowcomm.html

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
}
7676
var t2 = msclock()
7777
var dt = Math.floor(t1+interval-t2) // attempt to keep the time between renders constant
78-
if (dt < 15) dt = 0 // becaause setTimeout is inaccurate for small dt's
78+
if (dt < 15) dt = 0 // because setTimeout is inaccurate for small dt's
7979
timer = setTimeout(send, dt)
8080
}
8181

@@ -138,7 +138,7 @@
138138
var lastautoscale = true
139139
var lastsliders = {}
140140
var lastkeysdown = []
141-
var interval = 33 // milliseconds
141+
var interval = 17 // milliseconds
142142

143143
function update_canvas() { // mouse location and other stuff
144144
"use strict";
@@ -243,13 +243,17 @@
243243
"use strict";
244244
var evt = {event: 'pick', 'canvas': cvs, 'pick': p, 'segment':seg}
245245
events.push(evt)
246+
if (timer !== null) clearTimeout(timer)
247+
send() // send the info NOW
246248
}
247249

248250
function send_compound(cvs, pos, size, up) {
249251
"use strict";
250252
var evt = {event: '_compound', 'canvas': cvs, 'pos': [pos.x, pos.y, pos.z],
251253
'size': [size.x, size.y, size.z], 'up': [up.x, up.y, up.z]}
252254
events.push(evt)
255+
if (timer !== null) clearTimeout(timer)
256+
send() // send the info NOW
253257
}
254258

255259
var waitfor_canvas = null
@@ -395,7 +399,7 @@
395399
'p':'left', 'q':'right', 'r':'top', 's':'bottom', 't':'_cloneid',
396400
'u':'logx', 'v':'logy', 'w':'dot', 'x':'dot_radius',
397401
'y':'markers', 'z':'legend', 'A':'label','B':'delta', 'C':'marker_color',
398-
'D':'size_units', 'E':'userpan', 'F':'scroll'}
402+
'D':'size_units', 'E':'userpan', 'F':'scroll', 'G':'choices'}
399403

400404
// methods are X in {'m': '23X....'}
401405
var methods = {'a':'select', 'b':'pos', 'c':'start', 'd':'stop', 'f':'clear', // unused eghijklmnopvxyzCDFAB
@@ -410,7 +414,7 @@
410414
'marker_color']
411415

412416
var textattrs = ['text', 'align', 'caption', 'title', 'title_align', 'xtitle', 'ytitle', 'selected', 'capture',
413-
'label', 'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint']
417+
'label', 'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint', 'choices']
414418

415419
// patt gets idx and attr code; vpatt gets x,y,z of a vector
416420
var patt = /(\d+)(.)(.*)/
@@ -454,8 +458,12 @@
454458
vs = [Number(val[1]), Number(val[2]), Number(val[3]), Number(val[4])]
455459
}
456460
} else if (textattrs.indexOf(attr) > -1) {
457-
// '\n' doesn't survive JSON transmission, so in vpython.py we replace '\n' with '<br>'
458-
val = m[3].replace(/<br>/g, "\n")
461+
if (attr == 'choices') { // menu choices to be wrapped in a list
462+
val = m[3].split(' ')
463+
} else {
464+
// '\n' doesn't survive JSON transmission, so in vpython.py we replace '\n' with '<br>'
465+
val = m[3].replace(/<br>/g, "\n")
466+
}
459467
} else if (attr == 'rotate') { // angle,x,y,z,x,y,z
460468
var temp = m[3]
461469
val = []
@@ -478,6 +486,9 @@
478486
}
479487
} else if (attr == 'waitfor' || attr == 'pause' || attr == 'delete') {
480488
val = m[3]
489+
} else if (attr == 'follow') {
490+
if (m[3] == 'None') val = null
491+
else val = Number(m[3])
481492
} else val = Number(m[3])
482493
out = {'idx':idx, 'attr':attr, 'val':val}
483494
if (datatype == 'attr') as.push(out)
@@ -875,7 +886,8 @@
875886
}
876887
obj.unbind(val, process_binding)
877888
} else if (method === "follow") {
878-
obj.camera.follow(glowObjs[val])
889+
if (val === null) obj.camera.follow(null)
890+
else obj.camera.follow(glowObjs[val])
879891
} else if (method === "capture") {
880892
await obj.capture(val)
881893
} else if (method === 'waitfor') {
@@ -892,7 +904,7 @@
892904
}
893905
process_pause()
894906
} else if (method === 'pick') {
895-
var p = glowObjs[val].mouse.pick() // wait for pick render; val is canvas
907+
var p = glowObjs[val].mouse.pick() // wait for pick render; val is canvas
896908
var seg = null
897909
if (p !== null) {
898910
if (p instanceof curve) seg = p.segment

vpython/vpython_libraries/glowcomm.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ var lastrange = 1
188188
var lastautoscale = true
189189
var lastsliders = {}
190190
var lastkeysdown = []
191-
var interval = 33 // milliseconds
191+
var interval = 17 // milliseconds
192192

193193
function update_canvas() { // mouse location and other stuff
194194
"use strict";
@@ -293,13 +293,17 @@ function send_pick(cvs, p, seg) {
293293
"use strict";
294294
var evt = {event: 'pick', 'canvas': cvs, 'pick': p, 'segment':seg}
295295
events.push(evt)
296+
if (timer !== null) clearTimeout(timer)
297+
send() // send the info NOW
296298
}
297299

298300
function send_compound(cvs, pos, size, up) {
299301
"use strict";
300302
var evt = {event: '_compound', 'canvas': cvs, 'pos': [pos.x, pos.y, pos.z],
301303
'size': [size.x, size.y, size.z], 'up': [up.x, up.y, up.z]}
302304
events.push(evt)
305+
if (timer !== null) clearTimeout(timer)
306+
send() // send the info NOW
303307
}
304308

305309
var waitfor_canvas = null
@@ -445,7 +449,7 @@ var attrsb = {'a':'userzoom', 'b':'userspin', 'c':'range', 'd':'autoscale', 'e':
445449
'p':'left', 'q':'right', 'r':'top', 's':'bottom', 't':'_cloneid',
446450
'u':'logx', 'v':'logy', 'w':'dot', 'x':'dot_radius',
447451
'y':'markers', 'z':'legend', 'A':'label','B':'delta', 'C':'marker_color',
448-
'D':'size_units', 'E':'userpan', 'F':'scroll'}
452+
'D':'size_units', 'E':'userpan', 'F':'scroll', 'G':'choices'}
449453

450454
// methods are X in {'m': '23X....'}
451455
var methods = {'a':'select', 'b':'pos', 'c':'start', 'd':'stop', 'f':'clear', // unused eghijklmnopvxyzCDFAB
@@ -460,7 +464,7 @@ var vecattrs = ['pos', 'up', 'color', 'trail_color', 'axis', 'size', 'origin', '
460464
'marker_color']
461465

462466
var textattrs = ['text', 'align', 'caption', 'title', 'title_align', 'xtitle', 'ytitle', 'selected', 'capture',
463-
'label', 'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint']
467+
'label', 'append_to_caption', 'append_to_title', 'bind', 'unbind', 'pause', 'GSprint', 'choices']
464468

465469
// patt gets idx and attr code; vpatt gets x,y,z of a vector
466470
var patt = /(\d+)(.)(.*)/
@@ -504,8 +508,12 @@ function decode(data) {
504508
vs = [Number(val[1]), Number(val[2]), Number(val[3]), Number(val[4])]
505509
}
506510
} else if (textattrs.indexOf(attr) > -1) {
507-
// '\n' doesn't survive JSON transmission, so in vpython.py we replace '\n' with '<br>'
508-
val = m[3].replace(/<br>/g, "\n")
511+
if (attr == 'choices') { // menu choices to be wrapped in a list
512+
val = m[3].split(' ')
513+
} else {
514+
// '\n' doesn't survive JSON transmission, so in vpython.py we replace '\n' with '<br>'
515+
val = m[3].replace(/<br>/g, "\n")
516+
}
509517
} else if (attr == 'rotate') { // angle,x,y,z,x,y,z
510518
var temp = m[3]
511519
val = []
@@ -528,6 +536,9 @@ function decode(data) {
528536
}
529537
} else if (attr == 'waitfor' || attr == 'pause' || attr == 'delete') {
530538
val = m[3]
539+
} else if (attr == 'follow') {
540+
if (m[3] == 'None') val = null
541+
else val = Number(m[3])
531542
} else val = Number(m[3])
532543
out = {'idx':idx, 'attr':attr, 'val':val}
533544
if (datatype == 'attr') as.push(out)
@@ -938,7 +949,8 @@ async function handle_methods(dmeth) {
938949
}
939950
obj.unbind(val, process_binding)
940951
} else if (method === "follow") {
941-
obj.camera.follow(glowObjs[val])
952+
if (val === null) obj.camera.follow(null)
953+
else obj.camera.follow(glowObjs[val])
942954
} else if (method === "capture") {
943955
await obj.capture(val)
944956
} else if (method === 'waitfor') {
@@ -955,7 +967,7 @@ async function handle_methods(dmeth) {
955967
}
956968
process_pause()
957969
} else if (method === 'pick') {
958-
var p = glowObjs[val].mouse.pick() // wait for pick render; val is canvas
970+
var p = glowObjs[val].mouse.pick() // wait for pick render; val is canvas
959971
var seg = null
960972
if (p !== null) {
961973
if (p instanceof curve) seg = p.segment

0 commit comments

Comments
 (0)