chore(quark): sync vendored keyed and layout with standalone
This commit is contained in:
parent
088f0df47a
commit
fc310da14d
2 changed files with 64 additions and 48 deletions
|
|
@ -133,6 +133,7 @@ export namespace Keyed {
|
||||||
|
|
||||||
function publish(slot: Writable<A>, value: A) {
|
function publish(slot: Writable<A>, value: A) {
|
||||||
const current = slot()
|
const current = slot()
|
||||||
|
// alien-signals uses SameValueZero-compatible identity for primitive writes.
|
||||||
if (current === value || equivalent(current, value)) {
|
if (current === value || equivalent(current, value)) {
|
||||||
if (options.metrics) options.metrics.equivalenceSuppressions++
|
if (options.metrics) options.metrics.equivalenceSuppressions++
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,10 @@ export namespace Layout {
|
||||||
readonly fields: StructFields
|
readonly fields: StructFields
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Union<Tag extends PropertyKey, Variants extends Readonly<Record<string, Field<unknown>>>>
|
export interface Union<
|
||||||
extends Field<Variant<Tag, Variants>> {
|
Tag extends PropertyKey,
|
||||||
|
Variants extends Readonly<Record<string, Field<unknown>>>,
|
||||||
|
> extends Field<Variant<Tag, Variants>> {
|
||||||
readonly type: "union"
|
readonly type: "union"
|
||||||
readonly tag: Tag
|
readonly tag: Tag
|
||||||
readonly variants: Variants
|
readonly variants: Variants
|
||||||
|
|
@ -66,7 +68,7 @@ export namespace Layout {
|
||||||
readonly equivalent: (left: A, right: A) => boolean
|
readonly equivalent: (left: A, right: A) => boolean
|
||||||
/** Bitmask of changed top-level fields; 0 means equivalent. Consistent with `equivalent`. */
|
/** Bitmask of changed top-level fields; 0 means equivalent. Consistent with `equivalent`. */
|
||||||
readonly diff: (left: A, right: A) => number
|
readonly diff: (left: A, right: A) => number
|
||||||
/** Every known top-level property name to its change bit; immutable, key, and tag names map to 0. */
|
/** Every known top-level property name to its change bit; immutable and key names map to 0. */
|
||||||
readonly bits: ReadonlyMap<PropertyKey, number>
|
readonly bits: ReadonlyMap<PropertyKey, number>
|
||||||
readonly keyOf: (value: A) => Key
|
readonly keyOf: (value: A) => Key
|
||||||
make(initial?: readonly A[], options?: { readonly metrics?: Keyed.Metrics }): Keyed.Keyed<A, Key>
|
make(initial?: readonly A[], options?: { readonly metrics?: Keyed.Metrics }): Keyed.Keyed<A, Key>
|
||||||
|
|
@ -215,8 +217,7 @@ export namespace Layout {
|
||||||
>(layout: KeyedUnion<Name, A, Tag, Variants>, options?: CompileOptions): Plan<KeyedVariant<Name, A, Tag, Variants>, A>
|
>(layout: KeyedUnion<Name, A, Tag, Variants>, options?: CompileOptions): Plan<KeyedVariant<Name, A, Tag, Variants>, A>
|
||||||
export function compile(input: unknown, options: CompileOptions = {}): unknown {
|
export function compile(input: unknown, options: CompileOptions = {}): unknown {
|
||||||
const layout = input as
|
const layout = input as
|
||||||
| Struct<Fields>
|
Struct<Fields> | KeyedUnion<PropertyKey, unknown, PropertyKey, Readonly<Record<string, Field<unknown>>>>
|
||||||
| KeyedUnion<PropertyKey, unknown, PropertyKey, Readonly<Record<string, Field<unknown>>>>
|
|
||||||
if (layout.type === "keyed-union") {
|
if (layout.type === "keyed-union") {
|
||||||
const model = unionDiffModel(layout.key.name, layout.tag, layout.variants)
|
const model = unionDiffModel(layout.key.name, layout.tag, layout.variants)
|
||||||
return makePlan(
|
return makePlan(
|
||||||
|
|
@ -311,17 +312,14 @@ export namespace Layout {
|
||||||
initial: readonly A[],
|
initial: readonly A[],
|
||||||
options?: { readonly metrics?: Keyed.Metrics },
|
options?: { readonly metrics?: Keyed.Metrics },
|
||||||
): Collection<A, Key, Definitions> {
|
): Collection<A, Key, Definitions> {
|
||||||
// The most recent publication's field diff, captured from the equivalence
|
// `pending` hands a diff the collection already computed for a staged
|
||||||
// callback so index refresh can gate projections on it. `pending` hands a
|
// mutation to the comparator, so equivalence costs one comparison, not two.
|
||||||
// diff the collection already computed for a staged mutation to the
|
let pending: number | ReadonlyMap<Key, number> | undefined
|
||||||
// comparator, so equivalence costs one comparison, not two.
|
|
||||||
let lastDiff = -1
|
|
||||||
let pending = -1
|
|
||||||
const values = Keyed.make<A, Key>({
|
const values = Keyed.make<A, Key>({
|
||||||
key: plan.keyOf,
|
key: plan.keyOf,
|
||||||
equivalent(left, right) {
|
equivalent(left, right) {
|
||||||
lastDiff = pending !== -1 ? pending : plan.diff(left, right)
|
const mask = typeof pending === "number" ? pending : pending?.get(plan.keyOf(left))
|
||||||
return lastDiff === 0
|
return (mask ?? plan.diff(left, right)) === 0
|
||||||
},
|
},
|
||||||
metrics: options?.metrics,
|
metrics: options?.metrics,
|
||||||
})
|
})
|
||||||
|
|
@ -348,15 +346,20 @@ export namespace Layout {
|
||||||
},
|
},
|
||||||
getOwnPropertyDescriptor(_, property) {
|
getOwnPropertyDescriptor(_, property) {
|
||||||
trackMask = -1
|
trackMask = -1
|
||||||
return Reflect.getOwnPropertyDescriptor(trackTarget!, property)
|
const descriptor = Reflect.getOwnPropertyDescriptor(trackTarget!, property)
|
||||||
|
return descriptor && { ...descriptor, configurable: true }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
function tracked<T>(value: A, f: (value: A) => T) {
|
function tracked<T>(value: A, f: (value: A) => T) {
|
||||||
trackTarget = value as Record<PropertyKey, unknown>
|
trackTarget = value as Record<PropertyKey, unknown>
|
||||||
trackMask = 0
|
trackMask = 0
|
||||||
const result = f(trackProxy as A)
|
try {
|
||||||
trackTarget = undefined
|
const result = f(trackProxy as A)
|
||||||
return result
|
return { result, reads: trackMask }
|
||||||
|
} finally {
|
||||||
|
trackTarget = undefined
|
||||||
|
trackMask = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each index entry stages user-code projections before any state mutation
|
// Each index entry stages user-code projections before any state mutation
|
||||||
|
|
@ -394,18 +397,22 @@ export namespace Layout {
|
||||||
name,
|
name,
|
||||||
reads: (key) => readsByKey.get(key),
|
reads: (key) => readsByKey.get(key),
|
||||||
stage(key, value) {
|
stage(key, value) {
|
||||||
const source = tracked(value, extract)
|
|
||||||
const reads = trackMask
|
|
||||||
const previous = byKey.get(key)
|
const previous = byKey.get(key)
|
||||||
const members = source === previous?.source ? previous.members : new Set(source)
|
const trackedMembers = tracked(value, (candidate) => {
|
||||||
return () => {
|
const source = extract(candidate)
|
||||||
readsByKey.set(key, reads)
|
return {
|
||||||
if (!previous) members.forEach((member) => adjust(member, 1))
|
source,
|
||||||
if (previous && previous.members !== members) {
|
members: source === previous?.source ? previous.members : new Set(source),
|
||||||
members.forEach((member) => !previous.members.has(member) && adjust(member, 1))
|
|
||||||
previous.members.forEach((member) => !members.has(member) && adjust(member, -1))
|
|
||||||
}
|
}
|
||||||
byKey.set(key, { source, members })
|
})
|
||||||
|
return () => {
|
||||||
|
readsByKey.set(key, trackedMembers.reads)
|
||||||
|
if (!previous) trackedMembers.result.members.forEach((member) => adjust(member, 1))
|
||||||
|
if (previous && previous.members !== trackedMembers.result.members) {
|
||||||
|
trackedMembers.result.members.forEach((member) => !previous.members.has(member) && adjust(member, 1))
|
||||||
|
previous.members.forEach((member) => !trackedMembers.result.members.has(member) && adjust(member, -1))
|
||||||
|
}
|
||||||
|
byKey.set(key, trackedMembers.result)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applyDelta(key, change) {
|
applyDelta(key, change) {
|
||||||
|
|
@ -468,18 +475,17 @@ export namespace Layout {
|
||||||
reads: (key) => readsByKey.get(key),
|
reads: (key) => readsByKey.get(key),
|
||||||
stage(key, value, mode) {
|
stage(key, value, mode) {
|
||||||
const matched = tracked(value, matches)
|
const matched = tracked(value, matches)
|
||||||
const reads = trackMask
|
|
||||||
return (valueSlot) => {
|
return (valueSlot) => {
|
||||||
readsByKey.set(key, reads)
|
readsByKey.set(key, matched.reads)
|
||||||
const previous = matching.has(key)
|
const previous = matching.has(key)
|
||||||
if (matched) matching.add(key)
|
if (matched.result) matching.add(key)
|
||||||
if (!matched) matching.delete(key)
|
if (!matched.result) matching.delete(key)
|
||||||
if (mode === "add") return
|
if (mode === "add") return
|
||||||
if (slot === valueSlot && !matched) {
|
if (slot === valueSlot && !matched.result) {
|
||||||
findFirst()
|
findFirst()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (slot !== valueSlot && !previous && matched) afterPlacement(valueSlot)
|
if (slot !== valueSlot && !previous && matched.result) afterPlacement(valueSlot)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
remove(key, removedSlot) {
|
remove(key, removedSlot) {
|
||||||
|
|
@ -524,19 +530,25 @@ export namespace Layout {
|
||||||
// Indexes are plain data, not reactive state; the transaction exists so
|
// Indexes are plain data, not reactive state; the transaction exists so
|
||||||
// subscribers cannot observe published values with stale indexes. With
|
// subscribers cannot observe published values with stale indexes. With
|
||||||
// no staged commits there is nothing to observe out of order.
|
// no staged commits there is nothing to observe out of order.
|
||||||
if (!staged) {
|
if (!staged) return publishValue()
|
||||||
pending = mask
|
|
||||||
const changed = values.update(value)
|
|
||||||
pending = -1
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
return Transaction.run(() => {
|
return Transaction.run(() => {
|
||||||
pending = mask
|
const changed = publishValue()
|
||||||
const changed = values.update(value)
|
|
||||||
pending = -1
|
|
||||||
if (changed) staged.forEach((commit) => commit(slot))
|
if (changed) staged.forEach((commit) => commit(slot))
|
||||||
return changed
|
return changed
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function publishValue() {
|
||||||
|
return withPending(mask, () => values.update(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function withPending<T>(mask: number | ReadonlyMap<Key, number>, f: () => T) {
|
||||||
|
pending = mask
|
||||||
|
try {
|
||||||
|
return f()
|
||||||
|
} finally {
|
||||||
|
pending = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const collection: Collection<A, Key, Definitions> = {
|
const collection: Collection<A, Key, Definitions> = {
|
||||||
|
|
@ -549,6 +561,7 @@ export namespace Layout {
|
||||||
const value = slot()
|
const value = slot()
|
||||||
existing.set(plan.keyOf(value), value)
|
existing.set(plan.keyOf(value), value)
|
||||||
})
|
})
|
||||||
|
const masks = new Map<Key, number>()
|
||||||
// Stage user-code projections for new and changed values only;
|
// Stage user-code projections for new and changed values only;
|
||||||
// retained equivalent values keep their index state and reads.
|
// retained equivalent values keep their index state and reads.
|
||||||
const staged: Array<{ readonly key: Key; readonly commits: readonly Commit[] }> = []
|
const staged: Array<{ readonly key: Key; readonly commits: readonly Commit[] }> = []
|
||||||
|
|
@ -559,13 +572,14 @@ export namespace Layout {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const mask = plan.diff(existing.get(key)!, value)
|
const mask = plan.diff(existing.get(key)!, value)
|
||||||
|
masks.set(key, mask)
|
||||||
if (mask === 0) return
|
if (mask === 0) return
|
||||||
const commits = stageRefresh(key, value, mask)
|
const commits = stageRefresh(key, value, mask)
|
||||||
if (commits) staged.push({ key, commits })
|
if (commits) staged.push({ key, commits })
|
||||||
})
|
})
|
||||||
const retained = new Set(keys)
|
const retained = new Set(keys)
|
||||||
return Transaction.run(() => {
|
return Transaction.run(() => {
|
||||||
const changed = values.set(next)
|
const changed = withPending(masks, () => values.set(next))
|
||||||
existing.forEach((_value, key) => {
|
existing.forEach((_value, key) => {
|
||||||
if (!retained.has(key)) entries.forEach((entry) => entry.remove(key))
|
if (!retained.has(key)) entries.forEach((entry) => entry.remove(key))
|
||||||
})
|
})
|
||||||
|
|
@ -713,7 +727,7 @@ export namespace Layout {
|
||||||
variants: Readonly<Record<string, Field<unknown>>>,
|
variants: Readonly<Record<string, Field<unknown>>>,
|
||||||
): UnionDiffModel {
|
): UnionDiffModel {
|
||||||
const bits = new Map<PropertyKey, number>()
|
const bits = new Map<PropertyKey, number>()
|
||||||
bits.set(tag, 0)
|
bits.set(tag, 1)
|
||||||
bits.set(keyName, 0)
|
bits.set(keyName, 0)
|
||||||
const assigned = new Map<PropertyKey, number>()
|
const assigned = new Map<PropertyKey, number>()
|
||||||
const perVariant = new Map<string, ReadonlyArray<DiffField> | undefined>()
|
const perVariant = new Map<string, ReadonlyArray<DiffField> | undefined>()
|
||||||
|
|
@ -731,7 +745,7 @@ export namespace Layout {
|
||||||
if (!assigned.has(name) && !bits.has(name)) bits.set(name, 0)
|
if (!assigned.has(name) && !bits.has(name)) bits.set(name, 0)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const bit = assigned.get(name) ?? 1 << Math.min(count++, 30)
|
const bit = assigned.get(name) ?? 1 << Math.min(count++ + 1, 30)
|
||||||
assigned.set(name, bit)
|
assigned.set(name, bit)
|
||||||
bits.set(name, bit)
|
bits.set(name, bit)
|
||||||
fields.push({ name, field, bit })
|
fields.push({ name, field, bit })
|
||||||
|
|
@ -777,7 +791,8 @@ export namespace Layout {
|
||||||
const label = JSON.stringify(name)
|
const label = JSON.stringify(name)
|
||||||
const fields = model.perVariant.get(name)
|
const fields = model.perVariant.get(name)
|
||||||
if (!fields) {
|
if (!fields) {
|
||||||
const index = emitter.custom.push(variants[name].equivalent) - 1
|
const variant = variants[name]
|
||||||
|
const index = emitter.custom.push((left, right) => variant.equivalent(left, right)) - 1
|
||||||
return `case ${label}: return custom[${index}](left, right) ? 0 : -1`
|
return `case ${label}: return custom[${index}](left, right) ? 0 : -1`
|
||||||
}
|
}
|
||||||
const statements = fields.map((field) => {
|
const statements = fields.map((field) => {
|
||||||
|
|
@ -841,7 +856,7 @@ export namespace Layout {
|
||||||
|
|
||||||
function expression(emitter: Emitter, field: Field<unknown>, left: string, right: string): string {
|
function expression(emitter: Emitter, field: Field<unknown>, left: string, right: string): string {
|
||||||
if (field.immutable) return "true"
|
if (field.immutable) return "true"
|
||||||
if (field.primitive) return `Object.is(${left}, ${right})`
|
if (field.primitive && field.equivalent === Object.is) return `Object.is(${left}, ${right})`
|
||||||
const layout = field as Field<unknown> & {
|
const layout = field as Field<unknown> & {
|
||||||
readonly type?: "struct" | "union" | "keyed-union" | "array"
|
readonly type?: "struct" | "union" | "keyed-union" | "array"
|
||||||
readonly fields?: Fields
|
readonly fields?: Fields
|
||||||
|
|
@ -876,12 +891,12 @@ export namespace Layout {
|
||||||
const name = declare(emitter, layout, (fn) => {
|
const name = declare(emitter, layout, (fn) => {
|
||||||
const property = JSON.stringify(String(layout.key!.name))
|
const property = JSON.stringify(String(layout.key!.name))
|
||||||
const key = expression(emitter, layout.key!.field, `l[${property}]`, `r[${property}]`)
|
const key = expression(emitter, layout.key!.field, `l[${property}]`, `r[${property}]`)
|
||||||
const union = declareUnion(emitter, layout.variants, layout.tag!, layout.variants!)
|
const union = declareUnion(emitter, {}, layout.tag!, layout.variants!)
|
||||||
return `function ${fn}(l, r) { return ${key === "true" ? "" : `${key} && `}${union}(l, r) }`
|
return `function ${fn}(l, r) { return ${key === "true" ? "" : `${key} && `}${union}(l, r) }`
|
||||||
})
|
})
|
||||||
return `${name}(${left}, ${right})`
|
return `${name}(${left}, ${right})`
|
||||||
}
|
}
|
||||||
const index = emitter.custom.push(field.equivalent) - 1
|
const index = emitter.custom.push((left, right) => field.equivalent(left, right)) - 1
|
||||||
return `custom[${index}](${left}, ${right})`
|
return `custom[${index}](${left}, ${right})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue