Code owners
Assign users and groups as approvers for specific file changes. Learn more.
bufferarray.cpp 8.12 KiB
/**
* @file bufferarray.cpp
* @brief Implements the BufferArray scatter/gather buffer
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2012, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "bufferarray.h"
#include "llexception.h"
#include "llmemory.h"
// BufferArray is a list of chunks, each a BufferArray::Block, of contiguous
// data presented as a single array. Chunks are at least BufferArray::BLOCK_ALLOC_SIZE
// in length and can be larger. Any chunk may be partially filled or even
// empty.
//
// The BufferArray itself is sharable as a RefCounted entity. As shared
// reads don't work with the concept of a current position/seek value,
// none is kept with the object. Instead, the read and write operations
// all take position arguments. Single write/shared read isn't supported
// directly and any such attempts have to be serialized outside of this
// implementation.
namespace LLCore
{
// ==================================
// BufferArray::Block Declaration
// ==================================
class BufferArray::Block
{
public:
~Block();
void operator delete(void *);
void operator delete(void *, size_t len);
protected:
Block(size_t len);
Block(const Block &); // Not defined
void operator=(const Block &); // Not defined
// Allocate the block with the additional space for the
// buffered data at the end of the object.
void * operator new(size_t len, size_t addl_len);
public:
// Only public entry to get a block.
static Block * alloc(size_t len);
public:
size_t mUsed;
size_t mAlloced;
// *NOTE: Must be last member of the object. We'll
// overallocate as requested via operator new and index
// into the array at will.
char mData[1];
};
// ==================================
// BufferArray Definitions
// ==================================
#if ! LL_WINDOWS
const size_t BufferArray::BLOCK_ALLOC_SIZE;
#endif // ! LL_WINDOWS
BufferArray::BufferArray()
: LLCoreInt::RefCounted(true),
mLen(0)
{}
BufferArray::~BufferArray()
{
for (container_t::iterator it(mBlocks.begin());
it != mBlocks.end();
++it)
{
delete *it;
*it = NULL;
}
mBlocks.clear();
}
size_t BufferArray::append(const void * src, size_t len)
{
const size_t ret(len);
const char * c_src(static_cast<const char *>(src));
// First, try to copy into the last block
if (len && ! mBlocks.empty())
{
Block & last(*mBlocks.back());
if (last.mUsed < last.mAlloced)
{
// Some will fit...
const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed)));
memcpy(&last.mData[last.mUsed], c_src, copy_len);
last.mUsed += copy_len;
llassert_always(last.mUsed <= last.mAlloced);
mLen += copy_len;
c_src += copy_len;
len -= copy_len;
}
}
// Then get new blocks as needed
while (len)
{
const size_t copy_len((std::min)(len, BLOCK_ALLOC_SIZE));
if (mBlocks.size() >= mBlocks.capacity())
{
mBlocks.reserve(mBlocks.size() + 5);
}
Block * block;
try
{
block = Block::alloc(BLOCK_ALLOC_SIZE);
}
catch (const std::bad_alloc&)
{
LLMemory::logMemoryInfo(TRUE);
//output possible call stacks to log file.
LLError::LLCallStacks::print();
LL_WARNS() << "Bad memory allocation in thrown by Block::alloc in read!" << LL_ENDL;
break;
}
memcpy(block->mData, c_src, copy_len);
block->mUsed = copy_len;
llassert_always(block->mUsed <= block->mAlloced);
mBlocks.push_back(block);
mLen += copy_len;
c_src += copy_len;
len -= copy_len;
}
return ret - len;
}
void * BufferArray::appendBufferAlloc(size_t len)
{
// If someone asks for zero-length, we give them a valid pointer.
if (mBlocks.size() >= mBlocks.capacity())
{
mBlocks.reserve(mBlocks.size() + 5);
}
Block * block = Block::alloc((std::max)(BLOCK_ALLOC_SIZE, len));
block->mUsed = len;
mBlocks.push_back(block);
mLen += len;
return block->mData;
}
size_t BufferArray::read(size_t pos, void * dst, size_t len)
{
char * c_dst(static_cast<char *>(dst));
if (pos >= mLen)
return 0;
size_t len_limit(mLen - pos);
len = (std::min)(len, len_limit);
if (0 == len)
return 0;
size_t result(0), offset(0);
const int block_limit(mBlocks.size());
int block_start(findBlock(pos, &offset));
if (block_start < 0)
return 0;
do
{
Block & block(*mBlocks[block_start]);
size_t block_limit(block.mUsed - offset);
size_t block_len((std::min)(block_limit, len));
memcpy(c_dst, &block.mData[offset], block_len);
result += block_len;
len -= block_len;
c_dst += block_len;
offset = 0;
++block_start;
}
while (len && block_start < block_limit);
return result;
}
size_t BufferArray::write(size_t pos, const void * src, size_t len)
{
const char * c_src(static_cast<const char *>(src));
if (pos > mLen || 0 == len)
return 0;
size_t result(0), offset(0);
const int block_limit(mBlocks.size());
int block_start(findBlock(pos, &offset));
if (block_start >= 0)
{
// Some or all of the write will be on top of
// existing data.
do
{
Block & block(*mBlocks[block_start]);
size_t block_limit(block.mUsed - offset);
size_t block_len((std::min)(block_limit, len));
memcpy(&block.mData[offset], c_src, block_len);
result += block_len;
c_src += block_len;
len -= block_len;
offset = 0;
++block_start;
}
while (len && block_start < block_limit);
}
// Something left, see if it will fit in the free
// space of the last block.
if (len && ! mBlocks.empty())
{
Block & last(*mBlocks.back());
if (last.mUsed < last.mAlloced)
{
// Some will fit...
const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed)));
memcpy(&last.mData[last.mUsed], c_src, copy_len);
last.mUsed += copy_len;
result += copy_len;
llassert_always(last.mUsed <= last.mAlloced);
mLen += copy_len;
c_src += copy_len;
len -= copy_len;
}
}
if (len)
{
// Some or all of the remaining write data will
// be an append.
result += append(c_src, len);
}
return result;
}
int BufferArray::findBlock(size_t pos, size_t * ret_offset)
{
*ret_offset = 0;
if (pos >= mLen)
return -1; // Doesn't exist
const int block_limit(mBlocks.size());
for (int i(0); i < block_limit; ++i)
{
if (pos < mBlocks[i]->mUsed)
{
*ret_offset = pos;
return i;
}
pos -= mBlocks[i]->mUsed;
}
// Shouldn't get here but...
return -1;
}
bool BufferArray::getBlockStartEnd(int block, const char ** start, const char ** end)
{
if (block < 0 || block >= mBlocks.size())
{
return false;
}
const Block & b(*mBlocks[block]);
*start = &b.mData[0];
*end = &b.mData[b.mUsed];
return true;
}
// ==================================
// BufferArray::Block Definitions
// ==================================
BufferArray::Block::Block(size_t len)
: mUsed(0),
mAlloced(len)
{
memset(mData, 0, len);
}
BufferArray::Block::~Block()
{
mUsed = 0;
mAlloced = 0;
}
void * BufferArray::Block::operator new(size_t len, size_t addl_len)
{
void * mem = new char[len + addl_len + sizeof(void *)];
return mem;
}
void BufferArray::Block::operator delete(void * mem)
{
char * cmem = static_cast<char *>(mem);
delete [] cmem;
}
void BufferArray::Block::operator delete(void * mem, size_t)
{
operator delete(mem);
}
BufferArray::Block * BufferArray::Block::alloc(size_t len)
{
Block * block = new (len) Block(len);
return block;
}
} // end namespace LLCore