Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pattern for avoiding clogging / backpressure #394

Open
monfera opened this issue Jan 11, 2017 · 4 comments
Open

Pattern for avoiding clogging / backpressure #394

monfera opened this issue Jan 11, 2017 · 4 comments

Comments

@monfera
Copy link

monfera commented Jan 11, 2017

Think of a timer stream generated by most.periodic and consumed by a most.observe that e.g. renders something on the canvas. This naive approach led to either of these two things, or both:

  • if the period is short e.g. 30ms, then initially, animation is fairly fluid, yet as rendering cumulatively takes a bit more time than the average time budget, backpressure (I suppose) causes a gradual performance degradation, e.g. from around 30FPS to a few FPS then a browser tab crash
  • if the period is longer e.g. 90ms, degradation doesn't seem to happen, but then of course I underutilize the rendering capabilities, leading to low frame rates

I think the average rendering time is a bit above 30ms, of course, depending on hardware and amount of data shown and non-deterministic HW drivers etc., i.e. I can't fully control the per-frame or aggregate rendering time.

Would a better approach use a rAF loop to emit signals? Also, as there seems to be no dedicated API notion for manually emitting an event (unlike in e.g. flyd where you can call the stream as a function with a value), perhaps the best option is something like the promise based iterate except using rAF instead of setTimeout?

@TylorS
Copy link
Collaborator

TylorS commented Jan 11, 2017

Hello @monfera, thanks for the issue. Deciding anything related to timing is a difficult problem and it can be even more difficult when it comes to rendering. Its difficult to give a blanket answer to that, as it really depends on so many different things.

As for emitting values on raf it's entirely possible, here's an example of that: https://github.com/briancavalier/most-behavior/blob/master/src/animationFrames.js.

Also, as there seems to be no dedicated API notion for manually emitting an event

This is 100% on purpose. We would like to promote declarative uses of reactive streams over imperative solutions.

@axefrog
Copy link
Contributor

axefrog commented Jan 11, 2017

If you must emit events imperatively, the following two packages are an option:

@monfera
Copy link
Author

monfera commented Jan 11, 2017

@TylorS @axefrog thank you for the suggestions, I gave it a shot, based on the primitives in your link, rendering was nicely triggered by rAF. Also I agree with the principle of avoiding manual event firing (except maybe by necessity as new primary input streams, like WebSockets etc.). I code arrowized FRP inspired style where functions are lifted.

As you implied, rAF didn't solve the likely backpressure issue (probably causing queue buildup and after a few minutes, the browser crash) and Subject probably wouldn't help on its own either. To let off extra pressure, I winged:

const time = animationFrames()
const renderTime = most.filter(
  d => d.diff < 18, // rAF diff is 16.67ms when there's no or little load
  most.scan((p, time) => ({time, diff: time - p.time}), 0, time)
)

naturally it underutilizes resources as it only triggers a render when the 16.7ms frame length, 60FPS cadence resumes, but good 1st experiment.

Still, the above mentioned deterioration remained, which might be because while renderTime is filtered, time and the scan, filter functions get executed on every rAF call so maybe some task queue still gets accumulated. I didn't run into it with flyd, it's synchronous (no task buildup) and lots of other huge differences, or kefir so it requires a different mindset.

If there's a pattern for 'render on each rAF as they come' without deterioration, and without massively underutilizing resources, it would be interesting. (Ofc in real life, there are other stream inputs to an animation than just a rAF timestamp; and the above issue comes up whether it's rendering time or computation time in the graph.) To clarify, I'm more interested in minimizing latency than maximizing throughput, let alone avoiding skipping. Think of rendering a mouse cursor, it's better if a % of frames are dropped than if there is a 2s lag between mouse movement and pointer even if the resulting pointer move will not be as faithful to the mouse movement.

@monfera
Copy link
Author

monfera commented Jan 11, 2017

@TylorS @axefrog ... on a second thought, perhaps armed with Subject, I can make hot observables as described in this article by Ben Lesh? I'm sure there's some solution esp. that most is foundational to motorcycle.js too (DOM events like mousemove can actually fire even more rapidly than every 16ms).

function makeHot(cold) {
  const subject = new Subject();
  cold.subscribe(subject);
  return new Observable((observer) => subject.subscribe(observer));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants