Do some cleanup in the Buffer class

This commit is contained in:
Jean Pierre Cimalando 2020-04-19 18:05:43 +02:00
parent 364a2afb3e
commit 43a7df2de3
3 changed files with 119 additions and 84 deletions

View file

@ -64,12 +64,12 @@ public:
* @return true if the resize worked * @return true if the resize worked
* @return false otherwise * @return false otherwise
*/ */
bool resize(size_t newSize) bool resize(size_t newSize, std::nothrow_t) noexcept
{ {
bool returnedOK = true; bool returnedOK = true;
for (size_t i = 0; i < numChannels; ++i) for (size_t i = 0; i < numChannels; ++i)
returnedOK &= buffers[i]->resize(newSize); returnedOK &= buffers[i]->resize(newSize, std::nothrow);
if (returnedOK) if (returnedOK)
numFrames = newSize; numFrames = newSize;
@ -77,6 +77,19 @@ public:
return returnedOK; return returnedOK;
} }
/**
* @brief Resizes all the underlying buffers to a new size.
*
* @param newSize
*/
void resize(size_t newSize)
{
for (size_t i = 0; i < numChannels; ++i)
buffers[i]->resize(newSize);
numFrames = newSize;
}
/** /**
* @brief Return an iterator to a specific channel with a non-const type. * @brief Return an iterator to a specific channel with a non-const type.
* *

View file

@ -45,8 +45,8 @@ namespace sfz
class BufferCounter class BufferCounter
{ {
public: public:
BufferCounter() = default; BufferCounter() noexcept = default;
~BufferCounter() ~BufferCounter() noexcept
{ {
#ifndef NDEBUG #ifndef NDEBUG
std::cout << "Remaining buffers on destruction: " << numBuffers << '\n'; std::cout << "Remaining buffers on destruction: " << numBuffers << '\n';
@ -54,41 +54,41 @@ public:
#endif #endif
} }
void newBuffer(int size) void newBuffer(int size) noexcept
{ {
numBuffers++; numBuffers++;
bytes.fetch_add(size); bytes.fetch_add(size);
} }
void bufferResized(int oldSize, int newSize) void bufferResized(int oldSize, int newSize) noexcept
{ {
bytes.fetch_add(newSize); bytes.fetch_add(newSize);
bytes.fetch_sub(oldSize); bytes.fetch_sub(oldSize);
} }
void bufferDeleted(int size) void bufferDeleted(int size) noexcept
{ {
numBuffers--; numBuffers--;
bytes.fetch_sub(size); bytes.fetch_sub(size);
} }
void bufferDeleted(size_t size) void bufferDeleted(size_t size) noexcept
{ {
bufferDeleted(static_cast<int>(size)); bufferDeleted(static_cast<int>(size));
} }
void bufferResized(size_t oldSize, size_t newSize) void bufferResized(size_t oldSize, size_t newSize) noexcept
{ {
bufferResized(static_cast<int>(oldSize), static_cast<int>(newSize)); bufferResized(static_cast<int>(oldSize), static_cast<int>(newSize));
} }
void newBuffer(size_t size) void newBuffer(size_t size) noexcept
{ {
newBuffer(static_cast<int>(size)); newBuffer(static_cast<int>(size));
} }
int getNumBuffers() { return numBuffers; } int getNumBuffers() const noexcept { return numBuffers; }
int getTotalBytes() { return bytes; } int getTotalBytes() const noexcept { return bytes; }
private: private:
std::atomic<int> numBuffers { 0 }; std::atomic<int> numBuffers { 0 };
std::atomic<int> bytes { 0 }; std::atomic<int> bytes { 0 };
@ -128,9 +128,8 @@ public:
* @brief Construct a new Buffer object that is empty * @brief Construct a new Buffer object that is empty
* *
*/ */
Buffer() Buffer() noexcept
{ {
counter().newBuffer(0);
} }
/** /**
@ -138,9 +137,8 @@ public:
* *
* @param size * @param size
*/ */
Buffer(size_t size) explicit Buffer(size_t size)
{ {
counter().newBuffer(0);
resize(size); resize(size);
} }
@ -152,7 +150,7 @@ public:
* @return true if allocation succeeded * @return true if allocation succeeded
* @return false otherwise * @return false otherwise
*/ */
bool resize(size_t newSize) bool resize(size_t newSize, std::nothrow_t) noexcept
{ {
if (newSize == 0) { if (newSize == 0) {
clear(); clear();
@ -160,19 +158,24 @@ public:
} }
auto tempSize = newSize + 2 * AlignmentMask; // To ensure that we have leeway at the beginning and at the end auto tempSize = newSize + 2 * AlignmentMask; // To ensure that we have leeway at the beginning and at the end
auto* newData = paddedData != nullptr ? std::realloc(paddedData, tempSize * sizeof(value_type)) : std::malloc(tempSize * sizeof(value_type)); auto* newData = std::realloc(paddedData.get(), tempSize * sizeof(value_type));
if (newData == nullptr) { if (newData == nullptr) {
return false; return false;
} }
counter().bufferResized(largerSize * sizeof(value_type), tempSize * sizeof(value_type)); if (largerSize > 0)
_counter.bufferResized(largerSize * sizeof(value_type), tempSize * sizeof(value_type));
else
_counter.newBuffer(tempSize * sizeof(value_type));
largerSize = tempSize; largerSize = tempSize;
alignedSize = newSize; alignedSize = newSize;
paddedData = static_cast<pointer>(newData); paddedData.release(); // realloc has invalidated the old pointer
paddedData.reset(static_cast<pointer>(newData));
normalData = static_cast<pointer>(align(Alignment, alignedSize, newData, tempSize)); normalData = static_cast<pointer>(align(Alignment, alignedSize, newData, tempSize));
normalEnd = normalData + alignedSize; normalEnd = normalData + alignedSize;
auto endMisalignment = (alignedSize & TypeAlignmentMask); auto endMisalignment = (alignedSize & TypeAlignmentMask);
if (endMisalignment != 0) if (endMisalignment != 0)
_alignedEnd = normalEnd + Alignment - endMisalignment; _alignedEnd = normalEnd + Alignment - endMisalignment;
else else
_alignedEnd = normalEnd; _alignedEnd = normalEnd;
@ -180,24 +183,32 @@ public:
return true; return true;
} }
/**
* @brief Resizes the buffer. Given that std::realloc may return either the same pointer
* or a new one, you need to account for both cases in the code if you are using deep pointers.
*
* @param newSize the new buffer size in bytes
*/
void resize(size_t newSize)
{
if (!resize(newSize, std::nothrow))
throw std::bad_alloc();
}
/** /**
* @brief Clear the buffers and frees the underlying memory * @brief Clear the buffers and frees the underlying memory
* *
*/ */
void clear() void clear() noexcept
{ {
counter().bufferResized(largerSize * sizeof(value_type), static_cast<size_t>(0)); if (largerSize > 0)
largerSize = 0; _counter.bufferDeleted(largerSize * sizeof(value_type));
alignedSize = 0; _clear();
std::free(paddedData);
normalData = nullptr;
normalEnd = nullptr;
_alignedEnd = nullptr;
} }
~Buffer() ~Buffer() noexcept
{ {
counter().bufferDeleted(largerSize * sizeof(value_type)); if (largerSize > 0)
std::free(paddedData); _counter.bufferDeleted(largerSize * sizeof(value_type));
} }
/** /**
@ -207,10 +218,8 @@ public:
*/ */
Buffer(const Buffer<Type>& other) Buffer(const Buffer<Type>& other)
{ {
counter().newBuffer(0); resize(other.size());
if (resize(other.size())) { std::memcpy(this->data(), other.data(), other.size() * sizeof(value_type));
std::memcpy(this->data(), other.data(), other.size() * sizeof(value_type));
}
} }
/** /**
@ -218,45 +227,41 @@ public:
* *
* @param other * @param other
*/ */
Buffer(Buffer<Type>&& other) = delete; Buffer(Buffer<Type>&& other) noexcept
// { : largerSize(other.largerSize),
// if (this != &other) { alignedSize(other.alignedSize),
// counter().bufferDeleted(largerSize * sizeof(value_type)); normalData(other.normalData),
// std::free(paddedData); paddedData(std::move(other.paddedData)),
// largerSize = std::exchange(other.largerSize, 0); normalEnd(other.normalEnd),
// alignedSize = std::exchange(other.alignedSize, 0); _alignedEnd(other._alignedEnd)
// paddedData = std::exchange(other.paddedData, nullptr); {
// normalData = std::exchange(other.normalData, nullptr); other._clear();
// normalEnd = std::exchange(other.normalEnd, nullptr); }
// _alignedEnd = std::exchange(other._alignedEnd, nullptr);
// }
// }
Buffer<Type>& operator=(const Buffer<Type>& other) Buffer<Type>& operator=(const Buffer<Type>& other)
{ {
if (this != &other) { if (this != &other) {
if (resize(other.size())) resize(other.size());
std::memcpy(this->data(), other.data(), other.size() * sizeof(value_type)); std::memcpy(this->data(), other.data(), other.size() * sizeof(value_type));
} }
return *this; return *this;
} }
Buffer<Type>& operator=(Buffer<Type>&& other) = delete; Buffer<Type>& operator=(Buffer<Type>&& other) noexcept
// { {
// if (this != &other) { if (this != &other) {
// counter().bufferDeleted(largerSize * sizeof(value_type)); largerSize = other.largerSize;
// std::free(paddedData); alignedSize = other.alignedSize;
// largerSize = std::exchange(other.largerSize, 0); normalData = other.normalData;
// alignedSize = std::exchange(other.alignedSize, 0); paddedData = std::move(other.paddedData);
// paddedData = std::exchange(other.paddedData, nullptr); normalEnd = other.normalEnd;
// normalData = std::exchange(other.normalData, nullptr); _alignedEnd = other._alignedEnd;
// normalEnd = std::exchange(other.normalEnd, nullptr); other._clear();
// _alignedEnd = std::exchange(other._alignedEnd, nullptr); }
// } return *this;
// return *this; }
// }
Type& operator[](size_t idx) { return *(normalData + idx); } Type& operator[](size_t idx) noexcept { return *(normalData + idx); }
constexpr pointer data() const noexcept { return normalData; } constexpr pointer data() const noexcept { return normalData; }
constexpr size_type size() const noexcept { return alignedSize; } constexpr size_type size() const noexcept { return alignedSize; }
constexpr bool empty() const noexcept { return alignedSize == 0; } constexpr bool empty() const noexcept { return alignedSize == 0; }
@ -264,19 +269,27 @@ public:
constexpr iterator end() const noexcept { return normalEnd; } constexpr iterator end() const noexcept { return normalEnd; }
constexpr pointer alignedEnd() const noexcept { return _alignedEnd; } constexpr pointer alignedEnd() const noexcept { return _alignedEnd; }
/** /**
* @brief Return the buffer counter object. * @brief Return the buffer counter object.
* *
* TODO: In C++ 17 all of this can be static inline.
*
* @return The buffer counter on which you can call various methods. * @return The buffer counter on which you can call various methods.
*/ */
static BufferCounter& counter() static BufferCounter& counter() noexcept
{ {
static BufferCounter counter; return _counter;
return counter;
} }
private:
void _clear()
{
largerSize = 0;
alignedSize = 0;
paddedData.reset();
normalData = nullptr;
normalEnd = nullptr;
_alignedEnd = nullptr;
}
private: private:
static constexpr int AlignmentMask { Alignment - 1 }; static constexpr int AlignmentMask { Alignment - 1 };
static constexpr int TypeAlignment { Alignment / sizeof(value_type) }; static constexpr int TypeAlignment { Alignment / sizeof(value_type) };
@ -295,13 +308,22 @@ private:
return ptr = reinterpret_cast< void * >( aligned ); return ptr = reinterpret_cast< void * >( aligned );
} }
struct deleter {
void operator()(void *p) const noexcept { std::free(p); }
};
size_type largerSize { 0 }; size_type largerSize { 0 };
size_type alignedSize { 0 }; size_type alignedSize { 0 };
pointer normalData { nullptr }; pointer normalData { nullptr };
pointer paddedData { nullptr }; std::unique_ptr<value_type[], deleter> paddedData;
pointer normalEnd { nullptr }; pointer normalEnd { nullptr };
pointer _alignedEnd { nullptr }; pointer _alignedEnd { nullptr };
static BufferCounter _counter;
LEAK_DETECTOR(Buffer); LEAK_DETECTOR(Buffer);
}; };
template <class Type, unsigned int Alignment>
BufferCounter Buffer<Type, Alignment>::_counter;
} }

View file

@ -70,12 +70,12 @@ TEST_CASE("[Buffer] Resize 10 floats ")
std::fill(buffer.begin(), buffer.end(), 1.0f); std::fill(buffer.begin(), buffer.end(), 1.0f);
REQUIRE(buffer.resize(smallSize)); REQUIRE(buffer.resize(smallSize, std::nothrow));
checkBoundaries(buffer, smallSize); checkBoundaries(buffer, smallSize);
REQUIRE(std::all_of(buffer.begin(), buffer.end(), [](float value) { return value == 1.0f; })); REQUIRE(std::all_of(buffer.begin(), buffer.end(), [](float value) { return value == 1.0f; }));
REQUIRE(buffer.resize(bigSize)); REQUIRE(buffer.resize(bigSize, std::nothrow));
checkBoundaries(buffer, bigSize); checkBoundaries(buffer, bigSize);
for (auto i = 0; i < smallSize; ++i) for (auto i = 0; i < smallSize; ++i)
REQUIRE(buffer[i] == 1.0f); REQUIRE(buffer[i] == 1.0f);
@ -92,12 +92,12 @@ TEST_CASE("[Buffer] Resize 4096 floats ")
std::fill(buffer.begin(), buffer.end(), 1.0f); std::fill(buffer.begin(), buffer.end(), 1.0f);
REQUIRE(buffer.resize(smallSize)); REQUIRE(buffer.resize(smallSize, std::nothrow));
checkBoundaries(buffer, smallSize); checkBoundaries(buffer, smallSize);
REQUIRE(std::all_of(buffer.begin(), buffer.end(), [](float value) { return value == 1.0f; })); REQUIRE(std::all_of(buffer.begin(), buffer.end(), [](float value) { return value == 1.0f; }));
REQUIRE(buffer.resize(bigSize)); REQUIRE(buffer.resize(bigSize, std::nothrow));
checkBoundaries(buffer, bigSize); checkBoundaries(buffer, bigSize);
for (auto i = 0; i < smallSize; ++i) for (auto i = 0; i < smallSize; ++i)
REQUIRE(buffer[i] == 1.0f); REQUIRE(buffer[i] == 1.0f);
@ -114,12 +114,12 @@ TEST_CASE("[Buffer] Resize 65536 floats ")
std::fill(buffer.begin(), buffer.end(), 1.0f); std::fill(buffer.begin(), buffer.end(), 1.0f);
REQUIRE(buffer.resize(smallSize)); REQUIRE(buffer.resize(smallSize, std::nothrow));
checkBoundaries(buffer, smallSize); checkBoundaries(buffer, smallSize);
REQUIRE(std::all_of(buffer.begin(), buffer.end(), [](float value) { return value == 1.0f; })); REQUIRE(std::all_of(buffer.begin(), buffer.end(), [](float value) { return value == 1.0f; }));
REQUIRE(buffer.resize(bigSize)); REQUIRE(buffer.resize(bigSize, std::nothrow));
checkBoundaries(buffer, bigSize); checkBoundaries(buffer, bigSize);
for (auto i = 0; i < smallSize; ++i) for (auto i = 0; i < smallSize; ++i)
REQUIRE(buffer[i] == 1.0f); REQUIRE(buffer[i] == 1.0f);
@ -140,8 +140,8 @@ TEST_CASE("[Buffer] Copy and move")
checkBoundaries(copyConstructed, baseSize); checkBoundaries(copyConstructed, baseSize);
REQUIRE(std::all_of(copyConstructed.begin(), copyConstructed.end(), [](float value) { return value == 1.0f; })); REQUIRE(std::all_of(copyConstructed.begin(), copyConstructed.end(), [](float value) { return value == 1.0f; }));
// sfz::Buffer<float> moveConstructed { std::move(buffer) }; sfz::Buffer<float> moveConstructed { std::move(buffer) };
// REQUIRE(buffer.empty()); REQUIRE(buffer.empty());
// checkBoundaries(moveConstructed, baseSize); checkBoundaries(moveConstructed, baseSize);
// REQUIRE(std::all_of(moveConstructed.begin(), moveConstructed.end(), [](auto value) { return value == 1.0f; })); REQUIRE(std::all_of(moveConstructed.begin(), moveConstructed.end(), [](float value) { return value == 1.0f; }));
} }