An example (completely untested), of what I would do for input transmission towards the host, and back.
// single set of inputs.
struct Inputs
{
float mousex;
float mousey;
float mousez;
int mousebuttons;
int keyboard;
};
typedef unsigned short Sqn;
Sqn sqnAdd(Sqn s1, Sqn s2)
{
return Sqn(s1 + s2);
}
Sqn sqnDiff(Sqn s1, Sqn s2)
{
return Sqn(s1 - s2);
}
bool sqnGreater(Sqn s1, Sqn s2)
{
return (s1 > s2) && (s1 - s2 <= 0x8000) ||
(s2 > s1) && (s2 - s1 > 0x8000);
}
bool sqnGreaterEqual(Sqn s1, unsigned char s2)
{
return (s1 == s2) || sqnGreater(s1, s2);
}
class InputStream
{
public:
InputStream()
: m_head(0)
, m_tail(0)
{}
// add inputs at the top.
bool pushHead(const Inputs& inputs)
{
Sqn len = sqnDiff(m_head, m_tail);
// to many inputs not acknowledged.
// Either pause the game, or carry on, but there will be a
// server correction later on when the server catches up.
if(len >= MAX_INPUTS)
return false;
m_inputs[m_head % MAX_INPUTS] = inputs;
m_head = sqnAdd(m_head, 1);
return true;
}
// read inputs from the tail.
bool popTail(Inputs& inputs)
{
NEint len = sizeof(Inputs);
// no more to read.
if(len <= 0)
return false;
// get first input in the stream.
inputs = m_inputs[m_tail & MAX_INPUTS];
// push tail up.
m_tail = sqnAdd(m_tail, 1);
return true;
}
private:
// gives a 2 second window at 60 hz, before the server is stared of inputs.
enum { MAX_INPUTS = 128 };
Inputs m_inputs[MAX_INPUTS];
Sqn m_head;
Sqn m_tail;
};
class ClientInputStream: public InputStream
{
public:
ClientInputStream()
: InputStream()
, m_send(0)
{}
// acknowledge inputs from the tail.
bool acknowledgeInputs(Sqn sqn)
{
// check sqn validity.
if(sqnGreater(m_tail, sqn) || sqnGreater(sqn, m_head))
return false;
// re-align send sqn, if acknowledged past it.
if(sqnGreater(sqn, m_send))
m_send = sqn;
// tail moved forward.
m_tail = sqn;
return true;
}
// add a set of inputs into a packet.
int packInputs(const Inputs* inputs, char* packet, int packetLimit)
{
// send full state
NEint len = sizeof(Inputs);
// too much data.
if(len > packetLimit)
return 0;
// crappy memcpy.
memcpy(packet, inputs, len);
return len;
}
// add a set of inputs into a packet. Delta-compress if possible.
int deltaCompressInputs(const Inputs* inputs, const Inputs* prevInputs, char* packet, int packetLimit)
{
// we have a reference state.
// do some delta compression between the two input blocks.
if(prevInputs)
{
// meh... just send inputs uncompressed atm. work out some compression algo later.
return packInputs(inputs, packet, packetLimit);
}
// no reference state. pack full-state.
else
{
// send full state.
return packInputs(inputs, packet, packetLimit);
}
}
// build an input packet.
int buildPacket(char* packet, int packetLimit) const
{
// sanity check. Make sure we have room for packet header.
// we pack in the first sqn, and the last sqn added to packet.
if(packetLimit <= (sizeof(sqn) * 2))
return 0;
// iterators.
int packetLen = 0;
Sqn sqn = m_send; // Start from the send sqn.
// pack sequence number of first inputs.
memcpy(packet + packetLen, &sqn, sizeof(sqn));
packetLen += sizeof(sqn);
// pack sequence number of last inputs (placeholder value, until we know where we end up).
int placeholderPtr = packetLen;
memcpy(packet + packetLen, &sqn, sizeof(sqn));
packetLen += sizeof(sqn);
// write as many inputs as we can into the buffer.
const Inputs* prevInputs = NULL;
while((packetLen < packetLimit) && sqnGreater(m_head, sqn))
{
const Inputs* inputs = &(m_inputs[sqn % MAX_INPUTS]);
// delta compress a single set of inputs, against the previous set of inputs (if available).
int inputSize = deltaCompressInputs(inputs, prevInputs, (packet + packetLen), (packetLimit - packetLen));
// fail to pack inputs, not enough room in packet.
if(inputSize == 0)
{
// that was our first input, and we failed.
if(sqn == m_tail)
{
// failed to pack any at all. Just bail. No need to send anything else.
return 0;
}
else
{
// stop adding inputs and finish the packet.
break;
}
}
// push packet pointer.
packetLen += inputSize;
// push the sequence number to the next input slot.
sqn = sqnAdd(sqn, 1);
// now is prev inputs, for delta compression.
prevInputs = inputs;
}
// override the placeholder value for the last sqn added to packet.
memcpy(packet + placeholderPtr, &sqn, sizeof(sqn));
// update the sqn of the next send sqn.
m_send = sqn;
// we've reached the head. zip back to the tail, and start re-sending inputs.
if(m_send == m_head)
m_send = m_tail;
// size of the data used by the inputs packet.
return packetLen;
}
private:
// sqn of the next inputs to send.
Sqn m_send;
};
class ServerInputStream: public InputStream
{
public:
ServerInputStream()
{}
// read a set of inputs from a packet.
int unpackInputs(Inputs* inputs, const char* packet, int packetLimit)
{
// send full state
NEint len = sizeof(Inputs);
// too much data.
if(len > packetLimit)
return 0;
// crappy memcpy.
memcpy(inputs, packet, len);
return len;
}
// read a set of inputs from a packet. Delta-decompress if possible.
int deltaDecompressInputs(Inputs* inputs, const Inputs* prevInputs, const char* packet, int packetLimit)
{
// we have a reference state.
// do some delta compression between the two input blocks.
if(prevInputs)
{
// meh... just send inputs uncompressed atm. work out some compression algo later.
return unpackInputs(inputs, packet, packetLimit);
}
// no reference state. pack full-state.
else
{
// send full state.
return unpackInputs(inputs, packet, packetLimit);
}
}
// get the Sqn to send back to the client for acknowledgement.
Sqn getAcknowledgmentSqn() const
{
return m_head;
}
// [SERVER] parse an input packet.
int parsePacket(const char* packet, int packetLimit) const
{
// sanity check. Make sure we have room for packet header.
// we pack in the first sqn, and the last sqn added to packet.
if(packetLimit <= (sizeof(sqn) * 2))
return 0;
// iterators.
int packetLen = 0;
Sqn sqn; // input packet iterator.
Sqn last; // end of iterator.
// unpack sequence number of first inputs.
memcpy(&sqn, packet + packetLen, sizeof(sqn));
packetLen += sizeof(sqn);
// unpack sequence number of last inputs.
memcpy(&last, packet + packetLen, sizeof(last));
packetLen += sizeof(last);
// read as many inputs as we can from the buffer.
const Inputs* prevInputs = NULL;
while((packetLen < packetLimit) && sqnGreater(last, sqn))
{
// decompressed input from stream.
Inputs inputs;
// delta compress a single set of inputs, against the previous set of inputs (if available).
int inputSize = deltaDecompressInputs(inputs, prevInputs, (packet + packetLen), (packetLimit - packetLen));
// fail to unpack inputs, explode!
if(inputSize == 0)
break;
// all right, we've got the next input to add at the end of the stream.
if(sqn == m_head)
{
// add inputs to the head of the stream.
// If we failed (because the stream is full), no matter.
pushHead(inputs);
}
// push packet pointer.
packetLen += inputSize;
// push the sequence number to the next input slot.
sqn = sqnAdd(sqn, 1);
// now is prev inputs, for delta decompression.
prevInputs = inputs;
}
// when reading inputs we should be reading up to the last sqn, no more, no less.
SANITY_CHECK(sqn == last);
// size of the input packet.
return packetLen;
}
};