turbolist/TurboList.hpp

135 lines
2.8 KiB
C++
Raw Normal View History

#ifndef TURBO_LIST_HPP
#define TURBO_LIST_HPP
2024-08-27 09:32:53 +02:00
#include<cstdint>
#include<cstdlib>
#include<cassert>
2024-08-27 09:32:53 +02:00
2024-08-27 11:08:58 +02:00
#ifndef TL_NOINLINE
#define TL_NOINLINE __attribute__((noinline))
#endif /* TL_NOINLINE */
#ifndef TL_LIKELY
#define TL_LIKELY(x) __builtin_expect(!!(x), 1)
#endif /* TL_LIKELY */
#ifndef TL_UNLIKELY
#define TL_UNLIKELY(x) __builtin_expect(!!(x), 0)
#endif /* TL_UNLIKELY */
typedef void*(MallocLike)(size_t);
typedef void(FreeLike)(void*);
template<typename T, MallocLike MALLOC = malloc, FreeLike FREE = free>
2024-08-27 09:32:53 +02:00
class TurboList {
2024-08-27 15:54:03 +02:00
T *old;
T *nex;
2024-08-27 09:32:53 +02:00
uint32_t mid; // non-inclusive . . . m
uint32_t end; // non-inclusive e . . . .
uint32_t capacity;
2024-08-27 11:08:58 +02:00
2024-08-30 13:34:06 +02:00
TL_NOINLINE T& grow_and_insert(T elem) noexcept {
2024-08-27 11:08:58 +02:00
// assert(mid == 0);
if(old) FREE(old);
2024-08-27 11:08:58 +02:00
old = nex;
mid = end;
capacity *= 2;
nex = (int *) MALLOC(this->capacity * sizeof(T));
2024-08-27 11:08:58 +02:00
// Will go into the INSERT code path here
2024-08-30 13:34:06 +02:00
return insert(elem);
2024-08-27 11:08:58 +02:00
}
2024-08-27 09:32:53 +02:00
public:
inline TurboList(uint32_t initial_size = 0, uint32_t initial_cap = 16) noexcept :
2024-08-27 09:32:53 +02:00
old(nullptr),
mid(0),
end(initial_size),
capacity(initial_cap) {
2024-08-27 09:32:53 +02:00
nex = (int *) MALLOC(this->capacity * sizeof(T));
2024-08-27 09:32:53 +02:00
}
inline ~TurboList() noexcept {
if(nex) FREE(nex);
if(old) FREE(old);
2024-08-27 09:32:53 +02:00
}
2024-08-27 15:54:03 +02:00
inline T& operator[](uint32_t i) noexcept {
2024-08-27 10:53:44 +02:00
return (i < mid) ? old[i] : nex[i];
}
2024-08-28 13:16:13 +02:00
/** This is much faster than operator[] if you do small amounts of work per access */
inline void iterate(void(callback)(T&)) noexcept {
// old
for(uint32_t i = 0; i < mid; ++i) {
callback(old[i]);
}
// nex
for(uint32_t i = mid; i < end; ++i) {
callback(nex[i]);
}
}
2024-08-30 13:34:06 +02:00
inline T& insert(T elem) noexcept {
if(TL_LIKELY(end < capacity)) {
// INSERT
2024-08-27 11:20:20 +02:00
/* Same as this:
if(mid > 0) {
nex[mid - 1] = old[mid - 1];
--mid;
}
2024-08-27 11:20:20 +02:00
*/
bool hasmid = (mid > 0);
mid -= hasmid;
nex[mid] = hasmid ? old[mid] : nex[mid];
2024-08-30 13:34:06 +02:00
return (nex[end++] = elem);
} else {
// GROW
2024-08-30 13:34:06 +02:00
return grow_and_insert(elem);
}
}
2024-08-27 10:29:55 +02:00
template<typename... Args>
2024-08-30 13:34:06 +02:00
inline T& emplace(Args&&... args) {
if(TL_LIKELY(end < capacity)) {
// INSERT
/* Same as this:
if(mid > 0) {
nex[mid - 1] = old[mid - 1];
--mid;
}
*/
bool hasmid = (mid > 0);
mid -= hasmid;
nex[mid] = hasmid ? old[mid] : nex[mid];
// Placement new
return *new (nex + end++) T(std::forward<Args>(args)...);
} else {
// GROW
//
// Rem.: I just chose this to be less optimized than
// it is possible by making a copy and reusing
// the existing grow and insert code instead of
// writing a new "grow_and_emplace" again.
//
// This happens rarely so its probably fine and
// makes less template instantiations, smaller binary.
return grow_and_insert(T(std::forward<Args>(args)...));
}
}
2024-08-27 10:29:55 +02:00
inline uint32_t size() noexcept {
return end;
2024-08-27 10:29:55 +02:00
}
2024-08-27 09:32:53 +02:00
};
#endif /* TURBO_LIST_HPP */