diff --git a/animate.go b/animate.go index 269c896..73a601c 100644 --- a/animate.go +++ b/animate.go @@ -5,11 +5,12 @@ import ( "time" "github.com/gopherjs/gopherjs/js" + "github.com/gopherjs/vecty" "github.com/status-im/simulation/propagation" ) +// TODO(divan): move this as variables to the frontend const ( - // TODO(divan): move this as variables to the frontend BlinkDecay = 100 * time.Millisecond // time for highlighted node/link to be active AnimationSlowdown = 1 // slowdown factor for propagation animation FPS = 30 // default FPS @@ -42,7 +43,18 @@ func (w *WebGLScene) animate() { w.updatePositions() } - w.renderer.Render(w.scene, w.camera) + // some render throttling magic to prevent wasting CPU/GPU while idle + // if auto rotation or other effects are active, render always + var needRendering bool = w.wobble || w.autoRotate + if !needRendering { + // else, consult render throttler + needRendering = w.rt.NeedRendering() + } + + if needRendering { + w.renderer.Render(w.scene, w.camera) + w.rt.ReenableIfNeeded() + } } // ToggleAutoRotation switches auto rotation option. @@ -75,6 +87,7 @@ func (w *WebGLScene) BlinkEdge(id int) { // AnimatePropagation visualizes propagation of message based on plog. func (w *WebGLScene) AnimatePropagation(plog *propagation.Log) { fmt.Println("Animating plog") + w.rt.Disable() for i, ts := range plog.Timestamps { duration := time.Duration(time.Duration(ts) * time.Millisecond) duration = duration * AnimationSlowdown @@ -94,3 +107,17 @@ func (w *WebGLScene) AnimatePropagation(plog *propagation.Log) { time.AfterFunc(duration, fn) } } + +// MouseMoveListener implements listener for mousemove events. +// We use it for disabling render throttling, as mousemove events +// correlates with user moving inside of the WebGL canvas. We +// may switch to use mousedown or drag events, but let's see how +// mousemove works. +// This is sort of a hack, as the proper approach would be to get +// data from controls code (w.controls.Update), but it's currently +// a JS code, so it's easier use this hack. +func (p *Page) MouseMoveListener(e *vecty.Event) { + if !p.webgl.rt.NeedRendering() { + p.webgl.rt.Disable() + } +} diff --git a/page.go b/page.go index 3568f04..84e43aa 100644 --- a/page.go +++ b/page.go @@ -105,6 +105,7 @@ func (p *Page) Render() vecty.ComponentOrHTML { ), vecty.Markup( event.KeyDown(p.KeyListener), + event.MouseMove(p.MouseMoveListener), event.VisibilityChange(p.VisibilityListener), ), ) diff --git a/render_throttler.go b/render_throttler.go new file mode 100644 index 0000000..56ed403 --- /dev/null +++ b/render_throttler.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "time" +) + +// DefaultRenderThrottleDecay defines a decay period after which throttling should reenabled. +const DefaultRenderThrottleDecay = 5 // sec + +// RenderThrottler is a helper to enable rendering only when it's really need to. +// Rendering can be enabled/requested externally, but will be turned off/slowed down +// after some period of time. +// TODO(divan): disable/enable throttling is inverse of enable/disable rendering, and may be confusing. Rename maybe? +// TODO(divan): also, instead of disabling, might be a good idea to just decrease FPS. Explore this. +type RenderThrottler struct { + needRendering bool + lastUpdate int64 + decay int64 +} + +// NewRenderThrottler returns a new render throttler. +func NewRenderThrottler() *RenderThrottler { + r := &RenderThrottler{ + needRendering: true, + lastUpdate: time.Now().Unix(), + decay: DefaultRenderThrottleDecay, + } + + return r +} + +// Disable disables render throttling. +func (r *RenderThrottler) Disable() { + fmt.Printf("Switching ON rendering") + r.needRendering = true + r.lastUpdate = time.Now().Unix() +} + +// Enable enables render throttling. +func (r *RenderThrottler) Enable() { + fmt.Printf("Switching off rendering") + r.needRendering = false +} + +// ReenableIfNeeded checks if sufficient time has passed since throttling has been +// disabled, and enables throttling back if so. +func (r *RenderThrottler) ReenableIfNeeded() { + now := time.Now().Unix() + if r.lastUpdate+r.decay < now { + r.Enable() + } +} + +// NeedRendering returns true if next frame should be rendered. +func (r *RenderThrottler) NeedRendering() bool { + return r.needRendering +} diff --git a/scene.go b/scene.go index 8f09e9a..60f7b0c 100644 --- a/scene.go +++ b/scene.go @@ -31,11 +31,15 @@ type WebGLScene struct { // TODO(divan): as soon as three.js wrappers allow us to access children, get rid of it here nodes []*Mesh lines []*Line + + rt *RenderThrottler // used as a helper to reduce rendering calls when animation is not needed (experimental) } // NewWebGLScene inits and returns new WebGL scene and canvas. func NewWebGLScene() *WebGLScene { - w := &WebGLScene{} + w := &WebGLScene{ + rt: NewRenderThrottler(), + } w.WebGLRenderer = vthree.NewWebGLRenderer(vthree.WebGLOptions{ Init: w.init, Shutdown: w.shutdown,