Skip to content

Commit

Permalink
proxySet changes and added keyvals solution
Browse files Browse the repository at this point in the history
  • Loading branch information
overthemike committed Oct 13, 2024
1 parent 8c749e2 commit fb29830
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/vanilla/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export { watch } from './utils/watch.ts'
export { devtools } from './utils/devtools.ts'
export { deepClone } from './utils/deepClone.ts'
export { proxySet } from './utils/proxySet.ts'
export { proxyMap } from './utils/proxyMap-indexMap-filled.ts'
export { proxyMap } from './utils/proxyMap-indexMap-keyvals.ts'
143 changes: 143 additions & 0 deletions src/vanilla/utils/proxyMap-indexMap-keyvals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { proxy, unstable_getInternalStates } from '../../vanilla.ts'

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.5.5)

An import path cannot end with a '.ts' extension. Consider importing '../../vanilla.js' instead.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.6.4)

An import path cannot end with a '.ts' extension. Consider importing '../../vanilla.js' instead.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.4.5)

An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.9.5)

An import path cannot end with a '.ts' extension. Consider importing '../../vanilla.js' instead.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.3.3)

An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.0.4)

An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.8.4)

An import path cannot end with a '.ts' extension. Consider importing '../../vanilla.js' instead.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.1.6)

An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.5.4)

An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.2.2)

An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

Check failure on line 1 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.7.4)

An import path cannot end with a '.ts' extension. Consider importing '../../vanilla.js' instead.
const { proxyStateMap } = unstable_getInternalStates()
const maybeProxify = (x: any) => (typeof x === 'object' ? proxy({ x }).x : x)
const isProxy = (x: any) => proxyStateMap.has(x)

type InternalProxyObject<K, V> = Map<K, V> & {
data: Array<K | V>
index: number
toJSON: () => Map<K, V>
}

export function proxyMap<K, V>(entries?: Iterable<[K, V]> | undefined | null) {
const initialData: Array<K | V> = []
let initialIndex = 0
const indexMap = new Map<K, number>()

if (entries !== null && typeof entries !== 'undefined') {
if (typeof entries[Symbol.iterator] !== 'function') {
throw new TypeError(
'proxyMap:\n\tinitial state must be iterable\n\t\ttip: structure should be [[key, value]]',
)
}
for (const [k, v] of entries) {
const key = maybeProxify(k)
const value = maybeProxify(v)
indexMap.set(key, initialIndex)
initialData[initialIndex++] = key
initialData[initialIndex++] = value
}
}

const vObject: InternalProxyObject<K, V> = {
data: initialData,
index: initialIndex,
get size() {
return indexMap.size
},
get(key: K) {
const k = maybeProxify(key)
const index = indexMap.get(k)
if (index !== undefined) {
return this.data[index + 1] as V
}
if (!isProxy(this)) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.index
}
return undefined
},
has(key: K) {
const k = maybeProxify(key)
const exists = indexMap.has(k)
if (!exists && !isProxy(this)) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.index
}
return exists
},
set(key: K, value: V) {
if (!isProxy(this)) {
throw new Error('Cannot perform mutations on a snapshot')
}
const k = maybeProxify(key)
const v = maybeProxify(value)
const index = indexMap.get(k)
if (index === undefined) {
let nextIndex = this.index
indexMap.set(k, nextIndex)
this.data[nextIndex++] = k
this.data[nextIndex++] = v
this.index = nextIndex
} else {
this.data[index + 1] = v
}
return this
},
delete(key: K) {
if (!isProxy(this)) {
throw new Error('Cannot perform mutations on a snapshot')
}
const k = maybeProxify(key)
const index = indexMap.get(k)
if (index !== undefined) {
delete this.data[index]
delete this.data[index + 1]
indexMap.delete(k)
return true
}
return false
},
clear() {
if (!isProxy(this)) {
throw new Error('Cannot perform mutations on a snapshot')
}
indexMap.clear()
this.index = 0
this.data.splice(0)
},
forEach(cb: (value: V, key: K, map: Map<K, V>) => void) {
indexMap.forEach((index) => {
cb(this.data[index + 1] as V, this.data[index] as K, this)
})
},
*entries(): MapIterator<[K, V]> {

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.5.5)

