src/Network/Buffer.cc
author terom
Thu, 04 Dec 2008 21:59:23 +0000
changeset 200 2dbf40661580
child 329 42ad4965424f
permissions -rw-r--r--
better NetworkBuffer/Packet stuff + some additional Physics+Network stuff + random fixes

#include "Buffer.hh"
#include "Config.hh"

#include <ClanLib/core.h>
#include <cstdlib>

/*
 * NetworkBufferBase
 */
NetworkBufferBase::NetworkBufferBase (NetworkSocket &socket, size_t size_hint) :
    socket(socket), buf(0), size(0), offset(0) {
    
    // allocate initial buffer
    if ((buf = (char *) malloc(size_hint)) == NULL)
       throw NetworkBufferError("malloc failed");
    
    // remember size
    size = size_hint;
}
        
NetworkBufferBase::~NetworkBufferBase (void) {
    free(buf);
}

void NetworkBufferBase::resize (size_t item_size) {
    size_t new_size = size;

    // grow new_size until item_size fits
    while (offset + item_size > new_size)
        new_size *= 2;
    
    // grow if needed
    if (new_size != size) {
        // realloc buffer
        if ((buf = (char *) realloc((void *) buf, new_size)) == NULL)
            throw NetworkBufferError("realloc failed");

        // update size
        size = new_size;

    } else if (new_size > (offset + item_size) * 4) {
        // XXX: shrink?
    }
}
        
void NetworkBufferBase::trim (size_t prefix_size) {
    // update offset
    offset -= prefix_size;

    // shift the buffer forwards from (buf + prefix) -> (buf), copying (old_offset - prefix) bytes
    memmove(buf, buf + prefix_size, offset);
}

/*
 * NetworkBufferInput
 */
NetworkBufferInput::NetworkBufferInput (NetworkSocket &socket, size_t size_hint) :
    NetworkBufferBase(socket, size_hint)
{

}

bool NetworkBufferInput::try_read (size_t item_size) {
    int ret;
    size_t to_read = item_size;

    // keept reads at at least NETWORK_BUFFER_CHUNK_SIZE bytes
    if (to_read < NETWORK_BUFFER_CHUNK_SIZE)
        to_read = NETWORK_BUFFER_CHUNK_SIZE;

    // resize buffer if needed
    resize(to_read);

    // read once
    try {
        ret = socket.recv(buf + offset, to_read);

    } catch (CL_Error &e) {
        if (errno == EAGAIN)
            return false;

        else
            throw NetworkSocketOSError(socket, "recv");
    }
    
    // handle EOF
    if (ret == 0)
        throw NetworkSocketEOFError(socket, "recv");

    assert(ret >= 0);

    // update offset
    offset += ret;

    // did we get enough?
    if ((unsigned int) ret < item_size)
        return false;
    else
        return true;
} 
        
bool NetworkBufferInput::have_data (size_t data_size) {
    return (offset >= data_size);
}

bool NetworkBufferInput::peek_prefix (uint16_t &val_ref) {
    if (!have_data(sizeof(uint16_t)))
        return false;

    val_ref = ntohs(*((uint16_t *) (buf)));

    return true;
}
    
bool NetworkBufferInput::peek_prefix (uint32_t &val_ref) {
    if (!have_data(sizeof(uint32_t)))
        return false;

    val_ref = ntohl(*((uint32_t *) (buf)));

    return true;
}
   
/*
 * NetworkBufferOutput
 */
NetworkBufferOutput::NetworkBufferOutput (NetworkSocket &socket, size_t size_hint) :
    NetworkBufferBase(socket, size_hint)
{

}


void NetworkBufferOutput::push_write (char *buf_ptr, size_t buf_size) {
    int ret;

    // try and short-circuit writes unless we have already buffered data
    if (offset == 0) {
        try {
            // attempt to send something
            ret = socket.send(buf_ptr, buf_size);

        } catch (CL_Error &e) {
            // ignore EAGAIN, detect this by setting ret to -1
            if (errno != EAGAIN)
                throw NetworkSocketOSError(socket, "send");

            ret = -1;
        }
        
        // if we managed to send something, adjust buf/size and buffer
        if (ret > 0) {
            // sanity-check
            assert(buf_size >= (unsigned int) ret);

            buf_ptr += ret;
            buf_size -= ret;

            // if that was all, we're done
            if (buf_size == 0)
                return;
        }
    }
    
    // resize to fit buf_size more bytes
    resize(buf_size);
    
    // copy into our internal buffer
    memcpy(buf + offset, buf_ptr, buf_size);
}
        
void NetworkBufferOutput::flush_write (void) {
    int ret;

    // ignore if we don't have any data buffered
    if (offset == 0)
        return;
    
    // attempt to write as much as possible
    try {
        ret = socket.send(buf, offset);

    } catch (CL_Error &e) {
        // ignore EAGAIN and just return
        if (errno == EAGAIN)
            return;

        else
            throw NetworkSocketOSError(socket, "send");
    }

    // trim the buffer
    trim(ret);
}
        
void NetworkBufferOutput::write_prefix (char *buf, uint16_t prefix) {
    uint16_t nval = htons(prefix);

    push_write((char*) &nval, sizeof(uint16_t)); 
    push_write(buf, prefix);
}

void NetworkBufferOutput::write_prefix (char *buf, uint32_t prefix) {
    uint32_t nval = htonl(prefix);

    push_write((char*) &nval, sizeof(uint32_t)); 
    push_write(buf, prefix);
}