aboutsummaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/mainmenu/common.lua21
-rw-r--r--builtin/mainmenu/serverlistmgr.lua125
-rw-r--r--builtin/mainmenu/tests/serverlistmgr_spec.lua2
3 files changed, 136 insertions, 12 deletions
diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua
index 81e28f2bb..471360581 100644
--- a/builtin/mainmenu/common.lua
+++ b/builtin/mainmenu/common.lua
@@ -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
diff --git a/builtin/mainmenu/serverlistmgr.lua b/builtin/mainmenu/serverlistmgr.lua
index 964d0c584..06dc15777 100644
--- a/builtin/mainmenu/serverlistmgr.lua
+++ b/builtin/mainmenu/serverlistmgr.lua
@@ -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
- 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
+ -- 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 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
diff --git a/builtin/mainmenu/tests/serverlistmgr_spec.lua b/builtin/mainmenu/tests/serverlistmgr_spec.lua
index ab7a6c60c..013bd0a28 100644
--- a/builtin/mainmenu/tests/serverlistmgr_spec.lua
+++ b/builtin/mainmenu/tests/serverlistmgr_spec.lua
@@ -1,4 +1,4 @@
-_G.core = {}
+_G.core = {get_once = function(_) end}
_G.vector = {metatable = {}}
_G.unpack = table.unpack
_G.serverlistmgr = {}