From 232ed2032f15b1ae1c6824bc5ad9989bb53fcbe3 Mon Sep 17 00:00:00 2001 From: cyberarm Date: Sat, 12 Feb 2022 08:47:48 -0600 Subject: [PATCH] Window is no longer a fiber, should prevent window from locking up due to a fiber not yielding, replaced ui's direct async calls with BackgroundWorker.foreground_job, show pulsing circle behind app logo on boot --- lib/api.rb | 18 +++-- lib/api/server_list_updater.rb | 58 +++++++-------- lib/background_worker.rb | 77 ++++++++++++++++++++ lib/cache.rb | 15 ++-- lib/pages/community.rb | 17 ++--- lib/pages/games.rb | 15 ++-- lib/pages/login.rb | 18 +++-- lib/pages/server_browser.rb | 31 ++++---- lib/states/boot.rb | 126 ++++++++++++++++++--------------- lib/window.rb | 5 +- w3d_hub_linux_launcher.rb | 9 +-- 11 files changed, 236 insertions(+), 153 deletions(-) create mode 100644 lib/background_worker.rb diff --git a/lib/api.rb b/lib/api.rb index eb91ea3..1805ffe 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -9,6 +9,10 @@ class W3DHub DEFAULT_HEADERS + [["Content-Type", "application/x-www-form-urlencoded"]] ).freeze + def self.on_fiber(method, *args, &callback) + BackgroundWorker.job(-> { Api.send(method, *args) }, callback) + end + #! === W3D Hub API === !# ENDPOINT = "https://secure.w3dhub.com".freeze @@ -165,6 +169,12 @@ class W3DHub SERVER_LIST_ENDPOINT = "https://gsh.w3dhub.com".freeze + def self.get(url, headers = DEFAULT_HEADERS, body = nil) + @client ||= Async::HTTP::Client.new(Async::HTTP::Endpoint.parse(SERVER_LIST_ENDPOINT, protocol: Async::HTTP::Protocol::HTTP10)) + + @client.get(url, headers, body) + end + # Method: GET # FORMAT: JSON @@ -182,8 +192,8 @@ class W3DHub # id, name, score, kills, deaths # ...players[]: # nick, team (index of teams array), score, kills, deaths - def self.server_list(internet, level = 1) - response = internet.get("#{SERVER_LIST_ENDPOINT}/listings/getAll/v2?statusLevel=#{level}", DEFAULT_HEADERS) + def self.server_list(level = 1) + response = get("#{SERVER_LIST_ENDPOINT}/listings/getAll/v2?statusLevel=#{level}") if response.success? data = JSON.parse(response.read, symbolize_names: true) @@ -204,8 +214,8 @@ class W3DHub # id, name, score, kills, deaths # ...players[]: # nick, team (index of teams array), score, kills, deaths - def self.server_details(internet, id, level) - response = internet.get("#{SERVER_LIST_ENDPOINT}/listings/getStatus/v2/#{id}?statusLevel=#{level}", DEFAULT_HEADERS) + def self.server_details(id, level) + response = get("#{SERVER_LIST_ENDPOINT}/listings/getStatus/v2/#{id}?statusLevel=#{level}") if response.success? hash = JSON.parse(response.read, symbolize_names: true) diff --git a/lib/api/server_list_updater.rb b/lib/api/server_list_updater.rb index 868ab71..b2262f3 100644 --- a/lib/api/server_list_updater.rb +++ b/lib/api/server_list_updater.rb @@ -74,45 +74,47 @@ class W3DHub end def run - Async do |task| - internet = Async::HTTP::Internet.instance + Thread.new do + Async do |task| + internet = Async::HTTP::Internet.instance - response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""]) - data = JSON.parse(response.read, symbolize_names: true) + response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""]) + data = JSON.parse(response.read, symbolize_names: true) - id = data[:connectionToken] - endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names) + id = data[:connectionToken] + endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names) - Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection| - connection.write({ protocol: "json", version: 1 }) - connection.flush - pp connection.read - connection.write({ "type": 6 }) + Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection| + connection.write({ protocol: "json", version: 1 }) + connection.flush + pp connection.read + connection.write({ "type": 6 }) - 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 + 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 - while (message = connection.read) - connection.write({ type: 6 }) if message.first[:type] == 6 + 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" + 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 + 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 + ensure + @@instance = nil end - ensure - @@instance = nil end end end diff --git a/lib/background_worker.rb b/lib/background_worker.rb new file mode 100644 index 0000000..291f177 --- /dev/null +++ b/lib/background_worker.rb @@ -0,0 +1,77 @@ +class W3DHub + class BackgroundWorker + @@instance = nil + @@alive = false + + def self.create + raise "BackgroundWorker instance already exists!" if @@instance + + @@alive = true + @@instance = self.new + + Async do + @@instance.handle_jobs + end + end + + def self.instance + @@instance + end + + def self.alive? + @@alive + end + + def self.shutdown! + @@alive = false + end + + def self.job(job, callback) + @@instance.add_job(Job.new(job, callback)) + end + + def self.foreground_job(job, callback) + @@instance.add_job(Job.new(job, callback, true)) + end + + def initialize + @jobs = [] + end + + def handle_jobs + while BackgroundWorker.alive? + job = @jobs.shift + + job&.do + + sleep 0.1 + end + end + + def add_job(job) + @jobs << job + end + + class Job + def initialize(job, callback, deliver_to_queue = false) + @job = job + @callback = callback + + @deliver_to_queue = deliver_to_queue + end + + def do + result = @job.call + deliver(result) + end + + def deliver(result) + if @deliver_to_queue + $window.main_thread_queue << -> { @callback.call(result) } + else + @callback.call(result) + end + end + end + end +end diff --git a/lib/cache.rb b/lib/cache.rb index 1116870..5f83420 100644 --- a/lib/cache.rb +++ b/lib/cache.rb @@ -13,17 +13,10 @@ class W3DHub if !force_fetch && File.exist?(path) path else - internet = Async::HTTP::Internet.instance - - response = internet.get(uri, W3DHub::Api::DEFAULT_HEADERS) - - if response.success? - response.save(path, "wb") - - return path - end - - false + BackgroundWorker.job( + -> { Async::HTTP::Internet.instance.get(uri, W3DHub::Api::DEFAULT_HEADERS) }, + ->(response) { response.save(path, "wb") if response.success? } + ) end end diff --git a/lib/pages/community.rb b/lib/pages/community.rb index e9195da..97a1f99 100644 --- a/lib/pages/community.rb +++ b/lib/pages/community.rb @@ -60,25 +60,20 @@ class W3DHub para I18n.t(:"games.fetching_news"), padding: 8 end - Async do - internet = Async::HTTP::Internet.instance - - fetch_w3dhub_news - populate_w3dhub_news - end + BackgroundWorker.foreground_job(-> { fetch_w3dhub_news }, ->(result) { populate_w3dhub_news }) end end def fetch_w3dhub_news news = Api.news("launcher-home") - if news - news.items[0..9].each do |item| - Cache.fetch(item.image) - end + return unless news - @w3dhub_news = news + news.items[0..9].each do |item| + Cache.fetch(item.image) end + + @w3dhub_news = news end def populate_w3dhub_news diff --git a/lib/pages/games.rb b/lib/pages/games.rb index 1a6c46f..b7f6880 100644 --- a/lib/pages/games.rb +++ b/lib/pages/games.rb @@ -171,23 +171,20 @@ class W3DHub title I18n.t(:"games.fetching_news"), padding: 8 end - Async do - fetch_game_news(game) - populate_game_news(game) - end + BackgroundWorker.foreground_job(-> { fetch_game_news(game) }, ->(result) { populate_game_news(game) }) end end def fetch_game_news(game) news = Api.news(game.id) - if news - news.items[0..9].each do |item| - Cache.fetch(item.image) - end + return unless news - @game_news[game.id] = news + news.items[0..9].each do |item| + Cache.fetch(item.image) end + + @game_news[game.id] = news end def populate_game_news(game) diff --git a/lib/pages/login.rb b/lib/pages/login.rb index 96c4ca7..989ac28 100644 --- a/lib/pages/login.rb +++ b/lib/pages/login.rb @@ -36,15 +36,12 @@ class W3DHub # Do network stuff - Async do - account = Api.user_login(@username.value, @password.value) - + Api.on_fiber(:user_login, @username.value, @password.value) do |account| if account Store.account = account Store.settings[:account][:data] = account Store.settings.save_settings - internet = Async::HTTP::Internet.instance Cache.fetch(account.avatar_uri, true) populate_account_info @@ -71,12 +68,13 @@ class W3DHub end if Store.account - Async do - Cache.fetch(Store.account.avatar_uri) - - populate_account_info - page(W3DHub::Pages::Games) - end + BackgroundWorker.foreground_job( + -> { Cache.fetch(Store.account.avatar_uri) }, + ->(result) { + populate_account_info + page(W3DHub::Pages::Games) + } + ) end end diff --git a/lib/pages/server_browser.rb b/lib/pages/server_browser.rb index 683c74a..ecb2abb 100644 --- a/lib/pages/server_browser.rb +++ b/lib/pages/server_browser.rb @@ -130,11 +130,14 @@ class W3DHub populate_server_list if @selected_server&.id == @refresh_server&.id - Async do - fetch_server_details(@refresh_server) if @refresh_server - populate_server_info(@refresh_server) if @refresh_server && @refresh_server == @selected_server - - @refresh_server = nil + if @refresh_server + BackgroundWorker.foreground_job( + -> { fetch_server_details(@refresh_server) }, + ->(result) { + populate_server_info(@refresh_server) if @refresh_server == @selected_server + @refresh_server = nil + } + ) end end end @@ -222,10 +225,10 @@ class W3DHub @selected_server = server - Async do - fetch_server_details(server) - populate_server_info(server) if server == @selected_server - end + BackgroundWorker.foreground_job( + -> { fetch_server_details(server) }, + ->(result) { populate_server_info(server) if server == @selected_server } + ) end stylize_selected_server(server_container) if server.id == @selected_server&.id @@ -352,12 +355,10 @@ class W3DHub end def fetch_server_details(server) - Async do - internet = Async::HTTP::Internet.instance - - server_data = Api.server_details(internet, server.id, 2) - server.update(server_data) if server_data - end + BackgroundWorker.foreground_job( + -> { Api.server_details(server.id, 2) }, + ->(server_data) { server.update(server_data) if server_data } + ) end def game_icon(server) diff --git a/lib/states/boot.rb b/lib/states/boot.rb index ebe1c51..9ea0131 100644 --- a/lib/states/boot.rb +++ b/lib/states/boot.rb @@ -31,17 +31,10 @@ class W3DHub inscription "#{I18n.t(:app_name)} #{W3DHub::VERSION}", width: 0.5, text_align: :right end end - - Async do - @tasks.keys.each do |key| - Sync do - send(key) - end - end - end end def draw + Gosu.draw_circle(window.width / 2, window.height / 2, @w3dhub_logo.width * Gosu.milliseconds / 1000.0 % 500, 128, 0x44_000000, 32) @w3dhub_logo.draw_rot(window.width / 2, window.height / 2, 32) super @@ -57,69 +50,94 @@ class W3DHub push_state(States::Interface) if @progressbar.value >= 1.0 && @task_index == @tasks.size + task = @tasks[@tasks.keys[@task_index]] + + if task && !task[:started] + task[:started] = true + send(@tasks.keys[@task_index]) + end + @task_index += 1 if @tasks.dig(@tasks.keys[@task_index], :complete) end def refresh_user_token + p :refresh_user_token + if Store.settings[:account, :data] account = Api::Account.new(Store.settings[:account, :data], {}) if (account.access_token_expiry - Time.now) / 60 <= 60 * 3 # Refresh if token expires within 3 hours puts "Refreshing user login..." - @account = Api.refresh_user_login(account.refresh_token) + + Api.on_fiber(:refresh_user_login, account.refresh_token) do |refreshed_account| + update_account_data(refreshed_account) + end + else - @account = account + update_account_data(account) end - if @account - Store.account = @account - - Store.settings[:account][:data] = @account - - Cache.fetch(@account.avatar_uri, true) - else - Store.settings[:account] = {} - end - - Store.settings.save_settings - - @tasks[:refresh_user_token][:complete] = true else @tasks[:refresh_user_token][:complete] = true end end - def service_status - @service_status = Api.service_status + def update_account_data(account) + if account + Store.account = account - if @service_status - Store.service_status = @service_status + Store.settings[:account][:data] = account - if !@service_status.authentication? || !@service_status.package_download? - @status_label.value = "Authentication is #{@service_status.authentication? ? 'Okay' : 'Down'}. Package Download is #{@service_status.package_download? ? 'Okay' : 'Down'}." - end - - @tasks[:service_status][:complete] = true + Cache.fetch(account.avatar_uri, true) else - @status_label.value = I18n.t(:"boot.w3dhub_service_is_down") + Store.settings[:account] = {} + end + + Store.settings.save_settings + + @tasks[:refresh_user_token][:complete] = true + end + + def service_status + p :service_status + + Api.on_fiber(:service_status) do |service_status| + @service_status = service_status + + if @service_status + Store.service_status = @service_status + + if !@service_status.authentication? || !@service_status.package_download? + @status_label.value = "Authentication is #{@service_status.authentication? ? 'Okay' : 'Down'}. Package Download is #{@service_status.package_download? ? 'Okay' : 'Down'}." + end + + @tasks[:service_status][:complete] = true + else + @status_label.value = I18n.t(:"boot.w3dhub_service_is_down") + end end end def applications + p :applications + @status_label.value = I18n.t(:"boot.checking_for_updates") - @applications = Api.applications + Api.on_fiber(:applications) do |applications| + if applications + Store.applications = applications - if @applications - Store.applications = @applications - - @tasks[:applications][:complete] = true - else - # FIXME: Failed to retreive! + @tasks[:applications][:complete] = true + else + # FIXME: Failed to retreive! + @status_label.value = "FAILED TO RETREIVE APPS LIST" + end end end def app_icons + puts :app_icons + return unless Store.applications packages = [] @@ -127,8 +145,10 @@ class W3DHub packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" } end - if (package_details = Api.package_details(packages)) - package_details.each do |package| + Api.on_fiber(:package_details, packages) do |package_details| + puts "Got response?" + + package_details&.each do |package| path = Cache.package_path(package.category, package.subcategory, package.name, package.version) generated_icon_path = "#{GAME_ROOT_PATH}/media/icons/#{package.subcategory}.png" @@ -145,37 +165,29 @@ class W3DHub if regenerate ico = ICO.new(file: path) - image = ico.images.sort_by(&:width).last + image = ico.images.max_by(&:width) ico.save(image, generated_icon_path) end end - end - @tasks[:app_icons][:complete] = true + @tasks[:app_icons][:complete] = true + end end def server_list + puts :server_list + @status_label.value = I18n.t(:"server_browser.fetching_server_list") - begin - internet = Async::HTTP::Internet.instance - - list = Api.server_list(internet, 2) - - if list - Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse - end + Api.on_fiber(:server_list, 2) do |list| + Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse if list Store.server_list_last_fetch = Gosu.milliseconds Api::ServerListUpdater.instance @tasks[:server_list][:complete] = true - rescue => e - # Something went wrong! - pp e - Store.server_list = [] end end end diff --git a/lib/window.rb b/lib/window.rb index 8185543..f585a72 100644 --- a/lib/window.rb +++ b/lib/window.rb @@ -23,9 +23,6 @@ class W3DHub super Store.application_manager.start_next_available_task if Store.application_manager.idle? - - current = Async::Task.current? - current&.yield end def gain_focus @@ -64,4 +61,4 @@ class W3DHub end end end -end \ No newline at end of file +end diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb index fcc238e..bc0938d 100644 --- a/w3d_hub_linux_launcher.rb +++ b/w3d_hub_linux_launcher.rb @@ -45,6 +45,7 @@ require_relative "lib/settings" require_relative "lib/mixer" require_relative "lib/ico" require_relative "lib/multicast_server" +require_relative "lib/background_worker" require_relative "lib/application_manager" require_relative "lib/application_manager/manifest" require_relative "lib/application_manager/status" @@ -79,8 +80,8 @@ require_relative "lib/pages/login" require_relative "lib/pages/settings" require_relative "lib/pages/download_manager" -Async do - W3DHub::Window.new(width: 980, height: 720, borderless: false).show - - exit # ensure reactor is shutdown when window is closed +Thread.new do + W3DHub::BackgroundWorker.create end + +W3DHub::Window.new(width: 980, height: 720, borderless: false).show