Added support for pinging servers, server list now reorders containers instead of recreating all of them for every refresh, server list updater should restart on crash

This commit is contained in:
2022-09-10 08:29:55 -05:00
parent af28013a7f
commit 50ec9fc1da
6 changed files with 168 additions and 55 deletions

View File

@@ -11,6 +11,7 @@ gem "async-http"
gem "async-websocket" gem "async-websocket"
gem "thread-local" gem "thread-local"
gem "ircparser" gem "ircparser"
gem "net-ping"
# group :windows_packaging do # group :windows_packaging do
# gem "rake" # gem "rake"

View File

@@ -1,7 +1,7 @@
class W3DHub class W3DHub
class Api class Api
class ServerListServer class ServerListServer
attr_reader :id, :game, :address, :port, :region, :channel, :status attr_reader :id, :game, :address, :port, :region, :channel, :ping, :status
def initialize(hash) def initialize(hash)
@data = hash @data = hash
@@ -12,8 +12,12 @@ class W3DHub
@port = @data[:port] @port = @data[:port]
@region = @data[:region] @region = @data[:region]
@channel = @data[:channel] || "release" @channel = @data[:channel] || "release"
@ping = -1
@status = @data[:status] ? Status.new(@data[:status]) : nil @status = @data[:status] ? Status.new(@data[:status]) : nil
@last_pinged = -1
@ping_interval = 30
end end
def update(hash) def update(hash)
@@ -29,6 +33,17 @@ class W3DHub
@status.instance_variable_set(:@teams, hash[:teams]&.map { |t| Team.new(t) }) if hash[:teams] @status.instance_variable_set(:@teams, hash[:teams]&.map { |t| Team.new(t) }) if hash[:teams]
@status.instance_variable_set(:@players, hash[:players]&.select { |t| t[:nick] != "Nod" && t[:nick] != "GDI" }&.map { |t| Player.new(t) }) if hash[:players] @status.instance_variable_set(:@players, hash[:players]&.select { |t| t[:nick] != "Nod" && t[:nick] != "GDI" }&.map { |t| Player.new(t) }) if hash[:players]
if Gosu.milliseconds - @last_pinged >= @ping_interval
@last_pinged = Gosu.milliseconds
Thread.new do
ping = Net::Ping::External.new(@address)
@ping = (ping.duration * 1000.0).round if ping.ping?
States::Interface.instance&.update_server_ping(self)
end
end
return true return true
end end

View File

@@ -77,53 +77,65 @@ class W3DHub
def run def run
Thread.new do Thread.new do
Async do |task| begin
internet = Async::HTTP::Internet.instance connect
rescue => e
puts e
puts e.backtrace
logger.debug(LOG_TAG) { "Requesting connection token..." } sleep 10
response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""]) retry
data = JSON.parse(response.read, symbolize_names: true) end
end
end
id = data[:connectionToken] def connect
endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names) Async do |task|
internet = Async::HTTP::Internet.instance
logger.debug(LOG_TAG) { "Connecting to websocket..." } logger.debug(LOG_TAG) { "Requesting connection token..." }
Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection| response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""])
logger.debug(LOG_TAG) { "Requesting json protocol, v1..." } data = JSON.parse(response.read, symbolize_names: true)
connection.write({ protocol: "json", version: 1 })
connection.flush
logger.debug(LOG_TAG) { "Received: #{connection.read}" }
logger.debug(LOG_TAG) { "Sending \"PING\"(?)" }
connection.write({ "type": 6 })
logger.debug(LOG_TAG) { "Subscribing to server changes..." } id = data[:connectionToken]
Store.server_list.each_with_index do |server, i| endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
i += 1
mode = 1 # 2 full details, 1 basic details
out = { "type": 1, "invocationId": "#{i}", "target": "SubscribeToServerStatusUpdates", "arguments": [server.id, mode] }
connection.write(out)
end
logger.debug(LOG_TAG) { "Waiting for data..." } logger.debug(LOG_TAG) { "Connecting to websocket..." }
while (message = connection.read) Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection|
connection.write({ type: 6 }) if message.first[:type] == 6 logger.debug(LOG_TAG) { "Requesting json protocol, v1..." }
connection.write({ protocol: "json", version: 1 })
connection.flush
logger.debug(LOG_TAG) { "Received: #{connection.read}" }
logger.debug(LOG_TAG) { "Sending \"PING\"(?)" }
connection.write({ "type": 6 })
if message&.first&.fetch(:type) == 1 logger.debug(LOG_TAG) { "Subscribing to server changes..." }
message.each do |rpc| Store.server_list.each_with_index do |server, i|
next unless rpc[:target] == "ServerStatusChanged" i += 1
mode = 1 # 2 full details, 1 basic details
out = { "type": 1, "invocationId": "#{i}", "target": "SubscribeToServerStatusUpdates", "arguments": [server.id, mode] }
connection.write(out)
end
id, data = rpc[:arguments] logger.debug(LOG_TAG) { "Waiting for data..." }
server = Store.server_list.find { |s| s.id == id } while (message = connection.read)
server_updated = server&.update(data) connection.write({ type: 6 }) if message.first[:type] == 6
States::Interface.instance&.update_server_browser(server) if server_updated
end if message&.first&.fetch(:type) == 1
message.each do |rpc|
next unless rpc[:target] == "ServerStatusChanged"
id, data = rpc[:arguments]
server = Store.server_list.find { |s| s.id == id }
server_updated = server&.update(data)
States::Interface.instance&.update_server_browser(server) if server_updated
end end
end end
end end
ensure
logger.debug(LOG_TAG) { "Cleaning up..." }
@@instance = nil
end end
ensure
logger.debug(LOG_TAG) { "Cleaning up..." }
@@instance = nil
end end
end end
end end

