@@ -166,6 +166,60 @@ impl<'fbb> FlatBufferBuilder<'fbb, DefaultAllocator> {
166166 pub fn with_capacity ( size : usize ) -> Self {
167167 Self :: from_vec ( vec ! [ 0 ; size] )
168168 }
169+ /// Create a FlatBufferBuilder that is ready for writing, with a
170+ /// ready-to-use capacity of the provided size and preallocated internal vecs.
171+ ///
172+ /// The maximum valid value for `size` is `FLATBUFFERS_MAX_BUFFER_SIZE`.
173+ ///
174+ /// # Arguments
175+ ///
176+ /// * `size` - The initial capacity of the backing buffer in bytes.
177+ /// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
178+ /// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
179+ /// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
180+ pub fn with_internal_capacity (
181+ size : usize ,
182+ field_locs_capacity : usize ,
183+ written_vtable_revpos_capacity : usize ,
184+ strings_pool_capacity : usize ,
185+ ) -> Self {
186+ Self :: from_vec_with_internal_capacity (
187+ vec ! [ 0 ; size] ,
188+ field_locs_capacity,
189+ written_vtable_revpos_capacity,
190+ strings_pool_capacity,
191+ )
192+ }
193+ /// Create a FlatBufferBuilder that is ready for writing, reusing
194+ /// an existing vector and preallocated internal vecs.
195+ ///
196+ /// # Arguments
197+ ///
198+ /// * `buffer` - An existing `Vec<u8>` to reuse as the backing buffer.
199+ /// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
200+ /// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
201+ /// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
202+ pub fn from_vec_with_internal_capacity (
203+ buffer : Vec < u8 > ,
204+ field_locs_capacity : usize ,
205+ written_vtable_revpos_capacity : usize ,
206+ strings_pool_capacity : usize ,
207+ ) -> Self {
208+ // we need to check the size here because we create the backing buffer
209+ // directly, bypassing the typical way of using grow_allocator:
210+ assert ! (
211+ buffer. len( ) <= FLATBUFFERS_MAX_BUFFER_SIZE ,
212+ "cannot initialize buffer bigger than 2 gigabytes"
213+ ) ;
214+ let allocator = DefaultAllocator :: from_vec ( buffer) ;
215+ Self :: new_in_with_internal_capacity (
216+ allocator,
217+ field_locs_capacity,
218+ written_vtable_revpos_capacity,
219+ strings_pool_capacity,
220+ )
221+ }
222+
169223 /// Create a FlatBufferBuilder that is ready for writing, reusing
170224 /// an existing vector.
171225 pub fn from_vec ( buffer : Vec < u8 > ) -> Self {
@@ -212,6 +266,40 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
212266 }
213267 }
214268
269+ /// Create a [`FlatBufferBuilder`] that is ready for writing with a custom [`Allocator`]
270+ /// and preallocated internal vecs.
271+ ///
272+ /// # Arguments
273+ ///
274+ /// * `allocator` - A custom [`Allocator`] to use as the backing buffer.
275+ /// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
276+ /// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
277+ /// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
278+ pub fn new_in_with_internal_capacity (
279+ allocator : A ,
280+ field_locs_capacity : usize ,
281+ written_vtable_revpos_capacity : usize ,
282+ strings_pool_capacity : usize ,
283+ ) -> Self {
284+ let head = ReverseIndex :: end ( ) ;
285+ FlatBufferBuilder {
286+ allocator,
287+ head,
288+
289+ field_locs : Vec :: with_capacity ( field_locs_capacity) ,
290+ written_vtable_revpos : Vec :: with_capacity ( written_vtable_revpos_capacity) ,
291+
292+ nested : false ,
293+ finished : false ,
294+
295+ min_align : 0 ,
296+ force_defaults : false ,
297+ strings_pool : Vec :: with_capacity ( strings_pool_capacity) ,
298+
299+ _phantom : PhantomData ,
300+ }
301+ }
302+
215303 /// Destroy the [`FlatBufferBuilder`], returning its [`Allocator`] and the index
216304 /// into it that represents the start of valid data.
217305 pub fn collapse_in ( self ) -> ( A , usize ) {
@@ -232,7 +320,9 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
232320 /// new object.
233321 pub fn reset ( & mut self ) {
234322 // memset only the part of the buffer that could be dirty:
235- self . allocator [ self . head . range_to_end ( ) ] . iter_mut ( ) . for_each ( |x| * x = 0 ) ;
323+ self . allocator [ self . head . range_to_end ( ) ]
324+ . iter_mut ( )
325+ . for_each ( |x| * x = 0 ) ;
236326
237327 self . head = ReverseIndex :: end ( ) ;
238328 self . written_vtable_revpos . clear ( ) ;
@@ -961,6 +1051,33 @@ impl<T> IndexMut<ReverseIndexRange> for [T] {
9611051#[ cfg( test) ]
9621052mod tests {
9631053 use super :: * ;
1054+ use core:: sync:: atomic:: { AtomicUsize , Ordering } ;
1055+ use std:: alloc:: { GlobalAlloc , Layout , System } ;
1056+
1057+ static ALLOC_COUNT : AtomicUsize = AtomicUsize :: new ( 0 ) ;
1058+
1059+ struct CountingAllocator ;
1060+
1061+ unsafe impl GlobalAlloc for CountingAllocator {
1062+ unsafe fn alloc ( & self , layout : Layout ) -> * mut u8 {
1063+ ALLOC_COUNT . fetch_add ( 1 , Ordering :: Relaxed ) ;
1064+ unsafe { System . alloc ( layout) }
1065+ }
1066+ unsafe fn dealloc ( & self , ptr : * mut u8 , layout : Layout ) {
1067+ unsafe { System . dealloc ( ptr, layout) }
1068+ }
1069+ }
1070+
1071+ #[ global_allocator]
1072+ static GLOBAL : CountingAllocator = CountingAllocator ;
1073+
1074+ fn reset_alloc_count ( ) {
1075+ ALLOC_COUNT . store ( 0 , Ordering :: Relaxed ) ;
1076+ }
1077+
1078+ fn alloc_count ( ) -> usize {
1079+ ALLOC_COUNT . load ( Ordering :: Relaxed )
1080+ }
9641081
9651082 #[ test]
9661083 fn reverse_index_test ( ) {
@@ -970,4 +1087,43 @@ mod tests {
9701087 assert_eq ! ( & buf[ idx. range_to( idx + 1 ) ] , & [ 4 ] ) ;
9711088 assert_eq ! ( idx. to_forward_index( & buf) , 4 ) ;
9721089 }
1090+
1091+ #[ test]
1092+ fn with_internal_capacity_preallocates_vecs ( ) {
1093+ let mut builder = FlatBufferBuilder :: with_internal_capacity ( 64 , 8 , 16 , 32 ) ;
1094+
1095+ assert ! ( builder. allocator. len( ) >= 64 ) ;
1096+ assert ! ( builder. field_locs. capacity( ) >= 8 ) ;
1097+ assert ! ( builder. written_vtable_revpos. capacity( ) >= 16 ) ;
1098+ assert ! ( builder. strings_pool. capacity( ) >= 32 ) ;
1099+
1100+ assert ! ( builder. field_locs. is_empty( ) ) ;
1101+ assert ! ( builder. written_vtable_revpos. is_empty( ) ) ;
1102+ assert ! ( builder. strings_pool. is_empty( ) ) ;
1103+
1104+ // Reset the allocation counter after builder construction
1105+ reset_alloc_count ( ) ;
1106+
1107+ // Add a shared string and verify it lands in the pool
1108+ let s1 = builder. create_shared_string ( "hello" ) ;
1109+ assert_eq ! ( builder. strings_pool. len( ) , 1 ) ;
1110+
1111+ // Adding the same string again should reuse the pooled entry
1112+ let s2 = builder. create_shared_string ( "hello" ) ;
1113+ assert_eq ! ( builder. strings_pool. len( ) , 1 ) ;
1114+ assert_eq ! ( s1. value( ) , s2. value( ) ) ;
1115+
1116+ // A different string should add a new entry
1117+ let _s3 = builder. create_shared_string ( "world" ) ;
1118+ assert_eq ! ( builder. strings_pool. len( ) , 2 ) ;
1119+
1120+ // With sufficient preallocated capacity, no additional allocations
1121+ // should have occurred for the internal vecs during the operations above
1122+ let allocs = alloc_count ( ) ;
1123+ assert_eq ! (
1124+ allocs, 0 ,
1125+ "expected 0 allocations after builder construction, got {}" ,
1126+ allocs
1127+ ) ;
1128+ }
9731129}
0 commit comments