Preserve node predictions until server catches up zefram/prediction_preservation
authorZefram <zefram@fysh.org>
Sat, 9 Aug 2014 09:53:57 +0000 (10:53 +0100)
committerZefram <zefram@fysh.org>
Sat, 9 Aug 2014 09:53:57 +0000 (10:53 +0100)
Apply implicit sequence numbers on actions sent by clients, and use them
for the server to indicate, in a node status update, how up to date it is
in processing the client's actions.  Rather than apply all node updates
blindly, the client now checks whether it has relevant predictions that
are more recent than what the server has used to produce the update,
and keep those predictions applied until the server has caught up.
This avoids lag-induced node glitches.

src/client.cpp
src/client.h
src/clientiface.h
src/clientserver.h
src/game.cpp
src/server.cpp
src/server.h
src/util/ringbuffer.h [new file with mode: 0644]

index 601561f7d4b57a04ce18058ee3bc84b3012ef4dc..eb7f5403d19526adb657930f24f6922909adb2d6 100644 (file)
@@ -258,6 +258,7 @@ Client::Client(
        m_itemdef_received(false),
        m_nodedef_received(false),
        m_media_downloader(new ClientMediaDownloader()),
+       m_last_action_seq(0),
        m_time_of_day_set(false),
        m_last_time_of_day_f(-1),
        m_time_of_day_update_timer(0),
@@ -1047,6 +1048,8 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
                m_con.Send(PEER_ID_SERVER, 1, reply, true);
 
                m_state = LC_Init;
+               m_last_action_seq = 0;
+               m_node_predictions.clear();
 
                return;
        }
@@ -1091,6 +1094,19 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
                p.X = readS16(&data[2]);
                p.Y = readS16(&data[4]);
                p.Z = readS16(&data[6]);
+               if(datasize >= 10)
+               {
+                       for(RingBuffer<NodePrediction, NODE_PREDICTION_QUEUE_CAPACITY>::iterator
+                                       i = firstPendingPrediction(readU16(&data[8])),
+                                       ie = m_node_predictions.end();
+                                       i != ie; ++i) {
+                               if(i->pos == p) {
+                                       // notification from server is
+                                       // superseded by a later prediction
+                                       return;
+                               }
+                       }
+               }
                removeNode(p);
        }
        else if(command == TOCLIENT_ADDNODE)
@@ -1112,6 +1128,20 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
                        remove_metadata = false;
                }
                
+               if(datasize >= index+3)
+               {
+                       for(RingBuffer<NodePrediction, NODE_PREDICTION_QUEUE_CAPACITY>::iterator
+                                       i = firstPendingPrediction(readU16(&data[index+1])),
+                                       ie = m_node_predictions.end();
+                                       i != ie; ++i) {
+                               if(i->pos == p) {
+                                       // notification from server is
+                                       // superseded by a later prediction
+                                       return;
+                               }
+                       }
+               }
+
                addNode(p, n, remove_metadata);
        }
        else if(command == TOCLIENT_BLOCKDATA)
@@ -1156,6 +1186,21 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
                        sector->insertBlock(block);
                }
 
+               try
+               {
+                       v3s16 brpos = block->getPosRelative();
+                       for(RingBuffer<NodePrediction, NODE_PREDICTION_QUEUE_CAPACITY>::iterator
+                                       i = firstPendingPrediction(readU16(istr)),
+                                       ie = m_node_predictions.end();
+                                       i != ie; ++i) {
+                               // replay prediction if it applies to this block
+                               v3s16 bipos = i->pos - brpos;
+                               if(block->isValidPosition(bipos))
+                                       block->setNodeNoCheck(bipos, i->node);
+                       }
+               }
+               catch(SerializationError& e) {}
+
                /*
                        Add it to mesh update queue and set it to be acknowledged after update.
                */
@@ -1966,6 +2011,30 @@ void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
        m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
 }
 
