/* ringallocator.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2020, 2021 Askar Almatov

    This file is part of Trafficmeter.

    Trafficmeter 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.

    Trafficmeter 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 Trafficmeter. If not, see
    <http://www.gnu.org/licenses/>.
*/

#include <cassert>
#include <new>
#include <sys/mman.h>

#include "ringallocator.h"
#include "tmdefs.h"

using namespace trafficmeter;

/**************************************************************************************************************/
inline static void*
chunkAllocate_()
{
    void*  area = mmap( nullptr, CHUNK_SIZE << 20, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0 );

    assert( area != MAP_FAILED );
    assert( mlock(area, CHUNK_SIZE<<20) == 0 );
    return area;
}

/**************************************************************************************************************/
inline static void
chunkDeallocate_( void* area )
{
    assert( munlock(area, CHUNK_SIZE<<20) == 0 );
    assert( munmap(area, CHUNK_SIZE<<20) == 0 );
}

/**************************************************************************************************************/
RingAllocator::RingAllocator( int nChunks ) :
    nFreeChunks_a_( nChunks )
{
    assert( nChunks > 0 );
    firstChunk_ = lastChunk_ = cursor_ = new( chunkAllocate_() ) Chunk_( this );
    firstChunk_->next_ = firstChunk_;

    while ( --nChunks > 0 )
    {
        lastChunk_->next_ = new( chunkAllocate_() ) Chunk_( this );
        lastChunk_ = lastChunk_->next_;
    }
}

/**************************************************************************************************************/
RingAllocator::~RingAllocator()
{
    cursor_ = firstChunk_;

    do
    {
        Chunk_*  next = cursor_->next_;

        chunkDeallocate_( cursor_ );
        cursor_ = next;
    }
    while ( cursor_ != firstChunk_ );
}

/**************************************************************************************************************/
void*
RingAllocator::allocate( RingAllocator* ring, unsigned size )
{
    unsigned    requiredSize = wordAlign( size + sizeof(Chunk_*) );
    Chunk_*     areaChunk = ring->getChunk_( requiredSize );
    uint8_t*    freeSpace = reinterpret_cast<uint8_t*>( areaChunk ) + areaChunk->offset_;

    *reinterpret_cast<Chunk_**>( freeSpace ) = areaChunk;
    areaChunk->offset_ += requiredSize;

    if ( areaChunk->nObjects_a_++ == 0 )
    {
        --ring->nFreeChunks_a_;
    }

    return freeSpace + sizeof( areaChunk );
}

/**************************************************************************************************************/
void
RingAllocator::deallocate( void* area )
{
    Chunk_*  areaChunk = *( reinterpret_cast<Chunk_**>(area) - 1 );

    if ( --areaChunk->nObjects_a_ == 0 )
    {
        areaChunk->offset_ = sizeof( Chunk_ );
        ++areaChunk->ring_->nFreeChunks_a_;
    }
}

/**************************************************************************************************************/
RingAllocator::Chunk_*
RingAllocator::getChunk_( unsigned size )
{
    if ( cursor_->offset_ + size > CHUNK_SIZE << 20 )
    {
        if ( nFreeChunks_a_ == 0 )
        {
            lastChunk_->next_ = new( chunkAllocate_() ) Chunk_( this );
            cursor_ = lastChunk_ = lastChunk_->next_;
            ++nFreeChunks_a_;
        }
        else
        {
            do
            {
                cursor_ = cursor_->next_;
            }
            while ( cursor_->nObjects_a_ > 0 );
        }
    }

    return cursor_;
}

/**************************************************************************************************************/
RingAllocator::Chunk_::Chunk_( RingAllocator* ring ) :
    next_( ring->firstChunk_ ),
    ring_( ring ),
    offset_( sizeof(Chunk_) ),
    nObjects_a_( 0 )
{
}
