@btc-vision/btc-runtime 1.10.2 → 1.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/runtime/contracts/OP20.ts +19 -8
- package/runtime/contracts/OP721.ts +19 -8
- package/runtime/env/BlockchainEnvironment.ts +90 -65
- package/runtime/env/global.ts +24 -14
- package/runtime/generic/AddressMap.ts +113 -24
- package/runtime/generic/Map.ts +78 -22
- package/runtime/generic/MapUint8Array.ts +115 -27
- package/runtime/storage/StoredAddress.ts +2 -2
- package/runtime/storage/arrays/StoredPackedArray.ts +117 -60
- package/runtime/types/SafeMath.ts +240 -337
|
@@ -1,60 +1,149 @@
|
|
|
1
|
-
import { Revert } from '../types/Revert';
|
|
2
|
-
import { Map } from './Map';
|
|
3
1
|
import { Address } from '../types/Address';
|
|
2
|
+
import { Revert } from '../types/Revert';
|
|
3
|
+
import { IMap } from './Map';
|
|
4
4
|
|
|
5
5
|
@final
|
|
6
|
-
export class AddressMap<V>
|
|
6
|
+
export class AddressMap<V> implements IMap<Address, V> {
|
|
7
|
+
protected _keys: Address[] = [];
|
|
8
|
+
protected _values: V[] = [];
|
|
9
|
+
|
|
10
|
+
// CACHE: Stores the index of the last successful lookup to make repeated access O(1)
|
|
11
|
+
private _lastIndex: i32 = -1;
|
|
12
|
+
|
|
13
|
+
@inline
|
|
14
|
+
public get size(): i32 {
|
|
15
|
+
return this._keys.length;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@inline
|
|
19
|
+
public keys(): Address[] {
|
|
20
|
+
return this._keys;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@inline
|
|
24
|
+
public values(): V[] {
|
|
25
|
+
return this._values;
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
public set(key: Address, value: V): this {
|
|
8
|
-
|
|
29
|
+
let index = -1;
|
|
30
|
+
|
|
31
|
+
// Check Cache (Fastest)
|
|
32
|
+
if (this._lastIndex !== -1) {
|
|
33
|
+
const cachedKey = unchecked(this._keys[this._lastIndex]);
|
|
34
|
+
// Check length first, then full content equality
|
|
35
|
+
if (cachedKey.length === key.length) {
|
|
36
|
+
if (memory.compare(cachedKey.dataStart, key.dataStart, key.length) === 0) {
|
|
37
|
+
index = this._lastIndex;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Full Scan if cache missed
|
|
43
|
+
if (index === -1) {
|
|
44
|
+
index = this.indexOf(key);
|
|
45
|
+
}
|
|
46
|
+
|
|
9
47
|
if (index === -1) {
|
|
10
48
|
this._keys.push(key);
|
|
11
49
|
this._values.push(value);
|
|
50
|
+
this._lastIndex = this._keys.length - 1;
|
|
12
51
|
} else {
|
|
13
|
-
this._values[index] = value;
|
|
52
|
+
unchecked((this._values[index] = value));
|
|
53
|
+
// Cache is already pointing to this index (from indexOf or the check above)
|
|
54
|
+
this._lastIndex = index;
|
|
14
55
|
}
|
|
15
56
|
|
|
16
57
|
return this;
|
|
17
58
|
}
|
|
18
59
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
60
|
+
/**
|
|
61
|
+
* HYPER-OPTIMIZED SEARCH
|
|
62
|
+
* Scans for Data Equality (not object equality).
|
|
63
|
+
* Uses a "Prefix Filter" to skip expensive memory comparisons.
|
|
64
|
+
*/
|
|
65
|
+
public indexOf(pointerHash: Address): i32 {
|
|
66
|
+
const len = this._keys.length;
|
|
67
|
+
if (len === 0) return -1;
|
|
68
|
+
|
|
69
|
+
const ptrLen = pointerHash.length;
|
|
70
|
+
const ptrData = pointerHash.dataStart;
|
|
22
71
|
|
|
23
|
-
|
|
24
|
-
|
|
72
|
+
// OPTIMIZATION: Prefix Filter
|
|
73
|
+
// If keys are long enough (hashes/addresses), we compare the first 8 bytes as a simple integer.
|
|
74
|
+
// This is 1 CPU cycle vs ~30+ cycles for a function call loop.
|
|
75
|
+
if (ptrLen >= 8) {
|
|
76
|
+
// Read the first 8 bytes of the SEARCH NEEDLE
|
|
77
|
+
const searchHeader = load<u64>(ptrData);
|
|
78
|
+
|
|
79
|
+
// Loop Backwards (finding most recently added items first is usually better for contracts)
|
|
80
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
81
|
+
const key = unchecked(this._keys[i]);
|
|
82
|
+
|
|
83
|
+
// Cheap Length Check
|
|
84
|
+
if (key.length !== ptrLen) continue;
|
|
85
|
+
|
|
86
|
+
// Cheap Integer Check (The Prefix Filter)
|
|
87
|
+
// This reads the CONTENT of the key, not the object pointer.
|
|
88
|
+
// If the first 8 bytes of data don't match, we skip the expensive check.
|
|
89
|
+
if (load<u64>(key.dataStart) !== searchHeader) continue;
|
|
90
|
+
|
|
91
|
+
// Expensive Full Check
|
|
92
|
+
// Only runs if length AND first 8 bytes match.
|
|
93
|
+
if (memory.compare(key.dataStart, ptrData, ptrLen) === 0) {
|
|
94
|
+
this._lastIndex = i;
|
|
95
|
+
return i;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// Fallback for small keys (< 8 bytes)
|
|
100
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
101
|
+
const key = unchecked(this._keys[i]);
|
|
102
|
+
if (key.length !== ptrLen) continue;
|
|
103
|
+
|
|
104
|
+
if (memory.compare(key.dataStart, ptrData, ptrLen) === 0) {
|
|
105
|
+
this._lastIndex = i;
|
|
106
|
+
return i;
|
|
107
|
+
}
|
|
25
108
|
}
|
|
26
109
|
}
|
|
27
110
|
|
|
28
111
|
return -1;
|
|
29
112
|
}
|
|
30
113
|
|
|
114
|
+
@inline
|
|
31
115
|
public has(key: Address): bool {
|
|
32
|
-
|
|
33
|
-
if (key.equals(this._keys[i])) {
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return false;
|
|
116
|
+
return this.indexOf(key) !== -1;
|
|
39
117
|
}
|
|
40
118
|
|
|
41
119
|
public get(key: Address): V {
|
|
42
|
-
const index
|
|
120
|
+
const index = this.indexOf(key);
|
|
43
121
|
if (index === -1) {
|
|
44
|
-
throw new Revert('Key not found in map
|
|
122
|
+
throw new Revert('Key not found in map');
|
|
45
123
|
}
|
|
46
|
-
return this._values[index];
|
|
124
|
+
return unchecked(this._values[index]);
|
|
47
125
|
}
|
|
48
126
|
|
|
49
127
|
public delete(key: Address): bool {
|
|
50
|
-
const index
|
|
51
|
-
if (index === -1)
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
128
|
+
const index = this.indexOf(key);
|
|
129
|
+
if (index === -1) return false;
|
|
54
130
|
|
|
55
131
|
this._keys.splice(index, 1);
|
|
56
132
|
this._values.splice(index, 1);
|
|
57
133
|
|
|
134
|
+
this._lastIndex = -1;
|
|
135
|
+
|
|
58
136
|
return true;
|
|
59
137
|
}
|
|
138
|
+
|
|
139
|
+
@inline
|
|
140
|
+
public clear(): void {
|
|
141
|
+
this._keys = [];
|
|
142
|
+
this._values = [];
|
|
143
|
+
this._lastIndex = -1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public toString(): string {
|
|
147
|
+
return `Map(size=${this._keys.length})`;
|
|
148
|
+
}
|
|
60
149
|
}
|
package/runtime/generic/Map.ts
CHANGED
|
@@ -2,19 +2,13 @@ import { Revert } from '../types/Revert';
|
|
|
2
2
|
|
|
3
3
|
export interface IMap<K, V> {
|
|
4
4
|
readonly size: i32;
|
|
5
|
-
|
|
6
5
|
has(key: K): bool;
|
|
7
|
-
|
|
8
6
|
set(key: K, value: V): this;
|
|
9
|
-
|
|
10
7
|
get(key: K): V;
|
|
11
|
-
|
|
12
8
|
delete(key: K): bool;
|
|
13
|
-
|
|
14
9
|
clear(): void;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
values(): V[]; // preliminary
|
|
10
|
+
keys(): K[];
|
|
11
|
+
values(): V[];
|
|
18
12
|
toString(): string;
|
|
19
13
|
}
|
|
20
14
|
|
|
@@ -22,33 +16,69 @@ export class Map<K, V> implements IMap<K, V> {
|
|
|
22
16
|
protected _keys: K[] = [];
|
|
23
17
|
protected _values: V[] = [];
|
|
24
18
|
|
|
19
|
+
// OPTIMIZATION: Cache the last found index.
|
|
20
|
+
// This makes has() -> get() sequences O(1) for the second call.
|
|
21
|
+
protected _lastIndex: i32 = -1;
|
|
22
|
+
|
|
23
|
+
@inline
|
|
25
24
|
public get size(): i32 {
|
|
26
25
|
return this._keys.length;
|
|
27
26
|
}
|
|
28
27
|
|
|
28
|
+
@inline
|
|
29
29
|
public keys(): K[] {
|
|
30
30
|
return this._keys;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
@inline
|
|
33
34
|
public values(): V[] {
|
|
34
35
|
return this._values;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
public set(key: K, value: V): this {
|
|
38
|
-
|
|
39
|
+
// Fast Cache Check
|
|
40
|
+
if (this._lastIndex != -1) {
|
|
41
|
+
if (unchecked(this._keys[this._lastIndex]) == key) {
|
|
42
|
+
unchecked((this._values[this._lastIndex] = value));
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Full Scan
|
|
48
|
+
const index = this.indexOf(key);
|
|
49
|
+
|
|
39
50
|
if (index == -1) {
|
|
40
51
|
this._keys.push(key);
|
|
41
52
|
this._values.push(value);
|
|
53
|
+
// Update cache to new item
|
|
54
|
+
this._lastIndex = this._keys.length - 1;
|
|
42
55
|
} else {
|
|
43
|
-
this._values[index] = value;
|
|
56
|
+
unchecked((this._values[index] = value));
|
|
57
|
+
// Update cache to found item (indexOf updates it too, but explicit here for safety)
|
|
58
|
+
this._lastIndex = index;
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
return this;
|
|
47
62
|
}
|
|
48
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Optimized Linear Scan.
|
|
66
|
+
* Iterates backwards (LIFO assumption: recent items are hotter).
|
|
67
|
+
*/
|
|
49
68
|
public indexOf(key: K): i32 {
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
const len = this._keys.length;
|
|
70
|
+
|
|
71
|
+
// Optimization: Check cache first
|
|
72
|
+
if (this._lastIndex != -1 && this._lastIndex < len) {
|
|
73
|
+
if (unchecked(this._keys[this._lastIndex]) == key) {
|
|
74
|
+
return this._lastIndex;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Reverse loop is often slightly faster for finding recent items
|
|
79
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
80
|
+
if (unchecked(this._keys[i] == key)) {
|
|
81
|
+
this._lastIndex = i; // Update cache
|
|
52
82
|
return i;
|
|
53
83
|
}
|
|
54
84
|
}
|
|
@@ -57,40 +87,66 @@ export class Map<K, V> implements IMap<K, V> {
|
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
public get(key: K): V {
|
|
60
|
-
const index
|
|
90
|
+
const index = this.indexOf(key);
|
|
61
91
|
if (index == -1) {
|
|
62
92
|
throw new Revert('Key not found in map (Map)');
|
|
63
93
|
}
|
|
64
|
-
return this._values[index];
|
|
94
|
+
return unchecked(this._values[index]);
|
|
65
95
|
}
|
|
66
96
|
|
|
97
|
+
@inline
|
|
67
98
|
public has(key: K): bool {
|
|
68
99
|
return this.indexOf(key) != -1;
|
|
69
100
|
}
|
|
70
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Optimized Delete (Swap and Pop).
|
|
104
|
+
* O(1) complexity instead of O(N) splice.
|
|
105
|
+
*/
|
|
71
106
|
public delete(key: K): bool {
|
|
72
|
-
const index
|
|
107
|
+
const index = this.indexOf(key);
|
|
73
108
|
if (index == -1) {
|
|
74
109
|
return false;
|
|
75
110
|
}
|
|
76
111
|
|
|
77
|
-
this._keys.
|
|
78
|
-
|
|
112
|
+
const lastIndex = this._keys.length - 1;
|
|
113
|
+
|
|
114
|
+
// If the element to delete is not the last one, swap it with the last one
|
|
115
|
+
if (index != lastIndex) {
|
|
116
|
+
unchecked((this._keys[index] = this._keys[lastIndex]));
|
|
117
|
+
unchecked((this._values[index] = this._values[lastIndex]));
|
|
118
|
+
|
|
119
|
+
// Fix cache if we moved the cached item
|
|
120
|
+
if (this._lastIndex == lastIndex) {
|
|
121
|
+
this._lastIndex = index;
|
|
122
|
+
} else if (this._lastIndex == index) {
|
|
123
|
+
this._lastIndex = -1;
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
// We are deleting the tail
|
|
127
|
+
if (this._lastIndex == lastIndex) this._lastIndex = -1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this._keys.pop();
|
|
131
|
+
this._values.pop();
|
|
132
|
+
|
|
79
133
|
return true;
|
|
80
134
|
}
|
|
81
135
|
|
|
136
|
+
@inline
|
|
82
137
|
public clear(): void {
|
|
83
138
|
this._keys = [];
|
|
84
139
|
this._values = [];
|
|
140
|
+
this._lastIndex = -1;
|
|
85
141
|
}
|
|
86
142
|
|
|
87
143
|
public toString(): string {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
144
|
+
// Warning: String concatenation in loops is O(N^2). Use sparingly.
|
|
145
|
+
let str = '';
|
|
146
|
+
const len = this._keys.length;
|
|
147
|
+
for (let i = 0; i < len; i++) {
|
|
148
|
+
str += `[${unchecked(this._keys[i])}] => [${unchecked(this._values[i])}]`;
|
|
92
149
|
}
|
|
93
|
-
|
|
94
150
|
return str;
|
|
95
151
|
}
|
|
96
152
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { Revert } from '../types/Revert';
|
|
2
2
|
import { IMap } from './Map';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Optimized equality check using WASM intrinsic memory comparison.
|
|
6
|
+
*/
|
|
7
|
+
@inline
|
|
4
8
|
export function eqUint(data: Uint8Array, data2: Uint8Array): bool {
|
|
5
9
|
if (data.length !== data2.length) return false;
|
|
6
|
-
|
|
7
|
-
for (let i = 0; i < data.length; i++) {
|
|
8
|
-
if (data[i] !== data2[i]) return false;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return true;
|
|
10
|
+
return memory.compare(data.dataStart, data2.dataStart, data.length) === 0;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
@final
|
|
@@ -16,78 +15,167 @@ export class MapUint8Array implements IMap<Uint8Array, Uint8Array> {
|
|
|
16
15
|
protected _keys: Uint8Array[] = [];
|
|
17
16
|
protected _values: Uint8Array[] = [];
|
|
18
17
|
|
|
18
|
+
// CACHE: Stores the index of the last successful lookup to make repeated access O(1)
|
|
19
|
+
private _lastIndex: i32 = -1;
|
|
20
|
+
|
|
21
|
+
@inline
|
|
19
22
|
public get size(): i32 {
|
|
20
23
|
return this._keys.length;
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
@inline
|
|
23
27
|
public keys(): Uint8Array[] {
|
|
24
28
|
return this._keys;
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
@inline
|
|
27
32
|
public values(): Uint8Array[] {
|
|
28
33
|
return this._values;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
public set(key: Uint8Array, value: Uint8Array): this {
|
|
32
|
-
|
|
37
|
+
let index = -1;
|
|
38
|
+
|
|
39
|
+
// Check Cache (Fastest)
|
|
40
|
+
if (this._lastIndex !== -1) {
|
|
41
|
+
const cachedKey = unchecked(this._keys[this._lastIndex]);
|
|
42
|
+
// Check length first, then full content equality
|
|
43
|
+
if (cachedKey.length === key.length) {
|
|
44
|
+
if (memory.compare(cachedKey.dataStart, key.dataStart, key.length) === 0) {
|
|
45
|
+
index = this._lastIndex;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Full Scan if cache missed
|
|
51
|
+
if (index === -1) {
|
|
52
|
+
index = this.indexOf(key);
|
|
53
|
+
}
|
|
54
|
+
|
|
33
55
|
if (index === -1) {
|
|
34
56
|
this._keys.push(key);
|
|
35
57
|
this._values.push(value);
|
|
58
|
+
this._lastIndex = this._keys.length - 1;
|
|
36
59
|
} else {
|
|
37
|
-
this._values[index] = value;
|
|
60
|
+
unchecked((this._values[index] = value));
|
|
61
|
+
// Cache is already pointing to this index (from indexOf or the check above)
|
|
62
|
+
this._lastIndex = index;
|
|
38
63
|
}
|
|
39
64
|
|
|
40
65
|
return this;
|
|
41
66
|
}
|
|
42
67
|
|
|
68
|
+
/**
|
|
69
|
+
* HYPER-OPTIMIZED SEARCH
|
|
70
|
+
* Scans for Data Equality (not object equality).
|
|
71
|
+
* Uses a "Prefix Filter" to skip expensive memory comparisons.
|
|
72
|
+
*/
|
|
43
73
|
public indexOf(pointerHash: Uint8Array): i32 {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
const len = this._keys.length;
|
|
75
|
+
if (len === 0) return -1;
|
|
76
|
+
|
|
77
|
+
const ptrLen = pointerHash.length;
|
|
78
|
+
const ptrData = pointerHash.dataStart;
|
|
79
|
+
|
|
80
|
+
// OPTIMIZATION: Prefix Filter
|
|
81
|
+
// If keys are long enough (hashes/addresses), we compare the first 8 bytes as a simple integer.
|
|
82
|
+
// This is 1 CPU cycle vs ~30+ cycles for a function call loop.
|
|
83
|
+
if (ptrLen >= 8) {
|
|
84
|
+
// Read the first 8 bytes of the SEARCH NEEDLE
|
|
85
|
+
const searchHeader = load<u64>(ptrData);
|
|
86
|
+
|
|
87
|
+
// Loop Backwards (finding most recently added items first is usually better for contracts)
|
|
88
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
89
|
+
const key = unchecked(this._keys[i]);
|
|
90
|
+
|
|
91
|
+
// Cheap Length Check
|
|
92
|
+
if (key.length !== ptrLen) continue;
|
|
93
|
+
|
|
94
|
+
// Cheap Integer Check (The Prefix Filter)
|
|
95
|
+
// This reads the CONTENT of the key, not the object pointer.
|
|
96
|
+
// If the first 8 bytes of data don't match, we skip the expensive check.
|
|
97
|
+
if (load<u64>(key.dataStart) !== searchHeader) continue;
|
|
98
|
+
|
|
99
|
+
// Expensive Full Check
|
|
100
|
+
// Only runs if length AND first 8 bytes match.
|
|
101
|
+
if (memory.compare(key.dataStart, ptrData, ptrLen) === 0) {
|
|
102
|
+
this._lastIndex = i;
|
|
103
|
+
return i;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// Fallback for small keys (< 8 bytes)
|
|
108
|
+
for (let i = len - 1; i >= 0; i--) {
|
|
109
|
+
const key = unchecked(this._keys[i]);
|
|
110
|
+
if (key.length !== ptrLen) continue;
|
|
111
|
+
|
|
112
|
+
if (memory.compare(key.dataStart, ptrData, ptrLen) === 0) {
|
|
113
|
+
this._lastIndex = i;
|
|
114
|
+
return i;
|
|
115
|
+
}
|
|
49
116
|
}
|
|
50
117
|
}
|
|
51
118
|
|
|
52
119
|
return -1;
|
|
53
120
|
}
|
|
54
121
|
|
|
122
|
+
@inline
|
|
55
123
|
public has(key: Uint8Array): bool {
|
|
56
124
|
return this.indexOf(key) !== -1;
|
|
57
125
|
}
|
|
58
126
|
|
|
59
127
|
public get(key: Uint8Array): Uint8Array {
|
|
60
|
-
const index
|
|
128
|
+
const index = this.indexOf(key);
|
|
61
129
|
if (index === -1) {
|
|
62
130
|
throw new Revert('Key not found in map (uint8array)');
|
|
63
131
|
}
|
|
64
|
-
return this._values[index];
|
|
132
|
+
return unchecked(this._values[index]);
|
|
65
133
|
}
|
|
66
134
|
|
|
67
135
|
public delete(key: Uint8Array): bool {
|
|
68
|
-
const index
|
|
69
|
-
if (index === -1)
|
|
70
|
-
|
|
136
|
+
const index = this.indexOf(key);
|
|
137
|
+
if (index === -1) return false;
|
|
138
|
+
|
|
139
|
+
const lastIndex = this._keys.length - 1;
|
|
140
|
+
|
|
141
|
+
// Swap and Pop (O(1) delete)
|
|
142
|
+
if (index !== lastIndex) {
|
|
143
|
+
// Move last element to the deleted position
|
|
144
|
+
unchecked((this._keys[index] = this._keys[lastIndex]));
|
|
145
|
+
unchecked((this._values[index] = this._values[lastIndex]));
|
|
146
|
+
|
|
147
|
+
// Update cache based on what was affected
|
|
148
|
+
if (this._lastIndex === lastIndex) {
|
|
149
|
+
// Cache pointed to last element, which moved to 'index'
|
|
150
|
+
this._lastIndex = index;
|
|
151
|
+
} else if (this._lastIndex === index) {
|
|
152
|
+
// Cache pointed to deleted element, invalidate it
|
|
153
|
+
this._lastIndex = -1;
|
|
154
|
+
}
|
|
155
|
+
// Note: If cache points to any other index, it remains valid
|
|
156
|
+
// because swap-and-pop only modifies positions 'index' and 'lastIndex'
|
|
157
|
+
} else {
|
|
158
|
+
// Deleting the last element (no swap needed)
|
|
159
|
+
if (this._lastIndex === lastIndex) {
|
|
160
|
+
this._lastIndex = -1;
|
|
161
|
+
}
|
|
162
|
+
// If cache points to any earlier index, it remains valid
|
|
71
163
|
}
|
|
72
164
|
|
|
73
|
-
this._keys.
|
|
74
|
-
this._values.
|
|
165
|
+
this._keys.pop();
|
|
166
|
+
this._values.pop();
|
|
75
167
|
|
|
76
168
|
return true;
|
|
77
169
|
}
|
|
78
170
|
|
|
171
|
+
@inline
|
|
79
172
|
public clear(): void {
|
|
80
173
|
this._keys = [];
|
|
81
174
|
this._values = [];
|
|
175
|
+
this._lastIndex = -1;
|
|
82
176
|
}
|
|
83
177
|
|
|
84
178
|
public toString(): string {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
for (let i: i32 = 0; i < this._keys.length; i++) {
|
|
88
|
-
str.concat(`[${this._keys[i].toString()}] => [${this._values[i].toString()}]`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return str;
|
|
179
|
+
return `Map(size=${this._keys.length})`;
|
|
92
180
|
}
|
|
93
181
|
}
|
|
@@ -15,7 +15,7 @@ export class StoredAddress {
|
|
|
15
15
|
this.addressPointer = encodePointer(pointer, EMPTY_POINTER, true, 'StoredAddress');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
private _value: Address = Address.
|
|
18
|
+
private _value: Address = Address.zero();
|
|
19
19
|
|
|
20
20
|
public get value(): Address {
|
|
21
21
|
this.ensureValue();
|
|
@@ -34,7 +34,7 @@ export class StoredAddress {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
public isDead(): bool {
|
|
37
|
-
return eqUint(Address.
|
|
37
|
+
return eqUint(Address.zero(), this.value);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
private ensureValue(): void {
|