Serious-Engine/Sources/Engine/Network/Buffer.cpp
2016-03-11 18:20:51 -06:00

656 lines
16 KiB
C++

/* Copyright (c) 2002-2012 Croteam Ltd.
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation
This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
#include "stdh.h"
#include <Engine/Math/Functions.h>
#include <Engine/Base/Memory.h>
#include <Engine/Base/Console.h>
#include <Engine/Base/Stream.h>
#include <Engine/Network/Buffer.h>
// default constructor
CBuffer::CBuffer(void)
{
bu_slAllocationStep = 1024;
bu_slWriteOffset = 0;
bu_slReadOffset = 0;
bu_slFree = 0;
bu_slSize = 0;
bu_pubBuffer = NULL;
}
// destructor
CBuffer::~CBuffer(void)
{
Clear();
}
// free buffer
void CBuffer::Clear(void)
{
bu_slWriteOffset = 0;
bu_slReadOffset = 0;
if (bu_slSize>0) {
ASSERT(bu_pubBuffer!=NULL);
FreeMemory(bu_pubBuffer);
}
bu_slFree = 0;
bu_slSize = 0;
bu_pubBuffer = NULL;
}
// expand buffer to be given number of bytes in size
void CBuffer::Expand(SLONG slNewSize)
{
ASSERT(slNewSize>0);
ASSERT(bu_slSize>=0);
// if not already allocated
if (bu_slSize==0) {
// allocate a new empty buffer
ASSERT(bu_pubBuffer==NULL);
bu_pubBuffer = (UBYTE*)AllocMemory(slNewSize);
bu_slWriteOffset = 0;
bu_slReadOffset = 0;
bu_slFree = slNewSize;
bu_slSize = slNewSize;
// if already allocated
} else {
ASSERT(slNewSize>bu_slSize);
SLONG slSizeDiff = slNewSize-bu_slSize;
ASSERT(bu_pubBuffer!=NULL);
// grow buffer
GrowMemory((void**)&bu_pubBuffer, slNewSize);
// if buffer is currently wrapping
if (bu_slReadOffset>bu_slWriteOffset||bu_slFree==0) {
// move part at the end of buffer to the end
memmove(bu_pubBuffer+bu_slReadOffset+slSizeDiff, bu_pubBuffer+bu_slReadOffset,
bu_slSize-bu_slReadOffset);
bu_slReadOffset+=slSizeDiff;
}
bu_slFree += slNewSize-bu_slSize;
bu_slSize = slNewSize;
ASSERT(bu_slReadOffset>=0 && bu_slReadOffset<bu_slSize);
ASSERT(bu_slFree>=0 && bu_slFree<=bu_slSize);
}
}
// set how many bytes to add when buffer overflows
void CBuffer::SetAllocationStep(SLONG slStep)
{
ASSERT(slStep>0);
bu_slAllocationStep = slStep;
}
// read bytes from buffer
SLONG CBuffer::ReadBytes(void *pv, SLONG slSize)
{
ASSERT(slSize>0 && pv!=NULL);
UBYTE *pub = (UBYTE*)pv;
// clamp size to amount of bytes actually in the buffer
SLONG slUsed = bu_slSize-bu_slFree;
if (slUsed<slSize) {
slSize = slUsed;
}
// if there is nothing to read
if (slSize==0) {
// do nothing
return 0;
}
// read part of block after read pointer to the end of buffer
SLONG slSizeEnd = __min(bu_slSize-bu_slReadOffset, slSize);
memcpy(pub, bu_pubBuffer+bu_slReadOffset, slSizeEnd);
pub+=slSizeEnd;
// if that is not all
if (slSizeEnd<slSize) {
// read rest from start of buffer
memcpy(pub, bu_pubBuffer, slSize-slSizeEnd);
}
// move read pointer
bu_slReadOffset+=slSize;
bu_slReadOffset%=bu_slSize;
bu_slFree+=slSize;
ASSERT(bu_slReadOffset>=0 && bu_slReadOffset<bu_slSize);
ASSERT(bu_slFree>=0 && bu_slFree<=bu_slSize);
return slSize;
}
// skip bytes from buffer (read without actually reading)
SLONG CBuffer::SkipBytes(SLONG slSize)
{
ASSERT(slSize>0);
// clamp size to amount of bytes actually in the buffer
SLONG slUsed = bu_slSize-bu_slFree;
if (slUsed<slSize) {
slSize = slUsed;
}
// if there is nothing to skip
if (slSize==0) {
// do nothing
return 0;
}
// move read pointer
bu_slReadOffset+=slSize;
bu_slReadOffset%=bu_slSize;
bu_slFree+=slSize;
ASSERT(bu_slReadOffset>=0 && bu_slReadOffset<bu_slSize);
ASSERT(bu_slFree>=0 && bu_slFree<=bu_slSize);
return slSize;
}
// read bytes from buffer to stream
SLONG CBuffer::ReadBytesToStream(CTStream &strm, SLONG slSize)
{
ASSERT(slSize>0);
// clamp size to amount of bytes actually in the buffer
SLONG slUsed = bu_slSize-bu_slFree;
if (slUsed<slSize) {
slSize = slUsed;
}
// if there is nothing to read
if (slSize==0) {
// do nothing
return 0;
}
// read part of block after read pointer to the end of buffer
SLONG slSizeEnd = __min(bu_slSize-bu_slReadOffset, slSize);
strm.Write_t(bu_pubBuffer+bu_slReadOffset, slSizeEnd);
// if that is not all
if (slSizeEnd<slSize) {
// read rest from start of buffer
strm.Write_t(bu_pubBuffer, slSize-slSizeEnd);
}
// move read pointer
bu_slReadOffset+=slSize;
bu_slReadOffset%=bu_slSize;
bu_slFree+=slSize;
ASSERT(bu_slReadOffset>=0 && bu_slReadOffset<bu_slSize);
ASSERT(bu_slFree>=0 && bu_slFree<=bu_slSize);
return slSize;
}
// unread bytes from buffer
void CBuffer::UnreadBytes(SLONG slSize)
{
ASSERT(bu_slFree>=slSize);
if (slSize==0) return;
bu_slReadOffset-=slSize;
bu_slReadOffset%=bu_slSize;
if (bu_slReadOffset<0) {
bu_slReadOffset+=bu_slSize;
}
bu_slFree-=slSize;
ASSERT(bu_slReadOffset>=0 && bu_slReadOffset<bu_slSize);
ASSERT(bu_slFree>=0 && bu_slFree<=bu_slSize);
}
// check how many bytes are there to read
SLONG CBuffer::QueryReadBytes(void)
{
// return amount of bytes actually in the buffer
return bu_slSize-bu_slFree;
}
// write bytes to buffer
void CBuffer::WriteBytes(const void *pv, SLONG slSize)
{
ASSERT(slSize>=0 && pv!=NULL);
// if there is nothing to write
if (slSize==0) {
// do nothing
return;
}
// check for errors
if (slSize<0) {
CPrintF("WARNING: WriteBytes(): slSize<0\n!");
return;
}
// if there is not enough free space
if (bu_slFree<slSize) {
// expand the buffer
Expand(bu_slSize+
((slSize-bu_slFree+bu_slAllocationStep-1)/bu_slAllocationStep)*bu_slAllocationStep );
ASSERT(bu_slFree>=slSize);
}
UBYTE *pub = (UBYTE*)pv;
// write part of block at the end of buffer
SLONG slSizeEnd = __min(bu_slSize-bu_slWriteOffset, slSize);
memcpy(bu_pubBuffer+bu_slWriteOffset, pub, slSizeEnd);
pub+=slSizeEnd;
memcpy(bu_pubBuffer, pub, slSize-slSizeEnd);
// move write pointer
bu_slWriteOffset+=slSize;
bu_slWriteOffset%=bu_slSize;
bu_slFree-=slSize;
ASSERT(bu_slWriteOffset>=0 && bu_slWriteOffset<bu_slSize);
ASSERT(bu_slFree>=0 && bu_slFree<=bu_slSize);
}
// move all data from another buffer to this one
void CBuffer::MoveBuffer(CBuffer &buFrom)
{
// repeat
for(;;){
// read a block from the other buffer
UBYTE aub[256];
SLONG slSize = buFrom.ReadBytes(aub, sizeof(aub));
// if nothing read
if (slSize<=0) {
// stop
return;
}
// write here what was read
WriteBytes(&aub, slSize);
}
}
void CBlockBufferStats::Clear(void)
{
bbs_tvTimeUsed.Clear();
}
// get time when block of given size will be finished if started now
CTimerValue CBlockBufferStats::GetBlockFinalTime(SLONG slSize)
{
CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();
// calculate how much should block be delayed due to latency and due to bandwidth
CTimerValue tvBandwidth;
if (bbs_fBandwidthLimit<=0.0f) {
tvBandwidth = CTimerValue(0.0);
} else {
tvBandwidth = CTimerValue(DOUBLE((slSize*8)/bbs_fBandwidthLimit));
}
CTimerValue tvLatency;
if (bbs_fLatencyLimit<=0.0f && bbs_fLatencyVariation<=0.0f) {
tvLatency = CTimerValue(0.0);
} else {
tvLatency = CTimerValue(DOUBLE(bbs_fLatencyLimit+(bbs_fLatencyVariation*rand())/RAND_MAX));
}
// start of packet receiving is later of
CTimerValue tvStart(
Max(
// current time plus latency and
(tvNow+tvLatency).tv_llValue,
// next free point in time
bbs_tvTimeUsed.tv_llValue));
// remember next free time and return it
bbs_tvTimeUsed = tvStart+tvBandwidth;
return bbs_tvTimeUsed;
}
// default constructor
CBlockBuffer::CBlockBuffer(void)
{
bb_slBlockSizeRead = 0;
bb_slBlockSizeWrite = 0;
bb_pbbsStats = NULL;
}
// destructor
CBlockBuffer::~CBlockBuffer(void)
{
bb_slBlockSizeRead = 0;
bb_slBlockSizeWrite = 0;
bb_pbbsStats = NULL;
}
// free buffer
void CBlockBuffer::Clear(void)
{
bb_slBlockSizeRead = 0;
bb_slBlockSizeWrite = 0;
bb_pbbsStats = NULL;
CBuffer::Clear();
}
struct BlockHeader {
SLONG bh_slSize; // block size
CTimerValue bh_tvFinalTime; // block may be read only after this moment in time
};
// read one block if possible
BOOL CBlockBuffer::ReadBlock(void *pv, SLONG &slSize)
{
// must not be inside block reading
ASSERT(bb_slBlockSizeRead==0);
// read header of next block in incoming buffer
struct BlockHeader bh;
SLONG slbhSize;
slbhSize = ReadBytes(&bh, sizeof(bh));
// if the header information is not in buffer
if (slbhSize < sizeof(bh)) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
// if the block has not yet been received
if (QueryReadBytes() < bh.bh_slSize) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
// if there is too much data for the receiving memory space
if (bh.bh_slSize > slSize) {
// unwind
UnreadBytes(slbhSize);
// mark how much space we would need
slSize = bh.bh_slSize;
// nothing to receive
ASSERT(FALSE); // this shouldn't happen
return FALSE;
}
// if using stats
if (bb_pbbsStats!=NULL) {
// if block could not have been received yet, due to time limits
if (bh.bh_tvFinalTime>_pTimer->GetHighPrecisionTimer()) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
}
// read the block
slSize = ReadBytes(pv, bh.bh_slSize);
ASSERT(slSize == bh.bh_slSize);
// received
return TRUE;
}
// read one block from buffer to stream
BOOL CBlockBuffer::ReadBlockToStream(CTStream &strm)
{
// must not be inside block reading
ASSERT(bb_slBlockSizeRead==0);
// read header of next block in incoming buffer
struct BlockHeader bh;
SLONG slbhSize;
slbhSize = ReadBytes(&bh, sizeof(bh));
// if the header information is not in buffer
if (slbhSize < sizeof(bh)) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
// if the block has not yet been received
if (QueryReadBytes() < bh.bh_slSize) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
// if using stats
if (bb_pbbsStats!=NULL) {
// if block could not have been received yet, due to time limits
if (bh.bh_tvFinalTime>_pTimer->GetHighPrecisionTimer()) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
}
// read from buffer to destination buffer
try {
SLONG slSize = ReadBytesToStream(strm, bh.bh_slSize);
ASSERT(slSize == bh.bh_slSize);
} catch (char *strError) {
ASSERT(FALSE);
CPrintF(TRANS("Buffer error reading to stream: %s\n"), strError);
return FALSE;
}
return TRUE;
}
// write one block
void CBlockBuffer::WriteBlock(const void *pv, SLONG slSize)
{
// must not be inside block writing
ASSERT(bb_slBlockSizeWrite==0);
// prepare block header
struct BlockHeader bh;
bh.bh_slSize = slSize;
if (bb_pbbsStats!=NULL) {
bh.bh_tvFinalTime = bb_pbbsStats->GetBlockFinalTime(slSize);
} else {
bh.bh_tvFinalTime.Clear();
}
// write the data to send-buffer
WriteBytes((void*)&bh, sizeof(bh));
WriteBytes(pv, slSize);
}
// unread one block
void CBlockBuffer::UnreadBlock(SLONG slSize)
{
UnreadBytes(slSize+sizeof(struct BlockHeader));
}
// read raw block data
SLONG CBlockBuffer::ReadRawBlock(void *pv, SLONG slSize)
{
// if inside block reading
if(bb_slBlockSizeRead>0) {
// clamp size to prevent reading across real blocks
slSize = Min(slSize, bb_slBlockSizeRead);
// read the raw block
SLONG slResult = ReadBytes(pv, slSize);
ASSERT(slResult==slSize);
// decrement block size counter
bb_slBlockSizeRead-=slResult;
// must not underflow
ASSERT(bb_slBlockSizeRead>=0);
return slResult;
// if not inside block reading
} else {
// read header of next block in incoming buffer
struct BlockHeader bh;
SLONG slbhSize;
slbhSize = ReadBytes(&bh, sizeof(bh));
// if the header information is not in buffer
if (slbhSize < sizeof(bh)) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
// if the block has not yet been received
if (QueryReadBytes() < bh.bh_slSize) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
// if using stats
if (bb_pbbsStats!=NULL) {
// if block could not have been received yet, due to time limits
if (bh.bh_tvFinalTime>_pTimer->GetHighPrecisionTimer()) {
// unwind
UnreadBytes(slbhSize);
// nothing to receive
return FALSE;
}
}
// remember block size counter
bb_slBlockSizeRead = bh.bh_slSize+sizeof(struct BlockHeader);
// unwind header
UnreadBytes(slbhSize);
// clamp size to prevent reading across real blocks
slSize = Min(slSize, bb_slBlockSizeRead);
// read the raw block with header
SLONG slResult = ReadBytes(pv, slSize);
ASSERT(slResult==slSize);
// decrement block size counter
bb_slBlockSizeRead-=slResult;
// must not underflow
ASSERT(bb_slBlockSizeRead>=0);
return slResult;
}
}
// write raw block data
void CBlockBuffer::WriteRawBlock(const void *pv, SLONG slSize)
{
// while there is something to write
while (slSize>0) {
// if inside block writing
if(bb_slBlockSizeWrite>0) {
SLONG slToWrite = Min(bb_slBlockSizeWrite, slSize);
// write the raw block
WriteBytes(pv, slToWrite);
slSize-=slToWrite;
((UBYTE*&)pv)+=slToWrite;
// decrement block size counter
bb_slBlockSizeWrite-=slToWrite;
// must not underflow
ASSERT(bb_slBlockSizeWrite>=0);
// if not inside block writing
} else {
// must contain at least the header
ASSERT(slSize>sizeof(struct BlockHeader));
// find the header in the raw block
struct BlockHeader &bh = *(struct BlockHeader*)pv;
// remember block size counter
bb_slBlockSizeWrite = bh.bh_slSize+sizeof(struct BlockHeader);
// create new block timestamp
if (bb_pbbsStats!=NULL) {
bh.bh_tvFinalTime = bb_pbbsStats->GetBlockFinalTime(bb_slBlockSizeWrite);
} else {
bh.bh_tvFinalTime.Clear();
}
SLONG slToWrite = Min(bb_slBlockSizeWrite, slSize);
// write the raw block, with the new header
WriteBytes(pv, slToWrite);
slSize-=slToWrite;
((UBYTE*&)pv)+=slToWrite;
// decrement block size counter
bb_slBlockSizeWrite-=slToWrite;
// must not underflow
ASSERT(bb_slBlockSizeWrite>=0);
}
}
}
// peek sizes of next block
void CBlockBuffer::PeekBlockSize(SLONG &slExpectedSize, SLONG &slReceivedSoFar)
{
// if inside block reading
if(bb_slBlockSizeRead>0) {
// no information available
slExpectedSize = 0;
slReceivedSoFar = 0;
// if not inside block reading
} else {
// read header of next block in incoming buffer
struct BlockHeader bh;
SLONG slbhSize;
slbhSize = ReadBytes(&bh, sizeof(bh));
// unwind
UnreadBytes(slbhSize);
// if the header information is not in buffer
if (slbhSize < sizeof(bh)) {
// no information available
slExpectedSize = 0;
slReceivedSoFar = 0;
// if the header information is present
} else {
// total size is size of block
slExpectedSize = bh.bh_slSize;
// received so far is how much is really present
slReceivedSoFar = QueryReadBytes()-sizeof(struct BlockHeader);
}
}
}
// unread raw block data
void CBlockBuffer::UnreadRawBlock(SLONG slSize)
{
bb_slBlockSizeRead+=slSize;
UnreadBytes(slSize);
}
// move all data from another buffer to this one
void CBlockBuffer::MoveBlockBuffer(CBlockBuffer &buFrom)
{
// repeat
for(;;){
// read a block from the other buffer
UBYTE aub[256];
SLONG slSize = buFrom.ReadRawBlock(aub, sizeof(aub));
// if nothing read
if (slSize<=0) {
// stop
return;
}
// write here what was read
WriteRawBlock(&aub, slSize);
}
}