Reputation: 79268
I have a cellular automaton which we can abbreviate like this:
// pretend slow update function called every tick of the metronome
const update = () => {
let i = 0
while (i < 10000) {
console.log(i)
i++
}
}
Then I have in the main thread a Tone.js "loop" running every tick of the metronome:
new Tone.Loop(time => {
// update() in worker
// then draw() in main thread
}, '4n').start(0)
This loop essentially runs every quarter note at 240 BPM. You can approximate it with setTimeout, but with some extra fancy logic around keeping track of the elapsed time.
My question is, what is the architecture to make sure this draws every tick of the beat and doesn't get out of sync?
If I do this in the web worker system, then I am not sure how it will behave:
// main.js
worker.onmessage = () => draw()
new Tone.Loop(time => worker.postMessage('update'), '4n').start(0)
// worker.js
worker.onmessage = () => {
update()
postMessage("draw")
}
Depending on the async nature of how long the postMessage
takes in both directions, the draw will come way after the beat potentially.
How do I do this correctly, architecture wise?
Note, the drawing to canvas must happen in the main thread, while the update
function of the (multiple instances of) cellular automata must be updated all at once in the worker, for performance. Then there will be a SharedArrayBuffer
to read the final computed values in the main.js
.
What is the general approach I should take to wire this up?
Upvotes: 3
Views: 179
Reputation: 31815
If I understand correctly you want to prevent frames from intertwining, preserve their order and do not delay them when they're ready, which means you need to skip those who don't respect the order.
You could pass the time back and forth, then use it to conditionally draw the frame.
// main.js
let lastFrameTime
worker.onmessage = ({ data: { time }}) => {
if(time > lastFrameTime) {
draw()
lastFrameTime = time
}
}
new Tone.Loop(time => worker.postMessage({ action: 'update', time }), '4n').start(0)
// worker.js
worker.onmessage = ({ data: { time }}) => {
update()
postMessage({ action: 'draw', time })
}
If update
takes more than 0.25s, then a callback will be queued in the worker before the previous one finishes, which will delay the next update
call, and so on.
So you actually need multiple workers with some kind of round-robin dispatch between them, and use the code above in order to prevent frame intertwining.
Upvotes: 1