View File

@@ -15,6 +15,9 @@ class W3DHub
Store.applications.games.each { |game| @filters[game.id.to_sym] = true if @filters[game.id.to_sym].nil? } Store.applications.games.each { |game| @filters[game.id.to_sym] = true if @filters[game.id.to_sym].nil? }
@ping_icons = {}
generate_ping_icons
body.clear do body.clear do
stack(width: 1.0, height: 1.0, padding: 8) do stack(width: 1.0, height: 1.0, padding: 8) do
stack(width: 1.0, height: 18) do stack(width: 1.0, height: 18) do
@@ -142,7 +145,8 @@ class W3DHub
if @refresh_server_list && Gosu.milliseconds >= @refresh_server_list if @refresh_server_list && Gosu.milliseconds >= @refresh_server_list
@refresh_server_list = nil @refresh_server_list = nil
populate_server_list # populate_server_list
reorder_server_list
if @selected_server&.id == @refresh_server&.id if @selected_server&.id == @refresh_server&.id
if @refresh_server if @refresh_server
@@ -158,11 +162,79 @@ class W3DHub
end end
end end
def generate_ping_icons
signal3 = get_image("#{GAME_ROOT_PATH}/media/ui_icons/signal3.png")
signal2 = get_image("#{GAME_ROOT_PATH}/media/ui_icons/signal2.png")
signal1 = get_image("#{GAME_ROOT_PATH}/media/ui_icons/signal1.png")
question = get_image("#{GAME_ROOT_PATH}/media/ui_icons/question.png")
good = Gosu.render(signal3.width, signal3.height) do
signal3.draw(0, 0, 0, 1, 1, 0xff_008000)
end
fair = Gosu.render(signal3.width, signal3.height) do
signal3.draw(0, 0, 0, 1, 1, 0xff_444444)
signal2.draw(0, 0, 0, 1, 1, 0xff_804000)
end
poor = Gosu.render(signal3.width, signal3.height) do
signal3.draw(0, 0, 0, 1, 1, 0xff_444444)
signal1.draw(0, 0, 0, 1, 1, 0xff_800000)
end
bad = Gosu.render(signal3.width, signal3.height) do
signal3.draw(0, 0, 0, 1, 1, 0xff_444444)
end
unknown = Gosu.render(signal3.width, signal3.height) do
signal3.draw(0, 0, 0, 1, 1, 0xff_222222)
question.draw(0, 0, 0, 1, 1, 0xff_888888)
end
@ping_icons[:good] = good
@ping_icons[:fair] = fair
@ping_icons[:poor] = poor
@ping_icons[:bad] = bad
@ping_icons[:unknown] = unknown
end
def ping_icon(ping)
case ping
when 0..160
@ping_icons[:good]
when 161..250
@ping_icons[:fair]
when 251..1_000
@ping_icons[:poor]
when 1_001..5_000
@ping_icons[:bad]
else
@ping_icons[:unknown]
end
end
def refresh_server_list(server) def refresh_server_list(server)
@refresh_server_list = Gosu.milliseconds + 3_000 @refresh_server_list = Gosu.milliseconds + 3_000
@refresh_server = server if @selected_server&.id == server.id @refresh_server = server if @selected_server&.id == server.id
end end
def update_server_ping(server)
container = @server_list_container.children.find do |child|
child.style.tag == server.id
end
if container
ping_image = container.children.map { |c| c.children }.flatten.find do |child|
child.style.tag == :ping
end
if ping_image
ping_image.value = ping_icon(server.ping)
ping_image.tip = "#{server.ping}ms"
end
end
end
def stylize_selected_server(server_container) def stylize_selected_server(server_container)
server_container.style.server_item_background = server_container.style.default[:background] server_container.style.server_item_background = server_container.style.default[:background]
server_container.style.server_item_hover_background = server_container.style.hover[:background] server_container.style.server_item_hover_background = server_container.style.hover[:background]
@@ -175,6 +247,19 @@ class W3DHub
server_container.style.active[:background] = @selected_color server_container.style.active[:background] = @selected_color
end end
def reorder_server_list
@server_list_container.children.sort_by! do |child|
s = Store.server_list.find { |s| s.id == child.style.tag }
[s&.status&.player_count, s&.id]
end.reverse!.each_with_index do |child, i|
child.style.background = 0xff_333333 if i.even?
child.style.background = 0 if i.odd?
end
@server_list_container.recalculate
end
def populate_server_list def populate_server_list
Store.server_list = Store.server_list.sort_by! { |s| [s&.status&.player_count, s&.id] }.reverse if Store.server_list Store.server_list = Store.server_list.sort_by! { |s| [s&.status&.player_count, s&.id] }.reverse if Store.server_list
@@ -188,41 +273,34 @@ class W3DHub
i += 1 i += 1
server_container = flow(width: 1.0, height: 48, hover: { background: 0xff_555566 }, active: { background: 0xff_555588 }) do server_container = flow(width: 1.0, height: 48, hover: { background: 0xff_555566 }, active: { background: 0xff_555588 }, tag: server.id) do
background 0xff_333333 if i.even? background 0xff_333333 if i.even?
flow(width: 48, height: 1.0, padding: 4) do flow(width: 48, height: 1.0, padding: 4) do
image game_icon(server), height: 1.0 image game_icon(server), height: 1.0, tag: :game_icon
end end
stack(width: 0.45, height: 1.0) do stack(width: 0.45, height: 1.0) do
inscription "<b>#{server&.status&.name}</b>" inscription "<b>#{server&.status&.name}</b>", tag: :server_name
flow(width: 1.0, height: 1.0) do flow(width: 1.0, height: 1.0) do
inscription server.channel, margin_right: 64, text_size: 14 inscription server.channel, margin_right: 64, text_size: 14, tag: :server_channel
inscription server.region, text_size: 14 inscription server.region, text_size: 14, tag: :server_region
end end
end end
flow(fill: true, height: 1.0) do flow(fill: true, height: 1.0) do
inscription "#{server&.status&.map}" inscription "#{server&.status&.map}", tag: :map_name
end end
flow(width: 0.11, height: 1.0) do flow(width: 0.11, height: 1.0) do
inscription "#{server&.status&.player_count}/#{server&.status&.max_players}" inscription "#{server&.status&.player_count}/#{server&.status&.max_players}", tag: :player_count
end end
# case rand(0..478)
# when 0..60
# image "#{GAME_ROOT_PATH}/media/ui_icons/signal3.png", width: 0.05, color: 0xff_008000
# when 61..160
# image "#{GAME_ROOT_PATH}/media/ui_icons/signal2.png", width: 0.05, color: 0xff_804000
# else
# image "#{GAME_ROOT_PATH}/media/ui_icons/signal1.png", width: 0.05, color: 0xff_800000
# end
flow(width: 48, height: 1.0, padding: 4) do flow(width: 48, height: 1.0, padding: 4) do
image "#{GAME_ROOT_PATH}/media/ui_icons/question.png", height: 1.0, color: 0xff_444444 puts "#{server&.status&.name}#{server.ping}"
image ping_icon(server.ping), height: 1.0, tip: "#{server.ping}ms", tag: :ping
end end
end end

View File

@@ -165,6 +165,12 @@ class W3DHub
@page.refresh_server_list(server) @page.refresh_server_list(server)
end end
def update_server_ping(server)
return unless @page.is_a?(Pages::ServerBrowser)
@page.update_server_ping(server)
end
def show_application_taskbar def show_application_taskbar
@application_taskbar_container.show @application_taskbar_container.show
end end

View File

@@ -50,6 +50,7 @@ require "async/http/internet/instance"
require "async/http/endpoint" require "async/http/endpoint"
require "async/websocket/client" require "async/websocket/client"
require "protocol/websocket/connection" require "protocol/websocket/connection"
require "net/ping"
I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"] I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"]
I18n.default_locale = :en I18n.default_locale = :en