+#include "pool.h"
+#include <stdexcept>
+#include <msp/io/print.h>
+
+using namespace std;
+
+namespace Msp::Game {
+
+unsigned PoolPool::get_next_id()
+{
+ static unsigned next_id = 0;
+ return next_id++;
+}
+
+
+PoolBase::PoolBase(uint32_t s, DeleteFunc d):
+ object_size(s),
+ deleter(d)
+{ }
+
+PoolBase::PoolBase(PoolBase &&other):
+ object_size(other.object_size),
+ blocks(other.blocks),
+ free_list(other.free_list),
+ object_count(other.object_count),
+ capacity(other.capacity),
+ deleter(other.deleter)
+{
+ other.blocks = nullptr;
+ other.free_list = nullptr;
+ other.object_count = 0;
+ other.capacity = 0;
+}
+
+PoolBase &PoolBase::operator=(PoolBase &&other)
+{
+ destroy_all();
+
+ object_size = other.object_size;
+ blocks = other.blocks;
+ free_list = other.free_list;
+ object_count = other.object_count;
+ capacity = other.capacity;
+ deleter = other.deleter;
+
+ other.blocks = nullptr;
+ other.free_list = nullptr;
+ other.object_count = 0;
+ other.capacity = 0;
+
+ return *this;
+}
+
+PoolBase::~PoolBase()
+{
+ destroy_all();
+}
+
+void PoolBase::destroy_all()
+{
+ if(object_count>0)
+ IO::print(IO::cerr, "Warning: pool is being destroyed but has %d live objects", object_count);
+
+ unsigned block_count = capacity/BLOCK_SIZE;
+ for(unsigned i=0; i<block_count; ++i)
+ delete[] blocks[i];
+ delete[] reinterpret_cast<char *>(blocks);
+}
+
+void *PoolBase::prepare_allocate()
+{
+ if(object_count>=capacity)
+ add_block();
+
+ uint32_t full_index = free_list[object_count];
+ unsigned block_index = full_index/BLOCK_SIZE;
+ unsigned object_index = full_index%BLOCK_SIZE;
+ return blocks[block_index]+object_index*object_size;
+}
+
+void PoolBase::commit_allocate(void *ptr)
+{
+ uint32_t full_index = free_list[object_count];
+ unsigned block_index = full_index/BLOCK_SIZE;
+ unsigned object_index = full_index%BLOCK_SIZE;
+ void *expected = blocks[block_index]+object_index*object_size;
+ if(ptr!=expected)
+ throw logic_error("PoolBase::commit_allocate does not match prepare_allocate");
+
+ FlagType *flags = reinterpret_cast<FlagType *>(blocks[block_index]+BLOCK_SIZE*object_size);
+ FlagType bit = 1<<(object_index%FLAG_BITS);
+ if(flags[object_index/FLAG_BITS]&bit)
+ throw logic_error("PoolBase::commit_allocate to a not-free index");
+
+ flags[object_index/FLAG_BITS] |= bit;
+ ++object_count;
+}
+
+void PoolBase::add_block()
+{
+ unsigned block_count = capacity/BLOCK_SIZE;
+ char *new_mem = new alignas(char *) char[(block_count+1)*(sizeof(char *)+BLOCK_SIZE*sizeof(uint32_t))];
+ char **new_blocks = reinterpret_cast<char **>(new_mem);
+ uint32_t *new_free = reinterpret_cast<uint32_t *>(new_mem+(block_count+1)*sizeof(char *));
+
+ copy(blocks, blocks+block_count, new_blocks);
+ copy(free_list, free_list+capacity, new_free);
+
+ char *block = new alignas(BLOCK_ALIGNMENT) char[BLOCK_SIZE*object_size+BLOCK_SIZE/8];
+ new_blocks[block_count] = block;
+ FlagType *flags = reinterpret_cast<FlagType *>(block+BLOCK_SIZE*object_size);
+ for(unsigned i=0; i<BLOCK_SIZE; ++i)
+ new_free[capacity+i] = capacity+i;
+ for(unsigned i=0; i<BLOCK_SIZE/FLAG_BITS; ++i)
+ flags[i] = 0;
+
+ delete[] reinterpret_cast<char *>(blocks);
+ blocks = new_blocks;
+ free_list = new_free;
+ capacity += BLOCK_SIZE;
+}
+
+void PoolBase::destroy(void *obj)
+{
+ unsigned block_index = 0;
+ unsigned object_index = BLOCK_SIZE;
+ intptr_t addr = reinterpret_cast<intptr_t>(obj);
+ unsigned block_count = capacity/BLOCK_SIZE;
+ while(block_index<block_count)
+ {
+ intptr_t mem = reinterpret_cast<intptr_t>(blocks[block_index]);
+ intptr_t mem_end = mem+BLOCK_SIZE*object_size;
+ if(addr>=mem && addr<mem_end)
+ {
+ object_index = (addr-mem)/object_size;
+ break;
+ }
+ ++block_index;
+ }
+
+ if(object_index>=BLOCK_SIZE)
+ throw invalid_argument("PoolBase::destroy");
+
+ FlagType *flags = reinterpret_cast<FlagType *>(blocks[block_index]+BLOCK_SIZE*object_size);
+ FlagType bit = 1<<(object_index%FLAG_BITS);
+ if(!(flags[object_index/FLAG_BITS]&bit))
+ throw invalid_argument("PoolBase::destroy");
+
+ deleter(obj);
+ flags[object_index/FLAG_BITS] &= ~bit;
+ --object_count;
+ free_list[object_count] = block_index*BLOCK_SIZE+object_index;
+}
+
+} // namespace Msp::Game