@@ -122,9 +122,113 @@ impl DynamicSchema {
122
122
}
123
123
}
124
124
125
+ /// Calculate the fixed overhead size for Bond schema encoding
126
+ const fn calculate_fixed_overhead ( ) -> usize {
127
+ // Bond header: "SP" + version
128
+ let mut size = 4 ; // [0x53, 0x50, 0x01, 0x00]
129
+
130
+ // Number of structs
131
+ size += 4 ; // 1u32
132
+
133
+ // Struct definition header (excluding variable name lengths)
134
+ size += 4 ; // attributes (0u32)
135
+ size += 1 ; // modifier (0u8)
136
+
137
+ // Default values block
138
+ size += 8 ; // default_uint (u64)
139
+ size += 8 ; // default_int (i64)
140
+ size += 8 ; // default_double (f64)
141
+ size += 4 ; // default_string (u32)
142
+ size += 4 ; // default_wstring (u32)
143
+ size += 1 ; // default_nothing (u8)
144
+
145
+ // Base def
146
+ size += 4 ; // 0u32
147
+
148
+ // Field count header
149
+ size += 3 ; // 3 zero bytes
150
+ size += 4 ; // field count (u32)
151
+
152
+ // Post-fields padding
153
+ size += 8 ; // alignment padding
154
+
155
+ // Root typedef
156
+ size += 1 ; // BondDataType::BT_STRUCT
157
+ size += 2 ; // struct index (u16)
158
+ size += 1 ; // element (u8)
159
+ size += 1 ; // key (u8)
160
+ size += 1 ; // bonded (u8)
161
+
162
+ // Final padding
163
+ size += 9 ; // 9 zero bytes
164
+
165
+ size
166
+ }
167
+
168
+ /// Calculate the fixed overhead per field (excluding variable field name length)
169
+ const fn calculate_per_field_fixed_overhead ( ) -> usize {
170
+ // Empty qualified name
171
+ let mut size = 4 ; // 0u32 for empty string
172
+
173
+ // Field attributes and data
174
+ size += 4 ; // attributes (0u32)
175
+ size += 1 ; // modifier (0u8)
176
+
177
+ // Default values block for field
178
+ size += 8 ; // default_uint (u64)
179
+ size += 8 ; // default_int (i64)
180
+ size += 8 ; // default_double (f64)
181
+ size += 4 ; // default_string (u32)
182
+ size += 4 ; // default_wstring (u32)
183
+ size += 1 ; // default_nothing (u8)
184
+
185
+ // Field metadata
186
+ size += 3 ; // 3 padding bytes
187
+ size += 2 ; // field_id (u16)
188
+ size += 1 ; // type_id (u8)
189
+
190
+ // Additional type info
191
+ size += 2 ; // struct_def (u16)
192
+ size += 1 ; // element (u8)
193
+ size += 1 ; // key (u8)
194
+ size += 1 ; // bonded_type (u8)
195
+ size += 1 ; // default_value_present (u8)
196
+
197
+ size
198
+ }
199
+
200
+ /// Calculate the exact size of the encoded schema in bytes
201
+ pub ( crate ) fn calculate_exact_encoded_size ( & self ) -> usize {
202
+ // Start with fixed overhead
203
+ let mut size = Self :: calculate_fixed_overhead ( ) ;
204
+
205
+ // Add variable struct name lengths
206
+ size += 4 + self . struct_name . len ( ) ; // struct_name length + bytes
207
+ size += 4 + self . qualified_name . len ( ) ; // qualified_name length + bytes
208
+
209
+ // Add field-specific sizes
210
+ for ( i, field) in self . fields . iter ( ) . enumerate ( ) {
211
+ let is_last = i == self . fields . len ( ) - 1 ;
212
+
213
+ // Fixed overhead per field
214
+ size += Self :: calculate_per_field_fixed_overhead ( ) ;
215
+
216
+ // Variable field name
217
+ size += 4 + field. name . len ( ) ; // name length + bytes
218
+
219
+ // Padding after each field except the last
220
+ if !is_last {
221
+ size += 8 ;
222
+ }
223
+ }
224
+
225
+ size
226
+ }
227
+
125
228
/// Encode the schema to Bond format
126
229
pub ( crate ) fn encode ( & self ) -> Result < Vec < u8 > > {
127
- let mut schema_bytes = Vec :: new ( ) ;
230
+ let exact_size = self . calculate_exact_encoded_size ( ) ;
231
+ let mut schema_bytes = Vec :: with_capacity ( exact_size) ;
128
232
129
233
// Write header
130
234
schema_bytes. write_all ( & [ 0x53 , 0x50 ] ) ?; // 'S','P'
@@ -375,4 +479,44 @@ mod tests {
375
479
let bytes = schema. as_bytes ( ) ;
376
480
assert ! ( !bytes. is_empty( ) ) ;
377
481
}
482
+
483
+ #[ test]
484
+ fn test_schema_exact_size_calculation ( ) {
485
+ // Test that the exact size calculation matches the actual encoded size
486
+ // This validates that pre-allocation is precise and no reallocations occur
487
+
488
+ // Test with different field counts and varying field name lengths
489
+ let test_cases = vec ! [
490
+ ( 0 , "no fields" ) ,
491
+ ( 1 , "single field" ) ,
492
+ ( 5 , "few fields" ) ,
493
+ ( 10 , "medium fields" ) ,
494
+ ( 20 , "many fields" ) ,
495
+ ] ;
496
+
497
+ for ( field_count, description) in test_cases {
498
+ // Create fields with varying name lengths to test the calculation accuracy
499
+ let fields: Vec < FieldDef > = ( 0 ..field_count)
500
+ . map ( |i| FieldDef {
501
+ name : Cow :: Owned ( format ! ( "field_with_long_name_{i}" ) ) ,
502
+ type_id : BondDataType :: BT_STRING ,
503
+ field_id : i as u16 + 1 ,
504
+ } )
505
+ . collect ( ) ;
506
+
507
+ let schema = DynamicSchema :: new ( "TestStruct" , "test.namespace" , fields) ;
508
+ let exact_size = schema. calculate_exact_encoded_size ( ) ;
509
+ let encoded = schema. encode ( ) . unwrap ( ) ;
510
+
511
+ // Verify encoding succeeded
512
+ assert ! ( !encoded. is_empty( ) , "Encoding failed for {description}" ) ;
513
+
514
+ // The exact calculation should match the actual encoded size precisely
515
+ let actual_size = encoded. len ( ) ;
516
+ assert_eq ! (
517
+ exact_size, actual_size,
518
+ "Exact size calculation {exact_size} does not match actual size {actual_size} for {description}"
519
+ ) ;
520
+ }
521
+ }
378
522
}
0 commit comments