This commit is contained in:
Nathan Rashleigh committed 2026-05-26 16:27:50 +10:00
1 parent 32adb5ddac
commit 1e49010aa9
17 files changed
+1049

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
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;
}
}