| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 487a4d8 commit 6aad60a
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,4 @@ | |||
| 1 | - import { describe, it, expect, beforeEach } from 'vitest'; | ||
| 1 | + import { describe, it, expect, beforeEach, vi } from 'vitest'; | ||
| 2 | 2 | ||
| 3 | 3 | import { View } from '../view'; | |
| 4 | 4 | import { Style } from '../../styling/style'; | |
@@ -168,6 +168,81 @@ describe('commitNativeUpdates', () => { | |||
| 168 | 168 | }); | |
| 169 | 169 | }); | |
| 170 | 170 | ||
| 171 | + describe('committing without a batch', () => { | ||
| 172 | + it('builds no batch for a node whose class can neither order nor observe one', () => { | ||
| 173 | + const plain: any = loaded(new TestView()); | ||
| 174 | + const ordered: any = loaded(new OrderedView()); | ||
| 175 | + const applyRemaining = vi.spyOn(NativeUpdateBatch.prototype, '_applyRemaining'); | ||
| 176 | + | ||
| 177 | + try { | ||
| 178 | + plain._batchUpdate(() => { | ||
| 179 | + plain.two = 'b'; | ||
| 180 | + plain.one = 'a'; | ||
| 181 | + }); | ||
| 182 | + NativeUpdates.batch(() => { | ||
| 183 | + plain.one = 'c'; | ||
| 184 | + }); | ||
| 185 | + plain.callUnloaded(); | ||
| 186 | + plain.two = 'd'; | ||
| 187 | + plain.flushNativeUpdates({ force: true }); | ||
| 188 | + plain.callLoaded(); | ||
| 189 | + | ||
| 190 | + expect(log).toEqual(['two=b', 'one=a', 'one=c', 'two=d']); | ||
| 191 | + expect(applyRemaining).not.toHaveBeenCalled(); | ||
| 192 | + | ||
| 193 | + log.length = 0; | ||
| 194 | + ordered._batchUpdate(() => { | ||
| 195 | + ordered.two = 'b'; | ||
| 196 | + ordered.one = 'a'; | ||
| 197 | + }); | ||
| 198 | + | ||
| 199 | + expect(log).toEqual(['commit', 'two=b', 'one=a']); | ||
| 200 | + expect(applyRemaining).toHaveBeenCalledTimes(1); | ||
| 201 | + } finally { | ||
| 202 | + applyRemaining.mockRestore(); | ||
| 203 | + } | ||
| 204 | + }); | ||
| 205 | + | ||
| 206 | + it('sweeps a mount in the same order the batch path does', () => { | ||
| 207 | + function mounted(view: any): string[] { | ||
| 208 | + log.length = 0; | ||
| 209 | + view.two = 'b'; | ||
| 210 | + view.style.three = 'c'; | ||
| 211 | + view.one = 'a'; | ||
| 212 | + view._setupUI({}); | ||
| 213 | + view.callLoaded(); | ||
| 214 | + | ||
| 215 | + return log.filter((entry) => entry !== 'commit'); | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + const withoutBatch = mounted(new TestView()); | ||
| 219 | + const withBatch = mounted(new OrderedView()); | ||
| 220 | + | ||
| 221 | + expect(withoutBatch).toEqual(['two=b', 'one=a', 'three=c']); | ||
| 222 | + expect(withBatch).toEqual(withoutBatch); | ||
| 223 | + }); | ||
| 224 | + | ||
| 225 | + it('sweeps a dirty set in the same order the batch path does', () => { | ||
| 226 | + function reloaded(view: any): string[] { | ||
| 227 | + loaded(view); | ||
| 228 | + view.callUnloaded(); | ||
| 229 | + view.two = 'b'; | ||
| 230 | + view.style.three = 'c'; | ||
| 231 | + view.one = 'a'; | ||
| 232 | + log.length = 0; | ||
| 233 | + view.callLoaded(); | ||
| 234 | + | ||
| 235 | + return log.filter((entry) => entry !== 'commit'); | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + const withoutBatch = reloaded(new TestView()); | ||
| 239 | + const withBatch = reloaded(new OrderedView()); | ||
| 240 | + | ||
| 241 | + expect(withoutBatch).toEqual(['two=b', 'three=c', 'one=a']); | ||
| 242 | + expect(withBatch).toEqual(withoutBatch); | ||
| 243 | + }); | ||
| 244 | + }); | ||
| 245 | + | ||
| 171 | 246 | describe('the batch', () => { | |
| 172 | 247 | function commitOf(view: TestView): NativeUpdateBatch { | |
| 173 | 248 | return view.commits[view.commits.length - 1]; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1554,7 +1554,24 @@ function applyNativeUpdate(batch: NativeUpdateBatch, entry: NativeUpdateEntry): | |||
| 1554 | 1554 | * `ViewBase.flushNativeUpdates` to push what is pending. | |
| 1555 | 1555 | */ | |
| 1556 | 1556 | export const initNativeView = profile('"properties".initNativeView', function initNativeView(view: ViewBase): void { | |
| 1557 | - const isMount = view._suspendedUpdates === undefined; | ||
| 1557 | + const dirty = view._suspendedUpdates; | ||
| 1558 | + const isMount = dirty === undefined; | ||
| 1559 | + | ||
| 1560 | + if (view._pendingInvalidations === undefined && view.commitNativeUpdates === defaultCommitNativeUpdates) { | ||
| 1561 | + // Nothing on this node can reach a batch, so the sweep runs straight off the dirty set, | ||
| 1562 | + // which is dropped first as on the batch path. | ||
| 1563 | + view._suspendedUpdates = {}; | ||
| 1564 | + view._pendingPrevious = undefined; | ||
| 1565 | + | ||
| 1566 | + if (isMount) { | ||
| 1567 | + applyAllNativeSetters(view); | ||
| 1568 | + } else { | ||
| 1569 | + applyDirtyNativeSetters(view, dirty); | ||
| 1570 | + } | ||
| 1571 | + | ||
| 1572 | + return; | ||
| 1573 | + } | ||
| 1574 | + | ||
| 1558 | 1575 | const batch = new NativeUpdateBatch(view, isMount, collectNativeUpdateEntries(view, isMount), view._pendingPrevious, applyNativeUpdate); | |
| 1559 | 1576 | ||
| 1560 | 1577 | // Dropped before the commit runs so that a write made from a handler queues against a fresh set. | |
@@ -1612,15 +1629,18 @@ function applyMountedNativeSetter(view: ViewBase, property: PropertyInterface): | |||
| 1612 | 1629 | * @deprecated Superseded by `ViewBase.commitNativeUpdates`, which applies the same dirty set | |
| 1613 | 1630 | * through a `NativeUpdateBatch` the class can reorder. | |
| 1614 | 1631 | */ | |
| 1615 | - export function applyPendingNativeSetters(view: ViewBase): void { | ||
| 1632 | + function applyDirtyNativeSetters(view: ViewBase, dirty: ViewBase['_suspendedUpdates']): void { | ||
| 1616 | 1633 | // TODO: Check what happens if a view was suspended and its value was reset, or set back to default! | |
| 1617 | - const suspendedUpdates = view._suspendedUpdates; | ||
| 1618 | - for (const propertyName in suspendedUpdates) { | ||
| 1619 | - if (!HAS_OWN.call(suspendedUpdates, propertyName)) continue; | ||
| 1620 | - applyPendingNativeSetter(view, <PropertyInterface>suspendedUpdates[propertyName]); | ||
| 1634 | + for (const propertyName in dirty) { | ||
| 1635 | + if (!HAS_OWN.call(dirty, propertyName)) continue; | ||
| 1636 | + applyPendingNativeSetter(view, <PropertyInterface>dirty[propertyName]); | ||
| 1621 | 1637 | } | |
| 1622 | 1638 | } | |
| 1623 | 1639 | ||
| 1640 | + export function applyPendingNativeSetters(view: ViewBase): void { | ||
| 1641 | + applyDirtyNativeSetters(view, view._suspendedUpdates); | ||
| 1642 | + } | ||
| 1643 | + | ||
| 1624 | 1644 | /** | |
| 1625 | 1645 | * @deprecated Superseded by `ViewBase.commitNativeUpdates` with a batch whose `isMount` is set. | |
| 1626 | 1646 | */ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,101 @@ | |||
| 1 | + import { describe, it, expect } from 'vitest'; | ||
| 2 | + | ||
| 3 | + import { View } from '../view'; | ||
| 4 | + import { Property } from './index'; | ||
| 5 | + | ||
| 6 | + /** | ||
| 7 | + * What a commit costs per node, where the batch is built. The numbers are printed rather than | ||
| 8 | + * asserted - the assertion is only that every pending value arrived. | ||
| 9 | + */ | ||
| 10 | + const COMMIT_PROPERTIES = 8; | ||
| 11 | + const RELOAD_PROPERTIES = 4; | ||
| 12 | + | ||
| 13 | + let calls = 0; | ||
| 14 | + | ||
| 15 | + class TestView extends View { | ||
| 16 | + createNativeView(): Object { | ||
| 17 | + return {}; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public _addViewToNativeVisualTree(): boolean { | ||
| 21 | + return true; | ||
| 22 | + } | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + const propertyNames: string[] = []; | ||
| 26 | + for (let i = 0; i < COMMIT_PROPERTIES; i++) { | ||
| 27 | + const property = new Property<TestView, number>({ name: `commit${i}`, defaultValue: -1 }); | ||
| 28 | + property.register(TestView); | ||
| 29 | + (TestView.prototype as any)[property.setNative] = function () { | ||
| 30 | + calls++; | ||
| 31 | + }; | ||
| 32 | + propertyNames.push(property.name); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + function write(view: any, count: number, value: number): void { | ||
| 36 | + for (let i = 0; i < count; i++) { | ||
| 37 | + view[propertyNames[i]] = value; | ||
| 38 | + } | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + function loadedView(): any { | ||
| 42 | + const view: any = new TestView(); | ||
| 43 | + view._setupUI({}); | ||
| 44 | + view.callLoaded(); | ||
| 45 | + | ||
| 46 | + return view; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + function measure(label: string, iterations: number, unit: string, callsEach: number, run: (iterations: number) => void): void { | ||
| 50 | + run(Math.max(1, Math.round(iterations / 10))); | ||
| 51 | + | ||
| 52 | + calls = 0; | ||
| 53 | + const started = performance.now(); | ||
| 54 | + run(iterations); | ||
| 55 | + const elapsed = performance.now() - started; | ||
| 56 | + | ||
| 57 | + console.log(`commit ${label}: ${iterations} ${unit} in ${elapsed.toFixed(1)}ms (${((elapsed * 1000) / iterations).toFixed(2)}us each)`); | ||
| 58 | + expect(calls).toBe(callsEach * iterations); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + describe('commit cost', () => { | ||
| 62 | + it('loads views carrying pending properties', () => { | ||
| 63 | + measure('load', 2000, 'views', COMMIT_PROPERTIES, (iterations) => { | ||
| 64 | + for (let i = 0; i < iterations; i++) { | ||
| 65 | + const view: any = new TestView(); | ||
| 66 | + write(view, COMMIT_PROPERTIES, i); | ||
| 67 | + view._setupUI({}); | ||
| 68 | + view.callLoaded(); | ||
| 69 | + } | ||
| 70 | + }); | ||
| 71 | + }); | ||
| 72 | + | ||
| 73 | + it('reloads views written to while unloaded', () => { | ||
| 74 | + const views: any[] = []; | ||
| 75 | + for (let i = 0; i < 2200; i++) { | ||
| 76 | + views.push(loadedView()); | ||
| 77 | + } | ||
| 78 | + let next = 0; | ||
| 79 | + | ||
| 80 | + measure('reload', 2000, 'views', RELOAD_PROPERTIES, (iterations) => { | ||
| 81 | + for (let i = 0; i < iterations; i++) { | ||
| 82 | + const view = views[next++ % views.length]; | ||
| 83 | + view.callUnloaded(); | ||
| 84 | + write(view, RELOAD_PROPERTIES, i); | ||
| 85 | + view.callLoaded(); | ||
| 86 | + } | ||
| 87 | + }); | ||
| 88 | + }); | ||
| 89 | + | ||
| 90 | + it('commits batched writes on a live view', () => { | ||
| 91 | + const view = loadedView(); | ||
| 92 | + | ||
| 93 | + measure('_batchUpdate', 5000, 'batches', COMMIT_PROPERTIES, (iterations) => { | ||
| 94 | + for (let i = 0; i < iterations; i++) { | ||
| 95 | + view._batchUpdate(() => { | ||
| 96 | + write(view, COMMIT_PROPERTIES, i); | ||
| 97 | + }); | ||
| 98 | + } | ||
| 99 | + }); | ||
| 100 | + }); | ||
| 101 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments