Inventory: Send dirty lists where appropriate (#8742)
authorSmallJoker <SmallJoker@users.noreply.github.com>
Sat, 24 Aug 2019 17:07:38 +0000 (19:07 +0200)
committerGitHub <noreply@github.com>
Sat, 24 Aug 2019 17:07:38 +0000 (19:07 +0200)
This change reduces the amount of sent data towards clients. Inventory lists that are already known to the player are skipped, saving quite some data over time.

Raises protocol version to 38 to ensure correct backwards-compatible code.

16 files changed:
src/client/client.cpp
src/client/client.h
src/client/game.cpp
src/inventory.cpp
src/inventory.h
src/network/clientpackethandler.cpp
src/network/networkprotocol.h
src/network/serverpackethandler.cpp
src/remoteplayer.h
src/script/lua_api/l_object.cpp
src/server.cpp
src/server.h
src/unittest/test_inventory.cpp
src/unittest/test_servermodmanager.cpp
src/unittest/test_world/do_not_remove.txt [new file with mode: 0644]
src/unittest/test_world/world.mt [deleted file]

index e78b9bb8afa872a59aebfc0c60d26536f8dbcddf..4c54853255d6e050206c39b0bf00507dee1d0bce 100644 (file)
@@ -557,8 +557,8 @@ void Client::step(float dtime)
                if (count_after != count_before) {
                        // Do this every <interval> seconds after TOCLIENT_INVENTORY
                        // Reset the locally changed inventory to the authoritative inventory
-                       m_env.getLocalPlayer()->inventory = *m_inventory_from_server;
-                       m_inventory_updated = true;
+                       player->inventory = *m_inventory_from_server;
+                       m_update_wielded_item = true;
                }
        }
 
@@ -1331,28 +1331,30 @@ void Client::setPlayerControl(PlayerControl &control)
 void Client::setPlayerItem(u16 item)
 {
        m_env.getLocalPlayer()->setWieldIndex(item);
-       m_inventory_updated = true;
+       m_update_wielded_item = true;
 
        NetworkPacket pkt(TOSERVER_PLAYERITEM, 2);
        pkt << item;
        Send(&pkt);
 }
 
-// Returns true if the inventory of the local player has been
-// updated from the server. If it is true, it is set to false.
-bool Client::getLocalInventoryUpdated()
+// Returns true once after the inventory of the local player
+// has been updated from the server.
+bool Client::updateWieldedItem()
 {
-       bool updated = m_inventory_updated;
-       m_inventory_updated = false;
-       return updated;
-}
+       if (!m_update_wielded_item)
+               return false;
+
+       m_update_wielded_item = false;
 
-// Copies the inventory of the local player to parameter
-void Client::getLocalInventory(Inventory &dst)
-{
        LocalPlayer *player = m_env.getLocalPlayer();
        assert(player);
-       dst = player->inventory;
+       if (auto *list = player->inventory.getList("main"))
+               list->setModified(false);
+       if (auto *list = player->inventory.getList("hand"))
+               list->setModified(false);
+
+       return true;
 }
 
 Inventory* Client::getInventory(const InventoryLocation &loc)
index 3bfb1631e1f117e4d24d8222f99e1e91e21938a1..85456fe4de32f53118c5b74103d6ea328d668310 100644 (file)
@@ -274,9 +274,7 @@ public:
 
        // Returns true if the inventory of the local player has been
        // updated from the server. If it is true, it is set to false.
-       bool getLocalInventoryUpdated();
-       // Copies the inventory of the local player to parameter
-       void getLocalInventory(Inventory &dst);
+       bool updateWieldedItem();
 
        /* InventoryManager interface */
        Inventory* getInventory(const InventoryLocation &loc) override;
@@ -504,7 +502,7 @@ private:
        // If 0, server init hasn't been received yet.
        u16 m_proto_ver = 0;
 
-       bool m_inventory_updated = false;
+       bool m_update_wielded_item = false;
        Inventory *m_inventory_from_server = nullptr;
        float m_inventory_from_server_age = 0.0f;
        PacketCounter m_packetcounter;
