author liangqin.fan gmail import { ReturnCode } from "as-contract-runtime" ; import { Codec , Hash , ScaleString } from "as-scale-codec" ; import { Storage } from "." ; import { Crypto } from "../primitives/crypto" ; import { DoubleLinkKVStore } from "./DoubleLinkKVStore" ; import { MapEntry } from "./MapEntry" ; import { NullHash } from "./storage" ; export class SpreadStorableMap < K extends Codec , V extends Codec > implements Codec { private synced : bool ; constructor ( ep = "" ) { this . keyPrefix = ep ; this . mapInner = new Map < K , V > ( ) ; this . synced = false ; } protected keyPrefix : string ; protected mapInner : Map < K , V > ; protected findKeyInner ( key ) { let keysInner = this . mapInner . keys ( ) ; for ( let i = 0 ; i < keysInner . length ; i ++ ) { if ( keysInner [ i ] . eq ( key ) ) return keysInner [ i ] ; } return null ; } protected loadMapEntry ( ) { let strg = new Storage ( Crypto . blake256s ( this . keyPrefix ) ) ; let entry = strg . load < MapEntry > ( ) ; return entry ; } protected storeMapEntry ( entries , size ) { let strg = new Storage ( Crypto . blake256s ( this . keyPrefix ) ) ; let entry = new MapEntry ( entries , size ) ; let r = strg . store ( entry ) ; assert ( r == ReturnCode . Success , "store entry point of map failed." ) ; } get entryKey ( ) : string { return this . keyPrefix ; } set entryKey ( str ) { this . keyPrefix = str ; } toU8a ( ) { let t = new ScaleString ( this . keyPrefix ) ; return t . toU8a ( ) ; } encodedLength ( ) { return ( new ScaleString ( this . keyPrefix ) ) . encodedLength ( ) ; } populateFromBytes ( bytes , index = 0 ) { let s = new ScaleString ( ) ; s . populateFromBytes ( bytes , index ) ; this . keyPrefix = s . toString ( ) ; } eq ( other ) { return this . keyPrefix == other . keyPrefix ; } notEq ( other ) { return ! this . eq ( other ) ; } has ( key ) { let innerKey = this . hasKey ( key ) ; return innerKey != null ; } @ operator ( "[]=" ) set ( key , value ) { this . setKeyValuePair ( key , value ) ; return this ; } @ operator ( "[]" ) get ( key ) { let innerkey = this . hasKey ( key ) ; if ( ! innerkey ) return instantiate < V > ( ) ; return this . mapInner . get ( innerkey ) ; } delete ( key ) { let innerkey = this . hasKey ( key ) ; if ( ! innerkey ) return false ; this . deleteKey ( innerkey ) ; return true ; } clear ( ) { this . clearAll ( ) ; } keys ( ) { return this . allKeys ( ) ; } values ( ) { return this . allValues ( ) ; } hasKey ( key ) { let keyHash = this . itemStoredPosition ( key ) ; let kv = this . loadKVStoreNode ( keyHash ) ; if ( ! kv ) return null ; let k = this . findKeyInner ( key ) ; if ( ! k ) { k = kv . key ; this . mapInner . set ( k , kv . value ) ; } return k ; } setKeyValuePair ( key , value ) { let isNew = this . storeAnItem ( key , value ) ; if ( ! isNew ) { let k = this . findKeyInner ( key ) ; if ( k ) key = k ; } this . mapInner . set ( key , value ) ; } deleteKey ( key ) { let k = this . findKeyInner ( key ) ; if ( k ) { this . mapInner . delete ( k ) ; } this . removeAnItem ( key ) ; } clearAll ( ) { this . loadAllItems ( ) ; let keys = this . mapInner . keys ( ) ; for ( let i = 0 ; i < keys . length ; ++ i ) { this . removeAnItem ( keys [ i ] ) ; } this . mapInner . clear ( ) ; this . storeMapEntry ( NullHash , 0 ) ; } allKeys ( ) { this . loadAllItems ( ) ; return this . mapInner . keys ( ) ; } allValues ( ) { this . loadAllItems ( ) ; return this . mapInner . values ( ) ; } private itemStoredPosition ( key ) { let pos = this . keyPrefix + String . UTF8 . decode ( changetype < ArrayBuffer > ( key . toU8a ( ) . dataStart ) ) ; return Crypto . blake256s ( pos ) ; } private loadKVStoreNode ( keyHash ) { let strg = new Storage ( keyHash ) ; let v = strg . load < DoubleLinkKVStore < K , V > > ( ) ; return v ; } private removeAnItem ( key ) { let keyHash = this . itemStoredPosition ( key ) ; let item = this . loadKVStoreNode ( keyHash ) ; if ( item != null ) { if ( item . prevkey == NullHash ) { if ( item . nextkey != NullHash ) { let strg = new Storage ( item . nextkey ) ; let newhead = strg . load < DoubleLinkKVStore < K , V > > ( ) ; newhead ! . prevkey = NullHash ; strg . store ( newhead ! ) ; } } else if ( item . nextkey == NullHash ) { if ( item . prevkey != NullHash ) { let strg = new Storage ( item . prevkey ) ; let newtail = strg . load < DoubleLinkKVStore < K , V > > ( ) ; newtail ! . nextkey = NullHash ; strg . store ( newtail ! ) ; } } else { let prevstrg = new Storage ( item . prevkey ) ; let previtem = prevstrg . load < DoubleLinkKVStore < K , V > > ( ) ! ; previtem . nextkey = item . nextkey ; prevstrg . store ( previtem ) ; let nextstrg = new Storage ( item . nextkey ) ; let nextitem = nextstrg . load < DoubleLinkKVStore < K , V > > ( ) ! ; nextitem . prevkey = item . prevkey ; nextstrg . store ( nextitem ) ; } let thisstrg = new Storage ( keyHash ) ; thisstrg . clear ( ) ; return true ; } return false ; } private storeAnItem ( key , value ) { let isNewItem = true ; let keyHash = this . itemStoredPosition ( key ) ; let strg = new Storage ( keyHash ) ; let item = strg . load < DoubleLinkKVStore < K , V > > ( ) ; if ( item == null ) { let newHead ; let size = 0 ; let entryInfo = this . loadMapEntry ( ) ; if ( ! entryInfo ) { newHead = new DoubleLinkKVStore < K , V > ( key , value , NullHash , NullHash ) ; size ++ ; } else { if ( entryInfo . entries . notEq ( NullHash ) ) { let keyHash = entryInfo . entries ; let strg = new Storage ( keyHash ) ; let oldHead = strg . load < DoubleLinkKVStore < K , V > > ( ) ; assert ( oldHead != null , "head item must be stored." ) ; oldHead ! . prevkey = keyHash ; strg . store ( oldHead ! ) ; } newHead = new DoubleLinkKVStore < K , V > ( key , value , entryInfo . entries , NullHash ) ; size = entryInfo . size . unwrap ( ) + 1 ; } strg . store ( newHead ) ; this . storeMapEntry ( keyHash , size ) ; } else { item . value = value ; strg . store ( item as DoubleLinkKVStore < K , V > ) ; isNewItem = false ; } return isNewItem ; } private loadAllItems ( ) { if ( this . synced ) return ; let entryInfo = this . loadMapEntry ( ) ; if ( entryInfo == null ) return ; let mapsize = entryInfo . size ; if ( mapsize . unwrap ( ) == this . mapInner . size ) return ; this . mapInner . clear ( ) ; let key = entryInfo . entries ; let mapStrg = new Storage ( NullHash ) ; while ( key != NullHash ) { mapStrg . updateKey ( key ) ; let item = mapStrg . load < DoubleLinkKVStore < K , V > > ( ) ; let k = item ! . key ; let v = item ! . value ; this . mapInner . set ( k , v ) ; key = item ! . nextkey ; } this . synced = true ; } }