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

Tyxml_lwd scheduler: WIP #27

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Tyxml_lwd scheduler: WIP #27

wants to merge 1 commit into from

Conversation

let-def
Copy link
Owner

@let-def let-def commented Nov 2, 2020

This PR introduces a scheduler for updating the DOM, such that it is no longer necessary to manually use Lwd.root and to call requestAnimationFrame.

The important part is this new module in Tyxml_lwd.Lwdom:

module Scheduler : sig

  val on_next_frame : (Lwd.release_queue -> unit) -> unit
  (** Add a function to execute at the beginning of next frame, before updating the DOM *)

  type job
  (** A scheduling job maintains a binding between a declarative specification,
      a value of type `_ node live`, and a real DOM node. *)

  val append_to_dom : 'a node live -> #Dom.node Js.t -> job
  (** [append_to_dom spec dom_node] returns a job that adds
      and maintains up to date all nodes from [spec] as children
      of [dom_node] *)

  val disable : job -> unit
  (** Stop a job. Associated nodes will no longer be updated.
      They are available for garbage collection, unless the job
      is [reenable]d later. *)

  val reenable : job -> unit
  (** Resume a stoped job. Changes will be tracked and the DOM updated again. *)
 
  (** Impose limit on non-stabilizing jobs.
      Normally, after a single pass, the document should be updated.
      However, in certain circumstances, multiple passes are necessary.
      For instance some updates triggered a change in a layout condition that requires
      the tree to be updated.
      However, these changes should converge quickly and yield a stable document after
      a few number of passes.
     
      The settings below determine how ill-behaved job should be handled. *)
  type limit = {

    cycles_warning: int;
    (** Warn in the console if a job has not stabilized after
        [cycles_warning] update cycles.
        default: 5 *)

    cycles_max: int;
    (** Stop updating a job in current frame if it has not stabilized
        after [cycles_max] update cycles.
        Evaluation will continue for next frame.*)
        default: 15 *)

    time_budget: float;
    (** Stop updating a job in current frame if it has not stabilized
        and [time_budget] milliseconds have already been spent computing
        the current frame.
        Evaluation will continue for next frame.
        The intention is to avoid missing too much frames in degenerate
        circumstances.
        default: 16.6 (in milliseconds) *)

    time_warning: bool;
    (** Warn, in error console, if a job exceeds its time budget.
        default: true *)
  }

  val set_limit : job -> limit -> unit
  (** Change the limits of a job *)
end

The limit strategies are intended to keep the application usable in degenerate conditions.
They play on two parameters: number of update cycles and time spent updating.

These limits should not be reached in a well-designed application, they are here to offer a "best-effort"
fallback and help debugging performance issues without freezing the page.

TODO:

  • improve the logging facility, especially in case of error
  • disabling a job does not but should remove bound nodes from the DOM
  • document API inline

@let-def
Copy link
Owner Author

let-def commented Nov 2, 2020

@voodoos
Copy link

voodoos commented Mar 2, 2022

@let-def any news on this ?

@let-def
Copy link
Owner Author

let-def commented Mar 3, 2022

There are two parts to the problem, that partially overlaps:

  • dealing with integration to the DOM
  • dealing with "non-linear" computations (that might need multiple passes to stabilize... or might even diverge).

Since this PR, I worked on a more generic / elegant solution for the second problem : the fix operator that is part of the 0.2 release. In theory, this allows one to locally specify a computation that is known to not be linear, and report nicer error messages when it is not the case. So for dealing with linearity, this is clearly the solution to favor.

I am not totally satisfied with fix yet.

The rest of the PR for Dom integration is still relevant though, I will clean that up soon, and also provide corresponding functions for Brr.

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

Successfully merging this pull request may close these issues.

2 participants