forked from BigfootDev/flatbuffers
* Add fallible try_* API for FlatBufferBuilder This is to support error propagation from Allocator trait. The Allocator grow_downwards() method returns Result<(), Self::Error>, but FlatBufferBuilder panics via .expect() when allocation fails instead of propagating the error. * Add rust fallible API docs
1413 lines
48 KiB
Rust
1413 lines
48 KiB
Rust
/*
|
|
* Copyright 2018 Google Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#[cfg(not(feature = "std"))]
|
|
use alloc::{vec, vec::Vec};
|
|
use core::cmp::max;
|
|
use core::convert::Infallible;
|
|
use core::fmt::{Debug, Display};
|
|
use core::iter::{DoubleEndedIterator, ExactSizeIterator};
|
|
use core::marker::PhantomData;
|
|
use core::ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut, Sub, SubAssign};
|
|
use core::ptr::write_bytes;
|
|
|
|
#[cfg(feature = "std")]
|
|
use std::collections::HashMap;
|
|
|
|
use crate::endian_scalar::emplace_scalar;
|
|
use crate::primitives::*;
|
|
use crate::push::{Push, PushAlignment};
|
|
use crate::read_scalar;
|
|
use crate::table::Table;
|
|
use crate::vector::Vector;
|
|
use crate::vtable::{field_index_to_field_offset, VTable};
|
|
use crate::vtable_writer::VTableWriter;
|
|
|
|
/// Trait to implement custom allocation strategies for [`FlatBufferBuilder`].
|
|
///
|
|
/// An implementation can be used with [`FlatBufferBuilder::new_in`], enabling a custom allocation
|
|
/// strategy for the [`FlatBufferBuilder`].
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The implementation of the allocator must match the defined behavior as described by the
|
|
/// comments.
|
|
pub unsafe trait Allocator: DerefMut<Target = [u8]> {
|
|
/// A type describing allocation failures
|
|
type Error: Display + Debug;
|
|
/// Grows the buffer, with the old contents being moved to the end.
|
|
///
|
|
/// NOTE: While not unsound, an implementation that doesn't grow the
|
|
/// internal buffer will get stuck in an infinite loop.
|
|
fn grow_downwards(&mut self) -> Result<(), Self::Error>;
|
|
|
|
/// Returns the size of the internal buffer in bytes.
|
|
fn len(&self) -> usize;
|
|
}
|
|
|
|
/// Default [`FlatBufferBuilder`] allocator backed by a [`Vec<u8>`].
|
|
#[derive(Default)]
|
|
pub struct DefaultAllocator(Vec<u8>);
|
|
|
|
impl DefaultAllocator {
|
|
/// Builds the allocator from an existing buffer.
|
|
pub fn from_vec(buffer: Vec<u8>) -> Self {
|
|
Self(buffer)
|
|
}
|
|
}
|
|
|
|
impl Deref for DefaultAllocator {
|
|
type Target = [u8];
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl DerefMut for DefaultAllocator {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.0
|
|
}
|
|
}
|
|
|
|
// SAFETY: The methods are implemented as described by the documentation.
|
|
unsafe impl Allocator for DefaultAllocator {
|
|
type Error = Infallible;
|
|
fn grow_downwards(&mut self) -> Result<(), Self::Error> {
|
|
let old_len = self.0.len();
|
|
let new_len = max(1, old_len * 2);
|
|
|
|
self.0.resize(new_len, 0);
|
|
|
|
if new_len == 1 {
|
|
return Ok(());
|
|
}
|
|
|
|
// calculate the midpoint, and safely copy the old end data to the new
|
|
// end position:
|
|
let middle = new_len / 2;
|
|
{
|
|
let (left, right) = &mut self.0[..].split_at_mut(middle);
|
|
right.copy_from_slice(left);
|
|
}
|
|
// finally, zero out the old end data.
|
|
{
|
|
let ptr = self.0[..middle].as_mut_ptr();
|
|
// Safety:
|
|
// ptr is byte aligned and of length middle
|
|
unsafe {
|
|
write_bytes(ptr, 0, middle);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
self.0.len()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
struct FieldLoc {
|
|
off: UOffsetT,
|
|
id: VOffsetT,
|
|
}
|
|
|
|
/// FlatBufferBuilder builds a FlatBuffer through manipulating its internal
|
|
/// state. It has an owned `Vec<u8>` that grows as needed (up to the hardcoded
|
|
/// limit of 2GiB, which is set by the FlatBuffers format).
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct FlatBufferBuilder<'fbb, A: Allocator = DefaultAllocator> {
|
|
allocator: A,
|
|
head: ReverseIndex,
|
|
|
|
field_locs: Vec<FieldLoc>,
|
|
written_vtable_revpos: Vec<UOffsetT>,
|
|
|
|
nested: bool,
|
|
finished: bool,
|
|
|
|
min_align: usize,
|
|
force_defaults: bool,
|
|
#[cfg(feature = "std")]
|
|
strings_pool: HashMap<String, WIPOffset<&'fbb str>>,
|
|
#[cfg(not(feature = "std"))]
|
|
strings_pool: Vec<WIPOffset<&'fbb str>>,
|
|
|
|
_phantom: PhantomData<&'fbb ()>,
|
|
}
|
|
|
|
impl<'fbb> FlatBufferBuilder<'fbb, DefaultAllocator> {
|
|
/// Create a FlatBufferBuilder that is ready for writing.
|
|
pub fn new() -> Self {
|
|
Self::with_capacity(0)
|
|
}
|
|
#[deprecated(note = "replaced with `with_capacity`", since = "0.8.5")]
|
|
pub fn new_with_capacity(size: usize) -> Self {
|
|
Self::with_capacity(size)
|
|
}
|
|
/// Create a FlatBufferBuilder that is ready for writing, with a
|
|
/// ready-to-use capacity of the provided size.
|
|
///
|
|
/// The maximum valid value is `FLATBUFFERS_MAX_BUFFER_SIZE`.
|
|
pub fn with_capacity(size: usize) -> Self {
|
|
Self::from_vec(vec![0; size])
|
|
}
|
|
/// Create a FlatBufferBuilder that is ready for writing, with a
|
|
/// ready-to-use capacity of the provided size and preallocated internal vecs.
|
|
///
|
|
/// The maximum valid value for `size` is `FLATBUFFERS_MAX_BUFFER_SIZE`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `size` - The initial capacity of the backing buffer in bytes.
|
|
/// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
|
|
/// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
|
|
/// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
|
|
pub fn with_internal_capacity(
|
|
size: usize,
|
|
field_locs_capacity: usize,
|
|
written_vtable_revpos_capacity: usize,
|
|
strings_pool_capacity: usize,
|
|
) -> Self {
|
|
Self::from_vec_with_internal_capacity(
|
|
vec![0; size],
|
|
field_locs_capacity,
|
|
written_vtable_revpos_capacity,
|
|
strings_pool_capacity,
|
|
)
|
|
}
|
|
/// Create a FlatBufferBuilder that is ready for writing, reusing
|
|
/// an existing vector and preallocated internal vecs.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `buffer` - An existing `Vec<u8>` to reuse as the backing buffer.
|
|
/// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
|
|
/// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
|
|
/// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
|
|
pub fn from_vec_with_internal_capacity(
|
|
buffer: Vec<u8>,
|
|
field_locs_capacity: usize,
|
|
written_vtable_revpos_capacity: usize,
|
|
strings_pool_capacity: usize,
|
|
) -> Self {
|
|
// we need to check the size here because we create the backing buffer
|
|
// directly, bypassing the typical way of using grow_allocator:
|
|
assert!(
|
|
buffer.len() <= FLATBUFFERS_MAX_BUFFER_SIZE,
|
|
"cannot initialize buffer bigger than 2 gigabytes"
|
|
);
|
|
let allocator = DefaultAllocator::from_vec(buffer);
|
|
Self::new_in_with_internal_capacity(
|
|
allocator,
|
|
field_locs_capacity,
|
|
written_vtable_revpos_capacity,
|
|
strings_pool_capacity,
|
|
)
|
|
}
|
|
|
|
/// Create a FlatBufferBuilder that is ready for writing, reusing
|
|
/// an existing vector.
|
|
pub fn from_vec(buffer: Vec<u8>) -> Self {
|
|
// we need to check the size here because we create the backing buffer
|
|
// directly, bypassing the typical way of using grow_allocator:
|
|
assert!(
|
|
buffer.len() <= FLATBUFFERS_MAX_BUFFER_SIZE,
|
|
"cannot initialize buffer bigger than 2 gigabytes"
|
|
);
|
|
let allocator = DefaultAllocator::from_vec(buffer);
|
|
Self::new_in(allocator)
|
|
}
|
|
|
|
/// Destroy the FlatBufferBuilder, returning its internal byte vector
|
|
/// and the index into it that represents the start of valid data.
|
|
pub fn collapse(self) -> (Vec<u8>, usize) {
|
|
let index = self.head.to_forward_index(&self.allocator);
|
|
(self.allocator.0, index)
|
|
}
|
|
}
|
|
|
|
impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
|
|
/// Create a [`FlatBufferBuilder`] that is ready for writing with a custom [`Allocator`].
|
|
pub fn new_in(allocator: A) -> Self {
|
|
let head = ReverseIndex::end();
|
|
FlatBufferBuilder {
|
|
allocator,
|
|
head,
|
|
|
|
field_locs: Vec::new(),
|
|
written_vtable_revpos: Vec::new(),
|
|
|
|
nested: false,
|
|
finished: false,
|
|
|
|
min_align: 0,
|
|
force_defaults: false,
|
|
#[cfg(feature = "std")]
|
|
strings_pool: HashMap::new(),
|
|
#[cfg(not(feature = "std"))]
|
|
strings_pool: Vec::new(),
|
|
|
|
_phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Create a [`FlatBufferBuilder`] that is ready for writing with a custom [`Allocator`]
|
|
/// and preallocated internal vecs.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `allocator` - A custom [`Allocator`] to use as the backing buffer.
|
|
/// * `field_locs_capacity` - Preallocated capacity for the field locations vec.
|
|
/// * `written_vtable_revpos_capacity` - Preallocated capacity for the written vtable reverse positions vec.
|
|
/// * `strings_pool_capacity` - Preallocated capacity for the shared strings pool vec.
|
|
pub fn new_in_with_internal_capacity(
|
|
allocator: A,
|
|
field_locs_capacity: usize,
|
|
written_vtable_revpos_capacity: usize,
|
|
strings_pool_capacity: usize,
|
|
) -> Self {
|
|
let head = ReverseIndex::end();
|
|
FlatBufferBuilder {
|
|
allocator,
|
|
head,
|
|
|
|
field_locs: Vec::with_capacity(field_locs_capacity),
|
|
written_vtable_revpos: Vec::with_capacity(written_vtable_revpos_capacity),
|
|
|
|
nested: false,
|
|
finished: false,
|
|
|
|
min_align: 0,
|
|
force_defaults: false,
|
|
#[cfg(feature = "std")]
|
|
strings_pool: HashMap::with_capacity(strings_pool_capacity),
|
|
#[cfg(not(feature = "std"))]
|
|
strings_pool: Vec::with_capacity(strings_pool_capacity),
|
|
|
|
_phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Destroy the [`FlatBufferBuilder`], returning its [`Allocator`] and the index
|
|
/// into it that represents the start of valid data.
|
|
pub fn collapse_in(self) -> (A, usize) {
|
|
let index = self.head.to_forward_index(&self.allocator);
|
|
(self.allocator, index)
|
|
}
|
|
|
|
/// Reset the FlatBufferBuilder internal state. Use this method after a
|
|
/// call to a `finish` function in order to re-use a FlatBufferBuilder.
|
|
///
|
|
/// This function is the only way to reset the `finished` state and start
|
|
/// again.
|
|
///
|
|
/// If you are using a FlatBufferBuilder repeatedly, make sure to use this
|
|
/// function, because it re-uses the FlatBufferBuilder's existing
|
|
/// heap-allocated `Vec<u8>` internal buffer. This offers significant speed
|
|
/// improvements as compared to creating a new FlatBufferBuilder for every
|
|
/// new object.
|
|
pub fn reset(&mut self) {
|
|
// memset only the part of the buffer that could be dirty:
|
|
self.allocator[self.head.range_to_end()]
|
|
.iter_mut()
|
|
.for_each(|x| *x = 0);
|
|
|
|
self.head = ReverseIndex::end();
|
|
self.written_vtable_revpos.clear();
|
|
|
|
self.nested = false;
|
|
self.finished = false;
|
|
|
|
self.min_align = 0;
|
|
self.strings_pool.clear();
|
|
}
|
|
|
|
/// Fallible version of [`push`](Self::push).
|
|
#[inline]
|
|
pub fn try_push<P: Push>(&mut self, x: P) -> Result<WIPOffset<P::Output>, A::Error> {
|
|
let sz = P::size();
|
|
self.align(sz, P::alignment())?;
|
|
self.make_space(sz)?;
|
|
{
|
|
let (dst, rest) = self.allocator[self.head.range_to_end()].split_at_mut(sz);
|
|
// Safety:
|
|
// Called make_space above
|
|
unsafe { x.push(dst, rest.len()) };
|
|
}
|
|
Ok(WIPOffset::new(self.used_space() as UOffsetT))
|
|
}
|
|
|
|
/// Push a Push'able value onto the front of the in-progress data.
|
|
///
|
|
/// This function uses traits to provide a unified API for writing
|
|
/// scalars, tables, vectors, and WIPOffsets.
|
|
#[inline]
|
|
pub fn push<P: Push>(&mut self, x: P) -> WIPOffset<P::Output> {
|
|
self.try_push(x).expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`push_slot`](Self::push_slot).
|
|
#[inline]
|
|
pub fn try_push_slot<X: Push + PartialEq>(
|
|
&mut self,
|
|
slotoff: VOffsetT,
|
|
x: X,
|
|
default: X,
|
|
) -> Result<(), A::Error> {
|
|
self.assert_nested("push_slot");
|
|
if x != default || self.force_defaults {
|
|
self.try_push_slot_always(slotoff, x)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Push a Push'able value onto the front of the in-progress data, and
|
|
/// store a reference to it in the in-progress vtable. If the value matches
|
|
/// the default, then this is a no-op.
|
|
#[inline]
|
|
pub fn push_slot<X: Push + PartialEq>(&mut self, slotoff: VOffsetT, x: X, default: X) {
|
|
self.try_push_slot(slotoff, x, default)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`push_slot_always`](Self::push_slot_always).
|
|
#[inline]
|
|
pub fn try_push_slot_always<X: Push>(
|
|
&mut self,
|
|
slotoff: VOffsetT,
|
|
x: X,
|
|
) -> Result<(), A::Error> {
|
|
self.assert_nested("push_slot_always");
|
|
let off = self.try_push(x)?;
|
|
self.track_field(slotoff, off.value());
|
|
Ok(())
|
|
}
|
|
|
|
/// Push a Push'able value onto the front of the in-progress data, and
|
|
/// store a reference to it in the in-progress vtable.
|
|
#[inline]
|
|
pub fn push_slot_always<X: Push>(&mut self, slotoff: VOffsetT, x: X) {
|
|
self.try_push_slot_always(slotoff, x)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Retrieve the number of vtables that have been serialized into the
|
|
/// FlatBuffer. This is primarily used to check vtable deduplication.
|
|
#[inline]
|
|
pub fn num_written_vtables(&self) -> usize {
|
|
self.written_vtable_revpos.len()
|
|
}
|
|
|
|
/// Start a Table write.
|
|
///
|
|
/// Asserts that the builder is not in a nested state.
|
|
///
|
|
/// Users probably want to use `push_slot` to add values after calling this.
|
|
#[inline]
|
|
pub fn start_table(&mut self) -> WIPOffset<TableUnfinishedWIPOffset> {
|
|
self.assert_not_nested(
|
|
"start_table can not be called when a table or vector is under construction",
|
|
);
|
|
self.nested = true;
|
|
|
|
WIPOffset::new(self.used_space() as UOffsetT)
|
|
}
|
|
|
|
/// Fallible version of [`end_table`](Self::end_table).
|
|
#[inline]
|
|
pub fn try_end_table(
|
|
&mut self,
|
|
off: WIPOffset<TableUnfinishedWIPOffset>,
|
|
) -> Result<WIPOffset<TableFinishedWIPOffset>, A::Error> {
|
|
self.assert_nested("end_table");
|
|
|
|
let o = self.write_vtable(off)?;
|
|
|
|
self.nested = false;
|
|
self.field_locs.clear();
|
|
|
|
Ok(WIPOffset::new(o.value()))
|
|
}
|
|
|
|
/// End a Table write.
|
|
///
|
|
/// Asserts that the builder is in a nested state.
|
|
#[inline]
|
|
pub fn end_table(
|
|
&mut self,
|
|
off: WIPOffset<TableUnfinishedWIPOffset>,
|
|
) -> WIPOffset<TableFinishedWIPOffset> {
|
|
self.try_end_table(off)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`start_vector`](Self::start_vector).
|
|
#[inline]
|
|
pub fn try_start_vector<T: Push>(&mut self, num_items: usize) -> Result<(), A::Error> {
|
|
self.assert_not_nested(
|
|
"start_vector can not be called when a table or vector is under construction",
|
|
);
|
|
self.align(num_items * T::size(), T::alignment().max_of(SIZE_UOFFSET))?;
|
|
self.nested = true;
|
|
Ok(())
|
|
}
|
|
|
|
/// Start a Vector write.
|
|
///
|
|
/// Asserts that the builder is not in a nested state.
|
|
///
|
|
/// Most users will prefer to call `create_vector`.
|
|
/// Speed optimizing users who choose to create vectors manually using this
|
|
/// function will want to use `push` to add values.
|
|
#[inline]
|
|
pub fn start_vector<T: Push>(&mut self, num_items: usize) {
|
|
self.try_start_vector::<T>(num_items)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`end_vector`](Self::end_vector).
|
|
#[inline]
|
|
pub fn try_end_vector<T: Push>(
|
|
&mut self,
|
|
num_elems: usize,
|
|
) -> Result<WIPOffset<Vector<'fbb, T>>, A::Error> {
|
|
self.assert_nested("end_vector");
|
|
let o = self.try_push::<UOffsetT>(num_elems as UOffsetT)?;
|
|
self.nested = false;
|
|
Ok(WIPOffset::new(o.value()))
|
|
}
|
|
|
|
/// End a Vector write.
|
|
///
|
|
/// Note that the `num_elems` parameter is the number of written items, not
|
|
/// the byte count.
|
|
///
|
|
/// Asserts that the builder is in a nested state.
|
|
#[inline]
|
|
pub fn end_vector<T: Push>(&mut self, num_elems: usize) -> WIPOffset<Vector<'fbb, T>> {
|
|
self.try_end_vector::<T>(num_elems)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`create_shared_string`](Self::create_shared_string).
|
|
///
|
|
/// Uses a HashMap to track previously written strings, providing O(1)
|
|
/// amortized lookup and insertion.
|
|
#[cfg(feature = "std")]
|
|
#[inline]
|
|
pub fn try_create_shared_string<'a: 'b, 'b>(
|
|
&'a mut self,
|
|
s: &'b str,
|
|
) -> Result<WIPOffset<&'fbb str>, A::Error> {
|
|
self.assert_not_nested(
|
|
"create_shared_string can not be called when a table or vector is under construction",
|
|
);
|
|
|
|
if let Some(&offset) = self.strings_pool.get(s) {
|
|
return Ok(offset);
|
|
}
|
|
|
|
let address = WIPOffset::new(self.try_create_byte_string(s.as_bytes())?.value());
|
|
self.strings_pool.insert(s.to_owned(), address);
|
|
Ok(address)
|
|
}
|
|
|
|
/// Create a utf8 string, and de-duplicate if already created.
|
|
///
|
|
/// Uses a HashMap to track previously written strings, providing O(1)
|
|
/// amortized lookup and insertion.
|
|
#[cfg(feature = "std")]
|
|
#[inline]
|
|
pub fn create_shared_string<'a: 'b, 'b>(&'a mut self, s: &'b str) -> WIPOffset<&'fbb str> {
|
|
self.try_create_shared_string(s)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`create_shared_string`](Self::create_shared_string).
|
|
///
|
|
/// Uses a sorted Vec with binary search to track previously written
|
|
/// strings when in `no_std` mode.
|
|
#[cfg(not(feature = "std"))]
|
|
#[inline]
|
|
pub fn try_create_shared_string<'a: 'b, 'b>(
|
|
&'a mut self,
|
|
s: &'b str,
|
|
) -> Result<WIPOffset<&'fbb str>, A::Error> {
|
|
self.assert_not_nested(
|
|
"create_shared_string can not be called when a table or vector is under construction",
|
|
);
|
|
|
|
// Saves a ref to allocator since rust doesnt like us refrencing it
|
|
// in the binary_search_by code.
|
|
let buf = &self.allocator;
|
|
|
|
let found = self.strings_pool.binary_search_by(|offset| {
|
|
let ptr = offset.value() as usize;
|
|
let str_memory = &buf[buf.len() - ptr..];
|
|
let size = u32::from_le_bytes([
|
|
str_memory[0],
|
|
str_memory[1],
|
|
str_memory[2],
|
|
str_memory[3],
|
|
]) as usize;
|
|
let stored = &str_memory[4..4 + size];
|
|
stored.cmp(s.as_bytes())
|
|
});
|
|
|
|
match found {
|
|
Ok(index) => Ok(self.strings_pool[index]),
|
|
Err(index) => {
|
|
let address =
|
|
WIPOffset::new(self.try_create_byte_string(s.as_bytes())?.value());
|
|
self.strings_pool.insert(index, address);
|
|
Ok(address)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create a utf8 string, and de-duplicate if already created.
|
|
///
|
|
/// Uses a sorted Vec with binary search to track previously written
|
|
/// strings when in `no_std` mode.
|
|
#[cfg(not(feature = "std"))]
|
|
#[inline]
|
|
pub fn create_shared_string<'a: 'b, 'b>(&'a mut self, s: &'b str) -> WIPOffset<&'fbb str> {
|
|
self.try_create_shared_string(s)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`create_string`](Self::create_string).
|
|
#[inline]
|
|
pub fn try_create_string<'a: 'b, 'b>(
|
|
&'a mut self,
|
|
s: &'b str,
|
|
) -> Result<WIPOffset<&'fbb str>, A::Error> {
|
|
self.assert_not_nested(
|
|
"create_string can not be called when a table or vector is under construction",
|
|
);
|
|
Ok(WIPOffset::new(
|
|
self.try_create_byte_string(s.as_bytes())?.value(),
|
|
))
|
|
}
|
|
|
|
/// Create a utf8 string.
|
|
///
|
|
/// The wire format represents this as a zero-terminated byte vector.
|
|
#[inline]
|
|
pub fn create_string<'a: 'b, 'b>(&'a mut self, s: &'b str) -> WIPOffset<&'fbb str> {
|
|
self.try_create_string(s)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`create_byte_string`](Self::create_byte_string).
|
|
#[inline]
|
|
pub fn try_create_byte_string(
|
|
&mut self,
|
|
data: &[u8],
|
|
) -> Result<WIPOffset<&'fbb [u8]>, A::Error> {
|
|
self.assert_not_nested(
|
|
"create_byte_string can not be called when a table or vector is under construction",
|
|
);
|
|
self.align(data.len() + 1, PushAlignment::new(SIZE_UOFFSET))?;
|
|
self.try_push(0u8)?;
|
|
self.push_bytes_unprefixed(data)?;
|
|
self.try_push(data.len() as UOffsetT)?;
|
|
Ok(WIPOffset::new(self.used_space() as UOffsetT))
|
|
}
|
|
|
|
/// Create a zero-terminated byte vector.
|
|
#[inline]
|
|
pub fn create_byte_string(&mut self, data: &[u8]) -> WIPOffset<&'fbb [u8]> {
|
|
self.try_create_byte_string(data)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`create_vector`](Self::create_vector).
|
|
#[inline]
|
|
pub fn try_create_vector<'a: 'b, 'b, T: Push + 'b>(
|
|
&'a mut self,
|
|
items: &'b [T],
|
|
) -> Result<WIPOffset<Vector<'fbb, T::Output>>, A::Error> {
|
|
let elem_size = T::size();
|
|
let slice_size = items.len() * elem_size;
|
|
self.align(slice_size, T::alignment().max_of(SIZE_UOFFSET))?;
|
|
self.ensure_capacity(slice_size + UOffsetT::size())?;
|
|
|
|
self.head -= slice_size;
|
|
let mut written_len = self.head.distance_to_end();
|
|
|
|
let buf = &mut self.allocator[self.head.range_to(self.head + slice_size)];
|
|
for (item, out) in items.iter().zip(buf.chunks_exact_mut(elem_size)) {
|
|
written_len -= elem_size;
|
|
|
|
// Safety:
|
|
// Called ensure_capacity and aligned to T above
|
|
unsafe { item.push(out, written_len) };
|
|
}
|
|
|
|
Ok(WIPOffset::new(
|
|
self.try_push::<UOffsetT>(items.len() as UOffsetT)?.value(),
|
|
))
|
|
}
|
|
|
|
/// Create a vector of Push-able objects.
|
|
///
|
|
/// Speed-sensitive users may wish to reduce memory usage by creating the
|
|
/// vector manually: use `start_vector`, `push`, and `end_vector`.
|
|
#[inline]
|
|
pub fn create_vector<'a: 'b, 'b, T: Push + 'b>(
|
|
&'a mut self,
|
|
items: &'b [T],
|
|
) -> WIPOffset<Vector<'fbb, T::Output>> {
|
|
self.try_create_vector(items)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`create_vector_from_iter`](Self::create_vector_from_iter).
|
|
#[inline]
|
|
pub fn try_create_vector_from_iter<T: Push>(
|
|
&mut self,
|
|
items: impl ExactSizeIterator<Item = T> + DoubleEndedIterator,
|
|
) -> Result<WIPOffset<Vector<'fbb, T::Output>>, A::Error> {
|
|
let elem_size = T::size();
|
|
self.align(items.len() * elem_size, T::alignment().max_of(SIZE_UOFFSET))?;
|
|
let mut actual = 0;
|
|
for item in items.rev() {
|
|
self.try_push(item)?;
|
|
actual += 1;
|
|
}
|
|
Ok(WIPOffset::new(self.try_push::<UOffsetT>(actual)?.value()))
|
|
}
|
|
|
|
/// Create a vector of Push-able objects.
|
|
///
|
|
/// Speed-sensitive users may wish to reduce memory usage by creating the
|
|
/// vector manually: use `start_vector`, `push`, and `end_vector`.
|
|
#[inline]
|
|
pub fn create_vector_from_iter<T: Push>(
|
|
&mut self,
|
|
items: impl ExactSizeIterator<Item = T> + DoubleEndedIterator,
|
|
) -> WIPOffset<Vector<'fbb, T::Output>> {
|
|
self.try_create_vector_from_iter(items)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Set whether default values are stored.
|
|
///
|
|
/// In order to save space, fields that are set to their default value
|
|
/// aren't stored in the buffer. Setting `force_defaults` to `true`
|
|
/// disables this optimization.
|
|
///
|
|
/// By default, `force_defaults` is `false`.
|
|
#[inline]
|
|
pub fn force_defaults(&mut self, force_defaults: bool) {
|
|
self.force_defaults = force_defaults;
|
|
}
|
|
|
|
/// Get the byte slice for the data that has been written, regardless of
|
|
/// whether it has been finished.
|
|
#[inline]
|
|
pub fn unfinished_data(&self) -> &[u8] {
|
|
&self.allocator[self.head.range_to_end()]
|
|
}
|
|
/// Get the byte slice for the data that has been written after a call to
|
|
/// one of the `finish` functions.
|
|
/// # Panics
|
|
/// Panics if the buffer is not finished.
|
|
#[inline]
|
|
pub fn finished_data(&self) -> &[u8] {
|
|
self.assert_finished("finished_bytes cannot be called when the buffer is not yet finished");
|
|
&self.allocator[self.head.range_to_end()]
|
|
}
|
|
/// Returns a mutable view of a finished buffer and location of where the flatbuffer starts.
|
|
/// Note that modifying the flatbuffer data may corrupt it.
|
|
/// # Panics
|
|
/// Panics if the flatbuffer is not finished.
|
|
#[inline]
|
|
pub fn mut_finished_buffer(&mut self) -> (&mut [u8], usize) {
|
|
let index = self.head.to_forward_index(&self.allocator);
|
|
(&mut self.allocator[..], index)
|
|
}
|
|
/// Assert that a field is present in the just-finished Table.
|
|
///
|
|
/// This is somewhat low-level and is mostly used by the generated code.
|
|
#[inline]
|
|
pub fn required(
|
|
&self,
|
|
tab_revloc: WIPOffset<TableFinishedWIPOffset>,
|
|
slot_byte_loc: VOffsetT,
|
|
assert_msg_name: &'static str,
|
|
) {
|
|
let idx = self.used_space() - tab_revloc.value() as usize;
|
|
|
|
// Safety:
|
|
// The value of TableFinishedWIPOffset is the offset from the end of the allocator
|
|
// to an SOffsetT pointing to a valid VTable
|
|
//
|
|
// `self.allocator.len() = self.used_space() + self.head`
|
|
// `self.allocator.len() - tab_revloc = self.used_space() - tab_revloc + self.head`
|
|
// `self.allocator.len() - tab_revloc = idx + self.head`
|
|
let tab = unsafe { Table::new(&self.allocator[self.head.range_to_end()], idx) };
|
|
let o = tab.vtable().get(slot_byte_loc) as usize;
|
|
assert!(o != 0, "missing required field {}", assert_msg_name);
|
|
}
|
|
|
|
/// Fallible version of [`finish_size_prefixed`](Self::finish_size_prefixed).
|
|
#[inline]
|
|
pub fn try_finish_size_prefixed<T>(
|
|
&mut self,
|
|
root: WIPOffset<T>,
|
|
file_identifier: Option<&str>,
|
|
) -> Result<(), A::Error> {
|
|
self.finish_with_opts(root, file_identifier, true)
|
|
}
|
|
|
|
/// Finalize the FlatBuffer by: aligning it, pushing an optional file
|
|
/// identifier on to it, pushing a size prefix on to it, and marking the
|
|
/// internal state of the FlatBufferBuilder as `finished`. Afterwards,
|
|
/// users can call `finished_data` to get the resulting data.
|
|
#[inline]
|
|
pub fn finish_size_prefixed<T>(&mut self, root: WIPOffset<T>, file_identifier: Option<&str>) {
|
|
self.try_finish_size_prefixed(root, file_identifier)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`finish`](Self::finish).
|
|
#[inline]
|
|
pub fn try_finish<T>(
|
|
&mut self,
|
|
root: WIPOffset<T>,
|
|
file_identifier: Option<&str>,
|
|
) -> Result<(), A::Error> {
|
|
self.finish_with_opts(root, file_identifier, false)
|
|
}
|
|
|
|
/// Finalize the FlatBuffer by: aligning it, pushing an optional file
|
|
/// identifier on to it, and marking the internal state of the
|
|
/// FlatBufferBuilder as `finished`. Afterwards, users can call
|
|
/// `finished_data` to get the resulting data.
|
|
#[inline]
|
|
pub fn finish<T>(&mut self, root: WIPOffset<T>, file_identifier: Option<&str>) {
|
|
self.try_finish(root, file_identifier)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
/// Fallible version of [`finish_minimal`](Self::finish_minimal).
|
|
#[inline]
|
|
pub fn try_finish_minimal<T>(&mut self, root: WIPOffset<T>) -> Result<(), A::Error> {
|
|
self.finish_with_opts(root, None, false)
|
|
}
|
|
|
|
/// Finalize the FlatBuffer by: aligning it and marking the internal state
|
|
/// of the FlatBufferBuilder as `finished`. Afterwards, users can call
|
|
/// `finished_data` to get the resulting data.
|
|
#[inline]
|
|
pub fn finish_minimal<T>(&mut self, root: WIPOffset<T>) {
|
|
self.try_finish_minimal(root)
|
|
.expect("Flatbuffer allocation failure")
|
|
}
|
|
|
|
#[inline]
|
|
fn used_space(&self) -> usize {
|
|
self.head.distance_to_end()
|
|
}
|
|
|
|
#[inline]
|
|
fn track_field(&mut self, slot_off: VOffsetT, off: UOffsetT) {
|
|
let fl = FieldLoc { id: slot_off, off };
|
|
self.field_locs.push(fl);
|
|
}
|
|
|
|
/// Write the VTable, if it is new.
|
|
fn write_vtable(
|
|
&mut self,
|
|
table_tail_revloc: WIPOffset<TableUnfinishedWIPOffset>,
|
|
) -> Result<WIPOffset<VTableWIPOffset>, A::Error> {
|
|
self.assert_nested("write_vtable");
|
|
|
|
// Write the vtable offset, which is the start of any Table.
|
|
// We fill its value later.
|
|
let object_revloc_to_vtable: WIPOffset<VTableWIPOffset> =
|
|
WIPOffset::new(self.try_push::<UOffsetT>(0xF0F0_F0F0)?.value());
|
|
|
|
// Layout of the data this function will create when a new vtable is
|
|
// needed.
|
|
// --------------------------------------------------------------------
|
|
// vtable starts here
|
|
// | x, x -- vtable len (bytes) [u16]
|
|
// | x, x -- object inline len (bytes) [u16]
|
|
// | x, x -- zero, or num bytes from start of object to field #0 [u16]
|
|
// | ...
|
|
// | x, x -- zero, or num bytes from start of object to field #n-1 [u16]
|
|
// vtable ends here
|
|
// table starts here
|
|
// | x, x, x, x -- offset (negative direction) to the vtable [i32]
|
|
// | aka "vtableoffset"
|
|
// | -- table inline data begins here, we don't touch it --
|
|
// table ends here -- aka "table_start"
|
|
// --------------------------------------------------------------------
|
|
//
|
|
// Layout of the data this function will create when we re-use an
|
|
// existing vtable.
|
|
//
|
|
// We always serialize this particular vtable, then compare it to the
|
|
// other vtables we know about to see if there is a duplicate. If there
|
|
// is, then we erase the serialized vtable we just made.
|
|
// We serialize it first so that we are able to do byte-by-byte
|
|
// comparisons with already-serialized vtables. This 1) saves
|
|
// bookkeeping space (we only keep revlocs to existing vtables), 2)
|
|
// allows us to convert to little-endian once, then do
|
|
// fast memcmp comparisons, and 3) by ensuring we are comparing real
|
|
// serialized vtables, we can be more assured that we are doing the
|
|
// comparisons correctly.
|
|
//
|
|
// --------------------------------------------------------------------
|
|
// table starts here
|
|
// | x, x, x, x -- offset (negative direction) to an existing vtable [i32]
|
|
// | aka "vtableoffset"
|
|
// | -- table inline data begins here, we don't touch it --
|
|
// table starts here: aka "table_start"
|
|
// --------------------------------------------------------------------
|
|
|
|
// fill the WIP vtable with zeros:
|
|
let vtable_byte_len = get_vtable_byte_len(&self.field_locs);
|
|
self.make_space(vtable_byte_len)?;
|
|
|
|
// compute the length of the table (not vtable!) in bytes:
|
|
let table_object_size = object_revloc_to_vtable.value() - table_tail_revloc.value();
|
|
debug_assert!(table_object_size < 0x10000); // vTable use 16bit offsets.
|
|
|
|
// Write the VTable (we may delete it afterwards, if it is a duplicate):
|
|
let vt_start_pos = self.head;
|
|
let vt_end_pos = self.head + vtable_byte_len;
|
|
// Zero out the vtable space - make_space only reserves but doesn't initialize
|
|
self.allocator[vt_start_pos.range_to(vt_end_pos)].fill(0);
|
|
{
|
|
// write the vtable header:
|
|
let vtfw =
|
|
&mut VTableWriter::init(&mut self.allocator[vt_start_pos.range_to(vt_end_pos)]);
|
|
vtfw.write_vtable_byte_length(vtable_byte_len as VOffsetT);
|
|
vtfw.write_object_inline_size(table_object_size as VOffsetT);
|
|
|
|
// serialize every FieldLoc to the vtable:
|
|
for &fl in self.field_locs.iter() {
|
|
let pos: VOffsetT = (object_revloc_to_vtable.value() - fl.off) as VOffsetT;
|
|
vtfw.write_field_offset(fl.id, pos);
|
|
}
|
|
}
|
|
let new_vt_bytes = &self.allocator[vt_start_pos.range_to(vt_end_pos)];
|
|
let found = self
|
|
.written_vtable_revpos
|
|
.binary_search_by(|old_vtable_revpos: &UOffsetT| {
|
|
let old_vtable_pos = self.allocator.len() - *old_vtable_revpos as usize;
|
|
// Safety:
|
|
// Already written vtables are valid by construction
|
|
let old_vtable = unsafe { VTable::init(&self.allocator, old_vtable_pos) };
|
|
new_vt_bytes.cmp(old_vtable.as_bytes())
|
|
});
|
|
let final_vtable_revpos = match found {
|
|
Ok(i) => {
|
|
// The new vtable is a duplicate so clear it.
|
|
VTableWriter::init(&mut self.allocator[vt_start_pos.range_to(vt_end_pos)]).clear();
|
|
self.head += vtable_byte_len;
|
|
self.written_vtable_revpos[i]
|
|
}
|
|
Err(i) => {
|
|
// This is a new vtable. Add it to the cache.
|
|
let new_vt_revpos = self.used_space() as UOffsetT;
|
|
self.written_vtable_revpos.insert(i, new_vt_revpos);
|
|
new_vt_revpos
|
|
}
|
|
};
|
|
// Write signed offset from table to its vtable.
|
|
let table_pos = self.allocator.len() - object_revloc_to_vtable.value() as usize;
|
|
if cfg!(debug_assertions) {
|
|
// Safety:
|
|
// Verified slice length
|
|
let tmp_soffset_to_vt = unsafe {
|
|
read_scalar::<UOffsetT>(&self.allocator[table_pos..table_pos + SIZE_UOFFSET])
|
|
};
|
|
assert_eq!(tmp_soffset_to_vt, 0xF0F0_F0F0);
|
|
}
|
|
|
|
let buf = &mut self.allocator[table_pos..table_pos + SIZE_SOFFSET];
|
|
// Safety:
|
|
// Verified length of buf above
|
|
unsafe {
|
|
emplace_scalar::<SOffsetT>(
|
|
buf,
|
|
final_vtable_revpos as SOffsetT - object_revloc_to_vtable.value() as SOffsetT,
|
|
);
|
|
}
|
|
|
|
self.field_locs.clear();
|
|
|
|
Ok(object_revloc_to_vtable)
|
|
}
|
|
|
|
// Only call this when you know it is safe to double the size of the buffer.
|
|
#[inline]
|
|
fn grow_allocator(&mut self) -> Result<(), A::Error> {
|
|
let starting_active_size = self.used_space();
|
|
self.allocator.grow_downwards()?;
|
|
|
|
let ending_active_size = self.used_space();
|
|
debug_assert_eq!(starting_active_size, ending_active_size);
|
|
Ok(())
|
|
}
|
|
|
|
// with or without a size prefix changes how we load the data, so finish*
|
|
// functions are split along those lines.
|
|
fn finish_with_opts<T>(
|
|
&mut self,
|
|
root: WIPOffset<T>,
|
|
file_identifier: Option<&str>,
|
|
size_prefixed: bool,
|
|
) -> Result<(), A::Error> {
|
|
self.assert_not_finished("buffer cannot be finished when it is already finished");
|
|
self.assert_not_nested(
|
|
"buffer cannot be finished when a table or vector is under construction",
|
|
);
|
|
self.written_vtable_revpos.clear();
|
|
|
|
let to_align = {
|
|
// for the root offset:
|
|
let a = SIZE_UOFFSET;
|
|
// for the size prefix:
|
|
let b = if size_prefixed { SIZE_UOFFSET } else { 0 };
|
|
// for the file identifier (a string that is not zero-terminated):
|
|
let c = if file_identifier.is_some() {
|
|
FILE_IDENTIFIER_LENGTH
|
|
} else {
|
|
0
|
|
};
|
|
a + b + c
|
|
};
|
|
|
|
{
|
|
let ma = PushAlignment::new(self.min_align);
|
|
self.align(to_align, ma)?;
|
|
}
|
|
|
|
if let Some(ident) = file_identifier {
|
|
debug_assert_eq!(ident.len(), FILE_IDENTIFIER_LENGTH);
|
|
self.push_bytes_unprefixed(ident.as_bytes())?;
|
|
}
|
|
|
|
self.try_push(root)?;
|
|
|
|
if size_prefixed {
|
|
let sz = self.used_space() as UOffsetT;
|
|
self.try_push::<UOffsetT>(sz)?;
|
|
}
|
|
self.finished = true;
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn align(&mut self, len: usize, alignment: PushAlignment) -> Result<(), A::Error> {
|
|
self.track_min_align(alignment.value());
|
|
let s = self.used_space() as usize;
|
|
self.make_space(padding_bytes(s + len, alignment.value()))?;
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn track_min_align(&mut self, alignment: usize) {
|
|
self.min_align = max(self.min_align, alignment);
|
|
}
|
|
|
|
#[inline]
|
|
fn push_bytes_unprefixed(&mut self, x: &[u8]) -> Result<UOffsetT, A::Error> {
|
|
let n = self.make_space(x.len())?;
|
|
self.allocator[n.range_to(n + x.len())].copy_from_slice(x);
|
|
|
|
Ok(n.to_forward_index(&self.allocator) as UOffsetT)
|
|
}
|
|
|
|
#[inline]
|
|
fn make_space(&mut self, want: usize) -> Result<ReverseIndex, A::Error> {
|
|
self.ensure_capacity(want)?;
|
|
self.head -= want;
|
|
Ok(self.head)
|
|
}
|
|
|
|
#[inline]
|
|
fn ensure_capacity(&mut self, want: usize) -> Result<usize, A::Error> {
|
|
if self.unused_ready_space() >= want {
|
|
return Ok(want);
|
|
}
|
|
assert!(
|
|
want <= FLATBUFFERS_MAX_BUFFER_SIZE,
|
|
"cannot grow buffer beyond 2 gigabytes"
|
|
);
|
|
|
|
while self.unused_ready_space() < want {
|
|
self.grow_allocator()?;
|
|
}
|
|
Ok(want)
|
|
}
|
|
#[inline]
|
|
fn unused_ready_space(&self) -> usize {
|
|
self.allocator.len() - self.head.distance_to_end()
|
|
}
|
|
#[inline]
|
|
fn assert_nested(&self, fn_name: &'static str) {
|
|
// we don't assert that self.field_locs.len() >0 because the vtable
|
|
// could be empty (e.g. for empty tables, or for all-default values).
|
|
debug_assert!(
|
|
self.nested,
|
|
"incorrect FlatBufferBuilder usage: {} must be called while in a nested state",
|
|
fn_name
|
|
);
|
|
}
|
|
#[inline]
|
|
fn assert_not_nested(&self, msg: &'static str) {
|
|
debug_assert!(!self.nested, "{}", msg);
|
|
}
|
|
#[inline]
|
|
fn assert_finished(&self, msg: &'static str) {
|
|
debug_assert!(self.finished, "{}", msg);
|
|
}
|
|
#[inline]
|
|
fn assert_not_finished(&self, msg: &'static str) {
|
|
debug_assert!(!self.finished, "{}", msg);
|
|
}
|
|
}
|
|
|
|
/// Compute the length of the vtable needed to represent the provided FieldLocs.
|
|
/// If there are no FieldLocs, then provide the minimum number of bytes
|
|
/// required: enough to write the VTable header.
|
|
#[inline]
|
|
fn get_vtable_byte_len(field_locs: &[FieldLoc]) -> usize {
|
|
let max_voffset = field_locs.iter().map(|fl| fl.id).max();
|
|
match max_voffset {
|
|
None => field_index_to_field_offset(0) as usize,
|
|
Some(mv) => mv as usize + SIZE_VOFFSET,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn padding_bytes(buf_size: usize, scalar_size: usize) -> usize {
|
|
// ((!buf_size) + 1) & (scalar_size - 1)
|
|
(!buf_size).wrapping_add(1) & (scalar_size.wrapping_sub(1))
|
|
}
|
|
|
|
impl<'fbb> Default for FlatBufferBuilder<'fbb> {
|
|
fn default() -> Self {
|
|
Self::with_capacity(0)
|
|
}
|
|
}
|
|
|
|
/// An index that indexes from the reverse of a slice.
|
|
///
|
|
/// Note that while the internal representation is an index
|
|
/// from the end of a buffer, operations like `Add` and `Sub`
|
|
/// behave like a regular index:
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```ignore
|
|
/// let buf = [0, 1, 2, 3, 4, 5];
|
|
/// let idx = ReverseIndex::end() - 2;
|
|
/// assert_eq!(&buf[idx.range_to_end()], &[4, 5]);
|
|
/// assert_eq!(idx.to_forward_index(&buf), 4);
|
|
/// ```
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
struct ReverseIndex(usize);
|
|
|
|
impl ReverseIndex {
|
|
/// Returns an index set to the end.
|
|
///
|
|
/// Note: Indexing this will result in an out of bounds error.
|
|
pub fn end() -> Self {
|
|
Self(0)
|
|
}
|
|
|
|
/// Returns a struct equivalent to the range `self..`
|
|
pub fn range_to_end(self) -> ReverseIndexRange {
|
|
ReverseIndexRange(self, ReverseIndex::end())
|
|
}
|
|
|
|
/// Returns a struct equivalent to the range `self..end`
|
|
pub fn range_to(self, end: ReverseIndex) -> ReverseIndexRange {
|
|
ReverseIndexRange(self, end)
|
|
}
|
|
|
|
/// Transforms this reverse index into a regular index for the given buffer.
|
|
pub fn to_forward_index<T>(self, buf: &[T]) -> usize {
|
|
buf.len() - self.0
|
|
}
|
|
|
|
/// Returns the number of elements until the end of the range.
|
|
pub fn distance_to_end(&self) -> usize {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl Sub<usize> for ReverseIndex {
|
|
type Output = Self;
|
|
|
|
fn sub(self, rhs: usize) -> Self::Output {
|
|
Self(self.0 + rhs)
|
|
}
|
|
}
|
|
|
|
impl SubAssign<usize> for ReverseIndex {
|
|
fn sub_assign(&mut self, rhs: usize) {
|
|
*self = *self - rhs;
|
|
}
|
|
}
|
|
|
|
impl Add<usize> for ReverseIndex {
|
|
type Output = Self;
|
|
|
|
fn add(self, rhs: usize) -> Self::Output {
|
|
Self(self.0 - rhs)
|
|
}
|
|
}
|
|
|
|
impl AddAssign<usize> for ReverseIndex {
|
|
fn add_assign(&mut self, rhs: usize) {
|
|
*self = *self + rhs;
|
|
}
|
|
}
|
|
impl<T> Index<ReverseIndex> for [T] {
|
|
type Output = T;
|
|
|
|
fn index(&self, index: ReverseIndex) -> &Self::Output {
|
|
let index = index.to_forward_index(self);
|
|
&self[index]
|
|
}
|
|
}
|
|
|
|
impl<T> IndexMut<ReverseIndex> for [T] {
|
|
fn index_mut(&mut self, index: ReverseIndex) -> &mut Self::Output {
|
|
let index = index.to_forward_index(self);
|
|
&mut self[index]
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
struct ReverseIndexRange(ReverseIndex, ReverseIndex);
|
|
|
|
impl<T> Index<ReverseIndexRange> for [T] {
|
|
type Output = [T];
|
|
|
|
fn index(&self, index: ReverseIndexRange) -> &Self::Output {
|
|
let start = index.0.to_forward_index(self);
|
|
let end = index.1.to_forward_index(self);
|
|
&self[start..end]
|
|
}
|
|
}
|
|
|
|
impl<T> IndexMut<ReverseIndexRange> for [T] {
|
|
fn index_mut(&mut self, index: ReverseIndexRange) -> &mut Self::Output {
|
|
let start = index.0.to_forward_index(self);
|
|
let end = index.1.to_forward_index(self);
|
|
&mut self[start..end]
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::alloc::{GlobalAlloc, Layout, System};
|
|
|
|
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
struct CountingAllocator;
|
|
|
|
unsafe impl GlobalAlloc for CountingAllocator {
|
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
|
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
|
|
unsafe { System.alloc(layout) }
|
|
}
|
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
|
unsafe { System.dealloc(ptr, layout) }
|
|
}
|
|
}
|
|
|
|
#[global_allocator]
|
|
static GLOBAL: CountingAllocator = CountingAllocator;
|
|
|
|
fn reset_alloc_count() {
|
|
ALLOC_COUNT.store(0, Ordering::Relaxed);
|
|
}
|
|
|
|
fn alloc_count() -> usize {
|
|
ALLOC_COUNT.load(Ordering::Relaxed)
|
|
}
|
|
|
|
#[test]
|
|
fn reverse_index_test() {
|
|
let buf = [0, 1, 2, 3, 4, 5];
|
|
let idx = ReverseIndex::end() - 2;
|
|
assert_eq!(&buf[idx.range_to_end()], &[4, 5]);
|
|
assert_eq!(&buf[idx.range_to(idx + 1)], &[4]);
|
|
assert_eq!(idx.to_forward_index(&buf), 4);
|
|
}
|
|
|
|
#[test]
|
|
fn with_internal_capacity_preallocates_vecs() {
|
|
let mut builder = FlatBufferBuilder::with_internal_capacity(64, 8, 16, 32);
|
|
|
|
assert!(builder.allocator.len() >= 64);
|
|
assert!(builder.field_locs.capacity() >= 8);
|
|
assert!(builder.written_vtable_revpos.capacity() >= 16);
|
|
assert!(builder.strings_pool.capacity() >= 32);
|
|
|
|
assert!(builder.field_locs.is_empty());
|
|
assert!(builder.written_vtable_revpos.is_empty());
|
|
assert!(builder.strings_pool.is_empty());
|
|
|
|
// Reset the allocation counter after builder construction
|
|
reset_alloc_count();
|
|
|
|
// Add a shared string and verify it lands in the pool
|
|
let s1 = builder.create_shared_string("hello");
|
|
assert_eq!(builder.strings_pool.len(), 1);
|
|
|
|
// Adding the same string again should reuse the pooled entry
|
|
let s2 = builder.create_shared_string("hello");
|
|
assert_eq!(builder.strings_pool.len(), 1);
|
|
assert_eq!(s1.value(), s2.value());
|
|
|
|
// A different string should add a new entry
|
|
let _s3 = builder.create_shared_string("world");
|
|
assert_eq!(builder.strings_pool.len(), 2);
|
|
|
|
// With sufficient preallocated capacity, no additional allocations
|
|
// should have occurred for the internal vecs during the operations above
|
|
let allocs = alloc_count();
|
|
assert_eq!(
|
|
allocs, 0,
|
|
"expected 0 allocations after builder construction, got {}",
|
|
allocs
|
|
);
|
|
}
|
|
|
|
/// A test allocator that fails after a specified number of grow operations.
|
|
struct FailingAllocator {
|
|
inner: DefaultAllocator,
|
|
grows_remaining: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
struct AllocationError;
|
|
|
|
impl core::fmt::Display for AllocationError {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
write!(f, "allocation failed")
|
|
}
|
|
}
|
|
|
|
impl FailingAllocator {
|
|
fn new(initial_size: usize, max_grows: usize) -> Self {
|
|
Self {
|
|
inner: DefaultAllocator::from_vec(vec![0u8; initial_size]),
|
|
grows_remaining: max_grows,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Deref for FailingAllocator {
|
|
type Target = [u8];
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.inner
|
|
}
|
|
}
|
|
|
|
impl DerefMut for FailingAllocator {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.inner
|
|
}
|
|
}
|
|
|
|
unsafe impl Allocator for FailingAllocator {
|
|
type Error = AllocationError;
|
|
|
|
fn grow_downwards(&mut self) -> Result<(), Self::Error> {
|
|
if self.grows_remaining == 0 {
|
|
return Err(AllocationError);
|
|
}
|
|
self.grows_remaining -= 1;
|
|
// DefaultAllocator returns Infallible, so unwrap is safe
|
|
self.inner.grow_downwards().unwrap();
|
|
Ok(())
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
self.inner.len()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn try_push_propagates_allocation_error() {
|
|
let allocator = FailingAllocator::new(1, 0);
|
|
let mut builder = FlatBufferBuilder::new_in(allocator);
|
|
|
|
let result = builder.try_push::<u64>(0x1234567890ABCDEF);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn try_create_string_propagates_allocation_error() {
|
|
let allocator = FailingAllocator::new(1, 0);
|
|
let mut builder = FlatBufferBuilder::new_in(allocator);
|
|
|
|
let result = builder.try_create_string("hello world");
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn try_create_vector_propagates_allocation_error() {
|
|
let allocator = FailingAllocator::new(1, 0);
|
|
let mut builder = FlatBufferBuilder::new_in(allocator);
|
|
|
|
let result = builder.try_create_vector(&[1u32, 2, 3, 4, 5]);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn try_methods_succeed_with_sufficient_capacity() {
|
|
let allocator = FailingAllocator::new(1, 10);
|
|
let mut builder = FlatBufferBuilder::new_in(allocator);
|
|
|
|
let result = builder.try_create_string("hello");
|
|
assert!(result.is_ok());
|
|
|
|
let result = builder.try_create_vector(&[1u32, 2, 3]);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn try_finish_propagates_allocation_error() {
|
|
let allocator = FailingAllocator::new(1, 3);
|
|
let mut builder = FlatBufferBuilder::new_in(allocator);
|
|
|
|
let start = builder.start_table();
|
|
let table = builder
|
|
.try_end_table(start)
|
|
.expect("end_table should succeed with 3 grows");
|
|
let result = builder.try_finish_minimal(table);
|
|
assert!(result.is_err(), "finish should fail after grows exhausted");
|
|
}
|
|
}
|