Cannot find name 'MapIterator'.

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.6.4)

Cannot find name 'MapIterator'.

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.4.5)

Cannot find name 'MapIterator'. Did you mean 'Iterator'?

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.9.5)

Cannot find name 'MapIterator'. Did you mean 'Iterator'?

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.3.3)

Cannot find name 'MapIterator'. Did you mean 'Iterator'?

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.0.4)

Cannot find name 'MapIterator'. Did you mean 'Iterator'?

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.8.4)

Cannot find name 'MapIterator'. Did you mean 'Iterator'?

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.1.6)

Cannot find name 'MapIterator'. Did you mean 'Iterator'?

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.5.4)

Cannot find name 'MapIterator'. Did you mean 'Iterator'?

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (5.2.2)

Cannot find name 'MapIterator'. Did you mean 'Iterator'?

Check failure on line 104 in src/vanilla/utils/proxyMap-indexMap-keyvals.ts

View workflow job for this annotation

GitHub Actions / test_matrix (4.7.4)

Cannot find name 'MapIterator'.
for (const index of indexMap.values()) {
yield [this.data[index], this.data[index + 1]] as [K, V]
}
},
*keys(): IterableIterator<K> {
for (const key of indexMap.keys()) {
yield key
}
},
*values(): IterableIterator<V> {
for (const index of indexMap.values()) {
yield this.data[index + 1] as V
}
},
[Symbol.iterator]() {
return this.entries()
},
get [Symbol.toStringTag]() {
return 'Map'
},
toJSON(): Map<K, V> {
return new Map([...indexMap].map(([k, i]) => [k, this.data[i + 1] as V]))
},
}

const proxiedObject = proxy(vObject)

Object.defineProperties(proxiedObject, {
size: { enumerable: false },
data: { enumerable: false },
toJSON: { enumerable: false },
})

Object.seal(proxiedObject)

return proxiedObject as unknown as Map<K, V> & {
$$valtioSnapshot: Omit<Map<K, V>, 'set' | 'delete' | 'clear'>
}
}
53 changes: 21 additions & 32 deletions src/vanilla/utils/proxySet.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { getVersion, proxy } from '../../vanilla.ts'

const maybeProxify = (x: any) => proxy({ x }).x

const isProxy = (x: any) => getVersion(x) !== undefined
import { proxy, unstable_getInternalStates } from '../../vanilla.ts'
const { proxyStateMap } = unstable_getInternalStates()
const maybeProxify = (x: any) => (typeof x === 'object' ? proxy({ x }).x : x)
const isProxy = (x: any) => proxyStateMap.has(x)

