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

This commit is contained in:
2022-02-12 08:47:48 -06:00
parent 202966fd08
commit 232ed2032f
11 changed files with 236 additions and 153 deletions

View File

@@ -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)

View File

@@ -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

77
lib/background_worker.rb Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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
end

View File

@@ -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