newnet: Group droid orders, efficiently encode small numbers.

master
Cyp 2010-07-05 13:17:36 +02:00
parent 3505c31c0e
commit f7e1a91761
5 changed files with 285 additions and 119 deletions

View File

@ -546,6 +546,73 @@ void NETuint32_t(uint32_t *ip)
queueAuto(*ip);
}
void NETuint32_tSmall(uint32_t *ip)
{
// Byte n is the final byte, iff it is less than 256-a[n].
// Some possible values of a[n], such that the maximum encodable value is 0xFFFFFFFF, satisfying the assertion below:
//const unsigned a[5] = {14, 127, 74, 127, 0}; // <242: 1 byte, <2048: 2 bytes, <325644: 3 bytes, <17298432: 4 bytes, <4294967296: 5 bytes
const unsigned a[5] = {78, 95, 32, 70, 0}; // <178: 1 byte, <12736: 2 bytes, <1672576: 3 bytes, <45776896: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {78, 95, 71, 31, 0}; // <178: 1 byte, <12736: 2 bytes, <1383586: 3 bytes, <119758336: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 19, 119, 0}; // <152: 1 byte, <19392: 2 bytes, <1769400: 3 bytes, <20989952: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 20, 113, 0}; // <152: 1 byte, <19392: 2 bytes, <1762016: 3 bytes, <22880256: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 24, 94, 0}; // <152: 1 byte, <19392: 2 bytes, <1732480: 3 bytes, <30441472: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 30, 75, 0}; // <152: 1 byte, <19392: 2 bytes, <1688176: 3 bytes, <41783296: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 38, 59, 0}; // <152: 1 byte, <19392: 2 bytes, <1629104: 3 bytes, <56905728: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 40, 56, 0}; // <152: 1 byte, <19392: 2 bytes, <1614336: 3 bytes, <60686336: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 57, 39, 0}; // <152: 1 byte, <19392: 2 bytes, <1488808: 3 bytes, <92821504: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 60, 37, 0}; // <152: 1 byte, <19392: 2 bytes, <1466656: 3 bytes, <98492416: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 76, 29, 0}; // <152: 1 byte, <19392: 2 bytes, <1348512: 3 bytes, <128737280: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 95, 23, 0}; // <152: 1 byte, <19392: 2 bytes, <1208216: 3 bytes, <164653056: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 114, 19, 0}; // <152: 1 byte, <19392: 2 bytes, <1067920: 3 bytes, <200568832: 4 bytes, <4294967296: 5 bytes
//const unsigned a[5] = {104, 71, 120, 18, 0}; // <152: 1 byte, <19392: 2 bytes, <1023616: 3 bytes, <211910656: 4 bytes, <4294967296: 5 bytes
ASSERT(0xFFFFFFFF == 255*(1 + a[0]*(1 + a[1]*(1 + a[2]*(1 + a[3]*(1 + a[4]))))), "Maximum encodable value not 0xFFFFFFFF.");
if (NETgetPacketDir() == PACKET_ENCODE)
{
uint32_t v = *ip;
for (int n = 0;; ++n)
{
if (v < 256 - a[n])
{
uint8_t b = v;
queue(writer, b);
break; // Last encoded byte.
}
else
{
v -= 256 - a[n];
uint8_t b = 255 - v%a[n];
queue(writer, b);
v /= a[n];
}
}
}
else if (NETgetPacketDir() == PACKET_DECODE)
{
uint32_t v = 0, m = 1;
for (int n = 0;; ++n)
{
uint8_t b;
queue(reader, b);
if (b < 256 - a[n])
{
v += b*m;
break; // Last encoded byte.
}
else
{
v += (256 - a[n] + 255 - b)*m;
m *= a[n];
}
}
*ip = v;
}
}
void NETfloat(float *fp)
{
queueAuto(*fp);

View File

@ -87,6 +87,7 @@ void NETint16_t(int16_t *ip);
void NETuint16_t(uint16_t *ip);
void NETint32_t(int32_t *ip);
void NETuint32_t(uint32_t *ip);
void NETuint32_tSmall(uint32_t *ip); ///< Encodes small values in less than 4 bytes, large values up to 5 bytes.
void NETfloat(float* fp);
void NETbool(BOOL *bp);
void NETnull(void);
@ -101,12 +102,12 @@ PACKETDIR NETgetPacketDir(void);
template <typename EnumT>
static void NETenum(EnumT* enumPtr)
{
int32_t val;
uint32_t val;
if (NETgetPacketDir() == PACKET_ENCODE)
val = *enumPtr;
NETint32_t(&val);
NETuint32_tSmall(&val);
if (NETgetPacketDir() == PACKET_DECODE)
*enumPtr = static_cast<EnumT>(val);
@ -120,11 +121,11 @@ static inline void squelchUninitialisedUseWarning(void *ptr) { (void)ptr; }
#define NETenum(enumPtr) \
do \
{ \
int32_t _val; \
uint32_t _val; \
squelchUninitialisedUseWarning(enumPtr); \
_val = (NETgetPacketDir() == PACKET_ENCODE) ? *(enumPtr) : 0; \
\
NETint32_t(&_val); \
NETuint32_tSmall(&_val); \
\
*(enumPtr) = _val; \
} while(0)

View File

@ -158,6 +158,9 @@ GAMECODE gameLoop(void)
if (gameTicked)
{
// Actually send pending droid orders.
sendQueuedDroidInfo();
sendPlayerGameTime();
gameSRand(gameTime); // Brute force way of synchronising the random number generator, which can't go out of synch.
}

View File

@ -45,10 +45,70 @@
#include "mapgrid.h"
#include "multirecv.h"
#include <vector>
#include <algorithm>
#define ANYPLAYER 99
#define DORDER_UNKNOWN 99
#define DORDER_UNKNOWN_ALT 100
struct QueuedDroidInfo
{
/// Sorts by order, then finally by droid id, to group multiple droids with the same order.
bool operator <(QueuedDroidInfo const &z) const
{
int orComp = orderCompare(z);
if (orComp != 0) return orComp < 0;
return droidId < z.droidId;
}
/// Returns 0 if order is the same, non-zero otherwise.
int orderCompare(QueuedDroidInfo const &z) const
{
if (player != z.player) return player < z.player ? -1 : 1;
if (order != z.order) return order < z.order ? -1 : 1;
if (subType != z.subType) return subType < z.subType ? -1 : 1;
if (subType)
{
if (destId != z.destId) return destId < z.destId ? -1 : 1;
if (destType != z.destType) return destType < z.destType ? -1 : 1;
}
else
{
if (x != z.x) return x < z.x ? -1 : 1;
if (y != z.y) return y < z.y ? -1 : 1;
}
if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
{
if (structRef != z.structRef) return structRef < z.structRef ? -1 : 1;
if (structId != z.structId) return structId < z.structId ? -1 : 1;
if (direction != z.direction) return direction < z.direction ? -1 : 1;
}
if (order == DORDER_LINEBUILD)
{
if (x2 != z.x2) return x2 < z.x2 ? -1 : 1;
if (y2 != z.y2) return y2 < z.y2 ? -1 : 1;
}
return 0;
}
uint8_t player;
uint32_t droidId;
DROID_ORDER order;
BOOL subType;
uint32_t destId; // if (subType)
OBJECT_TYPE destType; // if (subType)
uint32_t x; // if (!subType)
uint32_t y; // if (!subType)
uint32_t structRef; // if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
uint32_t structId; // if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
uint16_t direction; // if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
uint32_t x2; // if (order == DORDER_LINEBUILD)
uint32_t y2; // if (order == DORDER_LINEBUILD)
};
static std::vector<QueuedDroidInfo> queuedOrders;
// ////////////////////////////////////////////////////////////////////////////
// Local Prototypes
@ -675,6 +735,72 @@ BOOL recvGroupOrder(NETQUEUE queue)
return true;
}
/// Does not read/write info->droidId!
static void NETQueuedDroidInfo(QueuedDroidInfo *info)
{
NETuint8_t(&info->player);
NETenum(&info->order);
NETbool(&info->subType);
if (info->subType)
{
NETuint32_t(&info->destId);
NETenum(&info->destType);
}
else
{
NETuint32_tSmall(&info->x);
NETuint32_tSmall(&info->y);
}
if (info->order == DORDER_BUILD || info->order == DORDER_LINEBUILD)
{
NETuint32_t(&info->structRef);
NETuint32_t(&info->structId);
NETuint16_t(&info->direction);
}
if (info->order == DORDER_LINEBUILD)
{
NETuint32_tSmall(&info->x2);
NETuint32_tSmall(&info->y2);
}
}
// Actually send the droid info.
void sendQueuedDroidInfo()
{
// Sort queued orders, to group the same order to multiple droids.
std::sort(queuedOrders.begin(), queuedOrders.end());
std::vector<QueuedDroidInfo>::iterator eqBegin, eqEnd;
for (eqBegin = queuedOrders.begin(); eqBegin != queuedOrders.end(); eqBegin = eqEnd)
{
// Find end of range of orders which differ only by the droid ID.
for (eqEnd = eqBegin + 1; eqEnd != queuedOrders.end() && eqEnd->orderCompare(*eqBegin) == 0; ++eqEnd)
{}
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_DROIDINFO);
NETQueuedDroidInfo(&*eqBegin);
uint32_t num = eqEnd - eqBegin;
NETuint32_tSmall(&num);
uint32_t prevDroidId = 0;
for (unsigned n = 0; n < num; ++n)
{
uint32_t droidId = (eqBegin + n)->droidId;
// Encode deltas between droid IDs, since the deltas are smaller than the actual droid IDs, and will encode to less bytes on average.
uint32_t deltaDroidId = droidId - prevDroidId;
NETuint32_tSmall(&deltaDroidId);
prevDroidId = droidId;
}
NETend();
}
// Sent the orders. Don't send them again.
queuedOrders.clear();
}
// ////////////////////////////////////////////////////////////////////////////
// Droid update information
BOOL SendDroidInfo(const DROID* psDroid, DROID_ORDER order, uint32_t x, uint32_t y, const BASE_OBJECT* psObj, const BASE_STATS *psStats, uint32_t x2, uint32_t y2, uint16_t direction)
@ -687,49 +813,39 @@ BOOL SendDroidInfo(const DROID* psDroid, DROID_ORDER order, uint32_t x, uint32_t
return true;
}
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_DROIDINFO);
QueuedDroidInfo info;
memset(&info, 0x00, sizeof(info)); // Suppress uninitialised warnings. (The uninitialised values in the queue would be ignored when reading the queue.)
info.player = psDroid->player;
info.droidId = psDroid->id;
info.order = order;
info.subType = psObj != NULL;
if (info.subType)
{
uint32_t droidId = psDroid->id;
BOOL subType = (psObj) ? true : false;
uint8_t player = psDroid->player;
NETuint8_t(&player);
// Send the droid's ID
NETuint32_t(&droidId);
// Send the droid's order
NETenum(&order);
NETbool(&subType);
if (subType)
{
uint32_t destId = psObj->id;
uint32_t destType = psObj->type;
NETuint32_t(&destId);
NETenum(&destType);
}
else
{
NETuint32_t(&x);
NETuint32_t(&y);
}
if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
{
uint32_t structId = generateNewObjectId();
uint32_t structRef = psStats->ref;
NETuint32_t(&structRef);
NETuint32_t(&structId);
NETuint16_t(&direction);
}
if (order == DORDER_LINEBUILD)
{
NETuint32_t(&x2);
NETuint32_t(&y2);
}
info.destId = psObj->id;
info.destType = psObj->type;
}
return NETend();
else
{
info.x = x;
info.y = y;
}
if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
{
info.structRef = psStats->ref;
info.structId = generateNewObjectId();
info.direction = direction;
}
if (order == DORDER_LINEBUILD)
{
info.x2 = x2;
info.y2 = y2;
}
// Send later, grouped by order, so multiple droids with the same order can be encoded to much less data.
queuedOrders.push_back(info);
return true;
}
// ////////////////////////////////////////////////////////////////////////////
@ -738,92 +854,70 @@ BOOL recvDroidInfo(NETQUEUE queue)
{
NETbeginDecode(queue, GAME_DROIDINFO);
{
uint32_t droidId;
DROID* psDroid;
DROID_ORDER order = DORDER_NONE;
BOOL subType;
uint8_t player;
uint32_t destId = 0, destType = 0;
uint32_t x = 0, y = 0;
QueuedDroidInfo info;
memset(&info, 0x00, sizeof(info)); // Default to nothing, if bad packet.
info.droidId = 0; // droidId not set by NETQueuedDroidInfo.
NETQueuedDroidInfo(&info);
STRUCTURE_STATS *psStats = NULL;
uint32_t structId = 0;
uint32_t x2 = 0, y2 = 0;
uint16_t direction = 0;
NETuint8_t(&player); // actual player this belongs to
NETuint32_t(&droidId); // Get the droid
if (!IdToDroid(droidId, ANYPLAYER, &psDroid))
if (info.order == DORDER_BUILD || info.order == DORDER_LINEBUILD)
{
debug(LOG_ERROR, "Packet from %d refers to non-existent droid %u, [%s : p%d]",
queue.index, droidId, isHumanPlayer(player) ? "Human" : "AI", player);
return false;
}
// Get the droid's order
NETenum(&order);
NETbool(&subType);
if (subType)
{
NETuint32_t(&destId);
NETenum(&destType);
}
else
{
NETuint32_t(&x);
NETuint32_t(&y);
}
if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
{
uint32_t structRef = 0;
unsigned typeIndex;
NETuint32_t(&structRef);
NETuint32_t(&structId);
// Find structure target
for (typeIndex = 0; typeIndex < numStructureStats; typeIndex++)
for (unsigned typeIndex = 0; typeIndex < numStructureStats; typeIndex++)
{
if (asStructureStats[typeIndex].ref == structRef)
if (asStructureStats[typeIndex].ref == info.structRef)
{
psStats = asStructureStats + typeIndex;
break;
}
}
NETuint16_t(&direction);
}
if (order == DORDER_LINEBUILD)
{
NETuint32_t(&x2);
NETuint32_t(&y2);
}
psDroid->waitingForOwnReceiveDroidInfoMessage = false;
uint32_t num = 0;
NETuint32_tSmall(&num);
if (order == DORDER_BUILD)
for (unsigned n = 0; n < num; ++n)
{
turnOffMultiMsg(true); // Grrr, want to remove the turnOffMultiMsg calls, not add more... Trying to get building working in a sane way for now.
orderDroidStatsLocDir(psDroid, order, (BASE_STATS *)psStats, x, y, direction);
turnOffMultiMsg(false); // Grrr, want to remove the turnOffMultiMsg calls, not add more... Trying to get building working in a sane way for now.
}
else if (order == DORDER_LINEBUILD)
{
turnOffMultiMsg(true); // Grrr, want to remove the turnOffMultiMsg calls, not add more... Trying to get building working in a sane way for now.
orderDroidStatsTwoLocDir(psDroid, order, (BASE_STATS *)psStats, x, y, x2, y2, direction);
turnOffMultiMsg(false); // Grrr, want to remove the turnOffMultiMsg calls, not add more... Trying to get building working in a sane way for now.
}
else if (!subType && x == 0 && y == 0)
{
// If both the X _and_ Y coordinate are zero we've been given a
// "special" order.
turnOffMultiMsg(true);
orderDroid(psDroid, order);
turnOffMultiMsg(false);
}
// Otherwise it is just a normal "goto location" order
else
{
ProcessDroidOrder(psDroid, order, x, y, (OBJECT_TYPE)destType, destId);
// Get the next droid ID which is being given this order.
uint32_t deltaDroidId = 0;
NETuint32_tSmall(&deltaDroidId);
info.droidId += deltaDroidId;
DROID *psDroid = NULL;
if (!IdToDroid(info.droidId, ANYPLAYER, &psDroid))
{
debug(LOG_ERROR, "Packet from %d refers to non-existent droid %u, [%s : p%d]",
queue.index, info.droidId, isHumanPlayer(info.player) ? "Human" : "AI", info.player);
continue; // Can't find the droid, so skip this droid.
}
psDroid->waitingForOwnReceiveDroidInfoMessage = false;
if (info.order == DORDER_BUILD)
{
turnOffMultiMsg(true); // Grrr, want to remove the turnOffMultiMsg calls, not add more... Trying to get building working in a sane way for now.
orderDroidStatsLocDir(psDroid, info.order, (BASE_STATS *)psStats, info.x, info.y, info.direction);
turnOffMultiMsg(false); // Grrr, want to remove the turnOffMultiMsg calls, not add more... Trying to get building working in a sane way for now.
}
else if (info.order == DORDER_LINEBUILD)
{
turnOffMultiMsg(true); // Grrr, want to remove the turnOffMultiMsg calls, not add more... Trying to get building working in a sane way for now.
orderDroidStatsTwoLocDir(psDroid, info.order, (BASE_STATS *)psStats, info.x, info.y, info.x2, info.y2, info.direction);
turnOffMultiMsg(false); // Grrr, want to remove the turnOffMultiMsg calls, not add more... Trying to get building working in a sane way for now.
}
else if (!info.subType && info.x == 0 && info.y == 0)
{
// If both the X _and_ Y coordinate are zero we've been given a
// "special" order.
turnOffMultiMsg(true);
orderDroid(psDroid, info.order);
turnOffMultiMsg(false);
}
// Otherwise it is just a normal "goto location" order
else
{
ProcessDroidOrder(psDroid, info.order, info.x, info.y, info.destType, info.destId);
}
}
}
NETend();

View File

@ -180,6 +180,7 @@ void sendStructureInfo (STRUCTURE *psStruct, STRUCTURE_INFO str
extern BOOL SendDroid (const DROID_TEMPLATE* pTemplate, uint32_t x, uint32_t y, uint8_t player, uint32_t id, const INITIAL_DROID_ORDERS *initialOrders);
extern BOOL SendDestroyDroid (const DROID* psDroid);
extern BOOL SendDemolishFinished(STRUCTURE *psS,DROID *psD);
void sendQueuedDroidInfo(void); ///< Actually sends the droid orders which were queued by SendDroidInfo.
extern BOOL SendDroidInfo (const DROID* psDroid, DROID_ORDER order, uint32_t x, uint32_t y, const BASE_OBJECT* psObj, const BASE_STATS *psStats, uint32_t x2, uint32_t y2, uint16_t direction);
extern BOOL SendGroupOrderSelected(uint8_t player, uint32_t x, uint32_t y, const BASE_OBJECT* psObj, BOOL altOrder);
extern BOOL SendCmdGroup (DROID_GROUP *psGroup, UWORD x, UWORD y, BASE_OBJECT *psObj);