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.
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;
}
}
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)
// 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;
// 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;
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;
// Reinit runData
runData = GameRunData();
runData.time_from_last_punch = 10.0;
- runData.update_wielded_item_trigger = true;
m_game_ui->initFlags();
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;
}
/*
#include "inventory.h"
#include "serialization.h"
#include "debug.h"
+#include <algorithm>
#include <sstream>
#include "log.h"
#include "itemdef.h"
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"));
os<<"Item ";
item.serialize(os);
}
+ // TODO: Implement this:
+ // if (!incremental || item.checkModified())
+ // os << "Keep";
os<<"\n";
}
void InventoryList::deSerialize(std::istream &is)
{
//is.imbue(std::locale("C"));
+ setModified();
- clearItems();
u32 item_i = 0;
m_width = 0;
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;
if(item_i > getSize() - 1)
throw SerializationError("too many items");
m_items[item_i++].clear();
+ } else if (name == "Keep") {
+ ++item_i; // Unmodified item
}
}
ItemStack olditem = m_items[i];
m_items[i] = newitem;
- //setDirty(true);
+ setModified();
return olditem;
}
{
assert(i < m_items.size()); // Pre-condition
m_items[i].clear();
+ setModified();
}
ItemStack InventoryList::addItem(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;
}
return ItemStack();
ItemStack taken = m_items[i].takeItem(takecount);
- //if(!taken.empty())
- // setDirty(true);
+ if (!taken.empty())
+ setModified();
return taken;
}
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;
Inventory::Inventory(const Inventory &other)
{
*this = other;
- m_dirty = false;
}
Inventory & Inventory::operator = (const Inventory &other)
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";
void Inventory::deSerialize(std::istream &is)
{
- clear();
+ std::vector<InventoryList *> new_lists;
+ new_lists.reserve(m_lists.size());
while (is.good()) {
std::string line;
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;
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.
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);
// 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
~Inventory();
void clear();
- void clearContents();
Inventory(IItemDefManager *itemdef);
Inventory(const Inventory &other);
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);
// 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;
};
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);
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
// Eat the action
delete a;
- SendInventory(playersao);
+ SendInventory(playersao, true);
}
void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
// Apply returned ItemStack
if (playersao->setWieldedItem(item)) {
- SendInventory(playersao);
+ SendInventory(playersao, true);
}
}
item, playersao, pointed)) {
// Apply returned ItemStack
if (playersao->setWieldedItem(item)) {
- SendInventory(playersao);
+ SendInventory(playersao, true);
}
}
if (m_script->item_OnSecondaryUse(
item, playersao)) {
if( playersao->setWieldedItem(item)) {
- SendInventory(playersao);
+ SendInventory(playersao, true);
}
}
} // action == INTERACT_ACTIVATE
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)
{
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;
SendPlayerInventoryFormspec(peer_id);
// Send inventory
- SendInventory(playersao);
+ SendInventory(playersao, false);
// Send HP or death screen
if (playersao->isDead())
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:
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);
}
// 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);
}
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;
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);
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;
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"));
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";
#include "test.h"
#include <algorithm>
#include "server/mods.h"
+#include "settings.h"
#include "test_config.h"
class TestServerModManager : public TestBase
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);
}
+++ /dev/null
-gameid = minimal