janwas
6d25329412
c.f. http://www.wildfiregames.com/forum/index.php?showtopic=14541&st=0&p=217250&#entry217250 and 2011-03-19 meeting This was SVN commit r9090.
342 lines
9.4 KiB
C++
342 lines
9.4 KiB
C++
/* Copyright (c) 2010 Wildfire Games
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#include "lib/file/io/io.h"
|
|
|
|
#include "lib/posix/posix_aio.h"
|
|
#include "lib/allocators/allocators.h" // AllocatorChecker
|
|
#include "lib/file/file.h"
|
|
#include "lib/file/common/file_stats.h"
|
|
#include "lib/file/io/block_cache.h"
|
|
#include "lib/file/io/io_align.h"
|
|
|
|
static const size_t ioDepth = 16;
|
|
|
|
|
|
// the underlying aio implementation likes buffer and offset to be
|
|
// sector-aligned; if not, the transfer goes through an align buffer,
|
|
// and requires an extra memcpy.
|
|
//
|
|
// if the user specifies an unaligned buffer, there's not much we can
|
|
// do - we can't assume the buffer contains padding. therefore,
|
|
// callers should let us allocate the buffer if possible.
|
|
//
|
|
// if ofs misalign = buffer, only the first and last blocks will need
|
|
// to be copied by aio, since we read up to the next block boundary.
|
|
// otherwise, everything will have to be copied; at least we split
|
|
// the read into blocks, so aio's buffer won't have to cover the
|
|
// whole file.
|
|
|
|
// we don't do any caching or alignment here - this is just a thin
|
|
// AIO wrapper. rationale:
|
|
// - aligning the transfer isn't possible here since we have no control
|
|
// over the buffer, i.e. we cannot read more data than requested.
|
|
// instead, this is done in io_manager.
|
|
// - transfer sizes here are arbitrary (i.e. not block-aligned);
|
|
// that means the cache would have to handle this or also split them up
|
|
// into blocks, which would duplicate the abovementioned work.
|
|
// - if caching here, we'd also have to handle "forwarding" (i.e.
|
|
// desired block has been issued but isn't yet complete). again, it
|
|
// is easier to let the synchronous io_manager handle this.
|
|
// - finally, io_manager knows more about whether the block should be cached
|
|
// (e.g. whether another block request will follow), but we don't
|
|
// currently make use of this.
|
|
//
|
|
// disadvantages:
|
|
// - streamed data will always be read from disk. that's not a problem,
|
|
// because such data (e.g. music, long speech) is unlikely to be used
|
|
// again soon.
|
|
// - prefetching (issuing the next few blocks from archive/file during
|
|
// idle time to satisfy potential future IOs) requires extra buffers;
|
|
// this is a bit more complicated than just using the cache as storage.
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// allocator
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifndef NDEBUG
|
|
static AllocatorChecker allocatorChecker;
|
|
#endif
|
|
|
|
class IoDeleter
|
|
{
|
|
public:
|
|
IoDeleter(size_t paddedSize)
|
|
: m_paddedSize(paddedSize)
|
|
{
|
|
}
|
|
|
|
void operator()(u8* mem)
|
|
{
|
|
debug_assert(m_paddedSize != 0);
|
|
#ifndef NDEBUG
|
|
allocatorChecker.OnDeallocate(mem, m_paddedSize);
|
|
#endif
|
|
page_aligned_free(mem, m_paddedSize);
|
|
m_paddedSize = 0;
|
|
}
|
|
|
|
private:
|
|
size_t m_paddedSize;
|
|
};
|
|
|
|
|
|
shared_ptr<u8> io_Allocate(size_t size, off_t ofs)
|
|
{
|
|
debug_assert(size != 0);
|
|
|
|
const size_t paddedSize = (size_t)PaddedSize(size, ofs);
|
|
u8* mem = (u8*)page_aligned_alloc(paddedSize);
|
|
if(!mem)
|
|
throw std::bad_alloc();
|
|
|
|
#ifndef NDEBUG
|
|
allocatorChecker.OnAllocate(mem, paddedSize);
|
|
#endif
|
|
|
|
return shared_ptr<u8>(mem, IoDeleter(paddedSize));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// BlockIo
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class BlockIo
|
|
{
|
|
public:
|
|
LibError Issue(const PFile& file, off_t alignedOfs, u8* alignedBuf)
|
|
{
|
|
m_blockId = BlockId(file->Pathname(), alignedOfs);
|
|
if(file->Mode() == 'r')
|
|
{
|
|
if(s_blockCache.Retrieve(m_blockId, m_cachedBlock))
|
|
{
|
|
stats_block_cache(CR_HIT);
|
|
|
|
// copy from cache into user buffer
|
|
if(alignedBuf)
|
|
{
|
|
memcpy(alignedBuf, m_cachedBlock.get(), BLOCK_SIZE);
|
|
m_alignedBuf = alignedBuf;
|
|
}
|
|
// return cached block
|
|
else
|
|
{
|
|
m_alignedBuf = const_cast<u8*>(m_cachedBlock.get());
|
|
}
|
|
|
|
return INFO::OK;
|
|
}
|
|
else
|
|
{
|
|
stats_block_cache(CR_MISS);
|
|
// fall through to the actual issue..
|
|
}
|
|
}
|
|
|
|
stats_io_check_seek(m_blockId);
|
|
|
|
// transfer directly to/from user buffer
|
|
if(alignedBuf)
|
|
{
|
|
m_alignedBuf = alignedBuf;
|
|
}
|
|
// transfer into newly allocated temporary block
|
|
else
|
|
{
|
|
m_tempBlock = io_Allocate(BLOCK_SIZE);
|
|
m_alignedBuf = const_cast<u8*>(m_tempBlock.get());
|
|
}
|
|
|
|
return file->Issue(m_req, file->Mode(), alignedOfs, m_alignedBuf, BLOCK_SIZE);
|
|
}
|
|
|
|
LibError WaitUntilComplete(const u8*& block, size_t& blockSize)
|
|
{
|
|
if(m_cachedBlock)
|
|
{
|
|
block = m_alignedBuf;
|
|
blockSize = BLOCK_SIZE;
|
|
return INFO::OK;
|
|
}
|
|
|
|
RETURN_ERR(FileImpl::WaitUntilComplete(m_req, const_cast<u8*&>(block), blockSize));
|
|
|
|
if(m_tempBlock)
|
|
s_blockCache.Add(m_blockId, m_tempBlock);
|
|
|
|
return INFO::OK;
|
|
}
|
|
|
|
private:
|
|
static BlockCache s_blockCache;
|
|
|
|
BlockId m_blockId;
|
|
|
|
// the address that WaitUntilComplete will return
|
|
// (cached or temporary block, or user buffer)
|
|
u8* m_alignedBuf;
|
|
|
|
shared_ptr<u8> m_cachedBlock;
|
|
shared_ptr<u8> m_tempBlock;
|
|
|
|
aiocb m_req;
|
|
};
|
|
|
|
BlockCache BlockIo::s_blockCache;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// IoSplitter
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class IoSplitter
|
|
{
|
|
NONCOPYABLE(IoSplitter);
|
|
public:
|
|
IoSplitter(off_t ofs, u8* alignedBuf, off_t size)
|
|
: m_ofs(ofs), m_alignedBuf(alignedBuf), m_size(size)
|
|
, m_totalIssued(0), m_totalTransferred(0)
|
|
{
|
|
m_alignedOfs = AlignedOffset(ofs);
|
|
m_alignedSize = PaddedSize(size, ofs);
|
|
m_misalignment = size_t(ofs - m_alignedOfs);
|
|
}
|
|
|
|
LibError Run(const PFile& file, IoCallback cb = 0, uintptr_t cbData = 0)
|
|
{
|
|
ScopedIoMonitor monitor;
|
|
|
|
// (issue even if cache hit because blocks must be processed in order)
|
|
std::deque<BlockIo> pendingIos;
|
|
for(;;)
|
|
{
|
|
while(pendingIos.size() < ioDepth && m_totalIssued < m_alignedSize)
|
|
{
|
|
pendingIos.push_back(BlockIo());
|
|
const off_t alignedOfs = m_alignedOfs + m_totalIssued;
|
|
u8* const alignedBuf = m_alignedBuf? m_alignedBuf+m_totalIssued : 0;
|
|
RETURN_ERR(pendingIos.back().Issue(file, alignedOfs, alignedBuf));
|
|
m_totalIssued += BLOCK_SIZE;
|
|
}
|
|
|
|
if(pendingIos.empty())
|
|
break;
|
|
|
|
Process(pendingIos.front(), cb, cbData);
|
|
pendingIos.pop_front();
|
|
}
|
|
|
|
debug_assert(m_totalIssued >= m_totalTransferred && m_totalTransferred >= m_size);
|
|
|
|
monitor.NotifyOfSuccess(FI_AIO, file->Mode(), m_totalTransferred);
|
|
return INFO::OK;
|
|
}
|
|
|
|
off_t AlignedOfs() const
|
|
{
|
|
return m_alignedOfs;
|
|
}
|
|
|
|
private:
|
|
LibError Process(BlockIo& blockIo, IoCallback cb, uintptr_t cbData) const
|
|
{
|
|
const u8* block; size_t blockSize;
|
|
RETURN_ERR(blockIo.WaitUntilComplete(block, blockSize));
|
|
|
|
// first block: skip past alignment
|
|
if(m_totalTransferred == 0)
|
|
{
|
|
block += m_misalignment;
|
|
blockSize -= m_misalignment;
|
|
}
|
|
|
|
// last block: don't include trailing padding
|
|
if(m_totalTransferred + (off_t)blockSize > m_size)
|
|
blockSize = size_t(m_size - m_totalTransferred);
|
|
|
|
m_totalTransferred += (off_t)blockSize;
|
|
|
|
if(cb)
|
|
{
|
|
stats_cb_start();
|
|
LibError ret = cb(cbData, block, blockSize);
|
|
stats_cb_finish();
|
|
CHECK_ERR(ret);
|
|
}
|
|
|
|
return INFO::OK;
|
|
}
|
|
|
|
off_t m_ofs;
|
|
u8* m_alignedBuf;
|
|
off_t m_size;
|
|
|
|
size_t m_misalignment;
|
|
off_t m_alignedOfs;
|
|
off_t m_alignedSize;
|
|
|
|
// (useful, raw data: possibly compressed, but doesn't count padding)
|
|
mutable off_t m_totalIssued;
|
|
mutable off_t m_totalTransferred;
|
|
};
|
|
|
|
|
|
LibError io_Scan(const PFile& file, off_t ofs, off_t size, IoCallback cb, uintptr_t cbData)
|
|
{
|
|
u8* alignedBuf = 0; // use temporary block buffers
|
|
IoSplitter splitter(ofs, alignedBuf, size);
|
|
return splitter.Run(file, cb, cbData);
|
|
}
|
|
|
|
|
|
LibError io_Read(const PFile& file, off_t ofs, u8* alignedBuf, off_t size, u8*& data, IoCallback cb, uintptr_t cbData)
|
|
{
|
|
IoSplitter splitter(ofs, alignedBuf, size);
|
|
RETURN_ERR(splitter.Run(file, cb, cbData));
|
|
data = alignedBuf + ofs - splitter.AlignedOfs();
|
|
return INFO::OK;
|
|
}
|
|
|
|
|
|
LibError io_WriteAligned(const PFile& file, off_t alignedOfs, const u8* alignedData, off_t size, IoCallback cb, uintptr_t cbData)
|
|
{
|
|
debug_assert(IsAligned_Offset(alignedOfs));
|
|
debug_assert(IsAligned_Data(alignedData));
|
|
|
|
IoSplitter splitter(alignedOfs, const_cast<u8*>(alignedData), size);
|
|
return splitter.Run(file, cb, cbData);
|
|
}
|
|
|
|
|
|
LibError io_ReadAligned(const PFile& file, off_t alignedOfs, u8* alignedBuf, off_t size, IoCallback cb, uintptr_t cbData)
|
|
{
|
|
debug_assert(IsAligned_Offset(alignedOfs));
|
|
debug_assert(IsAligned_Data(alignedBuf));
|
|
|
|
IoSplitter splitter(alignedOfs, alignedBuf, size);
|
|
return splitter.Run(file, cb, cbData);
|
|
}
|