Skip to content

Instantly share code, notes, and snippets.

@jwmerrill
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jwmerrill/5a4c789d17380966a420 to your computer and use it in GitHub Desktop.
Save jwmerrill/5a4c789d17380966a420 to your computer and use it in GitHub Desktop.
Modification of elm's runtime to allow drawing more consistently at 60 fps
9345,9351c9345,9374
- var previousDrawId = 0;
- function domUpdate(newScene) {
- previousDrawId = ElmRuntime.draw(previousDrawId, function(_) {
- Render.update(elm.node.firstChild, savedScene, newScene);
- if (elm.Native.Window) elm.Native.Window.values.resizeIfNeeded();
- savedScene = newScene;
- });
+
+ // domUpdate is called whenever the main Signal changes. On domUpdate,
+ //
+ // 1. schedule a draw using requestAnimationFrame if there is no draw already scheduled
+ // 2. replace the scene to be drawn with newScene
+
+ var drawScheduled = false;
+ var newScene = currentScene;
+
+ function domUpdate(_newScene) {
+ newScene = _newScene;
+ if (!drawScheduled) scheduleDraw();
+ }
+
+ function scheduleDraw() {
+ drawScheduled = true;
+ if (typeof requestAnimationFrame === 'undefined') {
+ draw();
+ } else {
+ requestAnimationFrame(draw);
+ }
+ }
+
+ function draw() {
+ drawScheduled = false;
+ Render.update(elm.node.firstChild, savedScene, newScene);
+ if (elm.Native.Window) elm.Native.Window.values.resizeIfNeeded();
+ // Needed in case domUpdates are consistently called before draw in each frame.
+ if (savedScene !== newScene) scheduleDraw();
+ savedScene = newScene;
9352a9376
+
9397,9423d9420
- // define function for drawing efficiently
- //
- // draw : RequestID -> (() -> ()) -> RequestID
- //
- // Takes a "RequestID" allowing you to cancel old requests if possible.
- // Returns a "RequestID" so you can refer to past requests.
- //
- var vendors = ['ms', 'moz', 'webkit', 'o'];
- var win = typeof window !== 'undefined' ? window : {};
- for (var i = 0; i < vendors.length && !win.requestAnimationFrame; ++i) {
- win.requestAnimationFrame = win[vendors[i]+'RequestAnimationFrame'];
- win.cancelAnimationFrame = win[vendors[i]+'CancelAnimationFrame'] ||
- win[vendors[i]+'CancelRequestAnimationFrame'];
- }
-
- if (win.requestAnimationFrame && win.cancelAnimationFrame) {
- ElmRuntime.draw = function(previousRequestID, callback) {
- win.cancelAnimationFrame(previousRequestID);
- return win.requestAnimationFrame(callback);
- };
- } else {
- ElmRuntime.draw = function(previousRequestID, callback) {
- callback();
- return previousRequestID;
- };
- }
-
@jwmerrill
Copy link
Author

The main problem with the existing runtime draw scheduler is that it cancels existing scheduled draws whenever a new draw is scheduled. This would be fine, except for the behavior when a new draw is scheduled during the "same tick" that an existing draw was scheduled to run. In this case, the existing draw is cancelled, and the new draw waits for the next frame. If another new draw is scheduled in that frame, it cancels the existing draw, and if this pattern continues on every frame, no frame is ever drawn.

In practice, this only happens intermittently when using fps, but it is easy to make it happen always using Monitor.

Another non-ideal thing about the existing runtime is that it creates a new callback closure for every domUpdate, which probably increases GC pressure.

The modified implementation stores the new scene to be drawn in the existing closure, and it never uses cancelAnimationFrame. I also dropped the vendor prefixes on requestAnimationFrame because it appears that no recent browser uses them.

The full modified file is available here.

I'd like to turn this into a proper pull request once I can figure out how to rebuild Elm's runtime after modifying core.js.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment