writeFsceneb function
Serializes document to a .fsceneb container: the document's JSON
manifest followed by one chunk per payload.
Every payload in the document must carry its PayloadSpec.bytes; a
manifest-only payload (no bytes) cannot be embedded and throws a
FscenebFormatException. Payloads referenced by external ref rather than
an embedded chunk are not payloads, so they are unaffected.
Implementation
Uint8List writeFsceneb(SceneDocument document) {
final body = BytesBuilder();
void addChunk(String type, Uint8List data) {
final preamble = ByteData(8)..setUint32(0, data.length, Endian.little);
final typeBytes = ascii.encode(type);
for (var i = 0; i < 4; i++) {
preamble.setUint8(4 + i, typeBytes[i]);
}
body.add(preamble.buffer.asUint8List());
body.add(data);
final remainder = data.length % _alignment;
if (remainder != 0) body.add(Uint8List(_alignment - remainder));
}
addChunk(_chunkJson, utf8.encode(writeFscene(document)));
// Emit payloads in a deterministic (id-sorted) order, matching the JSON
// manifest's enumeration so two writes of the same document are identical.
final payloads = document.payloads.entries.toList()
..sort((a, b) => a.key.toToken().compareTo(b.key.toToken()));
for (final entry in payloads) {
final bytes = entry.value.bytes;
if (bytes == null) {
throw FscenebFormatException(
'Payload ${entry.key.toToken()} has no bytes to embed',
);
}
addChunk(_chunkBlob, _encodeBlob(entry.key, bytes));
}
final bodyBytes = body.toBytes();
final total = _headerByteLength + bodyBytes.length;
final out = Uint8List(total);
for (var i = 0; i < 4; i++) {
out[i] = _magic[i];
}
ByteData.sublistView(out)
..setUint32(4, kFscenebVersion, Endian.little)
..setUint32(8, total, Endian.little)
..setUint32(12, 0, Endian.little);
out.setRange(_headerByteLength, total, bodyBytes);
return out;
}