Tresjs / tres

Declarative ThreeJS using Vue Components
https://tresjs.org
MIT License
1.91k stars 85 forks source link

Render mode policies #689

Open alvarosabu opened 1 month ago

alvarosabu commented 1 month ago

Description

Via @andretchen0:

About answering the question, "Should the next tick update/render?", we currently have renderMode, manual, etc.

Problem?

The v4 implementation is mostly located in useTresContext, which is already very busy. <TresCanvas /> currently only accepts 1 string flag from the user. Using only flags means the system relies solely on us predicting users' needs and implementing a "mode" for them. Accepting only a single flag means we can't have a compound policy, e.g., "rerender if advance or invalidate were called or the window resized. Afaik, this currently leads to cases where manual is the mode – the canvas will be blank if the window is resized, until invalidate is called.

Suggested solution

Encapsulate the logic for "Should the next tick update/render?" in a module – maybe src/core/renderPolicy.ts? Let the render policy be specified via functions/objects as follows:

import { createRenderPolicy, manual } from '...'
const policy = createRenderPolicy(manual)
const { invalidate } = policy
...
<TresCanvas :render-policy="policy" />

Above,

In this way, we could have compound policies. E.g.,

// NOTE: This policy will approve the next tick of jobs if ...
// - `invalidate()` was called
// - the window was resized
// but cancel if ...
// - the canvas is off-screen
const { shouldUpdate, invalidate } = createRenderPolicy([manual, windowResize, cancelIfOffScreen])
shouldUpdate() // false
invalidate()
shouldUpdate() // true

Rules would be evaluated in FILO order, with the result of lower ranking rules being passed to higher ranking rules, so this ...

createRenderPolicy([manual, cancelIfOffScreen, windowResize])

... cancels true from windowResize if the canvas is offscreen. But if the canvas is off-screen, manual's advance(n) will still take precedence.

Whereas here ...

createRenderPolicy([cancelIfOffScreen, manual, windowResize])

... cancelIfOffScreen takes precedence over the other 2 rules and cancels both if the canvas is off-screen.

Rules are relatively simple to write and users could supply their own rules, if we surface that in the API. Here's a "complicated" rule – windowResize – in its current form:

export const windowResize: CreateUpdateRule = () => {
  let invalidated = true
  const invalidate = () => { invalidated = true }
  window.addEventListener('resize', invalidate)
  return {
    shouldUpdate: (lowRankResult: boolean) => {
      const result = invalidated
      invalidated = false
      return result || lowRankResult
    },
    dispose: () => {
      window.removeEventListener('resize', invalidate)
    }
  }
}

Here's manual:

export const manual: CreateUpdateRule = () => {
  let numFramesToAdvance = 0
  return {
    advance: (numFrames = 1) => {
      numFramesToAdvance = Math.max(numFramesToAdvance, numFrames)
    },
    shouldUpdate: (lowRankResult: boolean) => {
      const result = numFramesToAdvance > 0
      numFramesToAdvance = Math.max(0, numFramesToAdvance - 1)
      return result || lowRankResult
    }
  }
}

Alternative

No response

Additional context

No response

Validations

Edit (andretchen0): fixed formatting