|
void VectorData::Construct(u32 type_size_, bool trivial_construct, bool trivial_copy_construct, bool trivial_destruct) |
|
{ |
|
// Set default properties with type traits |
|
items = nullptr; |
|
size = 0; |
|
capacity = 0; |
|
type_size = type_size_; |
|
construct = !trivial_construct; |
|
copy_construct = !trivial_copy_construct; |
|
destruct = !trivial_destruct; |
|
} |
|
|
|
|
|
void VectorData::Construct(const VectorData& rhs, CopyConstructPtr copy_constructor) |
|
{ |
|
// Copy properties from source VectorData |
|
size = rhs.size; |
|
capacity = rhs.capacity; |
|
type_size = rhs.type_size; |
|
construct = rhs.construct; |
|
copy_construct = rhs.copy_construct; |
|
destruct = rhs.destruct; |
|
|
|
// Allocate capacity |
|
items = (u8*)malloc(capacity * type_size); |
|
|
|
if (copy_construct) |
|
{ |
|
// Copy construct all items from source VectorData |
|
u8* dest_item = items; |
|
u8* src_item = rhs.items; |
|
for (u32 i = 0; i < size; i++) |
|
{ |
|
copy_constructor(dest_item, src_item); |
|
dest_item += type_size; |
|
src_item += type_size; |
|
} |
|
} |
|
|
|
else |
|
{ |
|
// Memory copy is safe otherwise |
|
memcpy(items, rhs.items, size * type_size); |
|
} |
|
} |
|
|
|
|
|
void VectorData::Construct(u32 size_, u32 type_size_, bool trivial_construct, bool trivial_copy_construct, bool trivial_destruct, ConstructPtr constructor) |
|
{ |
|
size = size_; |
|
capacity = size_; |
|
type_size = type_size_; |
|
construct = !trivial_construct; |
|
copy_construct = !trivial_copy_construct; |
|
destruct = !trivial_destruct; |
|
|
|
// Allocate capacity |
|
items = (u8*)malloc(capacity * type_size); |
|
|
|
// Call default constructor for all items, only if required |
|
if (construct) |
|
{ |
|
u8* item_ptr = items; |
|
for (u32 i = 0; i < size; i++) |
|
{ |
|
constructor(item_ptr); |
|
item_ptr += type_size; |
|
} |
|
} |
|
} |
|
|
|
|
|
void VectorData::Destruct(DestructPtr destructor) |
|
{ |
|
if (items != nullptr) |
|
{ |
|
Clear(destructor); |
|
free(items); |
|
} |
|
} |
|
|
|
|
|
void VectorData::Reserve(u32 new_capacity, CopyConstructPtr copy_constructor, DestructPtr destructor) |
|
{ |
|
// Only reserve larger capacities |
|
if (new_capacity > capacity) |
|
{ |
|
// Allocate a new set of data with an aligned size |
|
capacity = AlignPow2(new_capacity, 16); |
|
u8* new_items = (u8*)malloc(capacity * type_size); |
|
|
|
// Need to copy existing items to the new storage area |
|
if (items != nullptr) |
|
{ |
|
// Memory walk is far more expensive than an extra outer loop branch so try to do everything in |
|
// one pass with trickier code than using the simpler two pass approach. |
|
|
|
if (copy_construct) |
|
{ |
|
if (destruct) |
|
{ |
|
// Copy construct the old data to the new location while destructing the old data |
|
u8* dest_item = new_items; |
|
u8* src_item = items; |
|
for (u32 i = 0; i < size; i++) |
|
{ |
|
copy_constructor(dest_item, src_item); |
|
destructor(src_item); |
|
dest_item += type_size; |
|
src_item += type_size; |
|
} |
|
} |
|
|
|
else |
|
{ |
|
// Destruction not required so just copy construct all items to the new location |
|
u8* dest_item = new_items; |
|
u8* src_item = items; |
|
for (u32 i = 0; i < size; i++) |
|
{ |
|
copy_constructor(dest_item, src_item); |
|
dest_item += type_size; |
|
src_item += type_size; |
|
} |
|
} |
|
} |
|
|
|
else |
|
{ |
|
// No construction required so use a byte copy |
|
memcpy(new_items, items, size * type_size); |
|
|
|
// Call destructor on old items if required |
|
if (destruct) |
|
{ |
|
u8* item_ptr = items; |
|
for (u32 i = 0; i < size; i++) |
|
{ |
|
destructor(item_ptr); |
|
item_ptr += type_size; |
|
} |
|
} |
|
} |
|
|
|
// Release old item storage |
|
free(items); |
|
} |
|
|
|
items = new_items; |
|
} |
|
} |
|
|
|
|
|
void VectorData::Resize(u32 new_size, ConstructPtr constructor, CopyConstructPtr copy_constructor, DestructPtr destructor) |
|
{ |
|
// Ensure there's enough allocated memory |
|
// TODO: Better growth strategy |
|
if (new_size > capacity) |
|
Reserve(new_size, copy_constructor, destructor); |
|
|
|
// Destruct elements above the new size |
|
if (new_size < size && destruct) |
|
{ |
|
u8* item_ptr = items + new_size * type_size; |
|
for (u32 i = new_size; i < size; i++) |
|
{ |
|
destructor(item_ptr); |
|
item_ptr += type_size; |
|
} |
|
} |
|
|
|
// Default construct elements above the old size |
|
else if (new_size > size && construct) |
|
{ |
|
u8* item_ptr = items + size * type_size; |
|
for (u32 i = size; i < new_size; i++) |
|
{ |
|
constructor(item_ptr); |
|
item_ptr += type_size; |
|
} |
|
} |
|
|
|
size = new_size; |
|
} |
|
|
|
|
|
// TODO: Remove |
|
void VectorData::ClearResize(u32 new_size, ConstructPtr constructor, CopyConstructPtr copy_constructor, DestructPtr destructor) |
|
{ |
|
Clear(destructor); |
|
Resize(new_size, constructor, copy_constructor, destructor); |
|
} |
|
|
|
|
|
void VectorData::Clear(DestructPtr destructor) |
|
{ |
|
// Destruct all items if required |
|
if (destruct) |
|
{ |
|
u8* item_ptr = items; |
|
for (u32 i = 0; i < size; i++) |
|
{ |
|
destructor(item_ptr); |
|
item_ptr += type_size; |
|
} |
|
} |
|
|
|
size = 0; |
|
} |
|
|
|
|
|
void* VectorData::PushBack(const void* object, CopyConstructPtr copy_constructor, DestructPtr destructor) |
|
{ |
|
// Ensure there's enough space to store the new item |
|
Reserve(size + 1, copy_constructor, destructor); |
|
|
|
// Copy new item to the end of the vector |
|
// TODO: Improve performance with a switch on common integer types sizes? |
|
u8* dest_item = items + size * type_size; |
|
if (copy_construct) |
|
copy_constructor(dest_item, object); |
|
else |
|
memcpy(dest_item, object, type_size); |
|
|
|
size++; |
|
|
|
return dest_item; |
|
} |
|
|
|
|
|
void VectorData::PushBackUnique(const void* object, CopyConstructPtr copy_constructor, DestructPtr destructor, EqualPtr equal) |
|
{ |
|
// Leave, not adding if the item already exists |
|
u8* item_ptr = items; |
|
for (u32 i = 0; i < size; i++) |
|
{ |
|
if (equal(item_ptr, object)) |
|
return; |
|
} |
|
|
|
PushBack(object, copy_constructor, destructor); |
|
} |
|
|
|
|
|
void VectorData::PopBack(DestructPtr destructor) |
|
{ |
|
Assert(size > 0); |
|
|
|
size--; |
|
|
|
if (destruct) |
|
destructor(items + size * type_size); |
|
} |
|
|
|
|
|
void VectorData::RemoveUnstable(u32 index, DestructPtr destructor, CopyPtr copy) |
|
{ |
|
// Copy from the back over the top of the item being removed |
|
// Copy function will always contain the optimal compiler-generated copy, |
|
// which includes calling the destructor on the dest object |
|
u8* last_item = items + (size - 1) * type_size; |
|
copy(items + index * type_size, last_item); |
|
|
|
// Destruct the copied item |
|
if (destruct) |
|
destructor(last_item); |
|
|
|
size--; |
|
} |
|
|
|
|
|
void VectorData::Remove(u32 index, DestructPtr destructor, CopyPtr copy) |
|
{ |
|
// Shift all items above the removed item, one down |
|
// TODO: memcpy everything for trivial destructor + assignment |
|
u8* item_ptr = items + index * type_size; |
|
for (u32 i = index; i < size - 1; i++) |
|
{ |
|
copy(item_ptr, item_ptr + type_size); |
|
item_ptr += type_size; |
|
} |
|
|
|
// Destruct the left over item |
|
if (destruct) |
|
destructor(item_ptr); |
|
|
|
size--; |
|
} |
|
|
|
|
|
void VectorData::RemoveRange(u32 start, u32 count, DestructPtr destructor, CopyPtr copy) |
|
{ |
|
u32 end = start + count; |
|
Assert(end <= size); |
|
u32 left_over = size - end; |
|
|
|
// Shift all items above the removed items down |
|
// TODO: memcpy? Needs trivial assign detection |
|
u8* dest_item = items + start * type_size; |
|
u8* src_item = dest_item + count * type_size; |
|
for (u32 i = 0; i < left_over; i++) |
|
{ |
|
copy(dest_item, src_item); |
|
dest_item += type_size; |
|
src_item += type_size; |
|
} |
|
|
|
// Destruct the items left behind at the top |
|
if (destruct) |
|
{ |
|
u8* item_ptr = items + end * type_size; |
|
for (u32 i = end; i < size; i++) |
|
{ |
|
destructor(item_ptr); |
|
item_ptr += type_size; |
|
} |
|
} |
|
|
|
size -= count; |
|
} |
|
|
|
|
|
void VectorData::CopyFrom(const VectorData& src, CopyConstructPtr copy_constructor, DestructPtr destructor) |
|
{ |
|
// Empty and ensure there's enough capacity |
|
Clear(destructor); |
|
Reserve(src.size, copy_constructor, destructor); |
|
|
|
if (copy_construct) |
|
{ |
|
// Copy construct all items over from source |
|
u8* dest_item = items; |
|
u8* src_item = src.items; |
|
for (u32 i = 0; i < src.size; i++) |
|
{ |
|
copy_constructor(dest_item, src_item); |
|
dest_item += type_size; |
|
src_item += type_size; |
|
} |
|
} |
|
else |
|
{ |
|
// Bit copy instead |
|
memcpy(items, src.items, src.size * type_size); |
|
} |
|
|
|
size = src.size; |
|
} |