type InternalProxySet<T> = Set<T> & {
data: T[]
toJSON: object
index: number
intersection: (other: Set<T>) => Set<T>
isDisjointFrom: (other: Set<T>) => boolean
isSubsetOf: (other: Set<T>) => boolean
Expand All @@ -16,8 +16,9 @@ type InternalProxySet<T> = Set<T> & {
}

export function proxySet<T>(initialValues?: Iterable<T> | null) {
const data: T[] = []
const initialData: T[] = []
const indexMap = new Map<T, number>()
let initialIndex = 0

if (initialValues !== null && typeof initialValues !== 'undefined') {
if (typeof initialValues[Symbol.iterator] !== 'function') {
Expand All @@ -26,46 +27,39 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
for (const v of initialValues) {
if (!indexMap.has(v)) {
const value = maybeProxify(v)
indexMap.set(value, data.length)
data.push(value)
indexMap.set(value, initialIndex)
initialData[initialIndex++] = value
}
}
}

const vObject: InternalProxySet<T> = {
data,
data: initialData,
index: initialIndex,
get size() {
return indexMap.size
},
add(v: T) {
if (!isProxy(this)) {
if (import.meta.env?.MODE !== 'production') {
throw new Error('Cannot perform mutations on a snapshot')
} else {
return this
}
throw new Error('Cannot perform mutations on a snapshot')
}
const value = maybeProxify(v)
if (!indexMap.has(value)) {
const index = this.data.length
this.data.push(value)
indexMap.set(value, index)
let nextIndex = this.index
indexMap.set(value, nextIndex)
this.data[nextIndex++] = value
this.index = nextIndex
}
return this
},
delete(v: T) {
if (!isProxy(this)) {
if (import.meta.env?.MODE !== 'production') {
throw new Error('Cannot perform mutations on a snapshot')
} else {
return false
}
throw new Error('Cannot perform mutations on a snapshot')
}
const value = maybeProxify(v)
const index = indexMap.get(value)
if (index !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.data.length
delete this.data[index]
indexMap.delete(value)
return true
Expand All @@ -74,13 +68,10 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
},
clear() {
if (!isProxy(this)) {
if (import.meta.env?.MODE !== 'production') {
throw new Error('Cannot perform mutations on a snapshot')
} else {
return
}
throw new Error('Cannot perform mutations on a snapshot')
}
this.data.splice(0)
this.index = 0
indexMap.clear()
},
forEach(cb) {
Expand All @@ -92,7 +83,7 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
const value = maybeProxify(v)
if (indexMap.has(value)) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.data.length
this.index
return true
}
return false
Expand All @@ -112,9 +103,7 @@ export function proxySet<T>(initialValues?: Iterable<T> | null) {
}
},
toJSON(): Set<T> {
// filtering is about twice as fast as creating a new set and deleting
// the undefined value because filter actually skips empty slots
return new Set(this.data.filter((v) => v !== undefined) as T[])
return new Set(this.data)
},
[Symbol.iterator]() {
return this.values()
Expand Down
40 changes: 38 additions & 2 deletions tests/proxyMap.bench.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-disable vitest/consistent-test-it */
import { bench, describe } from 'vitest'
import { proxyMap as newProxyMap } from '../src/vanilla/utils/proxyMap-indexMap'
import { proxyMap as newProxyMapKeyVals } from '../src/vanilla/utils/proxyMap-indexMap-keyvals'

// Helper function to generate test data
function generateTestData(size: number): [number, number][] {
const data: [number, number][] = []
const data: [any, any][] = []
for (let i = 0; i < size; i++) {
data.push([i, i])
data.push([{ id: i }, { i }])
}
return data
}
Expand All @@ -23,6 +24,13 @@ TEST_SIZES.forEach((size) => {
map.set(key, value)
})
})

bench('New proxyMapKeyVals', () => {
const map = newProxyMapKeyVals<number, number>()
testData.forEach(([key, value]) => {
map.set(key, value)
})
})
})

describe(`Retrieval -${size} items`, () => {
Expand All @@ -34,6 +42,13 @@ TEST_SIZES.forEach((size) => {
map.get(key)
})
})

bench('New proxyMapKeyVals', () => {
const map = newProxyMapKeyVals<number, number>(testData)
testData.forEach(([key]) => {
map.get(key)
})
})
})

describe(`Deletion -${size} items`, () => {
Expand All @@ -45,6 +60,13 @@ TEST_SIZES.forEach((size) => {
map.delete(key)
})
})

bench('New proxyMapKeyVals', () => {
const map = newProxyMapKeyVals<number, number>(testData)
testData.forEach(([key]) => {
map.delete(key)
})
})
})

describe(`Iteration -${size} items`, () => {
Expand All @@ -54,6 +76,11 @@ TEST_SIZES.forEach((size) => {
const map = newProxyMap<number, number>(testData)
testData.forEach(([key, value]) => {})
})

bench('New proxyMapKeyVals', () => {
const map = newProxyMapKeyVals<number, number>(testData)
testData.forEach(([key, value]) => {})
})
})

describe(`Insertion, Retrieval, and Deletion -${size} items`, () => {
Expand All @@ -67,5 +94,14 @@ TEST_SIZES.forEach((size) => {
map.delete(key)
})
})

bench('New proxyMapKeyVals', () => {
const map = newProxyMapKeyVals<number, number>(testData)
testData.forEach(([key, value]) => {
map.set(key, value)
map.get(key)
map.delete(key)
})
})
})
})

0 comments on commit fb29830

Please sign in to comment.