96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import { LE } from "./buf.ts";
|
|
|
|
export const PAGE_SIZE = 4096;
|
|
const HEADER_SIZE = 40;
|
|
const ROW_GROUP_SIZE = 36;
|
|
const ROWS_PER_GROUP = 16;
|
|
|
|
function align4(o: number): number { return (o + 3) & ~3; }
|
|
function numRowGroups(n: number): number { return Math.floor((n + ROWS_PER_GROUP - 1) / ROWS_PER_GROUP); }
|
|
|
|
export class PageBuilder {
|
|
pageIndex: number;
|
|
pageType: number;
|
|
nextPage: number;
|
|
rowBytes: Uint8Array[] = [];
|
|
rowOffsets: number[] = [];
|
|
heapCursor = 0;
|
|
|
|
constructor(pageIndex: number, pageType: number) {
|
|
this.pageIndex = pageIndex;
|
|
this.pageType = pageType;
|
|
this.nextPage = pageIndex;
|
|
}
|
|
|
|
tryAdd(row: Uint8Array): boolean {
|
|
const aligned = align4(this.heapCursor);
|
|
const end = aligned + row.length;
|
|
const footerAfter = numRowGroups(this.rowBytes.length + 1) * ROW_GROUP_SIZE;
|
|
if (end + HEADER_SIZE + footerAfter > PAGE_SIZE) return false;
|
|
this.rowOffsets.push(aligned);
|
|
this.rowBytes.push(row);
|
|
this.heapCursor = end;
|
|
return true;
|
|
}
|
|
|
|
numRows(): number { return this.rowBytes.length; }
|
|
|
|
finalize(): Uint8Array {
|
|
const n = this.rowBytes.length;
|
|
const numRgs = numRowGroups(n);
|
|
const footerSize = numRgs * ROW_GROUP_SIZE;
|
|
const heapPadding = PAGE_SIZE - HEADER_SIZE - footerSize;
|
|
const usedSize = this.heapCursor;
|
|
const freeSize = heapPadding - usedSize;
|
|
if (freeSize < 0) throw new Error("page overflow");
|
|
|
|
const pageFlags = n > 0 ? 0x00 : 0x44;
|
|
|
|
const h = new LE();
|
|
h.u32(0); // magic
|
|
h.u32(this.pageIndex);
|
|
h.u32(this.pageType);
|
|
h.u32(this.nextPage);
|
|
h.u32(0); // unknown1
|
|
h.u32(0); // unknown2
|
|
h.u8(n & 0xff); h.u8(0); h.u8(0); h.u8(pageFlags);
|
|
h.u16(freeSize); h.u16(usedSize);
|
|
h.u16(0); // unknown5
|
|
h.u16(n); // num_rows_large
|
|
h.u16(0); // unknown6
|
|
h.u16(0); // unknown7
|
|
const header = h.build();
|
|
if (header.length !== HEADER_SIZE) throw new Error(`hdr len ${header.length}`);
|
|
|
|
const heap = new Uint8Array(heapPadding);
|
|
for (let i = 0; i < this.rowBytes.length; i++) {
|
|
heap.set(this.rowBytes[i]!, this.rowOffsets[i]!);
|
|
}
|
|
|
|
const footer = new LE();
|
|
for (let g = numRgs - 1; g >= 0; g--) {
|
|
const start = g * ROWS_PER_GROUP;
|
|
const grpOffsets = this.rowOffsets.slice(start, start + ROWS_PER_GROUP);
|
|
const grpCount = grpOffsets.length;
|
|
const padded = grpOffsets.slice();
|
|
while (padded.length < ROWS_PER_GROUP) padded.push(0);
|
|
const presence = (1 << grpCount) - 1;
|
|
const rg = new LE();
|
|
for (let i = padded.length - 1; i >= 0; i--) rg.u16(padded[i]!);
|
|
rg.u16(presence);
|
|
rg.u16(16);
|
|
const rgBytes = rg.build();
|
|
if (rgBytes.length !== ROW_GROUP_SIZE) throw new Error("rg size");
|
|
footer.bytes(rgBytes);
|
|
}
|
|
const footerBytes = footer.build();
|
|
if (footerBytes.length !== footerSize) throw new Error("footer size");
|
|
|
|
const out = new Uint8Array(PAGE_SIZE);
|
|
out.set(header, 0);
|
|
out.set(heap, HEADER_SIZE);
|
|
out.set(footerBytes, HEADER_SIZE + heapPadding);
|
|
return out;
|
|
}
|
|
}
|