From 1df1d03e7995958b77cc7ab535db51113761cb85 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sat, 21 Dec 2024 13:55:13 -0500 Subject: [PATCH 1/4] wip: RFC: 669 --- packages/@glimmer/tracking/primitives/storage.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/@glimmer/tracking/primitives/storage.ts diff --git a/packages/@glimmer/tracking/primitives/storage.ts b/packages/@glimmer/tracking/primitives/storage.ts new file mode 100644 index 00000000000..e1cddf77bbd --- /dev/null +++ b/packages/@glimmer/tracking/primitives/storage.ts @@ -0,0 +1,3 @@ +export function createStorage() {} +export function getValue() {} +export function setValue() {} From 5fc2c12ad11e44f7dc20afeecb5025204b2979d7 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sun, 19 Jan 2025 11:16:29 -0500 Subject: [PATCH 2/4] eh, this needs to go in the glimmer-vm repo --- .../@ember/-internals/metal/lib/storage.ts | 21 ++++++++++++ .../@glimmer/tracking/primitives/storage.ts | 13 +++++-- .../@glimmer/tracking/tests/storage_test.ts | 34 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 packages/@ember/-internals/metal/lib/storage.ts create mode 100644 packages/@glimmer/tracking/tests/storage_test.ts diff --git a/packages/@ember/-internals/metal/lib/storage.ts b/packages/@ember/-internals/metal/lib/storage.ts new file mode 100644 index 00000000000..d36db6f6995 --- /dev/null +++ b/packages/@ember/-internals/metal/lib/storage.ts @@ -0,0 +1,21 @@ +import { trackedData } from '@glimmer/validator'; + +interface Storage { + /** + * This is not a real property. Don't use it. + * This is functioning as a brand-type for use with + * - createStorage + * - getValue + * - setValue + * + * from @glimmer/trcking/primitives/storage + */ + __storage: Value; +} + +export function createStorage( + initialValue?: Value, + isEqual?: (oldValue: Value, newValue: Value) => boolean +): Storage {} +export function getValue(storage: Storage): Value {} +export function setValue(storage: Storage, value: Value): void {} diff --git a/packages/@glimmer/tracking/primitives/storage.ts b/packages/@glimmer/tracking/primitives/storage.ts index e1cddf77bbd..a3ac3b4847d 100644 --- a/packages/@glimmer/tracking/primitives/storage.ts +++ b/packages/@glimmer/tracking/primitives/storage.ts @@ -1,3 +1,10 @@ -export function createStorage() {} -export function getValue() {} -export function setValue() {} +export { createStorage, getValue, setValue } from '@ember/-internals/metal/lib/storage'; + +/** + * NOTE: '@ember/-internals/metal' already exports a getValue function + * from @glimmer/tracking/primitives/cache.ts, + * so we can't use the pattern of re-export everything from + * @ember/-internals/metal + * + * At somepoint we need to untangle all that actually move things to their appropriate packages. + */ diff --git a/packages/@glimmer/tracking/tests/storage_test.ts b/packages/@glimmer/tracking/tests/storage_test.ts new file mode 100644 index 00000000000..58f40014b9f --- /dev/null +++ b/packages/@glimmer/tracking/tests/storage_test.ts @@ -0,0 +1,34 @@ +import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; + +import { createStorage, getValue, setValue } from '@glimmer/tracking/primitives/storage'; +import { createCache, getValue as getCacheValue } from '@glimmer/tracking/primitives/cache'; + +moduleFor( + '@glimmer/tracking/primitives/storage', + class extends AbstractTestCase { + ['@test it works'](assert: QUnit['assert']) { + let count = 0; + + let storage = createStorage(); + + let cache = createCache(() => { + getValue(storage); + return ++count; + }); + + assert.equal(getValue(storage), undefined, 'does not have a value initially'); + assert.equal(getCacheValue(cache), 1, 'cache runs the first time'); + assert.equal(getCacheValue(cache), 1, 'cache does not the second time'); + + setValue(storage, 123); + + assert.equal(getValue(storage), 123, 'value is set correctly'); + assert.equal(getCacheValue(cache), 2, 'cache ran after storage was set'); + + setValue(storage, 123); + + assert.equal(getValue(storage), 123, 'value remains the same'); + assert.equal(getCacheValue(cache), 2, 'cache not ran after storage was set to same value'); + } + } +); From d4487929ff7c4f076af7833d425f3659952bd413 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:22:56 -0500 Subject: [PATCH 3/4] Progress? --- .../@ember/-internals/metal/lib/storage.ts | 90 +++++++++++++++---- 1 file changed, 73 insertions(+), 17 deletions(-) diff --git a/packages/@ember/-internals/metal/lib/storage.ts b/packages/@ember/-internals/metal/lib/storage.ts index d36db6f6995..5b9eede60f8 100644 --- a/packages/@ember/-internals/metal/lib/storage.ts +++ b/packages/@ember/-internals/metal/lib/storage.ts @@ -1,21 +1,77 @@ -import { trackedData } from '@glimmer/validator'; - -interface Storage { - /** - * This is not a real property. Don't use it. - * This is functioning as a brand-type for use with - * - createStorage - * - getValue - * - setValue - * - * from @glimmer/trcking/primitives/storage - */ - __storage: Value; +import type { UpdatableTag } from '@glimmer/interfaces'; + +import { consumeTag } from './tracking'; +import { createUpdatableTag, DIRTY_TAG } from './validators'; +import { assert } from '@ember/debug'; + +const SET = Symbol.for('TrackedStorage.set'); +const READ = Symbol.for('TrackedStorage.read'); + +function tripleEq(a: Value, b: Value): boolean { + return a === b; +} + +class Storage { + #tag: UpdatableTag; + #value: Value; + #lastValue: Value; + #isEqual: (a: Value, b: Value) => boolean; + + get #current() { + consumeTag(this.#tag); + return this.#value; + } + set #current(value) { + if (this.#isEqual(this.#value, this.#lastValue)) { + return; + } + + this.#value = this.#lastValue = value; + + DIRTY_TAG(this.#tag); + } + + constructor(initialValue: Value, isEqual?: (a: Value, b: Value) => boolean) { + this.#tag = createUpdatableTag(); + this.#value = this.#lastValue = initialValue; + this.#isEqual = isEqual ?? tripleEq; + } + + [READ]() { + return this.#current; + } + + [SET](value: Value) { + this.#current = value; + } } export function createStorage( - initialValue?: Value, + initialValue: Value, isEqual?: (oldValue: Value, newValue: Value) => boolean -): Storage {} -export function getValue(storage: Storage): Value {} -export function setValue(storage: Storage, value: Value): void {} +): Storage { + assert( + 'the second parameter to `createStorage` must be an equality function or undefined', + isEqual === undefined || typeof isEqual === 'function' + ); + + return new Storage(initialValue, isEqual); +} + +export function getValue(storage: Storage): Value { + assert( + 'getValue must be passed a tracked store created with `createStorage`.', + storage instanceof Storage + ); + + return storage[READ](); +} + +export function setValue(storage: Storage, value: Value): void { + assert( + 'setValue must be passed a tracked store created with `createStorage`.', + storage instanceof Storage + ); + + storage[SET](value); +} From e6f07d5cf92d73910d47fc73cfc8833ca69a48e0 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:31:46 -0500 Subject: [PATCH 4/4] Copy tests from the polyfill --- .../metal/tests/tracked/storage_test.js | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 packages/@ember/-internals/metal/tests/tracked/storage_test.js diff --git a/packages/@ember/-internals/metal/tests/tracked/storage_test.js b/packages/@ember/-internals/metal/tests/tracked/storage_test.js new file mode 100644 index 00000000000..c34c8a94ebd --- /dev/null +++ b/packages/@ember/-internals/metal/tests/tracked/storage_test.js @@ -0,0 +1,85 @@ +import { createCache, getValue as getCacheValue } from '../../lib/cache'; + +import { createStorage, getValue, setValue } from '../../lib/storage'; + +import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; + +moduleFor( + '@ember/-internals/metal/storage', + class extends AbstractTestCase { + ['@test it works'](assert) { + let count = 0; + + let storage = createStorage(); + + let cache = createCache(() => { + getValue(storage); + return ++count; + }); + + assert.equal(getValue(storage), undefined, 'does not have a value initially'); + assert.equal(getCacheValue(cache), 1, 'cache runs the first time'); + assert.equal(getCacheValue(cache), 1, 'cache does not the second time'); + + setValue(storage, 123); + + assert.equal(getValue(storage), 123, 'value is set correctly'); + assert.equal(getCacheValue(cache), 2, 'cache ran after storage was set'); + + setValue(storage, 123); + + assert.equal(getValue(storage), 123, 'value remains the same'); + assert.equal(getCacheValue(cache), 2, 'cache not ran after storage was set to same value'); + } + + ['@test it can set an initial value'](assert) { + let count = 0; + + let storage = createStorage(123); + + let cache = createCache(() => { + getValue(storage); + return ++count; + }); + + assert.equal(getValue(storage), 123, 'has a initial value'); + assert.equal(getCacheValue(cache), 1, 'cache runs the first time'); + assert.equal(getCacheValue(cache), 1, 'cache does not the second time'); + + setValue(storage, 123); + + assert.equal(getValue(storage), 123, 'value is not updated'); + assert.equal(getCacheValue(cache), 1, 'cache not ran after storage was set to same value'); + + setValue(storage, 456); + + assert.equal(getValue(storage), 456, 'value updated'); + assert.equal(getCacheValue(cache), 2, 'cache ran after storage was set to different value'); + } + + ['@test it can set an equality function'](assert) { + let count = 0; + + let storage = createStorage(123, () => false); + + let cache = createCache(() => { + getValue(storage); + return ++count; + }); + + assert.equal(getValue(storage), 123, 'has a initial value'); + assert.equal(getCacheValue(cache), 1, 'cache runs the first time'); + assert.equal(getCacheValue(cache), 1, 'cache does not the second time'); + + setValue(storage, 123); + + assert.equal(getValue(storage), 123, 'value is not updated'); + assert.equal(getCacheValue(cache), 2, 'cache runs again'); + + setValue(storage, 456); + + assert.equal(getValue(storage), 456, 'value updated'); + assert.equal(getCacheValue(cache), 3, 'cache ran after storage was set to different value'); + } + } +);