+void Client::incrementActionSeq()
+{
+       m_last_action_seq++;
+       while(!m_node_predictions.empty() &&
+               m_node_predictions.front().action_seq == m_last_action_seq)
+                       m_node_predictions.pop_front();
+}
+
+RingBuffer<NodePrediction, NODE_PREDICTION_QUEUE_CAPACITY>::iterator
+Client::firstPendingPrediction(u16 last_action_considered)
+{
+       RingBuffer<NodePrediction, NODE_PREDICTION_QUEUE_CAPACITY>::iterator
+               i = m_node_predictions.end(), ib = m_node_predictions.begin();
+       u16 how_far_back = m_last_action_seq - last_action_considered;
+       while(i != ib) {
+               --i;
+               if(m_last_action_seq - i->action_seq >= how_far_back) {
+                       ++i;
+                       break;
+               }
+       }
+       return i;
+}
+
 void Client::interact(u8 action, const PointedThing& pointed)
 {
        if(m_state != LC_Ready){
@@ -2002,6 +2071,8 @@ void Client::interact(u8 action, const PointedThing& pointed)
 
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 void Client::sendNodemetaFields(v3s16 p, const std::string &formname,
@@ -2028,6 +2099,8 @@ void Client::sendNodemetaFields(v3s16 p, const std::string &formname,
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
        
 void Client::sendInventoryFields(const std::string &formname,
@@ -2053,6 +2126,8 @@ void Client::sendInventoryFields(const std::string &formname,
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 void Client::sendInventoryAction(InventoryAction *a)
@@ -2071,6 +2146,8 @@ void Client::sendInventoryAction(InventoryAction *a)
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 void Client::sendChatMessage(const std::wstring &message)
@@ -2103,6 +2180,8 @@ void Client::sendChatMessage(const std::wstring &message)
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 void Client::sendChangePassword(const std::wstring &oldpassword,
@@ -2139,6 +2218,8 @@ void Client::sendChangePassword(const std::wstring &oldpassword,
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 
@@ -2155,6 +2236,8 @@ void Client::sendDamage(u8 damage)
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 void Client::sendBreath(u16 breath)
@@ -2169,6 +2252,8 @@ void Client::sendBreath(u16 breath)
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 void Client::sendRespawn()
@@ -2183,6 +2268,8 @@ void Client::sendRespawn()
        SharedBuffer<u8> data((u8*)s.c_str(), s.size());
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 void Client::sendReady()
@@ -2286,6 +2373,8 @@ void Client::sendPlayerItem(u16 item)
 
        // Send as reliable
        Send(0, data, true);
+
+       incrementActionSeq();
 }
 
 void Client::removeNode(v3s16 p)
@@ -2332,6 +2421,14 @@ void Client::addNode(v3s16 p, MapNode n, bool remove_metadata)
                addUpdateMeshTaskWithEdge(i->first);
        }
 }
+
+void Client::notePredictedNode(v3s16 p)
+{
+       try {
+               m_node_predictions.push_back(NodePrediction(m_last_action_seq,
+                               p, m_env.getMap().getNode(p)));
+       } catch(InvalidPositionException &e) {}
+}
        
 void Client::setPlayerControl(PlayerControl &control)
 {
index 51ce5b8f22a30df5031b32b9fe6d2e3216fef9a4..f8a38f5642091fb124205d32b1b4273cecdb3bf0 100644 (file)
@@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "localplayer.h"
 #include "hud.h"
 #include "particles.h"
+#include "util/ringbuffer.h"
 
 struct MeshMakeData;
 class MapBlockMesh;
@@ -292,6 +293,45 @@ private:
        std::map<u16, u16> m_packets;
 };
 
+/*
+       node operation predictions
+
+       The client maintains a ring buffer recording predictions that it's
+       made for changes to nodes.  action_seq identifies which packet
+       sent by the client should cause the change to occur on the server.
+       (The sequence number is just the count of preceding game-action
+       packets sent by the client since TOSERVER_INIT2, modulo 2**16;
+       client and server both keep count.)  When the server sends the
+       client a packet with node state updates, it also indicates which
+       action packet it most recently saw from the client, and hence
+       which client actions have been taken into account in producing the
+       node update, and hence which client predictions are superseded
+       by the update.  The client then checks for predictions that are
+       relevant to the update and have not been superseded by it, and
+       replays them on top of the updated state, in order to preserve
+       the prediction until the server has processed the action.
+
+       A prediction is dropped from the buffer if the buffer overflows
+       (by having too many predictions pending), or if sequence numbers
+       have nearly wrapped such that its action_seq would be misleading.
+       Dropping the record of a prediction means that the predicted node
+       state might be overwritten by an update from the server that
+       doesn't reflect the pending action; this produces a glitch but
+       eventually self-heals when the server catches up.  Normally,
+       by the time a prediction is dropped the server has long ago
+       handled the action, so these glitches are avoided.
+*/
+struct NodePrediction {
+       u16 action_seq;
+       v3s16 pos;
+       MapNode node;
+       NodePrediction(const NodePrediction &v) { *this = v; }
+       NodePrediction(u16 seq, v3s16 p, MapNode n)
+               : action_seq(seq), pos(p), node(n) {}
+       NodePrediction() : action_seq(0), pos(), node() {}
+};
+enum { NODE_PREDICTION_QUEUE_CAPACITY = 0x3ff };
+
 class Client : public con::PeerHandler, public InventoryManager, public IGameDef
 {
 public:
@@ -363,6 +403,7 @@ public:
        // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent)
        void removeNode(v3s16 p);
        void addNode(v3s16 p, MapNode n, bool remove_metadata = true);
+       void notePredictedNode(v3s16 p);
        
        void setPlayerControl(PlayerControl &control);
 
@@ -473,6 +514,10 @@ private:
        void sendPlayerPos();
        // Send the item number 'item' as player item to the server
        void sendPlayerItem(u16 item);
+
+       void incrementActionSeq();
+       RingBuffer<NodePrediction, NODE_PREDICTION_QUEUE_CAPACITY>::iterator
+               firstPendingPrediction(u16 last_action_considered);
        
        float m_packetcounter_timer;
        float m_connection_reinit_timer;
@@ -517,6 +562,8 @@ private:
        bool m_itemdef_received;
        bool m_nodedef_received;
        ClientMediaDownloader *m_media_downloader;
+       u16 m_last_action_seq;
+       RingBuffer<NodePrediction, NODE_PREDICTION_QUEUE_CAPACITY> m_node_predictions;
 
        // time_of_day speed approximation for old protocol
        bool m_time_of_day_set;
index 5452ccddbdf5a08cb642ac35d3f4cab2adc8c129..8dfde1c34b29bf6ccf1c93f9f83a2d770c293494 100644 (file)
@@ -199,11 +199,14 @@ public:
        u8 serialization_version;
        //
        u16 net_proto_version;
+       // sequence number of last game action seen from client
+       u16 last_action_seq;
 
        RemoteClient():
                peer_id(PEER_ID_INEXISTENT),
                serialization_version(SER_FMT_VER_INVALID),
                net_proto_version(0),
+               last_action_seq(0),
                m_time_from_building(9999),
                m_pending_serialization_version(SER_FMT_VER_INVALID),
                m_state(CS_Created),
index f12384b18e5786b194c59feb61fa76ba404cf1eb..15785b3d73ebf0caedc5637d28330abc41544a42 100644 (file)
@@ -146,14 +146,26 @@ enum ToClientCommand
        */
 
        TOCLIENT_BLOCKDATA = 0x20, //TODO: Multiple blocks
+       /*
+               u16 command
+               v3s16 position
+               serialized block
+               u16 last_action_seq // Added in protocol version 23
+       */
        TOCLIENT_ADDNODE = 0x21,
        /*
                u16 command
                v3s16 position
                serialized mapnode
                u8 keep_metadata // Added in protocol version 22
+               u16 last_action_seq // Added in protocol version 23
        */
        TOCLIENT_REMOVENODE = 0x22,
+       /*
+               u16 command
+               v3s16 position
+               u16 last_action_seq // Added in protocol version 23
+       */
 
        TOCLIENT_PLAYERPOS = 0x23, // Obsolete
        /*
index 768193147dd575dff3f7bbe4ce1d7ff74cd5249e..d0573e384de519607f16ba2b1e5afc263d055dc1 100644 (file)
@@ -832,7 +832,8 @@ public:
 };
 
 bool nodePlacementPrediction(Client &client,
-               const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos)
+               const ItemDefinition &playeritem_def,
+               v3s16 nodepos, v3s16 neighbourpos, v3s16 *predicted_pos_p)
 {
        std::string prediction = playeritem_def.node_placement_prediction;
        INodeDefManager *nodedef = client.ndef();
@@ -916,6 +917,7 @@ bool nodePlacementPrediction(Client &client,
 
                                        // This triggers the required mesh update too
                                        client.addNode(p, n);
+                                       *predicted_pos_p = p;
                                        return true;
                                }
                }catch(InvalidPositionException &e){
@@ -2917,6 +2919,7 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
                                        client.setCrack(-1, v3s16(0,0,0));
                                        MapNode wasnode = map.getNode(nodepos);
                                        client.removeNode(nodepos);
+                                       client.notePredictedNode(nodepos);
 
                                        if (g_settings->getBool("enable_particles"))
                                        {
@@ -2990,13 +2993,16 @@ void the_game(bool &kill, bool random_input, InputHandler *input,
 
                                        // If the wielded item has node placement prediction,
                                        // make that happen
+                                       v3s16 predicted_pos;
                                        bool placed = nodePlacementPrediction(client,
                                                playeritem_def,
-                                               nodepos, neighbourpos);
+                                               nodepos, neighbourpos,
+                                               &predicted_pos);
 
                                        if(placed) {
                                                // Report to server
                                                client.interact(3, pointed);
+                                               client.notePredictedNode(predicted_pos);
                                                // Read the sound
                                                soundmaker.m_player_rightpunch_sound =
                                                        playeritem_def.sound_place;
index 40857f84d72954b673c90ddda6af9d5b91cd83f2..e0e8b947c14102bcd4684b457a5440cf37a2089f 100644 (file)
@@ -1617,6 +1617,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                verbosestream<<"Server: Got TOSERVER_INIT2 from "
                                <<peer_id<<std::endl;
 
+               RemoteClient* client = getClient(peer_id, CS_Created);
+
+               client->last_action_seq = 0;
+
                m_clients.event(peer_id, CSE_GotInit2);
                u16 protocol_version = m_clients.getProtocolVersion(peer_id);
 
@@ -1672,7 +1676,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                ///// end compatibility code
 
                // Warnings about protocol version can be issued here
-               if(getClient(peer_id)->net_proto_version < LATEST_PROTOCOL_VERSION)
+               if(client->net_proto_version < LATEST_PROTOCOL_VERSION)
                {
                        SendChatMessage(peer_id, L"# Server: WARNING: YOUR CLIENT'S "
                                        L"VERSION MAY NOT BE FULLY COMPATIBLE WITH THIS SERVER!");
@@ -1916,6 +1920,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_INVENTORY_ACTION)
        {
+               getClient(peer_id)->last_action_seq++;
                // Strip command and create a stream
                std::string datastring((char*)&data[2], datasize-2);
                verbosestream<<"TOSERVER_INVENTORY_ACTION: data="<<datastring<<std::endl;
@@ -2060,6 +2065,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_CHAT_MESSAGE)
        {
+               getClient(peer_id)->last_action_seq++;
                /*
                        u16 command
                        u16 length
@@ -2153,6 +2159,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_DAMAGE)
        {
+               getClient(peer_id)->last_action_seq++;
                std::string datastring((char*)&data[2], datasize-2);
                std::istringstream is(datastring, std::ios_base::binary);
                u8 damage = readU8(is);
@@ -2174,6 +2181,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_BREATH)
        {
+               getClient(peer_id)->last_action_seq++;
                std::string datastring((char*)&data[2], datasize-2);
                std::istringstream is(datastring, std::ios_base::binary);
                u16 breath = readU16(is);
@@ -2182,6 +2190,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_PASSWORD)
        {
+               getClient(peer_id)->last_action_seq++;
                /*
                        [0] u16 TOSERVER_PASSWORD
                        [2] u8[28] old password
@@ -2246,6 +2255,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_PLAYERITEM)
        {
+               getClient(peer_id)->last_action_seq++;
                if (datasize < 2+2)
                        return;
 
@@ -2254,6 +2264,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_RESPAWN)
        {
+               getClient(peer_id)->last_action_seq++;
                if(player->hp != 0 || !g_settings->getBool("enable_damage"))
                        return;
 
@@ -2267,6 +2278,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_INTERACT)
        {
+               getClient(peer_id)->last_action_seq++;
                std::string datastring((char*)&data[2], datasize-2);
                std::istringstream is(datastring, std::ios_base::binary);
 
@@ -2654,6 +2666,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_NODEMETA_FIELDS)
        {
+               getClient(peer_id)->last_action_seq++;
                std::string datastring((char*)&data[2], datasize-2);
                std::istringstream is(datastring, std::ios_base::binary);
 
@@ -2686,6 +2699,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        }
        else if(command == TOSERVER_INVENTORY_FIELDS)
        {
+               getClient(peer_id)->last_action_seq++;
                std::string datastring((char*)&data[2], datasize-2);
                std::istringstream is(datastring, std::ios_base::binary);
 
@@ -3665,14 +3679,6 @@ void Server::sendRemoveNode(v3s16 p, u16 ignore_id,
        float maxd = far_d_nodes*BS;
        v3f p_f = intToFloat(p, BS);
 
-       // Create packet
-       u32 replysize = 8;
-       SharedBuffer<u8> reply(replysize);
-       writeU16(&reply[0], TOCLIENT_REMOVENODE);
-       writeS16(&reply[2], p.X);
-       writeS16(&reply[4], p.Y);
-       writeS16(&reply[6], p.Z);
-
        std::list<u16> clients = m_clients.getClientIDs();
        for(std::list<u16>::iterator
                i = clients.begin();
@@ -3694,8 +3700,26 @@ void Server::sendRemoveNode(v3s16 p, u16 ignore_id,
                        }
                }
 
+               SharedBuffer<u8> reply(0);
+               m_clients.Lock();
+               RemoteClient* client = m_clients.lockedGetClientNoEx(*i);
+               if (client != 0)
+               {
+                       // Create packet
+                       u32 replysize = client->net_proto_version >= 23 ? 10 : 8;
+                       SharedBuffer<u8> reply(replysize);
+                       writeU16(&reply[0], TOCLIENT_REMOVENODE);
+                       writeS16(&reply[2], p.X);
+                       writeS16(&reply[4], p.Y);
+                       writeS16(&reply[6], p.Z);
+                       if (client->net_proto_version >= 23)
+                               writeU16(&reply[8], client->last_action_seq);
+               }
+               m_clients.Unlock();
+
                // Send as reliable
-               m_clients.send(*i, 0, reply, true);
+               if (reply.getSize() > 0)
+                       m_clients.send(*i, 0, reply, true);
        }
 }
 
@@ -3734,6 +3758,8 @@ void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
                {
                        // Create packet
                        u32 replysize = 9 + MapNode::serializedLength(client->serialization_version);
+                       if (client->net_proto_version >= 23)
+                               replysize += 2;
                        reply = SharedBuffer<u8>(replysize);
                        writeU16(&reply[0], TOCLIENT_ADDNODE);
                        writeS16(&reply[2], p.X);
@@ -3742,6 +3768,8 @@ void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
                        n.serialize(&reply[8], client->serialization_version);
                        u32 index = 8 + MapNode::serializedLength(client->serialization_version);
                        writeU8(&reply[index], remove_metadata ? 0 : 1);
+                       if (client->net_proto_version >= 23)
+                               writeU16(&reply[index+1], client->last_action_seq);
 
                        if (!remove_metadata) {
                                if (client->net_proto_version <= 21) {
@@ -3773,7 +3801,8 @@ void Server::setBlockNotSent(v3s16 p)
        m_clients.Unlock();
 }
 
-void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver, u16 net_proto_version)
+void Server::SendBlockNoLock(u16 peer_id, MapBlock *block,
+       u8 ver, u16 net_proto_version, u16 last_action_seq)
 {
        DSTACK(__FUNCTION_NAME);
 
@@ -3811,12 +3840,16 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver, u16 net_proto
        SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
 
        u32 replysize = 8 + blockdata.getSize();
+       if (net_proto_version >= 23)
+               replysize += 2;
        SharedBuffer<u8> reply(replysize);
        writeU16(&reply[0], TOCLIENT_BLOCKDATA);
        writeS16(&reply[2], p.X);
        writeS16(&reply[4], p.Y);
        writeS16(&reply[6], p.Z);
        memcpy(&reply[8], *blockdata, blockdata.getSize());
+       if (net_proto_version >= 23)
+               writeU16(&reply[8 + blockdata.getSize()], last_action_seq);
 
        /*infostream<<"Server: Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
                        <<":  \tpacket size: "<<replysize<<std::endl;*/
@@ -3891,7 +3924,7 @@ void Server::SendBlocks(float dtime)
                if(!client)
                        continue;
 
-               SendBlockNoLock(q.peer_id, block, client->serialization_version, client->net_proto_version);
+               SendBlockNoLock(q.peer_id, block, client->serialization_version, client->net_proto_version, client->last_action_seq);
 
                client->SentBlock(q.pos);
                total_sending++;
index cb0baceced8fd1358980192a9d8ae9b9cd2653e7..71aea992fe96886666237c6a9ea3e702c880bc74 100644 (file)
@@ -392,7 +392,7 @@ private:
        void setBlockNotSent(v3s16 p);
 
        // Environment and Connection must be locked when called
-       void SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver, u16 net_proto_version);
+       void SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver, u16 net_proto_version, u16 last_action_seq);
 
        // Sends blocks to clients (locks env and con on its own)
        void SendBlocks(float dtime);
diff --git a/src/util/ringbuffer.h b/src/util/ringbuffer.h
new file mode 100644 (file)
index 0000000..13077ad
--- /dev/null
@@ -0,0 +1,115 @@
+#ifndef UTIL_RINGBUFFER_HEADER
+#define UTIL_RINGBUFFER_HEADER
+
+#include <cstddef>
+
+// Ring buffer container (partial).  This is intended for very lightweight
+// value types, for which dynamically allocating storage would be
+// noticeably expensive.  The ring buffer has a statically-determined
+// capacity, and allocates in one go all the storage needed for the full
+// number of values.  Items can be pushed on the back of the buffer and
+// popped off the front very cheaply.  If more items are pushed than the
+// predeclared capacity, then items are lost from the front.  Thus this
+// can't be used where reliable storage is required; it should be used
+// where limiting the memory usage is more important than retaining
+// the information.
+
+template <typename VALUE_TYPE, size_t CAPACITY> class RingBuffer {
+public:
+       typedef VALUE_TYPE value_type;
+       typedef VALUE_TYPE &reference;
+       typedef const VALUE_TYPE &const_reference;
+       typedef VALUE_TYPE *pointer;
+       typedef const VALUE_TYPE *const_pointer;
+       typedef size_t size_type;
+private:
+       size_type front_pos, back_pos;
+       value_type elems[CAPACITY+1];
+public:
+       RingBuffer() : front_pos(0), back_pos(0) {}
+       size_type size() const {
+               size_type s = back_pos - front_pos;
+               if (back_pos < front_pos)
+                       s += CAPACITY+1;
+               return s;
+       }
+       size_type max_size() const { return CAPACITY; }
+       bool empty() const { return back_pos == front_pos; }
+       reference front() { return elems[front_pos]; }
+       const_reference front() const { return elems[front_pos]; }
+       reference back() { return elems[back_pos-1]; }
+       const_reference back() const { return elems[back_pos-1]; }
+       reference operator[](size_type n) {
+               size_type i = front_pos + n;
+               if (i >= CAPACITY+1)
+                       i -= CAPACITY+1;
+               return elems[i];
+       }
+       const_reference operator[](size_type n) const {
+               size_type i = front_pos + n;
+               if (i >= CAPACITY+1)
+                       i -= CAPACITY+1;
+               return elems[i];
+       }
+       void pop_front() {
+               front_pos++;
+               if (front_pos == CAPACITY+1) {
+                       front_pos = 0;
+                       if (back_pos == CAPACITY+1)
+                               back_pos = 0;
+               }
+       }
+       void push_back(const value_type &val) {
+               if (back_pos == CAPACITY+1)
+                       back_pos = 0;
+               elems[back_pos++] = val;
+               if (front_pos == (back_pos == CAPACITY+1 ? 0 : back_pos))
+                       pop_front();
+       }
+       void clear() {
+               front_pos = 0;
+               back_pos = 0;
+       }
+       class iterator {
+       private:
+               RingBuffer<VALUE_TYPE, CAPACITY> *buf;
+               size_type ix;
+               iterator(RingBuffer<VALUE_TYPE, CAPACITY> *bb, size_type ii)
+                       : buf(bb), ix(ii) {}
+               friend class RingBuffer<VALUE_TYPE, CAPACITY>;
+       public:
+               iterator(const iterator &it) : buf(it.buf), ix(it.ix) {}
+               reference operator*() const { return buf->elems[ix]; }
+               pointer operator->() const { return &buf->elems[ix]; }
+               iterator &operator++() {
+                       ix++;
+                       if (ix == CAPACITY+1 && buf->back_pos != CAPACITY+1)
+                               ix = 0;
+                       return *this;
+               }
+               iterator operator++(int) {
+                       iterator tmp = *this;
+                       ++*this;
+                       return tmp;
+               }
+               iterator &operator--() {
+                       if(ix == 0)
+                               ix = CAPACITY+1;
+                       ix--;
+                       return *this;
+               }
+               iterator operator--(int) {
+                       iterator tmp = *this;
+                       --*this;
+                       return tmp;
+               }
+               bool operator==(const iterator &it) {
+                       return buf == it.buf && ix == it.ix;
+               }
+               bool operator!=(const iterator &it) { return !(*this == it); }
+       };
+       iterator begin() { return iterator(this, front_pos); }
+       iterator end() { return iterator(this, back_pos); }
+};
+
+#endif