Lines
0 %
Functions
Branches
100 %
use crate::error::{Error, Result};
use crate::host;
use scripting_format::{
ENTITY_HEADER_SIZE, EntityHeader, EntityType, OUTPUT_HEADER_SIZE, Operation, OutputHeader,
TAG_DATA_SIZE, TagData,
};
pub struct OutputWriter {
buffer: &'static mut [u8],
input_entity_count: u32,
entity_count: u32,
next_entity_offset: u32,
strings_end: u32,
total_strings_len: u32,
}
impl OutputWriter {
#[must_use]
pub fn new(input_entity_count: u32, output_size: u32) -> Self {
let base = host::output_offset() as *mut u8;
// SAFETY: WASM linear memory is valid for the lifetime of the module.
// The host guarantees the output buffer region is reserved for us.
let buffer = unsafe { core::slice::from_raw_parts_mut(base, output_size as usize) };
Self {
buffer,
input_entity_count,
entity_count: 0,
next_entity_offset: OUTPUT_HEADER_SIZE as u32,
strings_end: output_size,
total_strings_len: 0,
fn remaining_space(&self) -> u32 {
self.strings_end.saturating_sub(self.next_entity_offset)
fn allocate_entity(&mut self, data_size: u32) -> Result<(u32, u32)> {
let total_size = ENTITY_HEADER_SIZE as u32 + data_size;
if self.remaining_space() < total_size {
return Err(Error::OutputBufferFull);
let header_offset = self.next_entity_offset;
let data_offset = header_offset + ENTITY_HEADER_SIZE as u32;
self.next_entity_offset += total_size;
Ok((header_offset, data_offset))
fn write_string(&mut self, s: &[u8]) -> Result<u32> {
let len = s.len() as u32;
if self.remaining_space() < len {
// Allocate from the end, growing down
self.strings_end -= len;
self.total_strings_len += len;
// Write string at the allocated position
let write_pos = self.strings_end as usize;
self.buffer[write_pos..write_pos + s.len()].copy_from_slice(s);
// Offset is distance from buffer end to string start
// Parser will use: buffer[strings_offset - offset .. strings_offset - offset + len]
// where strings_offset = buffer_len (set in finalize)
let output_size = self.buffer.len() as u32;
Ok(output_size - self.strings_end)
fn write_bytes(&mut self, offset: u32, data: &[u8]) {
let start = offset as usize;
self.buffer[start..start + data.len()].copy_from_slice(data);
pub fn create_tag(&mut self, parent_idx: i32, name: &str, value: &str) -> Result<u32> {
let name_bytes = name.as_bytes();
let value_bytes = value.as_bytes();
let (header_offset, data_offset) = self.allocate_entity(TAG_DATA_SIZE as u32)?;
let name_str_offset = self.write_string(name_bytes)?;
let value_str_offset = self.write_string(value_bytes)?;
let id = host::new_uuid();
let entity_header = EntityHeader::new(
EntityType::Tag,
Operation::Create,
0,
id,
parent_idx,
data_offset,
TAG_DATA_SIZE as u32,
);
let tag_data = TagData {
name_offset: name_str_offset,
value_offset: value_str_offset,
name_len: name_bytes.len() as u16,
value_len: value_bytes.len() as u16,
reserved: [0; 4],
self.write_bytes(header_offset, &entity_header.to_bytes());
self.write_bytes(data_offset, &tag_data.to_bytes());
let entity_idx = self.input_entity_count + self.entity_count;
self.entity_count += 1;
Ok(entity_idx)
pub fn delete_entity(&mut self, entity_id: [u8; 16], entity_type: EntityType) -> Result<u32> {
let (header_offset, data_offset) = self.allocate_entity(0)?;
entity_type,
Operation::Delete,
entity_id,
-1,
pub fn finalize(mut self) -> Result<()> {
let output_header = OutputHeader {
magic: scripting_format::MAGIC_OUTP,
output_entity_count: self.entity_count,
output_start_idx: self.input_entity_count,
strings_offset: output_size,
next_write_pos: self.next_entity_offset,
self.write_bytes(0, &output_header.to_bytes());
Ok(())
pub fn entity_count(&self) -> u32 {
self.entity_count