1
use crate::error::{Error, Result};
2
use crate::host;
3
use scripting_format::{
4
    ENTITY_HEADER_SIZE, EntityHeader, EntityType, OUTPUT_HEADER_SIZE, Operation, OutputHeader,
5
    TAG_DATA_SIZE, TagData,
6
};
7

            
8
pub struct OutputWriter {
9
    buffer: &'static mut [u8],
10
    input_entity_count: u32,
11
    entity_count: u32,
12
    next_entity_offset: u32,
13
    strings_end: u32,
14
    total_strings_len: u32,
15
}
16

            
17
impl OutputWriter {
18
    #[must_use]
19
    pub fn new(input_entity_count: u32, output_size: u32) -> Self {
20
        let base = host::output_offset() as *mut u8;
21

            
22
        // SAFETY: WASM linear memory is valid for the lifetime of the module.
23
        // The host guarantees the output buffer region is reserved for us.
24
        let buffer = unsafe { core::slice::from_raw_parts_mut(base, output_size as usize) };
25

            
26
        Self {
27
            buffer,
28
            input_entity_count,
29
            entity_count: 0,
30
            next_entity_offset: OUTPUT_HEADER_SIZE as u32,
31
            strings_end: output_size,
32
            total_strings_len: 0,
33
        }
34
    }
35

            
36
    fn remaining_space(&self) -> u32 {
37
        self.strings_end.saturating_sub(self.next_entity_offset)
38
    }
39

            
40
    fn allocate_entity(&mut self, data_size: u32) -> Result<(u32, u32)> {
41
        let total_size = ENTITY_HEADER_SIZE as u32 + data_size;
42
        if self.remaining_space() < total_size {
43
            return Err(Error::OutputBufferFull);
44
        }
45

            
46
        let header_offset = self.next_entity_offset;
47
        let data_offset = header_offset + ENTITY_HEADER_SIZE as u32;
48
        self.next_entity_offset += total_size;
49

            
50
        Ok((header_offset, data_offset))
51
    }
52

            
53
    fn write_string(&mut self, s: &[u8]) -> Result<u32> {
54
        let len = s.len() as u32;
55
        if self.remaining_space() < len {
56
            return Err(Error::OutputBufferFull);
57
        }
58

            
59
        // Allocate from the end, growing down
60
        self.strings_end -= len;
61
        self.total_strings_len += len;
62

            
63
        // Write string at the allocated position
64
        let write_pos = self.strings_end as usize;
65
        self.buffer[write_pos..write_pos + s.len()].copy_from_slice(s);
66

            
67
        // Offset is distance from buffer end to string start
68
        // Parser will use: buffer[strings_offset - offset .. strings_offset - offset + len]
69
        // where strings_offset = buffer_len (set in finalize)
70
        let output_size = self.buffer.len() as u32;
71
        Ok(output_size - self.strings_end)
72
    }
73

            
74
    fn write_bytes(&mut self, offset: u32, data: &[u8]) {
75
        let start = offset as usize;
76
        self.buffer[start..start + data.len()].copy_from_slice(data);
77
    }
78

            
79
    pub fn create_tag(&mut self, parent_idx: i32, name: &str, value: &str) -> Result<u32> {
80
        let name_bytes = name.as_bytes();
81
        let value_bytes = value.as_bytes();
82

            
83
        let (header_offset, data_offset) = self.allocate_entity(TAG_DATA_SIZE as u32)?;
84

            
85
        let name_str_offset = self.write_string(name_bytes)?;
86
        let value_str_offset = self.write_string(value_bytes)?;
87

            
88
        let id = host::new_uuid();
89

            
90
        let entity_header = EntityHeader::new(
91
            EntityType::Tag,
92
            Operation::Create,
93
            0,
94
            id,
95
            parent_idx,
96
            data_offset,
97
            TAG_DATA_SIZE as u32,
98
        );
99

            
100
        let tag_data = TagData {
101
            name_offset: name_str_offset,
102
            value_offset: value_str_offset,
103
            name_len: name_bytes.len() as u16,
104
            value_len: value_bytes.len() as u16,
105
            reserved: [0; 4],
106
        };
107

            
108
        self.write_bytes(header_offset, &entity_header.to_bytes());
109
        self.write_bytes(data_offset, &tag_data.to_bytes());
110

            
111
        let entity_idx = self.input_entity_count + self.entity_count;
112
        self.entity_count += 1;
113

            
114
        Ok(entity_idx)
115
    }
116

            
117
    pub fn delete_entity(&mut self, entity_id: [u8; 16], entity_type: EntityType) -> Result<u32> {
118
        let (header_offset, data_offset) = self.allocate_entity(0)?;
119

            
120
        let entity_header = EntityHeader::new(
121
            entity_type,
122
            Operation::Delete,
123
            0,
124
            entity_id,
125
            -1,
126
            data_offset,
127
            0,
128
        );
129

            
130
        self.write_bytes(header_offset, &entity_header.to_bytes());
131

            
132
        let entity_idx = self.input_entity_count + self.entity_count;
133
        self.entity_count += 1;
134

            
135
        Ok(entity_idx)
136
    }
137

            
138
    pub fn finalize(mut self) -> Result<()> {
139
        let output_size = self.buffer.len() as u32;
140
        let output_header = OutputHeader {
141
            magic: scripting_format::MAGIC_OUTP,
142
            output_entity_count: self.entity_count,
143
            output_start_idx: self.input_entity_count,
144
            strings_offset: output_size,
145
            next_write_pos: self.next_entity_offset,
146
            reserved: [0; 4],
147
        };
148

            
149
        self.write_bytes(0, &output_header.to_bytes());
150

            
151
        Ok(())
152
    }
153

            
154
    #[must_use]
155
    pub fn entity_count(&self) -> u32 {
156
        self.entity_count
157
    }
158
}