forked from Mirrorlandia_minetest/minetest
Take geographic distance into account for server list ordering (#12790)
This commit is contained in:
parent
5d8a4917c5
commit
87051fca26
@ -45,6 +45,27 @@ local function configure_selected_world_params(idx)
|
||||
end
|
||||
end
|
||||
|
||||
-- retrieved from https://wondernetwork.com/pings with (hopefully) representative cities
|
||||
-- Amsterdam, Auckland, Brasilia, Denver, Lagos, Singapore
|
||||
local latency_matrix = {
|
||||
["AF"] = { ["AS"]=258, ["EU"]=100, ["NA"]=218, ["OC"]=432, ["SA"]=308 },
|
||||
["AS"] = { ["EU"]=168, ["NA"]=215, ["OC"]=125, ["SA"]=366 },
|
||||
["EU"] = { ["NA"]=120, ["OC"]=298, ["SA"]=221 },
|
||||
["NA"] = { ["OC"]=202, ["SA"]=168 },
|
||||
["OC"] = { ["SA"]=411 },
|
||||
["SA"] = {}
|
||||
}
|
||||
function estimate_continent_latency(own, spec)
|
||||
local there = spec.geo_continent
|
||||
if not own or not there then
|
||||
return nil
|
||||
end
|
||||
if own == there then
|
||||
return 0
|
||||
end
|
||||
return latency_matrix[there][own] or latency_matrix[own][there]
|
||||
end
|
||||
|
||||
function render_serverlist_row(spec)
|
||||
local text = ""
|
||||
if spec.name then
|
||||
|
@ -15,28 +15,101 @@
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
serverlistmgr = {}
|
||||
serverlistmgr = {
|
||||
-- continent code we detected for ourselves
|
||||
my_continent = core.get_once("continent"),
|
||||
|
||||
-- list of locally favorites servers
|
||||
favorites = nil,
|
||||
|
||||
-- list of servers fetched from public list
|
||||
servers = nil,
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Efficient data structure for normalizing arbitrary scores attached to objects
|
||||
-- e.g. {{"a", 3.14}, {"b", 3.14}, {"c", 20}, {"d", 0}}
|
||||
-- -> {["d"] = 0, ["a"] = 0.5, ["b"] = 0.5, ["c"] = 1}
|
||||
local Normalizer = {}
|
||||
|
||||
function Normalizer:new()
|
||||
local t = {
|
||||
map = {}
|
||||
}
|
||||
setmetatable(t, self)
|
||||
self.__index = self
|
||||
return t
|
||||
end
|
||||
|
||||
function Normalizer:push(obj, score)
|
||||
if not self.map[score] then
|
||||
self.map[score] = {}
|
||||
end
|
||||
local t = self.map[score]
|
||||
t[#t + 1] = obj
|
||||
end
|
||||
|
||||
function Normalizer:calc()
|
||||
local list = {}
|
||||
for k, _ in pairs(self.map) do
|
||||
list[#list + 1] = k
|
||||
end
|
||||
table.sort(list)
|
||||
local ret = {}
|
||||
for i, k in ipairs(list) do
|
||||
local score = #list == 1 and 1 or ( (i - 1) / (#list - 1) )
|
||||
for _, obj in ipairs(self.map[k]) do
|
||||
ret[obj] = score
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- how much the pre-sorted server list contributes to the final ranking
|
||||
local WEIGHT_SORT = 2
|
||||
-- how much the estimated latency contributes to the final ranking
|
||||
local WEIGHT_LATENCY = 1
|
||||
|
||||
local function order_server_list(list)
|
||||
local res = {}
|
||||
--orders the favorite list after support
|
||||
-- calculate the scores
|
||||
local s1 = Normalizer:new()
|
||||
local s2 = Normalizer:new()
|
||||
for i, fav in ipairs(list) do
|
||||
-- first: the original position
|
||||
s1:push(fav, #list - i)
|
||||
-- second: estimated latency
|
||||
local ping = (fav.ping or 0) * 1000
|
||||
if ping < 400 then
|
||||
-- If ping is over 400ms, assume the server has latency issues
|
||||
-- anyway and don't estimate
|
||||
ping = estimate_continent_latency(serverlistmgr.my_continent, fav) or ping
|
||||
end
|
||||
s2:push(fav, -ping)
|
||||
end
|
||||
s1 = s1:calc()
|
||||
s2 = s2:calc()
|
||||
|
||||
-- make a shallow copy and pre-calculate ordering
|
||||
local res, order = {}, {}
|
||||
for i = 1, #list do
|
||||
local fav = list[i]
|
||||
if is_server_protocol_compat(fav.proto_min, fav.proto_max) then
|
||||
res[#res + 1] = fav
|
||||
end
|
||||
end
|
||||
for i = 1, #list do
|
||||
local fav = list[i]
|
||||
if not is_server_protocol_compat(fav.proto_min, fav.proto_max) then
|
||||
res[#res + 1] = fav
|
||||
end
|
||||
res[i] = fav
|
||||
|
||||
local n = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY
|
||||
order[fav] = n
|
||||
end
|
||||
|
||||
-- now sort the list
|
||||
table.sort(res, function(fav1, fav2)
|
||||
return order[fav1] > order[fav2]
|
||||
end)
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
local public_downloading = false
|
||||
local geoip_downloading = false
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function serverlistmgr.sync()
|
||||
@ -56,6 +129,36 @@ function serverlistmgr.sync()
|
||||
return
|
||||
end
|
||||
|
||||
-- only fetched once per MT instance
|
||||
if not serverlistmgr.my_continent and not geoip_downloading then
|
||||
geoip_downloading = true
|
||||
core.handle_async(
|
||||
function(param)
|
||||
local http = core.get_http_api()
|
||||
local url = core.settings:get("serverlist_url") .. "/geoip"
|
||||
|
||||
local response = http.fetch_sync({ url = url })
|
||||
if not response.succeeded then
|
||||
return
|
||||
end
|
||||
|
||||
local retval = core.parse_json(response.data)
|
||||
return retval and type(retval.continent) == "string" and retval.continent
|
||||
end,
|
||||
nil,
|
||||
function(result)
|
||||
geoip_downloading = false
|
||||
serverlistmgr.my_continent = result
|
||||
core.set_once("continent", result)
|
||||
-- reorder list if we already have it
|
||||
if serverlistmgr.servers then
|
||||
serverlistmgr.servers = order_server_list(serverlistmgr.servers)
|
||||
core.event_handler("Refresh")
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
if public_downloading then
|
||||
return
|
||||
end
|
||||
@ -79,7 +182,7 @@ function serverlistmgr.sync()
|
||||
end,
|
||||
nil,
|
||||
function(result)
|
||||
public_downloading = nil
|
||||
public_downloading = false
|
||||
local favs = order_server_list(result)
|
||||
if favs[1] then
|
||||
serverlistmgr.servers = favs
|
||||
|
@ -1,4 +1,4 @@
|
||||
_G.core = {}
|
||||
_G.core = {get_once = function(_) end}
|
||||
_G.vector = {metatable = {}}
|
||||
_G.unpack = table.unpack
|
||||
_G.serverlistmgr = {}
|
||||
|
@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "client/renderingengine.h"
|
||||
#include "network/networkprotocol.h"
|
||||
#include "content/mod_configuration.h"
|
||||
#include "threading/mutex_auto_lock.h"
|
||||
|
||||
/******************************************************************************/
|
||||
std::string ModApiMainMenu::getTextData(lua_State *L, std::string name)
|
||||
@ -1007,6 +1008,44 @@ int ModApiMainMenu::l_do_async_callback(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// this is intentionally a global and not part of MainMenuScripting or such
|
||||
namespace {
|
||||
std::unordered_map<std::string, std::string> once_values;
|
||||
std::mutex once_mutex;
|
||||
}
|
||||
|
||||
int ModApiMainMenu::l_set_once(lua_State *L)
|
||||
{
|
||||
std::string key = readParam<std::string>(L, 1);
|
||||
if (lua_isnil(L, 2))
|
||||
return 0;
|
||||
std::string value = readParam<std::string>(L, 2);
|
||||
|
||||
{
|
||||
MutexAutoLock lock(once_mutex);
|
||||
once_values[key] = value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ModApiMainMenu::l_get_once(lua_State *L)
|
||||
{
|
||||
std::string key = readParam<std::string>(L, 1);
|
||||
|
||||
{
|
||||
MutexAutoLock lock(once_mutex);
|
||||
auto it = once_values.find(key);
|
||||
if (it == once_values.end())
|
||||
lua_pushnil(L);
|
||||
else
|
||||
lua_pushstring(L, it->second.c_str());
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
void ModApiMainMenu::Initialize(lua_State *L, int top)
|
||||
{
|
||||
@ -1054,6 +1093,8 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
|
||||
API_FCT(open_dir);
|
||||
API_FCT(share_file);
|
||||
API_FCT(do_async_callback);
|
||||
API_FCT(set_once);
|
||||
API_FCT(get_once);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -156,6 +156,9 @@ private:
|
||||
|
||||
static int l_share_file(lua_State *L);
|
||||
|
||||
static int l_set_once(lua_State *L);
|
||||
|
||||
static int l_get_once(lua_State *L);
|
||||
|
||||
// async
|
||||
static int l_do_async_callback(lua_State *L);
|
||||
|
Loading…
Reference in New Issue
Block a user