Tobi
Tobi

Reputation: 1312

node: run function consistently X times per second while tracking how long it runs

I want to write a system that automates max. 8192 different values consistently 40 times per second. The kind of automation, I want to do is e.g. letting some values form a sinus from 0 to 255, others ramp up from 50 to 80, others switch between 255 and 0 etc.

I need to know how long each execution takes and if the execution takes longer than allowed it should "drop a frame".

my first approach would be

// simplified code
const fps = 40
const executionStats : number[] = []
executionStats.length = fps * 10

const startFrame = new Date()

type SingleValue = {current: number, effectType: string, effectStarted: number}
const values : SingleValue[] = [...] // up to 8192 entries

// this is calculating the target value based on some lookups in other objects
// and based on when the effect started and in what tick we are in right now
const runSingleEffect = (value: SingleValue, tick: number) : number => {...}

const handler = async (tick: number) => {
   values.forEach(value => {value.current = runSingleEffect(value, tick)})
}

setInterval(async () => {
   const start = new Date()
   await handler((start - startFrame) / fps);
   const end = new Date()
   executionStats.push(end - start)
   executionStats.unshift()
}, 1000 / fps)

is there any better approach than using a simple setInterval? (obviously I could use a ring buffer for the execution stats) but is there a better way to make sure that only one handler runs at a time? Is there maybe a library for that kind of stuff?

Upvotes: 0

Views: 290

Answers (1)

tenbits
tenbits

Reputation: 8008

I would use setTimeout with continuous resubscription. You would recalculate the next tick on every "loop" end.

function defer (ms) {
    const frameBudgetMs = 1000 / fps;
    setTimeout(async () => {
        const start = Date.now();
        try {
            await handler((start - startFrame) / fps);
        } finally {
            const end = Date.now();
            const ms = end - start;
            executionStats.push(ms);
            executionStats.unshift();

            // a) In case we are over our frame, start next defer immediately.
            let nextTickMs = Math.max(0, frameBudgetMs - ms);
            defer(nextTickMs);

            // b) In case we want to execute at specific frame interval, but just dropping the frames
            let nextTickMs = frameBudget - ms % frameBudget;
            defer(nextTickMs);
        }
    }, ms)
}

// start our loop
defer(0);

As you can see, you are completely flexible in resubscription scenarios, and no parallel runs are possible in such a "loop".

Upvotes: 1

Related Questions