/* Copyright (c) 2013 yvt based on code of pysnip (c) Mathias Kaerlev 2011-2012. This file is part of OpenSpades. OpenSpades is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenSpades is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenSpades. If not, see . */ #pragma once #include "Exception.h" #include "Debug.h" namespace spades { // FIXME: namespace pollution... static const size_t NoFreeRegion = static_cast(-1); class MiniHeap { public: typedef size_t Ref; template struct Handle { MiniHeap *heap; Ref ref; inline Handle(MiniHeap *heap, Ref ref): heap(heap), ref(ref){} inline T *operator ->() const { return heap->Dereference(ref); } inline operator T *() const { return heap->Dereference(ref); } inline operator Ref() const { return ref; } inline void Delete() { // FIXME: call destructor? heap->Free(ref, sizeof(T)); } }; private: std::vector buffer; std::vector freeRegionPool; Ref firstFreeRegion; Ref lastFreeRegion; struct FreeRegion { Ref start, len; Ref prev, next; Ref GetEnd() const { return start + len; } }; Handle AllocFreeRegion() { if(freeRegionPool.empty()) return Alloc(); else { auto r = freeRegionPool.back(); freeRegionPool.pop_back(); return Handle(this, r); } } void ReleaseFreeRegion(Ref r) { freeRegionPool.push_back(r); auto *fr = Dereference(r); fr->start = 0xdeadbeef; fr->len = 0xdeadbeef; fr->prev = 0xdeadbeef; fr->next = 0xdeadbeef; } Ref FindFreeRegion(size_t bytes) { Ref fl = firstFreeRegion; while(fl != NoFreeRegion) { auto *f = Dereference(fl); if(f->len >= bytes) return fl; SPAssert(f->next != fl); fl = f->next; } return NoFreeRegion; } void DeleteFreeListNode(Ref freg) { auto *f = Dereference(freg); if(f->prev == NoFreeRegion) { if(f->next == NoFreeRegion) { firstFreeRegion = NoFreeRegion; lastFreeRegion = NoFreeRegion; }else{ firstFreeRegion = f->next; Dereference(f->next)->prev = NoFreeRegion; } }else{ if(f->next == NoFreeRegion) { lastFreeRegion = f->prev; Dereference(f->prev)->next = NoFreeRegion; }else{ Dereference(f->next)->prev = f->prev; Dereference(f->prev)->next = f->next; } } ReleaseFreeRegion(freg); SPAssert(Validate()); } public: MiniHeap(size_t initialSize) { if(initialSize < sizeof(FreeRegion)) initialSize = sizeof(FreeRegion); buffer.resize(initialSize); // initialize first free region if(initialSize > sizeof(FreeRegion)){ firstFreeRegion = 0; lastFreeRegion = 0; auto *r = Dereference(firstFreeRegion); r->start = sizeof(FreeRegion); r->len = buffer.size() - r->start; r->prev = NoFreeRegion; r->next = NoFreeRegion; }else{ firstFreeRegion = NoFreeRegion; lastFreeRegion = NoFreeRegion; } SPAssert(Validate()); } bool Validate(); void Reserve(size_t bytes) { size_t newSize = buffer.size(); while(newSize < bytes) newSize <<= 1; if(newSize == buffer.size()) return; size_t oldSize = buffer.size(); buffer.resize(newSize); if(lastFreeRegion == NoFreeRegion) { firstFreeRegion = oldSize; lastFreeRegion = oldSize; auto *r = Dereference(firstFreeRegion); r->start = oldSize + sizeof(FreeRegion); r->len = buffer.size() - r->start; r->prev = NoFreeRegion; r->next = NoFreeRegion; }else{ Ref oldLastFree = lastFreeRegion; auto *r = Dereference(lastFreeRegion); if(r->GetEnd() == oldSize) { r->len = buffer.size() - r->start; }else{ lastFreeRegion = buffer.size() - sizeof(FreeRegion); auto *nr = Dereference(lastFreeRegion); nr->start = oldSize; nr->len = lastFreeRegion - nr->start; nr->prev = oldLastFree; nr->next = NoFreeRegion; r->next = lastFreeRegion; } } SPAssert(Validate()); } Ref Alloc(size_t bytes) { auto freg = FindFreeRegion(bytes); if(freg == NoFreeRegion) { Reserve(buffer.size() + bytes); freg = FindFreeRegion(bytes); } SPAssert(freg != NoFreeRegion); auto *f = Dereference(freg); SPAssert(f->len >= bytes); auto pos = f->start; if(f->len == bytes) { // free region is removed DeleteFreeListNode(freg); }else{ f->start += bytes; f->len -= bytes; } SPAssert(pos + bytes <= buffer.size()); SPAssert(Validate()); return pos; } template Handle Alloc() { Ref r = Alloc(sizeof(T)); // FIXME: call constructor? return Handle(this, r); } void Free(Ref offset, Ref len) { Ref fl = firstFreeRegion; Ref last = fl; Validate(); auto extraFreeRegion = NoFreeRegion; TryAgain: fl = firstFreeRegion; while(fl != NoFreeRegion) { last = fl; auto *f = Dereference(fl); if(f->GetEnd() == offset) { f->len += len; // recombine? if(f->next != NoFreeRegion ) { auto *fn = Dereference(f->next); if(fn->start == f->GetEnd()) { // recombine f->len += fn->len; DeleteFreeListNode(f->next); } } SPAssert(Validate()); goto EoP; }else if(f->GetEnd() > offset && f->start < offset + len) { SPRaise("Internal inconsistency detected: double free"); }else if(f->start == offset + len){ f->len += len; f->start -= len; // recombine? if(f->prev != NoFreeRegion ) { auto *fn = Dereference(f->prev); if(fn->GetEnd() == f->start) { // recombine f->len += fn->len; f->start -= fn->len; DeleteFreeListNode(f->prev); } } SPAssert(Validate()); goto EoP; }else if(f->start > offset + len){ // insert here. Ref prev = f->prev; auto reg = extraFreeRegion; if(reg == NoFreeRegion) { extraFreeRegion = AllocFreeRegion(); goto TryAgain; } extraFreeRegion = NoFreeRegion; f = Dereference(fl); f->prev = reg; FreeRegion *r = Dereference(reg); r->next = fl; r->prev = prev; r->start = offset; r->len = len; if(prev == NoFreeRegion) { firstFreeRegion = reg; }else{ Dereference(prev)->next = reg; } SPAssert(Validate()); goto EoP; } SPAssert(f->start >= offset + len || f->GetEnd() <= offset); fl = f->next; } // push back. if(last == NoFreeRegion){ auto reg = AllocFreeRegion(); FreeRegion *r = reg; r->next = NoFreeRegion; r->prev = NoFreeRegion; r->start = offset; r->len = len; firstFreeRegion = reg; lastFreeRegion = reg; }else{ auto reg = AllocFreeRegion(); FreeRegion *r = reg; r->next = NoFreeRegion; r->prev = last; r->start = offset; r->len = len; lastFreeRegion = reg; Dereference(last)->next = reg; } EoP: if(extraFreeRegion != NoFreeRegion){ ReleaseFreeRegion(extraFreeRegion); } SPAssert(Validate()); } template T *Dereference(Ref ref) { return reinterpret_cast(buffer.data() + ref); } }; }