• 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!
  • 2026 staff recruitment is open! Check it out and consider applying!

TFS 1.2 Any Optimization suggestion

Tbol

Well-Known Member
Joined
Apr 7, 2019
Messages
625
Reaction score
71
[No, using new distro is not the option, sticking with legendary tfs 1.2, so any optimization comits would be appreciated, but legit ones that are tested]
Code:
[22/09/2025 18:06:07]
Thread: 1 Cpu usage: 94.0832% Idle: 5.35844% Other: 0.55837%
 Time (ms)     Calls     Rel usage %    Real usage % Description
      5586       295       19.79187%       18.62082% std::bind(&Game::checkCreatures, this, (index + 1) % EVENT_CREATURECOUNT)
      4999     91145       17.71325%       16.66519% std::bind(&Game::checkCreatureWalk, &g_game, getID())
      3621     29551       12.83180%       12.07257% std::bind(&Game::updateCreatureWalk, &g_game, getID())
      2897      1720       10.26586%        9.65845% functor
      2319      1792        8.21824%        7.73198% std::bind(&Game::checkPlayersAttack, this)
      1956     16016        6.93175%        6.52161% &Game::playerSay
      1658       119        5.87537%        5.52773% std::bind(&Game::checkDecay, this)
      1139     45850        4.03695%        3.79809% std::bind(&Game::checkCreatureAttack, &g_game, getID())
       952      1809        3.37476%        3.17508% &Game::parsePlayerExtendedOpcode
       782     10892        2.77308%        2.60900% std::bind(&LuaEnvironment::executeTimerEvent, &g_luaEnvironment, lastTimerEventId)
       686      3358        2.43282%        2.28887% std::bind(&Spawn::scheduleSpawn, this, spawnId, sb, interval - 1400)
       653      1160        2.31692%        2.17983% std::bind(&Game::executeDeath, &g_game, getID())
       564      5437        1.99918%        1.88089% std::bind(&ProtocolGame::parseNewWalk, getThis(), playerWalkId, predictiveWalkId, playerId, playerPosition, flags, path)
       121      3400        0.43089%        0.40539% &Game::playerUseItem
        84      1647        0.30066%        0.28287% &Game::playerSetAttackedCreature
        47      2733        0.16898%        0.15898% std::bind(&Spawn::checkSpawn, this)
        44     17451        0.15692%        0.14764% std::bind(&ProtocolGame::sendNewPing, getThis(), pingId)
        37      2039        0.13223%        0.12440% &Game::playerTurn
        36     17451        0.12937%        0.12171% &Game::playerReceiveNewPing
 
Yea when it reaches 100 it becomes a problem becaue lag spikes appear. About checkCreatureWalk i have party module that displays party members maybe thats consuming a lot of resources now aswell particulary for checkCreatures.
IF you use my system you could increase update interval from 2s to 5, its sending request per user so if you have a lot of members in party they send request to server every 2s and it needs to calculate
 
IF you use my system you could increase update interval from 2s to 5, its sending request per user so if you have a lot of members in party they send request to server every 2s and it needs to calculate
isnt your system has serious backdoor issues?
 
645 2219 12.36401% 2.15064% data/creaturescripts/scripts/extendedopcode.lua:onExtendedOpcode
If these 2.15% are from @Sorky party module, I would remove it.

If players like that system, you should rewrite it, to send party info to all players with some onThink. All party members should receive same network packet with info about all party members, so this system won't generate 20 different packets for 20 members of single party. It should also use binary communication, not JSON, as it does now in Sorky module.
 
If these 2.15% are from @Sorky party module, I would remove it.

If players like that system, you should rewrite it, to send party info to all players with some onThink. All party members should receive same network packet with info about all party members, so this system won't generate 20 different packets for 20 members of single party. It should also use binary communication, not JSON, as it does now in Sorky module.
I use this for party module tfs side. Creaturescript
LUA:
local OPCODE_PARTY = 160