index 4d5fbfb97b68a96ae890ad064b6afeced05669a0..5bf41bcd6efd0bb99ed83bdacc1d92923849f29e 100644 (file)
@@ -599,7 +599,6 @@ struct GameRunData {
        bool dig_instantly;
        bool digging_blocked;
        bool left_punch;
-       bool update_wielded_item_trigger;
        bool reset_jump_timer;
        float nodig_delay_timer;
        float dig_time;
@@ -1018,7 +1017,6 @@ bool Game::startup(bool *kill,
        // Reinit runData
        runData = GameRunData();
        runData.time_from_last_punch = 10.0;
-       runData.update_wielded_item_trigger = true;
 
        m_game_ui->initFlags();
 
@@ -3736,19 +3734,11 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
        if (player->getWieldIndex() != runData.new_playeritem)
                client->setPlayerItem(runData.new_playeritem);
 
-       // Update local inventory if it has changed
-       if (client->getLocalInventoryUpdated()) {
-               //infostream<<"Updating local inventory"<<std::endl;
-               runData.update_wielded_item_trigger = true;
-       }
-
-       if (runData.update_wielded_item_trigger) {
+       if (client->updateWieldedItem()) {
                // Update wielded tool
                ItemStack selected_item, hand_item;
                ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
                camera->wield(tool_item);
-
-               runData.update_wielded_item_trigger = false;
        }
 
        /*
index f2cc2ede39796ee9e3bff9ee0c28e0692962b921..375f0f147e2ea318e6fe407ffbddb2c210c98e7b 100644 (file)
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "inventory.h"
 #include "serialization.h"
 #include "debug.h"
+#include <algorithm>
 #include <sstream>
 #include "log.h"
 #include "itemdef.h"
@@ -382,27 +383,32 @@ void InventoryList::clearItems()
                m_items.emplace_back();
        }
 
-       //setDirty(true);
+       setModified();
 }
 
 void InventoryList::setSize(u32 newsize)
 {
-       if(newsize != m_items.size())
-               m_items.resize(newsize);
+       if (newsize == m_items.size())
+               return;
+
+       m_items.resize(newsize);
        m_size = newsize;
+       setModified();
 }
 
 void InventoryList::setWidth(u32 newwidth)
 {
        m_width = newwidth;
+       setModified();
 }
 
 void InventoryList::setName(const std::string &name)
 {
        m_name = name;
+       setModified();
 }
 
-void InventoryList::serialize(std::ostream &os) const
+void InventoryList::serialize(std::ostream &os, bool incremental) const
 {
        //os.imbue(std::locale("C"));
 
@@ -415,6 +421,9 @@ void InventoryList::serialize(std::ostream &os) const
                        os<<"Item ";
                        item.serialize(os);
                }
+               // TODO: Implement this:
+               // if (!incremental || item.checkModified())
+               // os << "Keep";
                os<<"\n";
        }
 
@@ -424,8 +433,8 @@ void InventoryList::serialize(std::ostream &os) const
 void InventoryList::deSerialize(std::istream &is)
 {
        //is.imbue(std::locale("C"));
+       setModified();
 
-       clearItems();
        u32 item_i = 0;
        m_width = 0;
 
@@ -439,12 +448,12 @@ void InventoryList::deSerialize(std::istream &is)
                std::string name;
                std::getline(iss, name, ' ');
 
-               if (name == "EndInventoryList")
-                       return;
-
-               // This is a temporary backwards compatibility fix
-               if (name == "end")
+               if (name == "EndInventoryList" || name == "end") {
+                       // If partial incremental: Clear leftover items (should not happen!)
+                       for (size_t i = item_i; i < m_items.size(); ++i)
+                               m_items[i].clear();
                        return;
+               }
 
                if (name == "Width") {
                        iss >> m_width;
@@ -464,6 +473,8 @@ void InventoryList::deSerialize(std::istream &is)
                        if(item_i > getSize() - 1)
                                throw SerializationError("too many items");
                        m_items[item_i++].clear();
+               } else if (name == "Keep") {
+                       ++item_i; // Unmodified item
                }
        }
 
@@ -557,7 +568,7 @@ ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem)
 
        ItemStack olditem = m_items[i];
        m_items[i] = newitem;
-       //setDirty(true);
+       setModified();
        return olditem;
 }
 
@@ -565,6 +576,7 @@ void InventoryList::deleteItem(u32 i)
 {
        assert(i < m_items.size()); // Pre-condition
        m_items[i].clear();
+       setModified();
 }
 
 ItemStack InventoryList::addItem(const ItemStack &newitem_)
@@ -612,8 +624,8 @@ ItemStack InventoryList::addItem(u32 i, const ItemStack &newitem)
                return newitem;
 
        ItemStack leftover = m_items[i].addItem(newitem, m_itemdef);
-       //if(leftover != newitem)
-       //      setDirty(true);
+       if (leftover != newitem)
+               setModified();
        return leftover;
 }
 
@@ -682,8 +694,8 @@ ItemStack InventoryList::takeItem(u32 i, u32 takecount)
                return ItemStack();
 
        ItemStack taken = m_items[i].takeItem(takecount);
-       //if(!taken.empty())
-       //      setDirty(true);
+       if (!taken.empty())
+               setModified();
        return taken;
 }
 
@@ -788,16 +800,6 @@ void Inventory::clear()
        m_lists.clear();
 }
 
-void Inventory::clearContents()
-{
-       m_dirty = true;
-       for (InventoryList *list : m_lists) {
-               for (u32 j=0; j<list->getSize(); j++) {
-                       list->deleteItem(j);
-               }
-       }
-}
-
 Inventory::Inventory(IItemDefManager *itemdef)
 {
        m_dirty = false;
@@ -807,7 +809,6 @@ Inventory::Inventory(IItemDefManager *itemdef)
 Inventory::Inventory(const Inventory &other)
 {
        *this = other;
-       m_dirty = false;
 }
 
 Inventory & Inventory::operator = (const Inventory &other)
@@ -838,11 +839,15 @@ bool Inventory::operator == (const Inventory &other) const
        return true;
 }
 
-void Inventory::serialize(std::ostream &os) const
+void Inventory::serialize(std::ostream &os, bool incremental) const
 {
-       for (InventoryList *list : m_lists) {
-               os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
-               list->serialize(os);
+       for (const InventoryList *list : m_lists) {
+               if (!incremental || list->checkModified()) {
+                       os << "List " << list->getName() << " " << list->getSize() << "\n";
+                       list->serialize(os, incremental);
+               } else {
+                       os << "KeepList " << list->getName() << "\n";
+               }
        }
 
        os<<"EndInventory\n";
@@ -850,7 +855,8 @@ void Inventory::serialize(std::ostream &os) const
 
 void Inventory::deSerialize(std::istream &is)
 {
-       clear();
+       std::vector<InventoryList *> new_lists;
+       new_lists.reserve(m_lists.size());
 
        while (is.good()) {
                std::string line;
@@ -861,12 +867,20 @@ void Inventory::deSerialize(std::istream &is)
                std::string name;
                std::getline(iss, name, ' ');
 
-               if (name == "EndInventory")
-                       return;
+               if (name == "EndInventory" || name == "end") {
+                       // Remove all lists that were not sent
+                       for (auto &list : m_lists) {
+                               if (std::find(new_lists.begin(), new_lists.end(), list) != new_lists.end())
+                                       continue;
 
-               // This is a temporary backwards compatibility fix
-               if (name == "end")
+                               delete list;
+                               list = nullptr;
+                               m_dirty = true;
+                       }
+                       m_lists.erase(std::remove(m_lists.begin(), m_lists.end(),
+                                       nullptr), m_lists.end());
                        return;
+               }
 
                if (name == "List") {
                        std::string listname;
@@ -875,15 +889,33 @@ void Inventory::deSerialize(std::istream &is)
                        std::getline(iss, listname, ' ');
                        iss>>listsize;
 
-                       InventoryList *list = new InventoryList(listname, listsize, m_itemdef);
+                       InventoryList *list = getList(listname);
+                       bool create_new = !list;
+                       if (create_new)
+                               list = new InventoryList(listname, listsize, m_itemdef);
+                       else
+                               list->setSize(listsize);
                        list->deSerialize(is);
 
-                       m_lists.push_back(list);
-               }
-               else
-               {
-                       throw SerializationError("invalid inventory specifier: " + name);
+                       new_lists.push_back(list);
+                       if (create_new)
+                               m_lists.push_back(list);
+
+               } else if (name == "KeepList") {
+                       // Incrementally sent list
+                       std::string listname;
+                       std::getline(iss, listname, ' ');
+
+                       InventoryList *list = getList(listname);
+                       if (list) {
+                               new_lists.push_back(list);
+                       } else {
+                               errorstream << "Inventory::deSerialize(): Tried to keep list '" <<
+                                       listname << "' which is non-existent." << std::endl;
+                       }
                }
+               // Any additional fields will throw errors when received by a client
+               // older than PROTOCOL_VERSION 38
        }
 
        // Contents given to deSerialize() were not terminated properly: throw error.
index 3f299993a27026284d1b456ea81ec04e58e333e2..b7a93553d879aacf3a159a3da51edbf2bc4c4eb6 100644 (file)
@@ -194,7 +194,7 @@ public:
        void setSize(u32 newsize);
        void setWidth(u32 newWidth);
        void setName(const std::string &name);
-       void serialize(std::ostream &os) const;
+       void serialize(std::ostream &os, bool incremental) const;
        void deSerialize(std::istream &is);
 
        InventoryList(const InventoryList &other);
@@ -265,12 +265,16 @@ public:
        // also with optional rollback recording
        void moveItemSomewhere(u32 i, InventoryList *dest, u32 count);
 
+       inline bool checkModified() const { return m_dirty; }
+       inline void setModified(bool dirty = true) { m_dirty = dirty; }
+
 private:
        std::vector<ItemStack> m_items;
        std::string m_name;
        u32 m_size;
        u32 m_width = 0;
        IItemDefManager *m_itemdef;
+       bool m_dirty = true;
 };
 
 class Inventory
@@ -279,7 +283,6 @@ public:
        ~Inventory();
 
        void clear();
-       void clearContents();
 
        Inventory(IItemDefManager *itemdef);
        Inventory(const Inventory &other);
@@ -290,7 +293,8 @@ public:
                return !(*this == other);
        }
 
-       void serialize(std::ostream &os) const;
+       // Never ever serialize to disk using "incremental"!
+       void serialize(std::ostream &os, bool incremental = false) const;
        void deSerialize(std::istream &is);
 
        InventoryList * addList(const std::string &name, u32 size);
@@ -301,28 +305,35 @@ public:
        // A shorthand for adding items. Returns leftover item (possibly empty).
        ItemStack addItem(const std::string &listname, const ItemStack &newitem)
        {
-               m_dirty = true;
                InventoryList *list = getList(listname);
                if(list == NULL)
                        return newitem;
                return list->addItem(newitem);
        }
 
-       bool checkModified() const
+       inline bool checkModified() const
        {
-               return m_dirty;
+               if (m_dirty)
+                       return true;
+
+               for (const auto &list : m_lists)
+                       if (list->checkModified())
+                               return true;
+
+               return false;
        }
 
-       void setModified(const bool x)
+       inline void setModified(bool dirty)
        {
-               m_dirty = x;
+               m_dirty = dirty;
+               for (const auto &list : m_lists)
+                       list->setModified(dirty);
        }
-
 private:
        // -1 if not found
        const s32 getListIndex(const std::string &name) const;
 
        std::vector<InventoryList*> m_lists;
        IItemDefManager *m_itemdef;
-       bool m_dirty = false;
+       bool m_dirty = true;
 };
index 520fcfa812a8e5a1ac0ee16bf49e6968a519d4b6..91829474e35d3ca6e242c47057fa9134dee7eb44 100644 (file)
@@ -333,7 +333,7 @@ void Client::handleCommand_Inventory(NetworkPacket* pkt)
 
        player->inventory.deSerialize(is);
 
-       m_inventory_updated = true;
+       m_update_wielded_item = true;
 
        delete m_inventory_from_server;
        m_inventory_from_server = new Inventory(player->inventory);
index 451518bbf5deb45fcc8dd8672594daa66161c180..05737d0a93dafaf7a7cc11452a80f5e80c69d9e7 100644 (file)
@@ -195,9 +195,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
                ContentFeatures version 13
                Add full Euler rotations instead of just yaw
                Add TOCLIENT_PLAYER_SPEED
+       PROTOCOL VERSION 38:
+               Incremental inventory sending mode
+               Unknown inventory serialization fields no longer throw an error
 */
 
-#define LATEST_PROTOCOL_VERSION 37
+#define LATEST_PROTOCOL_VERSION 38
 #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION)
 
 // Server's supported network protocol range
index 5ad60f48ab5c422d8927dc7a4fe3ce491e11c54e..1965d87aa75bb15e474645ee855e3255f4ca08ba 100644 (file)
@@ -732,7 +732,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
        // Eat the action
        delete a;
 
-       SendInventory(playersao);
+       SendInventory(playersao, true);
 }
 
 void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
@@ -1310,7 +1310,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
 
                        // Apply returned ItemStack
                        if (playersao->setWieldedItem(item)) {
-                               SendInventory(playersao);
+                               SendInventory(playersao, true);
                        }
                }
 
@@ -1346,7 +1346,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                                item, playersao, pointed)) {
                        // Apply returned ItemStack
                        if (playersao->setWieldedItem(item)) {
-                               SendInventory(playersao);
+                               SendInventory(playersao, true);
                        }
                }
 
