From 50ec9fc1da44f139a770b254a33818e8dcf52f3f Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Sat, 10 Sep 2022 08:29:55 -0500 Subject: [PATCH] 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 --- Gemfile | 1 + lib/api/server_list_server.rb | 17 ++++- lib/api/server_list_updater.rb | 84 +++++++++++++---------- lib/pages/server_browser.rb | 114 ++++++++++++++++++++++++++----- lib/states/interface_redesign.rb | 6 ++ w3d_hub_linux_launcher.rb | 1 + 6 files changed, 168 insertions(+), 55 deletions(-) diff --git a/Gemfile b/Gemfile index b669e32..b62c3b0 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ gem "async-http" gem "async-websocket" gem "thread-local" gem "ircparser" +gem "net-ping" # group :windows_packaging do # gem "rake" diff --git a/lib/api/server_list_server.rb b/lib/api/server_list_server.rb index b7dac07..a75aee1 100644 --- a/lib/api/server_list_server.rb +++ b/lib/api/server_list_server.rb @@ -1,7 +1,7 @@ class W3DHub class Api class ServerListServer - attr_reader :id, :game, :address, :port, :region, :channel, :status + attr_reader :id, :game, :address, :port, :region, :channel, :ping, :status def initialize(hash) @data = hash @@ -12,8 +12,12 @@ class W3DHub @port = @data[:port] @region = @data[:region] @channel = @data[:channel] || "release" + @ping = -1 @status = @data[:status] ? Status.new(@data[:status]) : nil + + @last_pinged = -1 + @ping_interval = 30 end 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(:@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 end diff --git a/lib/api/server_list_updater.rb b/lib/api/server_list_updater.rb index 35aaf26..67e8082 100644 --- a/lib/api/server_list_updater.rb +++ b/lib/api/server_list_updater.rb @@ -77,53 +77,65 @@ class W3DHub def run Thread.new do - Async do |task| - internet = Async::HTTP::Internet.instance + begin + connect + rescue => e + puts e + puts e.backtrace - logger.debug(LOG_TAG) { "Requesting connection token..." } - response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""]) - data = JSON.parse(response.read, symbolize_names: true) + sleep 10 + retry + end + end + end - id = data[:connectionToken] - endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names) + def connect + Async do |task| + internet = Async::HTTP::Internet.instance - logger.debug(LOG_TAG) { "Connecting to websocket..." } - Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection| - 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 }) + logger.debug(LOG_TAG) { "Requesting connection token..." } + response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""]) + data = JSON.parse(response.read, symbolize_names: true) - logger.debug(LOG_TAG) { "Subscribing to server changes..." } - Store.server_list.each_with_index do |server, i| - 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[:connectionToken] + endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names) - logger.debug(LOG_TAG) { "Waiting for data..." } - while (message = connection.read) - connection.write({ type: 6 }) if message.first[:type] == 6 + logger.debug(LOG_TAG) { "Connecting to websocket..." } + Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection| + 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 - message.each do |rpc| - next unless rpc[:target] == "ServerStatusChanged" + logger.debug(LOG_TAG) { "Subscribing to server changes..." } + Store.server_list.each_with_index do |server, i| + 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] - 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 + logger.debug(LOG_TAG) { "Waiting for data..." } + while (message = connection.read) + connection.write({ type: 6 }) if message.first[:type] == 6 + + 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 - ensure - logger.debug(LOG_TAG) { "Cleaning up..." } - @@instance = nil end + ensure + logger.debug(LOG_TAG) { "Cleaning up..." } + @@instance = nil end end end diff --git a/lib/pages/server_browser.rb b/lib/pages/server_browser.rb index 6ca521b..e24d794 100644 --- a/lib/pages/server_browser.rb +++ b/lib/pages/server_browser.rb @@ -15,6 +15,9 @@ class W3DHub 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 stack(width: 1.0, height: 1.0, padding: 8) do stack(width: 1.0, height: 18) do @@ -142,7 +145,8 @@ class W3DHub if @refresh_server_list && Gosu.milliseconds >= @refresh_server_list @refresh_server_list = nil - populate_server_list + # populate_server_list + reorder_server_list if @selected_server&.id == @refresh_server&.id if @refresh_server @@ -158,11 +162,79 @@ class W3DHub 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) @refresh_server_list = Gosu.milliseconds + 3_000 @refresh_server = server if @selected_server&.id == server.id 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) server_container.style.server_item_background = server_container.style.default[: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 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 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 - 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? 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 stack(width: 0.45, height: 1.0) do - inscription "#{server&.status&.name}" + inscription "#{server&.status&.name}", tag: :server_name flow(width: 1.0, height: 1.0) do - inscription server.channel, margin_right: 64, text_size: 14 - inscription server.region, text_size: 14 + inscription server.channel, margin_right: 64, text_size: 14, tag: :server_channel + inscription server.region, text_size: 14, tag: :server_region end end flow(fill: true, height: 1.0) do - inscription "#{server&.status&.map}" + inscription "#{server&.status&.map}", tag: :map_name end 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 - # 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 - 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 diff --git a/lib/states/interface_redesign.rb b/lib/states/interface_redesign.rb index 15997cd..925854e 100644 --- a/lib/states/interface_redesign.rb +++ b/lib/states/interface_redesign.rb @@ -165,6 +165,12 @@ class W3DHub @page.refresh_server_list(server) end + def update_server_ping(server) + return unless @page.is_a?(Pages::ServerBrowser) + + @page.update_server_ping(server) + end + def show_application_taskbar @application_taskbar_container.show end diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb index c845522..5d4b15d 100644 --- a/w3d_hub_linux_launcher.rb +++ b/w3d_hub_linux_launcher.rb @@ -50,6 +50,7 @@ require "async/http/internet/instance" require "async/http/endpoint" require "async/websocket/client" require "protocol/websocket/connection" +require "net/ping" I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"] I18n.default_locale = :en