function onThink(creature, interval)
    -- only players have parties
    if not creature:isPlayer() then
        return true
    end

    local party = creature:getParty()
    if not party then
        return true
    end

    local pos = creature:getPosition()
    local _data = {
        name = creature:getName(),
        pos = {x = pos.x, y = pos.y, z = pos.z}
    }

    -- send update to all members + leader
    local leader = party:getLeader()
    if leader then
        leader:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "update", player = _data}))
    end
    for _, member in ipairs(party:getMembers()) do
        member:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "update", player = _data}))
    end

    return true
end

and this is for party.lua
Code:
local OPCODE_PARTY = 160

function Party:onJoin(player)
    addEvent(function() Arena:onPartySizeChange(self) end, 1)

    -- PARTY ICON START
    local _members = {}
    local leader = self:getLeader()

    table.insert(_members, {name = player:getName(), vocation = player:getVocation():getClientId(), pos = player:getPosition()})
    for _, member in ipairs(self:getMembers()) do
        table.insert(_members, {name = member:getName(), vocation = member:getVocation():getClientId(), pos = member:getPosition()})
    end
    table.insert(_members, {name = leader:getName(), vocation = leader:getVocation():getClientId(), pos = leader:getPosition()})

    player:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "join", members = _members}))
    leader:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "join", members = _members}))

    for _, member in ipairs(self:getMembers()) do
        member:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "join", members = _members}))
    end

    player:registerEvent("partyExtended")

    if #self:getMembers() == 0 then
        leader:registerEvent("partyExtended")
    end
    -- PARTY ICON END
    return true
end

function Party:onLeave(player)
    Arena:onPartySizeChange(self)

    -- PARTY ICON START
    local members = self:getMembers()

    for _, member in ipairs(members) do
        member:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "leave", name = player:getName()}))
    end
    self:getLeader():sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "leave", name = player:getName()}))

    player:unregisterEvent("partyExtended")
    -- PARTY ICON END
    return true
end

function Party:onDisband()
    -- PARTY ICON START
    local members = self:getMembers()

    for _, member in ipairs(members) do
        member:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "leave", name = member:getName()}))
        member:unregisterEvent("partyExtended")
    end
    self:getLeader():sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "leave", name = self:getLeader():getName()}))
    self:getLeader():unregisterEvent("partyExtended")
    -- PARTY ICON END
    return true
end
So idk if its bad code or not and maybe thats the reason why its such a high cpu usage
 
I use this for party module tfs side. Creaturescript
LUA:
local OPCODE_PARTY = 160

function onThink(creature, interval)
    -- only players have parties
    if not creature:isPlayer() then
        return true
    end

    local party = creature:getParty()
    if not party then
        return true
    end

    local pos = creature:getPosition()
    local _data = {
        name = creature:getName(),
        pos = {x = pos.x, y = pos.y, z = pos.z}
    }

    -- send update to all members + leader
    local leader = party:getLeader()
    if leader then
        leader:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "update", player = _data}))
    end
    for _, member in ipairs(party:getMembers()) do
        member:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "update", player = _data}))
    end

    return true
end

and this is for party.lua
Code:
local OPCODE_PARTY = 160

function Party:onJoin(player)
    addEvent(function() Arena:onPartySizeChange(self) end, 1)

    -- PARTY ICON START
    local _members = {}
    local leader = self:getLeader()

    table.insert(_members, {name = player:getName(), vocation = player:getVocation():getClientId(), pos = player:getPosition()})
    for _, member in ipairs(self:getMembers()) do
        table.insert(_members, {name = member:getName(), vocation = member:getVocation():getClientId(), pos = member:getPosition()})
    end
    table.insert(_members, {name = leader:getName(), vocation = leader:getVocation():getClientId(), pos = leader:getPosition()})

    player:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "join", members = _members}))
    leader:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "join", members = _members}))

    for _, member in ipairs(self:getMembers()) do
        member:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "join", members = _members}))
    end

    player:registerEvent("partyExtended")

    if #self:getMembers() == 0 then
        leader:registerEvent("partyExtended")
    end
    -- PARTY ICON END
    return true
end

