Open Shauren opened 8 months ago
We added support for marking methods as either world only, map only or both. I went through and changed a good amount of these to map/world only, but I probably missed some. I think this would be the best way to handle it, allowing the "global" state to deal with guild/party etc in multistate.
If for whatever reason some of these actions need to be done from a map state script, then I suppose LuaVal could be used as some form of message queue which could be checked during the global state execution and handled accordingly without the need for any extra form of queueing.
None of these are marked as world only
https://github.com/ElunaLuaEngine/Eluna/blob/6d3cdb74e397bf60804846ccedfcbf024b038e64/TrinityCore/GroupMethods.h#L451-L457 https://github.com/ElunaLuaEngine/Eluna/blob/6d3cdb74e397bf60804846ccedfcbf024b038e64/TrinityCore/GroupMethods.h#L476-L477 https://github.com/ElunaLuaEngine/Eluna/blob/6d3cdb74e397bf60804846ccedfcbf024b038e64/TrinityCore/GuildMethods.h#L262-L266 https://github.com/ElunaLuaEngine/Eluna/blob/6d3cdb74e397bf60804846ccedfcbf024b038e64/TrinityCore/GuildMethods.h#L271-L277 https://github.com/ElunaLuaEngine/Eluna/blob/6d3cdb74e397bf60804846ccedfcbf024b038e64/TrinityCore/PlayerMethods.h#L4077-L4078 https://github.com/ElunaLuaEngine/Eluna/blob/6d3cdb74e397bf60804846ccedfcbf024b038e64/TrinityCore/PlayerMethods.h#L4050-L4053
You can obtain a reference to guild/group from player methods https://github.com/ElunaLuaEngine/Eluna/blob/6d3cdb74e397bf60804846ccedfcbf024b038e64/TrinityCore/PlayerMethods.h#L3811-L3812 https://github.com/ElunaLuaEngine/Eluna/blob/6d3cdb74e397bf60804846ccedfcbf024b038e64/TrinityCore/PlayerMethods.h#L3864
all exposed functions mutating group and guild
Getting members of guild/group can also be unsafe depending on use. Tricky.
queue the code for later execution on world thread (queue itself must be threadsafe)
For executing functions from map states, communicating actions between states is one option. However, that is not maybe the most efficient way to handle executing functions from map context. It is also slightly limited as passing data between the states is limited and relatively slow. Having a mechanism for delayed execution of some code would make usage much more user friendly, but adds implementation complexity.
In my mind this would indeed mean that when some kind of function is called, a function of the current lua state is queued to be run on the world update thread later. We have had some discussion before on how we could achieve this.
Modify each function to be asynchronous, for example DoForPlayersInWorld(function (player) ...code... end)
where a closure would run for all players on world update later.
Alternatively create a kind of privileged mode achieved in similar manner, which is delayed to world update, but allows one to synchronously call unsafe functions. This would be more generic and user friendly I feel.
Deferred(function(unsafe_functions)
-- This code runs on world update
-- We could have access to unsafe functions only through a parameter like unsafe_functions here
local players = unsafe_functions:GetPlayersInWorld()
-- Or we could have them as global functions, that only exist/work inside deferred function
local players = GetPlayersInWorld()
end)
I myself feel like something along the lines of Deferred(function(unsafe_functions)
is the best choice so there is just one asynchonous function to make things simple and unsafe_functions would make it clear what functions exist in which scope.
Some problems to solve/consider:
local group = player:GetGroup()
then how do we handle calling/blocking local members = group:GetMembers()
when in and out of privilaged mode? Global functions are a bit easier to move into unsafe_functions, but unsafe member functions are less clear on how they could be handled. I somewhat dislike the idea of having functions that sometimes work and sometimes dont. But I feel like that is the only good option I can think of here. The member functions would print an error if not in privileged mode.I think step one is marking the methods outlined above as world only for now, and then we can think of a cleaner way to either defer or queue inline functions to be executed post map update.
I'm not overly worried about GetGuild and GetGroup in the map thread, I feel these are probably valid to have available, though they would be limited in scope to what methods would work on said objects with a lot of the methods marked as world only.
Oh I only pointed those out as a way to access a group/guild objects outside of their dedicated group/guild event hooks that run in world thread only
Also, the unbind/bind methods in player, are these not inherently safe in the map context since they are strictly tied to the player that already exists in the map context?
Also, the unbind/bind methods in player, are these not inherently safe in the map context since they are strictly tied to the player that already exists in the map context?
Ignore it, bind/unbind are safe (I thought at first that the InstanceSave object isnt safe to use but it has a mutex)
As for deferring execution, I did not think that far, only imagined doing that purely in c++, for example having LuaGuild::AddMember
be implemented as
int AddMember(Eluna* E, Guild* guild)
{
Player* player = E->CHECKOBJ<Player>(2);
uint8 rankId = E->CHECKVAL<uint8>(3, GUILD_RANK_NONE);
sWorld->IMAGINARY_ASYNC_QUEUE_QUEUEING_FUNCTION([guildId = guild->GetId(), guid = player->GET_GUID(), rankId]()
{
if (Guild* guild = sGuildMgr->GetGuildById(guildId))
{
CharacterDatabaseTransaction trans(nullptr);
guild->AddMember(trans, guid, rankId);
}
});
return 0;
}
Pushed an update to flag the outlined methods above as world only, let me know if you find others and I'll flag those accordingly as well.
The approach in https://github.com/ElunaLuaEngine/Eluna/issues/474#issuecomment-2038338535 would work fine for most if not all cases. The only difficulty is with the GetMembers of guild, group and the get players in world.
We could just keep the player object getter functions on world state. Or they could return guids instead of objects and if the guild/group functions would operate on guids rather than objects it would allow doing actions on players that are not on the same map through the guids.
The approach in #474 (comment) would work fine for most if not all cases. The only difficulty is with the GetMembers of guild, group and the get players in world.
We could just keep the player object getter functions on world state. Or they could return guids instead of objects and if the guild/group functions would operate on guids rather than objects it would allow doing actions on players that are not on the same map through the guids.
Latest version of #469 should actually handle that case, it prevents accessing objects whose bound map is different than current eluna state bound map
In multistate mode and worldserver map update threads > 1, all exposed functions mutating group and guild are missing synchronization
I'm talking about actions whose regular ingame packet handlers are marked as THREADUNSAFE, forced to only ever happen in main thread such as:
Solution could be one of