//
// Copyright 2022 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// RingBufferAllocator.cpp:
//    Implements classes used for ring buffer allocators.
//

#include "common/RingBufferAllocator.h"

namespace angle
{

// RingBufferAllocator implementation.
RingBufferAllocator::RingBufferAllocator(RingBufferAllocator &&other)
{
    *this = std::move(other);
}

RingBufferAllocator &RingBufferAllocator::operator=(RingBufferAllocator &&other)
{
    mOldBuffers      = std::move(other.mOldBuffers);
    mBuffer          = std::move(other.mBuffer);
    mDataBegin       = other.mDataBegin;
    mDataEnd         = other.mDataEnd;
    mFragmentEnd     = other.mFragmentEnd;
    mFragmentEndR    = other.mFragmentEndR;
    mFragmentReserve = other.mFragmentReserve;

    mMinCapacity      = other.mMinCapacity;
    mCurrentCapacity  = other.mCurrentCapacity;
    mAllocationMargin = other.mAllocationMargin;
    mDecaySpeedFactor = other.mDecaySpeedFactor;

    ASSERT(other.mOldBuffers.size() == 0);
    ASSERT(other.mBuffer.isEmpty());
    other.mBuffer.resetId();
    other.mDataBegin       = nullptr;
    other.mDataEnd         = nullptr;
    other.mFragmentEnd     = nullptr;
    other.mFragmentEndR    = nullptr;
    other.mFragmentReserve = 0;

    other.mMinCapacity      = 0;
    other.mCurrentCapacity  = 0;
    other.mAllocationMargin = 0;
    other.mDecaySpeedFactor = 0;

    return *this;
}

void RingBufferAllocator::reset()
{
    mListener         = nullptr;
    mFragmentReserve  = 0;
    mDecaySpeedFactor = kDefaultDecaySpeedFactor;
    mMinCapacity      = kMinRingBufferAllocationCapacity;
    resize(mMinCapacity);
    mOldBuffers.clear();
}

void RingBufferAllocator::setListener(RingBufferAllocateListener *listener)
{
    ASSERT(!mListener || !listener);
    mListener = listener;
}

void RingBufferAllocator::setDecaySpeedFactor(uint32_t decaySpeedFactor)
{
    ASSERT(valid());
    mDecaySpeedFactor = std::max(decaySpeedFactor, 1u);
}

RingBufferAllocatorCheckPoint RingBufferAllocator::getReleaseCheckPoint() const
{
    ASSERT(valid());
    RingBufferAllocatorCheckPoint result;
    result.mBufferId   = mBuffer.getId();
    result.mReleasePtr = mDataEnd;
    return result;
}

void RingBufferAllocator::release(const RingBufferAllocatorCheckPoint &checkPoint)
{
    ASSERT(valid());
    ASSERT(checkPoint.valid());

    if (mOldBuffers.size() > 0)
    {
        // mOldBuffers are sorted by id
        int removeCount = 0;
        for (uint32_t i = 0;
             (i < mOldBuffers.size()) && (mOldBuffers[i].getId() < checkPoint.mBufferId); ++i)
        {
            ++removeCount;
        }
        mOldBuffers.erase(mOldBuffers.begin(), mOldBuffers.begin() + removeCount);
    }

    if (checkPoint.mBufferId == mBuffer.getId())
    {
        const uint32_t allocatedBefore = getNumAllocatedInBuffer();

        release(checkPoint.mReleasePtr);

        if (allocatedBefore >= mAllocationMargin)
        {
            if ((mCurrentCapacity > mMinCapacity) && (allocatedBefore * 6 <= mCurrentCapacity))
            {
                resize(std::max(allocatedBefore * 3, mMinCapacity));
            }
            else
            {
                mAllocationMargin = mCurrentCapacity;
            }
        }
        else
        {
            const uint64_t numReleased      = (allocatedBefore - getNumAllocatedInBuffer());
            const uint64_t distanceToMargin = (mAllocationMargin - allocatedBefore);
            mAllocationMargin -=
                std::max(static_cast<uint32_t>(numReleased * distanceToMargin / mAllocationMargin /
                                               mDecaySpeedFactor),
                         1u);
        }
    }
}

void RingBufferAllocator::setFragmentReserve(uint32_t reserve)
{
    ASSERT(valid());
    mFragmentReserve = reserve;
    mFragmentEndR    = mBuffer.decClamped(mFragmentEnd, mFragmentReserve);
}

uint8_t *RingBufferAllocator::allocateInNewFragment(uint32_t size)
{
    if (mListener)
    {
        mListener->onRingBufferFragmentEnd();
    }

    uint8_t *newFragmentBegin = nullptr;
    if (mFragmentEnd != mDataBegin)
    {
        newFragmentBegin               = mBuffer.data();
        uint8_t *const newFragmentEnd  = mDataBegin;
        uint8_t *const newFragmentEndR = mBuffer.decClamped(newFragmentEnd, mFragmentReserve);

        // It should wrap around only if it can allocate.
        if (newFragmentEndR - newFragmentBegin >= static_cast<ptrdiff_t>(size))
        {
            mDataEnd      = newFragmentBegin;
            mFragmentEnd  = newFragmentEnd;
            mFragmentEndR = newFragmentEndR;

            if (mListener)
            {
                mListener->onRingBufferNewFragment();
            }

            mDataEnd = newFragmentBegin + size;
            return newFragmentBegin;
        }
    }

    resize(std::max(mCurrentCapacity + mCurrentCapacity / 2, size + mFragmentReserve));

    if (mListener)
    {
        mListener->onRingBufferNewFragment();
    }

    ASSERT(mFragmentEndR - mDataEnd >= static_cast<ptrdiff_t>(size));
    newFragmentBegin = mDataEnd;
    mDataEnd         = newFragmentBegin + size;
    return newFragmentBegin;
}

void RingBufferAllocator::resize(uint32_t newCapacity)
{
    ASSERT(newCapacity >= mMinCapacity);

    if (mBuffer.getId() != 0)
    {
        mOldBuffers.emplace_back(std::move(mBuffer));
    }

    mCurrentCapacity = newCapacity;
    mBuffer.incrementId();
    mBuffer.resize(mCurrentCapacity);
    resetPointers();

    mAllocationMargin = mCurrentCapacity;
}

void RingBufferAllocator::release(uint8_t *releasePtr)
{
    if (releasePtr == mDataEnd)
    {
        // Ensures "mDataEnd == mBuffer.data()" with 0 allocations.
        resetPointers();
        return;
    }

    if (mDataBegin == mFragmentEnd)
    {
        ASSERT((releasePtr >= mBuffer.data() && releasePtr < mDataEnd) ||
               (releasePtr >= mDataBegin && releasePtr <= mBuffer.data() + mCurrentCapacity));
        if (releasePtr < mDataBegin)
        {
            mFragmentEnd = mBuffer.data() + mCurrentCapacity;
        }
        else
        {
            mFragmentEnd = releasePtr;
        }
        mFragmentEndR = mBuffer.decClamped(mFragmentEnd, mFragmentReserve);
    }
    else
    {
        ASSERT(releasePtr >= mDataBegin && releasePtr < mDataEnd);
    }
    mDataBegin = releasePtr;
}

uint32_t RingBufferAllocator::getNumAllocatedInBuffer() const
{
    // 2 free fragments: [mBuffer.begin, mDataBegin)                    [mDataEnd, mBuffer.end);
    // 1 used fragment:                             [DataBegin, DataEnd)
    if (mFragmentEnd != mDataBegin)
    {
        ASSERT(mDataEnd >= mDataBegin);
        return static_cast<uint32_t>(mDataEnd - mDataBegin);
    }

    // 1 free fragment:                           [mDataEnd, mDataBegin)
    // 2 used fragments: [mBuffer.begin, mDataEnd)                      [mDataBegin, mBuffer.end)
    ASSERT(mDataBegin >= mDataEnd);
    return (mCurrentCapacity - static_cast<uint32_t>(mDataBegin - mDataEnd));
}

void RingBufferAllocator::resetPointers()
{
    mDataBegin    = mBuffer.data();
    mDataEnd      = mDataBegin;
    mFragmentEnd  = mDataEnd + mCurrentCapacity;
    mFragmentEndR = mBuffer.decClamped(mFragmentEnd, mFragmentReserve);
}

// SharedRingBufferAllocator implementation.
SharedRingBufferAllocator::SharedRingBufferAllocator()
{
    mAllocator.reset();
}

SharedRingBufferAllocator::~SharedRingBufferAllocator()
{
#if defined(ANGLE_ENABLE_ASSERTS)
    ASSERT(!mSharedCP || mSharedCP->mRefCount == 1);
#endif
    SafeDelete(mSharedCP);
}

SharedRingBufferAllocatorCheckPoint *SharedRingBufferAllocator::acquireSharedCP()
{
    if (!mSharedCP)
    {
        mSharedCP = new SharedRingBufferAllocatorCheckPoint();
    }
#if defined(ANGLE_ENABLE_ASSERTS)
    ASSERT(++mSharedCP->mRefCount > 1);
    // Must always be 1 ref before
#endif
    return mSharedCP;
}

void SharedRingBufferAllocator::releaseToSharedCP()
{
    ASSERT(mSharedCP);
    const RingBufferAllocatorCheckPoint releaseCP = mSharedCP->pop();
    if (releaseCP.valid())
    {
        mAllocator.release(releaseCP);
    }
}

void SharedRingBufferAllocatorCheckPoint::releaseAndUpdate(RingBufferAllocatorCheckPoint *newValue)
{
    ASSERT(newValue && newValue->valid());
#if defined(ANGLE_ENABLE_ASSERTS)
    ASSERT(--mRefCount >= 1);
    // Must always remain 1 ref
#endif
    {
        std::lock_guard<std::mutex> lock(mMutex);
        mValue = *newValue;
    }
    newValue->reset();
}

RingBufferAllocatorCheckPoint SharedRingBufferAllocatorCheckPoint::pop()
{
    std::lock_guard<std::mutex> lock(mMutex);
    RingBufferAllocatorCheckPoint value = mValue;
    mValue.reset();
    return value;
}

}  // namespace angle
