• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

TFS 1.X+ Optimizing player saves & when saves execute

Itutorial

Legendary OT User
Joined
Dec 23, 2014
Messages
2,412
Solutions
68
Reaction score
1,074
Hello everyone,

I noticed that all player save information happens in one place. IOLoginData::savePlayer() I do not really like that in order to save player items I have to save everything else with it. So I fragmented the IOLoginData::savePlayer() method into the following methods.

C++:
IOLoginData::savePlayerInstantSpells();
IOLoginData::savePlayerInventoryItems();
IOLoginData::savePlayerDepotItems();
IOLoginData::savePlayerInboxItems();
IOLoginData::savePlayerStoreInboxItems();

I added the instant spell saves in Player::learnInstantSpell() and Player::unLearnInstantSpell()

Then I added the following into Game::playerMoveItem()

C++:
ReturnValue ret =
    internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player, nullptr, &fromPos, &toPos);
if (ret != RETURNVALUE_NOERROR) {
    player->sendCancelMessage(ret);
    return;
}

// Player moved item into or out of depot
if (dynamic_cast<const DepotChest*>(fromCylinder) || dynamic_cast<const DepotChest*>(toCylinder)) {
    player->saveDepotItems();
}

// Player moved item out of store inbox
if (dynamic_cast<const StoreInbox*>(fromCylinder) && !dynamic_cast<const StoreInbox*>(toCylinder)) {
    player->saveStoreInboxItems();
}

// Player moved item into or out of inbox.
if (dynamic_cast<const Inbox*>(fromCylinder) || dynamic_cast<const Inbox*>(toCylinder)) {
    player->saveInboxItems();
}

// Player moved an inventory item.
if (fromPos.x == inventoryXPosition || toPos.x == inventoryXPosition) {
    player->saveInventoryItems();
}

I also slowed down item moves to 100ms.

For the inbox items I also had to make it save when players send mail to another.

Right now everything works properly, anytime an item is moved in each scenario that it would need to be saved it is. I am not using store inbox atm but I assume it works just as well. This freed up a lot of "lag" with player saves

So my question is what might I be missing? If I am not missing anything, would this be something that we should see in the source code? Doing it this way allows the database to be completely up-to-date incase of a crash. I also added saving with house items and limited them to being moved every 2 seconds because it has to save all house items. Though that is not included in this.
 
Last edited:
Obviously the best solution is a server that never crashes but….
Yes. This is a real solution.

There is also other solution: save server on crash. You can process SIGSEGV signal (crash) and call function that saves all players online and houses. Of course there is a chance that item structure of some player/house is a reason of crash (or failure of connection to MySQL), in this case it won't save, because save itself will crash again. EDIT: You can do it in C++, you don't need any special 'anti rollback script' for gdb.

You cannot save items every time someone moves item. It was discussed and benchmarked a lot 10-20 years ago. As I remember, if was on list of planned TFS 1.0 features, but it was dropped.
When player moves single item, it's not a problem, but if player moves around BP with 1k items inside depot, it will call 1k INSERTs every move. Even, if you replace current save code with binary format or save items into Redis, it's still use too much CPU just to serialize items.
It was tested in most optimized scenario: 'save' did not write anything anywhere, just cloned items in RAM and passed them to second thread, that saved them. It still used too much CPU, when players moved BP around in house (as in your system, just added to HouseTile to save house, when item is moved).

Binary items format even with BLOB in MySQL is much faster than current relational version. Players load/save time is 3-40 times faster after changing items to binary format. You can make it even faster by saving items directly in files on SSD/RAM disk.
Binary items save for TFS 1.4:
 
Last edited:
Thanks for that gesior. As I suspected the reason it won’t work has to do with these weird things TFS allows. I wonder, how tibia handles it. There aren’t many games that allow 1 millions items to be in a single bag stack which is in 5 other bags all having a million. This is where I have to move away from TFS development for my own server. I won’t have to deal with this type of situation because bag space and capacity are going to limit it. My server will utilize a system closer to WoWs bag and bank system. Including bags not being able to be put inside each other other than your first bag (const_slot_backpack) which cannot be replaced and is never lost. Which is limited to 8 bags that can be added to it.

I will say it’s possible to limit the queries by utilizing a loop to get all the items that need to be updated to insert into a single query for execution but yes I see how it can get bogged down very easily with large item counts.

Thank you everyone that said something. I think I know what I should be doing from here. Unfortunately, TFS is stuck until someone better comes along (not me) lmao.
 
Last edited:
There aren’t many games that allow 1 millions items to be in a single bag stack which is in 5 other bags all having a million.
This is a thing you must fix.
That's what RL Tibia did long time ago:
  • limit number of items in depot
  • save players/houses only on server restart, if it crashes, all players/items go back to last server restart

and this is what many big OTSes do:
  • limit number of items on house tile (including items inside bags)
  • limit total number of items in container (bag/depot) - including sending parcels to full depot (except adding items to depot, when player leaves house)
  • limit 'deepness' of containers (how many containers inside other containers server allow to add)

You can't add it with few simple IFs ex. 'on add item -> count items inside bag and block adding new item, if more than 5k' :(
In case of 1 million items, it would loop over all 1kk items in bag to count them every time players try to move item. It must be added like 'weight' of containers, every item move it propagates to all 'parent' containers change in weight. When you check weight of container, it does not calculate anything, weight is kept as container attribute:
Count of items inside and deepness of containers must propagate same way.
 
Basically what Gesior and Nightwolf have said is the best solution. Your personal approach scared me to be honest, as I dislike a lot the idea of spamming sql queries.
 
Back
Top