-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(core): native update scheduler and commit hook (phase 0) #11414
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
Draft
edusperoni
wants to merge
6
commits into
main
Choose a base branch
from
feat/native-updates-phase0
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
7cba559
test(core): add native-update oracle specs
edusperoni f144b43
refactor(core): unify native property routing
edusperoni d316686
feat(core): add commitNativeUpdates hook and NativeUpdateBatch
edusperoni 8753f5d
feat(core): add NativeUpdates scheduler and flushNativeUpdates
edusperoni 487a4d8
feat(core): add invalidates option to properties
edusperoni 6aad60a
perf(core): commit without a batch for nodes that cannot observe one
edusperoni File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import type { ViewBase } from '../view-base'; | ||
| import type { CssAnimationProperty, CssProperty, Property } from '../properties'; | ||
| import type { Invalidation } from './invalidation'; | ||
|
|
||
| /** A change to a node's child list. Phase 0 never records one. */ | ||
| export interface ChildMutation { | ||
| readonly kind: 'insert' | 'remove' | 'move'; | ||
| readonly child: ViewBase; | ||
| readonly index: number; | ||
| } | ||
|
|
||
| export type NativeUpdateProperty = Property<any, any> | CssProperty<any, any> | CssAnimationProperty<any, any>; | ||
|
|
||
| /** What a commit applies: a property, through its `[setNative]`, or an aggregate invalidation. */ | ||
| export type NativeUpdateEntry = NativeUpdateProperty | Invalidation; | ||
|
|
||
| export type NativeUpdateApplier = (batch: NativeUpdateBatch, entry: NativeUpdateEntry) => void; | ||
|
|
||
| const NO_CHILDREN: readonly ChildMutation[] = Object.freeze([]); | ||
|
|
||
| /** | ||
| * Everything that is dirty on one node at commit time, in the order the commit will apply it. | ||
| * Handed to `ViewBase.commitNativeUpdates`, which may reorder, pre-empt or drop entries. | ||
| */ | ||
| export class NativeUpdateBatch { | ||
| /** Every value the node carries is dirty: it is being applied to a native view for the first time. */ | ||
| public readonly isMount: boolean; | ||
| public readonly children: readonly ChildMutation[] = NO_CHILDREN; | ||
|
|
||
| private readonly _entries: NativeUpdateEntry[]; | ||
| private readonly _handled: boolean[]; | ||
| private readonly _apply: NativeUpdateApplier; | ||
| private readonly _previous: Map<NativeUpdateProperty, unknown> | undefined; | ||
| private _index: Map<NativeUpdateEntry, number> | undefined; | ||
|
|
||
| constructor( | ||
| public readonly node: ViewBase, | ||
| isMount: boolean, | ||
| entries: NativeUpdateEntry[], | ||
| previous: Map<NativeUpdateProperty, unknown> | undefined, | ||
| apply: NativeUpdateApplier, | ||
| ) { | ||
| this.isMount = isMount; | ||
| this._entries = entries; | ||
| this._handled = new Array(entries.length).fill(false); | ||
| this._previous = previous; | ||
| this._apply = apply; | ||
| } | ||
|
|
||
| /** Everything this commit applies, in commit order; `has()` tells whether an entry is still pending. */ | ||
| public get entries(): ReadonlyArray<NativeUpdateEntry> { | ||
| return this._entries; | ||
| } | ||
|
|
||
| /** Whether the entry is still waiting to be applied by this commit. */ | ||
| public has(entry: NativeUpdateEntry): boolean { | ||
| const index = this.indexOf(entry); | ||
|
|
||
| return index >= 0 && !this._handled[index]; | ||
| } | ||
|
|
||
| /** The value the property held at the last commit; `undefined` on a mount. */ | ||
| public previous<T>(property: NativeUpdateProperty): T | undefined { | ||
| return this._previous?.get(property) as T | undefined; | ||
| } | ||
|
|
||
| /** Runs the entry's handler now. An entry that is not pending, or already handled, is a no-op. */ | ||
| public apply(entry: NativeUpdateEntry): void { | ||
| const index = this.indexOf(entry); | ||
| if (index < 0 || this._handled[index]) { | ||
| return; | ||
| } | ||
|
|
||
| this._handled[index] = true; | ||
| this._apply(this, this._entries[index]); | ||
| } | ||
|
|
||
| /** Marks the entry handled without touching the native view. */ | ||
| public skip(entry: NativeUpdateEntry): void { | ||
| const index = this.indexOf(entry); | ||
| if (index >= 0) { | ||
| this._handled[index] = true; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Applies everything still pending, in commit order. | ||
| * @private | ||
| */ | ||
| public _applyRemaining(): void { | ||
| const entries = this._entries; | ||
| const handled = this._handled; | ||
|
|
||
| for (let i = 0, length = entries.length; i < length; i++) { | ||
| // A handler may apply later entries itself, so the flag is re-read on every step. | ||
| if (!handled[i]) { | ||
| handled[i] = true; | ||
| this._apply(this, entries[i]); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private indexOf(entry: NativeUpdateEntry): number { | ||
| let index = this._index; | ||
| if (!index) { | ||
| index = this._index = new Map<NativeUpdateEntry, number>(); | ||
| const entries = this._entries; | ||
| for (let i = 0, length = entries.length; i < length; i++) { | ||
| index.set(entries[i], i); | ||
| } | ||
| } | ||
|
|
||
| const found = index.get(entry); | ||
|
|
||
| return found === undefined ? -1 : found; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export { Invalidation, InvalidationPhase } from './invalidation'; | ||
| export type { InvalidationOptions } from './invalidation'; | ||
| export { NativeUpdateBatch } from './batch'; | ||
| export type { ChildMutation, NativeUpdateApplier, NativeUpdateProperty, NativeUpdateEntry } from './batch'; | ||
| export { NativeUpdates } from './scheduler'; | ||
| export type { FlushNativeUpdatesOptions, NativeUpdatesMode } from './scheduler'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /** | ||
| * The order a commit applies invalidations in. Phase 0 carries the phase but does not order by | ||
| * it yet: nothing in core declares an invalidation, so every batch is properties then aggregates. | ||
| */ | ||
| export enum InvalidationPhase { | ||
| Style, | ||
| Structure, | ||
| Mount, | ||
| Props, | ||
| Content, | ||
| Layout, | ||
| Paint, | ||
| } | ||
|
|
||
| export interface InvalidationOptions { | ||
| readonly phase?: InvalidationPhase; | ||
| /** The handler needs a laid out size, so a commit may have to defer it until there is one. */ | ||
| readonly needsBounds?: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * A named dirty flag several properties can share, so that one native rebuild serves all of them. | ||
| * A node handles it with `[invalidation.apply](batch)`, mirroring `[property.setNative](value)`. | ||
| */ | ||
| export class Invalidation { | ||
| public readonly name: string; | ||
| public readonly apply: symbol; | ||
| public readonly phase: InvalidationPhase; | ||
| public readonly needsBounds: boolean; | ||
|
|
||
| constructor(name: string, options?: InvalidationOptions) { | ||
| this.name = name; | ||
| this.apply = Symbol(`${name}:applyInvalidation`); | ||
| this.phase = options?.phase ?? InvalidationPhase.Props; | ||
| this.needsBounds = options?.needsBounds ?? false; | ||
| } | ||
|
|
||
| public toString(): string { | ||
| return `Invalidation(${this.name})`; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.