-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpointer.ts
More file actions
150 lines (147 loc) · 4.89 KB
/
Copy pathpointer.ts
File metadata and controls
150 lines (147 loc) · 4.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import type {AppendableBuffer} from '../lib/appendable'
import * as assert from '../lib/assert'
import * as bufferString from '../lib/buffer-string'
import * as flexInt from '../lib/flex-int'
import {BufferOffset, readFlexInt} from '../lib/read-util'
import AbsoluteType from './absolute'
import AbstractType from './abstract'
import {Type} from './type'
//Map of write buffers to maps of binary strings to the location they were written
const pointers = new WeakMap<AppendableBuffer, Map<string, number>>()
//Map of read value buffers to maps of pointer types to maps of pointer values to read results
const pointerReads = new WeakMap<ArrayBuffer, Map<PointerType<unknown>, Map<number, unknown>>>()
export function rewindBuffer(buffer: AppendableBuffer): void {
const locations = pointers.get(buffer)
if (locations) {
const {length} = buffer
for (const [value, index] of locations) {
if (index >= length) locations.delete(value)
}
}
}
/**
* A type storing a value of another type through a pointer.
* If you expect to have the same large value repeated many times,
* using a pointer will decrease the size of the value `ArrayBuffer`.
* If the value has already been written, 1 to 2 bytes are
* likely needed to write the pointer (more if values are far apart
* in output buffer).
* In comparison to without a pointer type, only 1 extra byte will
* be used if the value has not yet been written to the output buffer.
*
* Example:
* ````javascript
* //If the same people will be used many times
* let personType = new sb.PointerType(
* new sb.StructType({
* dob: new sb.DayType,
* id: new sb.UnsignedShortType,
* name: new sb.StringType
* })
* )
* let tribeType = new sb.StructType({
* leader: personType,
* members: new sb.SetType(personType),
* money: new sb.MapType(personType, new sb.FloatType)
* })
* ````
*
* @param E The type of values that can be written
* @param READ_E The type of values that will be read
*/
export class PointerType<E, READ_E extends E = E> extends AbstractType<E, READ_E> {
static get _value(): number {
return 0x70
}
/**
* @param type The [[Type]] used to write the values being pointed to
*/
constructor(readonly type: Type<E, READ_E>) {
super()
assert.instanceOf(type, AbsoluteType)
}
addToBuffer(buffer: AppendableBuffer): boolean {
/*istanbul ignore else*/
if (super.addToBuffer(buffer)) {
this.type.addToBuffer(buffer)
return true
}
/*istanbul ignore next*/
return false
}
/**
* Appends value bytes to an [[AppendableBuffer]] according to the type
*
* Example:
* ````javascript
* let louis = {
* dob: new Date(1437592284193),
* id: 9,
* name: 'Louis'
* },
* garfield = {
* dob: new Date(1437592284194),
* id: 17,
* name: 'Garfield'
* }
* let value = {
* leader: {
* dob: new Date(1437592284192),
* id: 10,
* name: 'Joe'
* },
* members: new Set([louis, garfield]),
* money: new Map().set(louis, 23.05).set(garfield, -10.07)
* }
* tribeType.writeValue(buffer, value)
* ````
* @param buffer The buffer to which to append
* @param value The value to write
* @throws If the value doesn't match the type, e.g. `new sb.StringType().writeValue(buffer, 23)`
*/
writeValue(buffer: AppendableBuffer, value: E): void {
assert.isBuffer(buffer)
let bufferPointers = pointers.get(buffer)
if (!bufferPointers) {
//Initialize pointers map if it doesn't exist
pointers.set(buffer, bufferPointers = new Map)
}
const valueBuffer = this.type.valueBuffer(value)
const valueString = bufferString.toBinaryString(valueBuffer) //have to convert the buffer to a string because equivalent buffers are not ===
const index = bufferPointers.get(valueString)
const {length} = buffer
bufferPointers.set(valueString, length)
if (index === undefined) {
buffer
.addAll(flexInt.makeValueBuffer(0))
.addAll(valueBuffer)
}
else buffer.addAll(flexInt.makeValueBuffer(length - index))
}
consumeValue(bufferOffset: BufferOffset): READ_E {
const {buffer, offset} = bufferOffset
const offsetDiff = readFlexInt(bufferOffset)
let bufferPointerReads = pointerReads.get(buffer)
if (!bufferPointerReads) {
pointerReads.set(buffer, bufferPointerReads = new Map)
}
let bufferTypePointerReads = bufferPointerReads.get(this)
if (!bufferTypePointerReads) {
bufferPointerReads.set(this, bufferTypePointerReads = new Map)
}
const location = offset - offsetDiff
let value = bufferTypePointerReads.get(location) as READ_E | undefined
if (value === undefined) {
value = offsetDiff
//This value does exist, but it was serialized by a different type
? this.consumeValue({buffer, offset: location})
//This value is serialized here instead
: this.type.consumeValue(bufferOffset)
}
bufferTypePointerReads.set(offset, value)
return value
}
equals(otherType: unknown): boolean {
return this.isSameType(otherType) && this.type.equals(otherType.type)
}
}