function Party:onLeave(player)
    Arena:onPartySizeChange(self)

    -- PARTY ICON START
    local members = self:getMembers()

    for _, member in ipairs(members) do
        member:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "leave", name = player:getName()}))
    end
    self:getLeader():sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "leave", name = player:getName()}))

    player:unregisterEvent("partyExtended")
    -- PARTY ICON END
    return true
end

function Party:onDisband()
    -- PARTY ICON START
    local members = self:getMembers()

    for _, member in ipairs(members) do
        member:sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "leave", name = member:getName()}))
        member:unregisterEvent("partyExtended")
    end
    self:getLeader():sendExtendedOpcode(OPCODE_PARTY, json.encode({type = "leave", name = self:getLeader():getName()}))
    self:getLeader():unregisterEvent("partyExtended")
    -- PARTY ICON END
    return true
end
So idk if its bad code or not and maybe thats the reason why its such a high cpu usage
If you break down what is actually happening, it can be severely improved.

4 people in a party means that, 16 messages are sent from the server and each client is receiving 4 separate messages. Also, this information is being sent by JSON.

The server should be sending just one message to all members of the party, with updated positions for each members. So just one message to each client, and data should be sent in binary, not a string (JSON).
 
If you break down what is actually happening, it can be severely improved.

4 people in a party means that, 16 messages are sent from the server and each client is receiving 4 separate messages. Also, this information is being sent by JSON.

The server should be sending just one message to all members of the party, with updated positions for each members. So just one message to each client, and data should be sent in binary, not a string (JSON).
what you mean break down what is happening?
 
If these 2.15% come from the @Sorky party module, I would remove it.

If players like that system, you should rewrite it so that it sends party information to all players via an onThink function. All party members should receive the same network packet containing information about all party members, so that this system doesn't generate 20 different packets for the 20 members of a single party. It should also use binary communication rather than JSON as it does in the Sorky module now.
It generates the same message for all party members, but your idea of sending an update to all members every two seconds instead of requesting it from the client is good. I didn't think of that.

BUT
I don't send JSON at all — it's a string. It's faster and easier to debug than a byte array.
Packets are generated every two seconds, so it shouldn't impact performance that much

there is field for improvments but sadly i won't have time till november if not december to code anything better
 
the first huge win you can get is by eliminating std::bind.. that's got a pretty big overhead for no reason at all
Is there commit for this somewhere?

Edit - also in dispatcher-slow it logs out multiple messages of this ine one second
[25/09/2025 22:27:53] Execution time: 14 ms - std::bind(&Game::checkDecay, this) - checkDecay
[25/09/2025 22:27:53] Execution time: 12 ms - std::bind(&Game::checkCreatures, this, (index + 1) % EVENT_CREATURECOUNT) - checkCreatures
its basically spamming it every second
 
Last edited:
Is there commit for this somewhere?
I did some tests. lambda is around 2 times faster than std::bind (code like on OTS: every std::bind/lambda is used only once), but it does not matter much. In Debug compilation mode your server could save up to 0.16% CPU, in any other mode it could save 0.01%.
Replacing std::bind with lambda makes OTS Stats report all C++ functions as functor, so you cannot read anything from dispacher.log/slow/very_slow. You could only read Lua logs from OTS Stats.

My tests:
Cheap VPS can process (create and call) 3.6kk std::bind per second in worst case (C++ optimization by compiler disabled [-O0]) - lambda 7.6kk.
Same VPS with minimal C++ optimization (-O1) can process 40kk std::bind per second - lambda 64kk.

In your OTS Stats, we can count that your server calls 253-337k events per 30 seconds, which is 11k events per second.
11k / 3.6kk = 0.003 is 0.3% CPU. In best case scenario, you would reduce that 0.3% to 0.14% (save 0.16% CPU).
If you do not compile production server in Debug mode (cmake -DMAKE_BUILD_TYPE=Debug - disables C++ optimizer), it can process 40kk events per second with std::bind, which is 0.0275% CPU. With lambda, it could process 64kk, which would be 0.0171%, so you would save 0.0104% CPU.

My test C++ code, Dockerfile to run it and VPS results in attached file.
 

Attachments

Back
Top