Skip to content

Commit b9f3d45

Browse files
authored
perf(it-code): decoder (#8)
1 parent 5e0108c commit b9f3d45

File tree

3 files changed

+119
-46
lines changed

3 files changed

+119
-46
lines changed

app/components/Scan.vue

+9-7
Original file line numberDiff line numberDiff line change
@@ -429,13 +429,15 @@ function now() {
429429
</p>
430430
</div>
431431

432-
<div flex="~ gap-1 wrap" max-w-150 text-xs>
433-
<div v-for="i, idx of decoder.encodedBlocks" :key="idx" border="~ gray/10 rounded" p1>
434-
<template v-for="x, idy of i.indices" :key="x">
435-
<span v-if="idy !== 0" op25>, </span>
436-
<span :style="{ color: `hsl(${x * 40}, 40%, 60%)` }">{{ x }}</span>
437-
</template>
432+
<Collapsable label="Blocks">
433+
<div flex="~ gap-1 wrap" max-w-150 text-xs>
434+
<div v-for="i, idx of decoder.encodedBlocks" :key="idx" border="~ gray/10 rounded" p1>
435+
<template v-for="x, idy of i.indices" :key="x">
436+
<span v-if="idy !== 0" op25>, </span>
437+
<span :style="{ color: `hsl(${x * 40}, 40%, 60%)` }">{{ x }}</span>
438+
</template>
439+
</div>
438440
</div>
439-
</div>
441+
</Collapsable>
440442
</div>
441443
</template>

test/lt-blocks.test.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,22 @@ it('cross-blocks resolving 1', () => {
3030
expect(data).toEqual(buffer)
3131
})
3232

33+
it('cross-blocks resolving 2', () => {
34+
const { buffer, encoder } = createEncoderWithIndices(3)
35+
36+
const decoder = createDecoder()
37+
decoder.addBlock(encoder.createBlock([1, 2]))
38+
decoder.addBlock(encoder.createBlock([0, 1, 2]))
39+
// Here we know [0]
40+
decoder.addBlock(encoder.createBlock([0, 2]))
41+
42+
const data = decoder.getDecoded()
43+
expect(data).toBeDefined()
44+
expect(data).toEqual(buffer)
45+
})
46+
3347
// TODO: get it work
34-
it.skip('cross-blocks resolving 2', () => {
48+
it.skip('cross-blocks resolving 3', () => {
3549
const { buffer, encoder } = createEncoderWithIndices(5)
3650

3751
const decoder = createDecoder()

utils/lt-code/decoder.ts

+95-38
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-cond-assign */
12
import type { EncodedBlock } from './shared'
23
import { inflate } from 'pako'
34
import { getChecksum } from './checksum'
@@ -12,6 +13,10 @@ export class LtDecoder {
1213
public decodedCount = 0
1314
public encodedCount = 0
1415
public encodedBlocks: Set<EncodedBlock> = new Set()
16+
public encodedBlockKeyMap: Map<string, EncodedBlock> = new Map()
17+
public encodedBlockSubkeyMap: Map<string, Set<EncodedBlock>> = new Map()
18+
public encodedBlockIndexMap: Map<number, Set<EncodedBlock>> = new Map()
19+
public disposedEncodedBlocks: Map<number, (() => void)[]> = new Map()
1520
public meta: EncodedBlock = undefined!
1621

1722
constructor(blocks?: EncodedBlock[]) {
@@ -32,65 +37,113 @@ export class LtDecoder {
3237
if (block.checksum !== this.meta.checksum) {
3338
throw new Error('Adding block with different checksum')
3439
}
35-
this.encodedBlocks.add(block)
3640
this.encodedCount += 1
3741

38-
this.propagateDecoded()
42+
block.indices = block.indices.sort((a, b) => a - b)
43+
this.propagateDecoded(indicesToKey(block.indices), block)
3944

4045
return this.decodedCount === this.meta.k
4146
}
4247

43-
propagateDecoded() {
44-
let changed = false
45-
for (const block of this.encodedBlocks) {
46-
let { data, indices } = block
48+
propagateDecoded(key: string, block: EncodedBlock) {
49+
const { decodedData, encodedBlocks, encodedBlockIndexMap, encodedBlockKeyMap, encodedBlockSubkeyMap, disposedEncodedBlocks } = this
4750

48-
// We already have all the data from this block
49-
if (indices.every(index => this.decodedData[index] != null)) {
50-
this.encodedBlocks.delete(block)
51-
continue
51+
let index: number
52+
let blocks: Set<EncodedBlock> | undefined
53+
54+
let { data, indices } = block
55+
const indicesSet = new Set(indices)
56+
57+
let subblock: EncodedBlock | undefined
58+
let subIndicesSet: Set<number>
59+
60+
if (encodedBlockKeyMap.has(key) || indices.every(i => decodedData[i] != null)) {
61+
return
62+
}
63+
64+
// XOR the data
65+
// Current block > degree 1, find decoded subset degree blocks to decode
66+
if (indices.length > 1) {
67+
for (const index of indices) {
68+
if (decodedData[index] != null) {
69+
block.data = data = xorUint8Array(data, decodedData[index]!)
70+
indicesSet.delete(index)
71+
}
5272
}
73+
if (indicesSet.size !== indices.length) {
74+
block.indices = indices = Array.from(indicesSet)
75+
}
76+
}
5377

54-
// XOR the data
78+
// Use 1x2x3 XOR 2x3 to get 1
79+
if (indices.length > 2) {
80+
const subkeys: [index: number, subkey: string][] = []
5581
for (const index of indices) {
56-
if (this.decodedData[index] != null) {
57-
block.data = data = xorUint8Array(data, this.decodedData[index]!)
58-
block.indices = indices = indices.filter(i => i !== index)
59-
changed = true
82+
const subkey = indicesToKey(indices.filter(i => i !== index))
83+
if (subblock = encodedBlockKeyMap.get(subkey)) {
84+
block.data = data = xorUint8Array(data, subblock.data)
85+
subIndicesSet = new Set(subblock.indices)
86+
for (const i of subIndicesSet) {
87+
indicesSet.delete(i)
88+
}
89+
block.indices = indices = Array.from(indicesSet)
90+
break
91+
}
92+
else {
93+
subkeys.push([index, subkey])
6094
}
6195
}
6296

63-
if (indices.length === 1 && this.decodedData[indices[0]!] == null) {
64-
this.decodedData[indices[0]!] = block.data
65-
this.decodedCount++
66-
this.encodedBlocks.delete(block)
67-
changed = true
97+
// If we can't find a subblock, store the subkeys for future decoding
98+
if (indicesSet.size > 1) {
99+
subkeys.forEach(([index, subkey]) => {
100+
const dispose = () => encodedBlockSubkeyMap.get(subkey)?.delete(block)
101+
encodedBlockSubkeyMap.get(subkey)?.add(block) ?? encodedBlockSubkeyMap.set(subkey, new Set([block]))
102+
disposedEncodedBlocks.get(index)?.push(dispose) ?? disposedEncodedBlocks.set(index, [dispose])
103+
})
68104
}
69105
}
70106

71-
for (const block of this.encodedBlocks) {
72-
const { data, indices } = block
73-
74-
// Use 1x2x3 XOR 2x3 to get 1
75-
if (indices.length >= 3) {
76-
const lowerBlocks = Array.from(this.encodedBlocks).filter(i => i.indices.length === indices.length - 1)
77-
for (const lower of lowerBlocks) {
78-
const extraIndices = indices.filter(i => !lower.indices.includes(i))
79-
if (extraIndices.length === 1 && this.decodedData[extraIndices[0]!] == null) {
80-
const extraData = xorUint8Array(data, lower.data)
81-
const extraIndex = extraIndices[0]!
82-
this.decodedData[extraIndex] = extraData
83-
this.decodedCount++
84-
this.encodedBlocks.delete(lower)
85-
changed = true
107+
// After decoding, if the block > degree 1, store it as a pending block awaiting decoding
108+
if (indices.length > 1) {
109+
block.indices.forEach((i) => {
110+
encodedBlocks.add(block)
111+
encodedBlockIndexMap.get(i)?.add(block) ?? encodedBlockIndexMap.set(i, new Set([block]))
112+
})
113+
114+
encodedBlockKeyMap.set(key = indicesToKey(indices), block)
115+
116+
// Use 1x2 XOR 1x2x3 to get 3
117+
const superset = encodedBlockSubkeyMap.get(key)
118+
if (superset) {
119+
encodedBlockSubkeyMap.delete(key)
120+
for (const superblock of superset) {
121+
const superIndicesSet = new Set(superblock.indices)
122+
superblock.data = xorUint8Array(superblock.data, data)
123+
for (const i of indices) {
124+
superIndicesSet.delete(i)
86125
}
126+
superblock.indices = Array.from(superIndicesSet)
127+
this.propagateDecoded(indicesToKey(superblock.indices), superblock)
87128
}
88129
}
89130
}
90131

91-
// If making some progress, continue
92-
if (changed) {
93-
this.propagateDecoded()
132+
// After decoding, if the block is a degree 1 block, store it in decoded data and find blocks that can be decoded
133+
else if (decodedData[index = indices[0]!] == null) {
134+
encodedBlocks.delete(block)
135+
disposedEncodedBlocks.get(index)?.forEach(dispose => dispose())
136+
decodedData[index] = block.data
137+
this.decodedCount += 1
138+
139+
if (blocks = encodedBlockIndexMap.get(index)) {
140+
encodedBlockIndexMap.delete(index)
141+
for (const block of blocks) {
142+
key = indicesToKey(block.indices)
143+
encodedBlockKeyMap.delete(key)
144+
this.propagateDecoded(key, block)
145+
}
146+
}
94147
}
95148
}
96149

@@ -135,3 +188,7 @@ export class LtDecoder {
135188
throw new Error('Checksum mismatch')
136189
}
137190
}
191+
192+
function indicesToKey(indices: number[]) {
193+
return indices.join(',')
194+
}

0 commit comments

Comments
 (0)