From 353e86a59f329b281374c8905d3ae5bcb8875218 Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Mon, 23 May 2022 16:57:31 +0800 Subject: [PATCH] Add unordered map --- src/collections/index.js | 4 +- src/collections/unordered-map.js | 155 +++++++++++++++++++++++++++++++ src/collections/vector.js | 10 +- src/index.js | 6 +- src/utils.js | 8 ++ 5 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 src/collections/unordered-map.js diff --git a/src/collections/index.js b/src/collections/index.js index 258a34850..739be1a26 100644 --- a/src/collections/index.js +++ b/src/collections/index.js @@ -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 } \ No newline at end of file diff --git a/src/collections/unordered-map.js b/src/collections/unordered-map.js new file mode 100644 index 000000000..d69d26723 --- /dev/null +++ b/src/collections/unordered-map.js @@ -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} + } +} \ No newline at end of file diff --git a/src/collections/vector.js b/src/collections/vector.js index b11ec0092..264aa4681 100644 --- a/src/collections/vector.js +++ b/src/collections/vector.js @@ -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) { @@ -107,7 +115,7 @@ export class Vector { } } -class VectorIterator { +export class VectorIterator { constructor(vector) { this.current = 0 this.vector = vector diff --git a/src/index.js b/src/index.js index fd5aa4655..c9095e4c3 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,8 @@ import * as near from './api' import { LookupMap, Vector, - LookupSet + LookupSet, + UnorderedMap } from './collections' export { @@ -23,5 +24,6 @@ export { near, LookupMap, Vector, - LookupSet + LookupSet, + UnorderedMap } \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index 889f0189f..f78570d02 100644 --- a/src/utils.js +++ b/src/utils.js @@ -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 } \ No newline at end of file