在上一章 解码器 (Decoder) 中,我们认识了那位能够从蛛丝马迹中还原真相的数据侦探。我们了解到,解码器需要收集所有收到的数据包(无论是原始的还是恢复的)作为“线索”来破案。但你是否想过,解码器是如何管理这些线索的?它又是如何快速判断哪些线索已经到手,哪些还缺失呢?
这就引出了我们本章的主角:数据窗口 (AppDataWindow)。它虽然不像编码器和解码器那样身处一线,但它却是整个 fecal 工厂中不可或缺的“中央档案室”和“高效管家”。没有它,编码和解码过程将陷入一片混乱。
为什么需要一个“中央档案室”?
想象一下,编码器和解码器是两位忙碌的工匠。他们面前堆满了成百上千个数据包。
- 编码器需要知道:“我总共要处理多少个原始包?每个包应该多大?”
- 解码器的需求则更复杂:“我收到了哪些原始包?它们的索引分别是多少?哪些包丢失了?我又收到了哪些恢复包?”
如果让工匠们自己去翻找和记录这些信息,效率会极其低下。他们需要一个专门的助手来处理这些繁杂但至关重要的管理工作。AppDataWindow 就是这个助手。它的核心任务就是:为编码器和解码器提供一个统一、高效的数据管理中心,记录所有与数据包相关的元信息。
AppDataWindow:基础蓝图
AppDataWindow 是一个基础结构体,定义在 FecalCommon.h 文件中。它就像档案室的基础设计蓝图,规定了所有数据管理工作都必须记录的最基本信息。
// 文件: FecalCommon.h
struct AppDataWindow
{
// 应用参数
unsigned InputCount = 0; // 输入符号(原始包)的数量
uint64_t TotalBytes = 0; // 所有输入字节的总大小
unsigned FinalBytes = 0; // 最后一个符号的大小
unsigned SymbolBytes = 0; // 其他所有符号的大小
};
这段代码非常直白。它记录了:
InputCount:原始数据包的总数。这是最关键的信息之一。TotalBytes:所有原始数据包加起来的总大小。SymbolBytes和FinalBytes:由于数据总大小不一定能被包数整除,所以大部分包的大小是SymbolBytes,最后一个包的大小可能是FinalBytes。
无论是编码器还是解码器,都需要这些基本信息来正确地分配内存和处理数据。
DecoderAppDataWindow:解码器的专属高级档案员
虽然编码器只需要基础的档案管理,但解码器的工作要复杂得多,它需要一位更专业的“高级档案员”。因此,fecal 为解码器设计了一个专门的子类:DecoderAppDataWindow。
DecoderAppDataWindow 继承了 AppDataWindow 的所有基础功能,并在此之上添加了许多强大的追踪能力。
graph TD
A["AppDataWindow (基础档案蓝图)"] --> B{"DecoderAppDataWindow (解码器专属档案室)"};
subgraph "基础信息"
A_InputCount["总包数 (InputCount)"]
A_TotalBytes["总大小 (TotalBytes)"]
end
subgraph "解码器专属追踪"
B_Original["原始包存放区 (OriginalData)"]
B_Recovery["恢复包存放区 (RecoveryData)"]
B_Status["接收状态追踪 (Subwindows)"]
end
A_InputCount --> A
A_TotalBytes --> A
B --> B_Original
B --> B_Recovery
B --> B_Status
style B fill:#87ceeb,stroke:#333,stroke-width:2px
让我们看看 DecoderAppDataWindow 在 FecalDecoder.h 中的定义,它增加了哪些“专属装备”:
// 文件: FecalDecoder.h (简化版)
struct DecoderAppDataWindow : AppDataWindow
{
// 已收到的原始数据
std::vector<OriginalInfo> OriginalData;
// 已收到的恢复数据
std::vector<RecoveryInfo> RecoveryData;
// 追踪哪些条目已填充
std::vector<Subwindow> Subwindows;
// 已收到的独立原始包数量
unsigned OriginalGotCount = 0;
// ... 其他 ...
};
新增的核心部分是:
OriginalData:一个向量(可以看作是动态数组),用来存放所有已收到的原始包的指针和信息。RecoveryData:另一个向量,用来存放所有已收到的恢复包的指针和信息。Subwindows:这是最巧妙的设计!它是一个用来高效追踪哪些原始包已收到、哪些已丢失的系统。我们稍后会深入讲解。OriginalGotCount:一个计数器,记录当前收到了多少个不同的原始包。
“管家”是如何工作的?
当解码器收到一个数据包时,它会把它交给 DecoderAppDataWindow 这个“管家”进行归档。让我们通过一个时序图,看看当解码器收到一个原始数据包时,内部发生了什么。
sequenceDiagram
participant User as 用户
participant Decoder as 解码器
participant Window as DecoderAppDataWindow
User->>Decoder: 调用 fecal_decoder_add_original(symbol)
Decoder->>Window: 调用 AddOriginal(symbol.Index, symbol.Data)
Window->>Window: 检查该包是否已存在?(避免重复)
Window->>Window: 记录数据指针到 OriginalData 列表
Window->>Window: 调用 MarkGotElement(symbol.Index) 更新接收状态
Window->>Decoder: 返回 true (添加成功)
Decoder->>User: 返回 Fecal_Success
归档入库 (AddOriginal)
AddOriginal 方法负责将一个新收到的原始包登记入册。
// 文件: FecalDecoder.cpp (简化版)
bool DecoderAppDataWindow::AddOriginal(unsigned column, uint8_t* data)
{
// 如果我们已经有这个包了,就直接返回
if (OriginalData[column].Data)
return false;
// 记录这个包
OriginalData[column].Data = data;
MarkGotElement(column); // 关键一步:标记为已收到
++OriginalGotCount;
return true;
}
这个过程就像图书管理员把一本新书上架:
- 检查书架上这个位置是不是已经有书了。
- 把书(
data指针)放到对应的位置(OriginalData[column])。 - 在借阅卡片上打个勾(
MarkGotElement),表示这本书在馆。 - 总在馆书籍数量加一(
++OriginalGotCount)。
神奇的接收状态追踪系统
DecoderAppDataWindow 最核心的价值在于它能以极高的效率回答“哪些包丢了?”这个问题。这是通过 Subwindows 和 MarkGotElement 方法实现的。
这里的 Subwindow 内部包含一个叫做 CustomBitSet 的结构,你可以把它想象成一张巨大的打孔卡。总共有 InputCount 个位置,每个位置代表一个原始包。初始时,所有位置都是空的(值为 0)。
当 MarkGotElement被调用时,它就在这张卡片上对应的位置打一个孔(将值设为 1)。
// 文件: FecalDecoder.cpp (简化版)
void DecoderAppDataWindow::MarkGotElement(unsigned element)
{
// 1. 计算 element 属于哪个子窗口 (subwindow)
Subwindow& subwindow = Subwindows[element / kSubwindowSize];
// 2. 在该子窗口的“打孔卡”上,将对应的位设为 1
subwindow.Got.Set(element % kSubwindowSize);
// 3. 该子窗口的已收到计数加一
subwindow.GotCount++;
}
由于计算机操作位(0 和 1)的速度飞快,这个标记过程几乎是瞬时的。
快速查找失物 (FindNextLostElement)
当解码器准备构建恢复矩阵 (Recovery Matrix)时,它需要知道所有丢失包的索引。这时,FindNextLostElement 方法就派上用场了。它会快速扫描那张“打孔卡”,找到下一个值为 0 的位置。
// 文件: FecalDecoder.cpp (简化版)
unsigned DecoderAppDataWindow::FindNextLostElement(unsigned elementStart)
{
// 从 elementStart 开始,在各个 subwindow 中寻找
while (subwindowIndex < subwindowEnd)
{
// 如果这个子窗口可能还有丢失的包
if (Subwindows[subwindowIndex].GotCount < kSubwindowSize)
{
// 在“打孔卡”中寻找第一个为 0 的位
bitIndex = Subwindows[subwindowIndex].Got.FindFirstClear(bitIndex);
// 如果找到了,计算并返回它的全局索引
if (bitIndex < kSubwindowSize)
return subwindowIndex * kSubwindowSize + bitIndex;
}
// ... 检查下一个子窗口 ...
}
return InputCount; // 没找到,说明都收到了
}
这个函数同样非常高效。它不是一个一个地检查,而是可以整块(WordT,通常是64位)地检查位图,极大地加快了查找速度。这使得解码器在需要时,能够瞬间列出所有“失踪人员”的名单。
总结
在本章中,我们深入了解了 fecal 库的“中央档案室”——数据窗口 (AppDataWindow)。我们学到了:
- 它的使命:为编码器和解码器提供一个集中管理所有数据包信息的地方,避免混乱。
AppDataWindow是一个基础蓝图,定义了所有数据管理都需要的核心信息,如总包数和总大小。DecoderAppDataWindow是解码器的专属“高级管家”,它不仅记录基础信息,还通过列表和高效的位图(BitSet)来实时追踪哪些原始包已收到、哪些已丢失,以及收到了哪些恢复包。- 高效的追踪机制:通过
MarkGotElement和FindNextLostElement等方法,DecoderAppDataWindow能够以极快的速度更新和查询数据包的状态,为解码器的下一步工作(建立恢复矩阵)提供了坚实的基础。
现在,我们已经完全明白了,当数据包丢失时,解码器是如何通过它的得力助手 DecoderAppDataWindow 准确地知道“哪些包丢了”和“我手头有哪些恢复包”的。
知道了这些信息之后,解码器就可以开始真正的“破案”工作了——建立一个数学方程组来解出丢失的数据。这个方程组的具体形式,就是我们下一章将要探讨的主题:恢复矩阵 (Recovery Matrix)。