@@ -1364,7 +1364,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
                if (m_script->item_OnSecondaryUse(
                                item, playersao)) {
                        if( playersao->setWieldedItem(item)) {
-                               SendInventory(playersao);
+                               SendInventory(playersao, true);
                        }
                }
        } // action == INTERACT_ACTIVATE
index ea118e6045c599d68e895a6498ab27169eab7f80..260504fb4379400455a5a77ac139c34a1fb29f0b 100644 (file)
@@ -110,12 +110,7 @@ public:
 
        bool checkModified() const { return m_dirty || inventory.checkModified(); }
 
-       void setModified(const bool x)
-       {
-               m_dirty = x;
-               if (!x)
-                       inventory.setModified(x);
-       }
+       inline void setModified(const bool x) { m_dirty = x; }
 
        void setLocalAnimations(v2s32 frames[4], float frame_speed)
        {
index be2172f1b853d5cd4a80e37bb5ef4cdce244d43c..68cc1c0a9eb24cffb35f59637302651c762080ba 100644 (file)
@@ -355,7 +355,7 @@ int ObjectRef::l_set_wielded_item(lua_State *L)
        ItemStack item = read_item(L, 2, getServer(L)->idef());
        bool success = co->setWieldedItem(item);
        if (success && co->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
-               getServer(L)->SendInventory(((PlayerSAO*)co));
+               getServer(L)->SendInventory((PlayerSAO *)co, true);
        }
        lua_pushboolean(L, success);
        return 1;
index 871612e60c478e811877eb0ceb550ceea1a1652c..59bc125815557eb83f8d8a7db649930939469d24 100644 (file)
@@ -1057,7 +1057,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
        SendPlayerInventoryFormspec(peer_id);
 
        // Send inventory
-       SendInventory(playersao);
+       SendInventory(playersao, false);
 
        // Send HP or death screen
        if (playersao->isDead())
@@ -1241,19 +1241,22 @@ void Server::setInventoryModified(const InventoryLocation &loc, bool playerSend)
                break;
        case InventoryLocation::PLAYER:
        {
-               if (!playerSend)
-                       return;
 
                RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
 
                if (!player)
                        return;
 
+               player->setModified(true);
+
+               if (!playerSend)
+                       return;
+
                PlayerSAO *playersao = player->getPlayerSAO();
                if(!playersao)
                        return;
 
-               SendInventory(playersao);
+               SendInventory(playersao, true);
        }
                break;
        case InventoryLocation::NODEMETA:
@@ -1527,21 +1530,27 @@ void Server::SendNodeDef(session_t peer_id,
        Non-static send methods
 */
 
-void Server::SendInventory(PlayerSAO* playerSAO)
+void Server::SendInventory(PlayerSAO *sao, bool incremental)
 {
-       UpdateCrafting(playerSAO->getPlayer());
+       RemotePlayer *player = sao->getPlayer();
+
+       // Do not send new format to old clients
+       incremental &= player->protocol_version >= 38;
+
+       UpdateCrafting(player);
 
        /*
                Serialize it
        */
 
-       NetworkPacket pkt(TOCLIENT_INVENTORY, 0, playerSAO->getPeerID());
-
-       std::ostringstream os;
-       playerSAO->getInventory()->serialize(os);
+       NetworkPacket pkt(TOCLIENT_INVENTORY, 0, sao->getPeerID());
 
-       std::string s = os.str();
+       std::ostringstream os(std::ios::binary);
+       sao->getInventory()->serialize(os, incremental);
+       sao->getInventory()->setModified(false);
+       player->setModified(true);
 
+       const std::string &s = os.str();
        pkt.putRawString(s.c_str(), s.size());
        Send(&pkt);
 }
@@ -2595,8 +2604,9 @@ void Server::sendDetachedInventory(const std::string &name, session_t peer_id)
                // Serialization & NetworkPacket isn't a love story
                std::ostringstream os(std::ios_base::binary);
                inv_it->second->serialize(os);
+               inv_it->second->setModified(false);
 
-               std::string os_str = os.str();
+               const std::string &os_str = os.str();
                pkt << static_cast<u16>(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients
                pkt.putRawString(os_str);
        }
@@ -2822,6 +2832,11 @@ void Server::UpdateCrafting(RemotePlayer *player)
        if (!clist || clist->getSize() == 0)
                return;
 
+       if (!clist->checkModified()) {
+               verbosestream << "Skip Server::UpdateCrafting(): list unmodified" << std::endl;
+               return;
+       }
+
        // Get a preview for crafting
        ItemStack preview;
        InventoryLocation loc;
index 06adbad689beb40115b7a87ddf2b4ab5760d67c5..d1dcafac3e525fd7b5e47725ddd7d0610ecfb3a5 100644 (file)
@@ -333,7 +333,7 @@ public:
 
        void SendPlayerHPOrDie(PlayerSAO *player, const PlayerHPChangeReason &reason);
        void SendPlayerBreath(PlayerSAO *sao);
-       void SendInventory(PlayerSAO* playerSAO);
+       void SendInventory(PlayerSAO *playerSAO, bool incremental);
        void SendMovePlayer(session_t peer_id);
        void SendPlayerSpeed(session_t peer_id, const v3f &added_vel);
 
index 1a783afaebd0461cd1d67c9ec91171506d576ef2..5f71636c48abf548b659fb915fc3f4ad71de38a4 100644 (file)
@@ -33,8 +33,9 @@ public:
 
        void testSerializeDeserialize(IItemDefManager *idef);
 
-       static const char *serialized_inventory;
-       static const char *serialized_inventory_2;
+       static const char *serialized_inventory_in;
+       static const char *serialized_inventory_out;
+       static const char *serialized_inventory_inc;
 };
 
 static TestInventory g_test_instance;
@@ -49,7 +50,7 @@ void TestInventory::runTests(IGameDef *gamedef)
 void TestInventory::testSerializeDeserialize(IItemDefManager *idef)
 {
        Inventory inv(idef);
-       std::istringstream is(serialized_inventory, std::ios::binary);
+       std::istringstream is(serialized_inventory_in, std::ios::binary);
 
        inv.deSerialize(is);
        UASSERT(inv.getList("0"));
@@ -62,82 +63,64 @@ void TestInventory::testSerializeDeserialize(IItemDefManager *idef)
 
        inv.getList("main")->setWidth(5);
        std::ostringstream inv_os(std::ios::binary);
-       inv.serialize(inv_os);
-       UASSERTEQ(std::string, inv_os.str(), serialized_inventory_2);
+       inv.serialize(inv_os, false);
+       UASSERTEQ(std::string, inv_os.str(), serialized_inventory_out);
+
+       inv.setModified(false);
+       inv_os.str("");
+       inv_os.clear();
+       inv.serialize(inv_os, true);
+       UASSERTEQ(std::string, inv_os.str(), serialized_inventory_inc);
+
+       ItemStack leftover = inv.getList("main")->takeItem(7, 99 - 12);
+       ItemStack wanted = ItemStack("default:dirt", 99 - 12, 0, idef);
+       UASSERT(leftover == wanted);
+       leftover = inv.getList("main")->getItem(7);
+       wanted.count = 12;
+       UASSERT(leftover == wanted);
 }
 
-const char *TestInventory::serialized_inventory =
-       "List 0 32\n"
+const char *TestInventory::serialized_inventory_in =
+       "List 0 10\n"
        "Width 3\n"
        "Empty\n"
        "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
        "Item default:cobble 61\n"
        "Empty\n"
        "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
        "Item default:dirt 71\n"
        "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
        "Item default:dirt 99\n"
        "Item default:cobble 38\n"
        "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
+       "EndInventoryList\n"
+       "List abc 1\n"
+       "Item default:stick 3\n"
+       "Width 0\n"
        "EndInventoryList\n"
        "EndInventory\n";
 
-const char *TestInventory::serialized_inventory_2 =
-       "List main 32\n"
+const char *TestInventory::serialized_inventory_out =
+       "List main 10\n"
        "Width 5\n"
        "Empty\n"
        "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
        "Item default:cobble 61\n"
        "Empty\n"
        "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
        "Item default:dirt 71\n"
        "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
        "Item default:dirt 99\n"
        "Item default:cobble 38\n"
        "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
-       "Empty\n"
        "EndInventoryList\n"
+       "List abc 1\n"
+       "Width 0\n"
+       "Item default:stick 3\n"
+       "EndInventoryList\n"
+       "EndInventory\n";
+
+const char *TestInventory::serialized_inventory_inc =
+       "KeepList main\n"
+       "KeepList abc\n"
        "EndInventory\n";
index 72ac7c6bf3a3e1f76df6db2120427e885206b07c..0757323f4ea74b7e0a5c4e2e8884783fc931881e 100644 (file)
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "test.h"
 #include <algorithm>
 #include "server/mods.h"
+#include "settings.h"
 #include "test_config.h"
 
 class TestServerModManager : public TestBase
@@ -85,6 +86,10 @@ void TestServerModManager::runTests(IGameDef *gamedef)
 
 void TestServerModManager::testCreation()
 {
+       std::string path = std::string(TEST_WORLDDIR) + DIR_DELIM + "world.mt";
+       Settings world_config;
+       world_config.set("gameid", "minimal");
+       UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true);
        ServerModManager sm(TEST_WORLDDIR);
 }
 
diff --git a/src/unittest/test_world/do_not_remove.txt b/src/unittest/test_world/do_not_remove.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/unittest/test_world/world.mt b/src/unittest/test_world/world.mt
deleted file mode 100644 (file)
index ab9b541..0000000
+++ /dev/null
@@ -1 +0,0 @@
-gameid = minimal