11// CSS Data Arena - Single contiguous ArrayBuffer for all AST nodes
22//
3- // Each node occupies 40 bytes with the following layout:
3+ // Each node occupies 32 bytes with the following layout:
44// Offset | Size | Field
55// -------|------|-------------
66// 0 | 1 | type
77// 1 | 1 | flags
8- // 2 | 2 | (padding)
9- // 4 | 4 | startOffset
10- // 8 | 2 | length
11- // 10 | 2 | (padding)
12- // 12 | 2 | contentStartDelta (offset from startOffset, property name / at-rule name)
13- // 14 | 2 | contentLength
14- // 16 | 2 | valueStartDelta (offset from startOffset, declaration value / at-rule prelude)
15- // 18 | 2 | valueLength
16- // 20 | 4 | firstChild
17- // 24 | 4 | lastChild
18- // 28 | 4 | nextSibling
19- // 32 | 4 | startLine
20- // 36 | 2 | startColumn
21- // 38 | 2 | (padding)
8+ // 2 | 2 | length
9+ // 4 | 4 | firstChild
10+ // 8 | 4 | nextSibling
11+ // 12 | 4 | startOffset
12+ // 16 | 2 | contentStartDelta (offset from startOffset, property name / at-rule name)
13+ // 18 | 2 | valueStartDelta (offset from startOffset, declaration value / at-rule prelude)
14+ // 20 | 2 | contentLength
15+ // 22 | 2 | valueLength
16+ // 24 | 4 | startLine
17+ // 28 | 2 | startColumn
18+ // 30 | 1 | attr_operator (reusing padding)
19+ // 31 | 1 | attr_flags (reusing padding)
2220//
2321// HOW THE ARENA WORKS:
24- // 1. BYTES_PER_NODE defines the size of each node (40 bytes). The ArrayBuffer size is calculated
25- // as: capacity × BYTES_PER_NODE. For example, 1024 nodes = 40,960 bytes (~40KB ).
26- // Node indices map to byte offsets via: node_offset = node_index × 40 .
22+ // 1. BYTES_PER_NODE defines the size of each node (32 bytes). The ArrayBuffer size is calculated
23+ // as: capacity × BYTES_PER_NODE. For example, 1024 nodes = 32,768 bytes (32KB ).
24+ // Node indices map to byte offsets via: node_offset = node_index × 32 .
2725//
2826// 2. We use a single DataView over the ArrayBuffer to read/write different types at specific offsets.
2927// - Uint8: 1-byte reads/writes for type, flags (e.g., view.getUint8(offset))
3028// - Uint16: 2-byte reads/writes for length, deltas, column (e.g., view.getUint16(offset, true))
3129// - Uint32: 4-byte reads/writes for startOffset, pointers, line (e.g., view.getUint32(offset, true))
3230// The 'true' parameter specifies little-endian byte order (native on x86/ARM CPUs).
3331//
34- // 3. Padding (6 bytes total at offsets 2-3, 10-11, 38-39 ) ensures memory alignment for performance:
35- // - Uint32 fields align to 4-byte boundaries (offsets 4, 20, 24, 28, 32 )
36- // - Uint16 fields align to 2-byte boundaries (offsets 8, 10, 12, 14, 16, 18, 36, 38 )
32+ // 3. Padding (2 bytes total at offsets 30-31 ) ensures memory alignment for performance:
33+ // - Uint32 fields align to 4-byte boundaries (offsets 4, 8, 12, 24 )
34+ // - Uint16 fields align to 2-byte boundaries (offsets 2, 16, 18, 20, 22, 28 )
3735// Aligned access is faster (single CPU instruction) vs unaligned (multiple memory accesses).
3836// Modern CPUs penalize unaligned reads/writes, making padding essential for performance.
3937//
40- // 4. The padding at offset 2-3 is reused for attribute selector data (attr_operator, attr_flags),
38+ // 4. The padding at offset 30-31 is reused for attribute selector data (attr_operator, attr_flags),
4139// making efficient use of otherwise wasted bytes. This is a space optimization trick.
4240//
4341// 5. Delta offsets (contentStartDelta, valueStartDelta) save memory: instead of storing absolute
44- // positions as uint32 (4 bytes), we store relative offsets as uint16 (2 bytes). This reduced
45- // node size from 44→40 bytes (10% smaller), saving memory while maintaining performance.
42+ // positions as uint32 (4 bytes), we store relative offsets as uint16 (2 bytes). Removing unused
43+ // lastChild field saved another 4 bytes. This reduced node size from 44→40→36→32 bytes (27%
44+ // smaller than original), saving memory while maintaining performance.
4645
47- let BYTES_PER_NODE = 40
46+ let BYTES_PER_NODE = 32
4847
4948// Node type constants
5049export const STYLESHEET = 1
@@ -174,71 +173,66 @@ export class CSSDataArena {
174173
175174 // Read start offset in source
176175 get_start_offset ( node_index : number ) : number {
177- return this . view . getUint32 ( this . node_offset ( node_index ) + 4 , true )
176+ return this . view . getUint32 ( this . node_offset ( node_index ) + 12 , true )
178177 }
179178
180179 // Read length in source
181180 get_length ( node_index : number ) : number {
182- return this . view . getUint16 ( this . node_offset ( node_index ) + 8 , true )
181+ return this . view . getUint16 ( this . node_offset ( node_index ) + 2 , true )
183182 }
184183
185184 // Read content start offset (stored as delta from startOffset)
186185 get_content_start ( node_index : number ) : number {
187186 const startOffset = this . get_start_offset ( node_index )
188- const delta = this . view . getUint16 ( this . node_offset ( node_index ) + 12 , true )
187+ const delta = this . view . getUint16 ( this . node_offset ( node_index ) + 16 , true )
189188 return startOffset + delta
190189 }
191190
192191 // Read content length
193192 get_content_length ( node_index : number ) : number {
194- return this . view . getUint16 ( this . node_offset ( node_index ) + 14 , true )
193+ return this . view . getUint16 ( this . node_offset ( node_index ) + 20 , true )
195194 }
196195
197196 // Read attribute operator (for NODE_SELECTOR_ATTRIBUTE)
198197 get_attr_operator ( node_index : number ) : number {
199- return this . view . getUint8 ( this . node_offset ( node_index ) + 2 )
198+ return this . view . getUint8 ( this . node_offset ( node_index ) + 30 )
200199 }
201200
202201 // Read attribute flags (for NODE_SELECTOR_ATTRIBUTE)
203202 get_attr_flags ( node_index : number ) : number {
204- return this . view . getUint8 ( this . node_offset ( node_index ) + 3 )
203+ return this . view . getUint8 ( this . node_offset ( node_index ) + 31 )
205204 }
206205
207206 // Read first child index (0 = no children)
208207 get_first_child ( node_index : number ) : number {
209- return this . view . getUint32 ( this . node_offset ( node_index ) + 20 , true )
210- }
211-
212- // Read last child index (0 = no children)
213- get_last_child ( node_index : number ) : number {
214- return this . view . getUint32 ( this . node_offset ( node_index ) + 24 , true )
208+ return this . view . getUint32 ( this . node_offset ( node_index ) + 4 , true )
215209 }
216210
217211 // Read next sibling index (0 = no sibling)
218212 get_next_sibling ( node_index : number ) : number {
219- return this . view . getUint32 ( this . node_offset ( node_index ) + 28 , true )
213+ return this . view . getUint32 ( this . node_offset ( node_index ) + 8 , true )
220214 }
221215
222216 // Read start line
223217 get_start_line ( node_index : number ) : number {
224- return this . view . getUint32 ( this . node_offset ( node_index ) + 32 , true )
218+ return this . view . getUint32 ( this . node_offset ( node_index ) + 24 , true )
225219 }
226220
227221 // Read start column
228222 get_start_column ( node_index : number ) : number {
229- return this . view . getUint16 ( this . node_offset ( node_index ) + 36 , true )
223+ return this . view . getUint16 ( this . node_offset ( node_index ) + 28 , true )
230224 }
231225
232226 // Read value start offset (stored as delta from startOffset, declaration value / at-rule prelude)
233227 get_value_start ( node_index : number ) : number {
234228 const startOffset = this . get_start_offset ( node_index )
235- const delta = this . view . getUint16 ( this . node_offset ( node_index ) + 16 , true )
229+ const delta = this . view . getUint16 ( this . node_offset ( node_index ) + 18 , true )
236230 return startOffset + delta
237231 }
238232
239233 // Read value length
240234 get_value_length ( node_index : number ) : number {
241- return this . view . getUint16 ( this . node_offset ( node_index ) + 18 , true )
235+ return this . view . getUint16 ( this . node_offset ( node_index ) + 22 , true )
242236 }
243237
244238 // --- Write Methods ---
@@ -255,67 +249,62 @@ export class CSSDataArena {
255249
256250 // Write start offset in source
257251 set_start_offset ( node_index : number , offset : number ) : void {
258- this . view . setUint32 ( this . node_offset ( node_index ) + 4 , offset , true )
252+ this . view . setUint32 ( this . node_offset ( node_index ) + 12 , offset , true )
259253 }
260254
261255 // Write length in source
262256 set_length ( node_index : number , length : number ) : void {
263- this . view . setUint16 ( this . node_offset ( node_index ) + 8 , length , true )
257+ this . view . setUint16 ( this . node_offset ( node_index ) + 2 , length , true )
264258 }
265259
266260 // Write content start delta (offset from startOffset)
267261 set_content_start_delta ( node_index : number , delta : number ) : void {
268- this . view . setUint16 ( this . node_offset ( node_index ) + 12 , delta , true )
262+ this . view . setUint16 ( this . node_offset ( node_index ) + 16 , delta , true )
269263 }
270264
271265 // Write content length
272266 set_content_length ( node_index : number , length : number ) : void {
273- this . view . setUint16 ( this . node_offset ( node_index ) + 14 , length , true )
267+ this . view . setUint16 ( this . node_offset ( node_index ) + 20 , length , true )
274268 }
275269
276270 // Write attribute operator (for NODE_SELECTOR_ATTRIBUTE)
277271 set_attr_operator ( node_index : number , operator : number ) : void {
278- this . view . setUint8 ( this . node_offset ( node_index ) + 2 , operator )
272+ this . view . setUint8 ( this . node_offset ( node_index ) + 30 , operator )
279273 }
280274
281275 // Write attribute flags (for NODE_SELECTOR_ATTRIBUTE)
282276 set_attr_flags ( node_index : number , flags : number ) : void {
283- this . view . setUint8 ( this . node_offset ( node_index ) + 3 , flags )
277+ this . view . setUint8 ( this . node_offset ( node_index ) + 31 , flags )
284278 }
285279
286280 // Write first child index
287281 set_first_child ( node_index : number , childIndex : number ) : void {
288- this . view . setUint32 ( this . node_offset ( node_index ) + 20 , childIndex , true )
289- }
290-
291- // Write last child index
292- set_last_child ( node_index : number , childIndex : number ) : void {
293- this . view . setUint32 ( this . node_offset ( node_index ) + 24 , childIndex , true )
282+ this . view . setUint32 ( this . node_offset ( node_index ) + 4 , childIndex , true )
294283 }
295284
296285 // Write next sibling index
297286 set_next_sibling ( node_index : number , siblingIndex : number ) : void {
298- this . view . setUint32 ( this . node_offset ( node_index ) + 28 , siblingIndex , true )
287+ this . view . setUint32 ( this . node_offset ( node_index ) + 8 , siblingIndex , true )
299288 }
300289
301290 // Write start line
302291 set_start_line ( node_index : number , line : number ) : void {
303- this . view . setUint32 ( this . node_offset ( node_index ) + 32 , line , true )
292+ this . view . setUint32 ( this . node_offset ( node_index ) + 24 , line , true )
304293 }
305294
306295 // Write start column
307296 set_start_column ( node_index : number , column : number ) : void {
308- this . view . setUint16 ( this . node_offset ( node_index ) + 36 , column , true )
297+ this . view . setUint16 ( this . node_offset ( node_index ) + 28 , column , true )
309298 }
310299
311300 // Write value start delta (offset from startOffset, declaration value / at-rule prelude)
312301 set_value_start_delta ( node_index : number , delta : number ) : void {
313- this . view . setUint16 ( this . node_offset ( node_index ) + 16 , delta , true )
302+ this . view . setUint16 ( this . node_offset ( node_index ) + 18 , delta , true )
314303 }
315304
316305 // Write value length
317306 set_value_length ( node_index : number , length : number ) : void {
318- this . view . setUint16 ( this . node_offset ( node_index ) + 18 , length , true )
307+ this . view . setUint16 ( this . node_offset ( node_index ) + 22 , length , true )
319308 }
320309
321310 // --- Node Creation ---
@@ -351,10 +340,10 @@ export class CSSDataArena {
351340
352341 const offset = node_index * BYTES_PER_NODE
353342 this . view . setUint8 ( offset , type ) // +0: type
354- this . view . setUint32 ( offset + 4 , start_offset , true ) // +4: startOffset
355- this . view . setUint16 ( offset + 8 , length , true ) // +8: length
356- this . view . setUint32 ( offset + 32 , start_line , true ) // +32 : startLine
357- this . view . setUint16 ( offset + 36 , start_column , true ) // +36 : startColumn
343+ this . view . setUint16 ( offset + 2 , length , true ) // +2: length
344+ this . view . setUint32 ( offset + 12 , start_offset , true ) // +12: startOffset
345+ this . view . setUint32 ( offset + 24 , start_line , true ) // +24 : startLine
346+ this . view . setUint16 ( offset + 28 , start_column , true ) // +28 : startColumn
358347
359348 return node_index
360349 }
@@ -367,8 +356,7 @@ export class CSSDataArena {
367356 if ( children . length === 0 ) return
368357
369358 const offset = this . node_offset ( parent_index )
370- this . view . setUint32 ( offset + 20 , children [ 0 ] , true ) // firstChild
371- this . view . setUint32 ( offset + 24 , children [ children . length - 1 ] , true ) // lastChild
359+ this . view . setUint32 ( offset + 4 , children [ 0 ] , true ) // firstChild
372360
373361 // Chain siblings
374362 for ( let i = 0 ; i < children . length - 1 ; i ++ ) {
0 commit comments