Skip to content

Commit

Permalink
Add unordered map
Browse files Browse the repository at this point in the history
  • Loading branch information
ailisp committed May 23, 2022
1 parent b43fa07 commit 353e86a
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 4 deletions.
4 changes: 3 additions & 1 deletion src/collections/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { LookupMap } from "./lookup-map";
import { Vector } from "./vector";
import { LookupSet } from "./lookup-set";
import { UnorderedMap } from "./unordered-map";

export {
LookupMap,
Vector,
LookupSet
LookupSet,
UnorderedMap
}
155 changes: 155 additions & 0 deletions src/collections/unordered-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import * as near from '../api'
import {u8ArrayToString, stringToU8Array} from '../utils'
import { Vector, VectorIterator } from './vector'

const ERR_INCONSISTENT_STATE = "The collection is an inconsistent state. Did previous smart contract execution terminate unexpectedly?"

export class UnorderedMap {
constructor(prefix) {
this.length = 0
this.keyIndexPrefix = prefix + 'i'
let indexKey = prefix + 'k'
let indexValue = prefix + 'v'
this.keys = new Vector(indexKey)
this.values = new Vector(indexValue)
}

len() {
let keysLen = this.keys.len()
let valuesLen = this.values.len()
if (keysLen != valuesLen) {
throw new Error(ERR_INCONSISTENT_STATE)
}
return keysLen
}

isEmpty() {
let keysIsEmpty = this.keys.isEmpty()
let valuesIsEmpty = this.values.isEmpty()
if (keysIsEmpty != valuesIsEmpty) {
throw new Error(ERR_INCONSISTENT_STATE)
}
return keysIsEmpty
}

serializeIndex(index) {
let data = new Uint32Array([index])
let array = new Uint8Array(data.buffer)
return u8ArrayToString(array)
}

deserializeIndex(rawIndex) {
let array = stringToU8Array(rawIndex)
let data = new Uint32Array(array.buffer)
return data[0]
}

getIndexRaw(key) {
let indexLookup = this.keyIndexPrefix + key
let indexRaw = near.jsvmStorageRead(indexLookup)
return indexRaw
}

get(key) {
let indexRaw = this.getIndexRaw(key)
if (indexRaw) {
let index = this.deserializeIndex(indexRaw)
let value = this.values.get(index)
if (value) {
return value
} else {
throw new Error(ERR_INCONSISTENT_STATE)
}
}
return null
}

set(key, value) {
let indexLookup = this.keyIndexPrefix + key
let indexRaw = near.jsvmStorageRead(indexLookup)
if (indexRaw) {
let index = this.deserializeIndex(indexRaw)
return this.values.replace(index, value)
} else {
let nextIndex = this.len()
let nextIndexRaw = this.serializeIndex(nextIndex)
near.jsvmStorageWrite(indexLookup, nextIndexRaw)
this.keys.push(key)
this.values.push(value)
return null
}
}

remove(key) {
let indexLookup = this.keyIndexPrefix + key
let indexRaw = near.jsvmStorageRead(indexLookup)
if (indexRaw) {
if (this.len() == 1) {
// If there is only one element then swap remove simply removes it without
// swapping with the last element.
return near.jsvmStorageRemove(indexLookup)
} else {
// If there is more than one element then swap remove swaps it with the last
// element.
let lastKeyRaw = this.keys.get(this.len() - 1)
if (!lastKeyRaw) {
throw new Error(ERR_INCONSISTENT_STATE)
}
near.jsvmStorageRemove(indexLookup)
// If the removed element was the last element from keys, then we don't need to
// reinsert the lookup back.
if (lastKeyRaw != key) {
let lastLookupKey = this.keyIndexPrefix + lastKeyRaw
near.jsvmStorageWrite(lastLookupKey, indexRaw)
}
}
let index = this.deserializeIndex(indexRaw)
this.keys.swapRemove(index)
return this.values.swapRemove(index)
}
return null
}

clear() {
for (let key of this.keys) {
let indexLookup = this.keyIndexPrefix + key
near.jsvmStorageRemove(indexLookup)
}
this.keys.clear()
this.values.clear()
}

toArray() {
let ret = []
for (let v of this) {
ret.push(v)
}
return ret
}

[Symbol.iterator]() {
return new UnorderedMapIterator(this)
}

extend(kvs) {
for (let [k, v] of kvs) {
this.set(k, v)
}
}
}

class UnorderedMapIterator {
constructor(unorderedMap) {
this.keys = new VectorIterator(unorderedMap.keys)
this.values = new VectorIterator(unorderedMap.values)
}

next() {
let key = this.keys.next()
let value = this.values.next()
if (key.done != value.done) {
throw new Error(ERR_INCONSISTENT_STATE)
}
return {value: [key.value, value.value], done: key.done}
}
}
10 changes: 9 additions & 1 deletion src/collections/vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ export class Vector {
return new VectorIterator(this)
}

clear() {
for (let i = 0; i < this.length; i++) {
let key = this.indexToKey(this.length)
near.jsvmStorageRemove(key)
}
this.len = 0
}

toArray() {
let ret = []
for (let v of this) {
Expand All @@ -107,7 +115,7 @@ export class Vector {
}
}

class VectorIterator {
export class VectorIterator {
constructor(vector) {
this.current = 0
this.vector = vector
Expand Down
6 changes: 4 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import * as near from './api'
import {
LookupMap,
Vector,
LookupSet
LookupSet,
UnorderedMap
} from './collections'

export {
Expand All @@ -23,5 +24,6 @@ export {
near,
LookupMap,
Vector,
LookupSet
LookupSet,
UnorderedMap
}
8 changes: 8 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ export function u8ArrayToString(array) {
ret += String.fromCharCode(e)
}
return ret
}

export function stringToU8Array(string) {
let ret = new Uint8Array(string.length)
for (let i in string) {
ret[i] = string.charCodeAt(i)
}
return ret
}

0 comments on commit 353e86a

Please sign in to comment.