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

Asynchronous Routing #208

Open
seveibar opened this issue Oct 28, 2024 · 0 comments
Open

Asynchronous Routing #208

seveibar opened this issue Oct 28, 2024 · 0 comments

Comments

@seveibar
Copy link
Contributor

seveibar commented Oct 28, 2024

React is inherently synchronous, but when react components render they attach "async effects" to fibers (e.g. via useEffect). These async effects cause the subsequent rerenders.

Like React, tscircuit core should fully render synchronously and have async effects that trigger re-rendering.

Notes:

  • subcircuits are fully rendered prior to parent circuits being rendered
  • Introduce renderUntilSettled to render until all async effects have completed

For simplicity, routing should be moved out of Trace and into subcircuit Group to ensure that when autorouting requests are sent they are not required to execute serially (we don't want each Trace making a request to autoroute, instead we want one request for all traces to the external autorouter)

Proposal: "Dirty Pattern" (_markDirty) with _queueAsyncEffect

The "Dirty Pattern" is a common pattern in graphics programming, e.g. it's used by ThreeJS. This pattern involves marking objects as "dirty" to indicate they need to be re-rendered. Typically you would perform a state mutation

Implement `Renderable._markDirty(phase: string) to mark that an object needs to be re-rendered.

Implement _queueAsyncEffect which will allow tracking of async effects for determining if a render is fully settled.

Here's an example of how it works to do autorouting:

doInitialPcbTraceRender() {
  if (this._parsedProps.remotelyRoute) {
    this._queueAsyncEffect(async () => {
       const { job_id } = await fetch("autorouting.com/jobs/start", {/* ... */}).then(r => r.json())
       
       let partialResult = await fetch("autorouting.com/jobs/get?job_id=...")
       this.tracesCompleted = 0
       while (partialResult.isNotComplete) {
          if (partialResult.tracesCompleted > this.tracesCompleted) {
            this.tracesCompleted = partialResult.tracesCompleted
            this._markDirty("PcbTraceRender")
        }
    })
  }
}

Mark dirty can trigger a re-render if configured in Circuit. When render is called, descendants of the root circuit will be checked to see if they're dirty, and which phases need to be re-rendered.

Proposal: Early exit render pipeline when there are effects from the last executed render phase

This means that each render phase can expect that all previous render phases fully executed. This is problematic
while the render pipeline is completely linear because you could theoretically be waiting on the schematic to complete
just to see the PCB. If we had a proper DAG for phase dependencies, you wouldn't have to wait for independent phase-chains
to complete.

Alternative: Render Effects

Every renderable object will have an array, _renderEffects which contains RenderEffect and _renderStates which contains RenderState

interface IRenderEffect {
  phase: string
  hasExecuted: boolean
  execute: () => void
  cleanup: () => void
}

interface IRenderState {
  phase: string
  currentValue: any
  set: (val: any) => void
  on: ("change", fn: Function) => void
}
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

1 participant