From 340c083a4357114cc8d23baf9af465d9b4bca0b7 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Sun, 30 Oct 2022 18:10:47 -0500 Subject: [PATCH] Removed Async[websocket/http] due to excessive require times and reliablity issues on Windows --- Gemfile | 5 +- Gemfile.lock | 59 +++----------- lib/api.rb | 78 +++++++++--------- lib/api/server_list_server.rb | 2 + lib/api/server_list_updater.rb | 140 +++++++++++--------------------- lib/application_manager/task.rb | 4 +- lib/background_worker.rb | 22 ++++- lib/cache.rb | 10 +-- lib/common.rb | 8 ++ lib/states/boot.rb | 10 +-- lib/window.rb | 3 + w3d_hub_linux_launcher.rb | 45 +++++++--- 12 files changed, 176 insertions(+), 210 deletions(-) diff --git a/Gemfile b/Gemfile index c66db1c..092f5ee 100644 --- a/Gemfile +++ b/Gemfile @@ -6,12 +6,9 @@ gem "i18n" gem "rexml" gem "digest-crc" gem "ffi" -gem "async", "~>1.30.1" -gem "async-http" -gem "async-websocket" +gem "websocket-client-simple" gem "thread-local" gem "ircparser" -gem "net-ping" gem "win32-security", platforms: [:x64_mingw, :mingw] # group :windows_packaging do diff --git a/Gemfile.lock b/Gemfile.lock index e17d81f..6da53c9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,46 +1,22 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - async (1.30.3) - console (~> 1.10) - nio4r (~> 2.3) - timers (~> 4.1) - async-http (0.56.6) - async (>= 1.25) - async-io (>= 1.28) - async-pool (>= 0.2) - protocol-http (~> 0.22.0) - protocol-http1 (~> 0.14.0) - protocol-http2 (~> 0.14.0) - traces (~> 0.4.0) - async-io (1.33.0) - async - async-pool (0.3.10) - async (>= 1.25) - async-websocket (0.19.0) - async-http (~> 0.54) - async-io (~> 1.23) - protocol-websocket (~> 0.7.0) - clipboard (1.3.6) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) concurrent-ruby (1.1.10) - console (1.15.3) - fiber-local - cyberarm_engine (0.21.0) - clipboard (~> 1.3) + cyberarm_engine (0.22.0) excon (~> 0.88) gosu (~> 1.1) gosu_more_drawables (~> 0.3) digest-crc (0.6.4) rake (>= 12.0.0, < 14.0.0) - excon (0.92.4) + event_emitter (0.2.6) + excon (0.93.1) ffi (1.15.5) ffi (1.15.5-x64-mingw-ucrt) ffi (1.15.5-x64-mingw32) ffi-win32-extensions (1.0.4) ffi - fiber-local (1.0.0) gosu (1.4.3) gosu_more_drawables (0.3.1) i18n (1.12.0) @@ -48,24 +24,14 @@ GEM ircparser (1.0.0) launchy (2.5.0) addressable (~> 2.7) - net-ping (2.0.8) - nio4r (2.5.8) - protocol-hpack (1.4.2) - protocol-http (0.22.6) - protocol-http1 (0.14.4) - protocol-http (~> 0.22) - protocol-http2 (0.14.2) - protocol-hpack (~> 1.4) - protocol-http (~> 0.18) - protocol-websocket (0.7.5) - protocol-http (~> 0.2) - protocol-http1 (~> 0.2) - public_suffix (4.0.7) + public_suffix (5.0.0) rake (13.0.6) rexml (3.2.5) thread-local (1.1.0) - timers (4.3.3) - traces (0.4.1) + websocket (1.2.9) + websocket-client-simple (0.6.0) + event_emitter + websocket win32-security (0.5.0) ffi ffi-win32-extensions @@ -76,18 +42,15 @@ PLATFORMS x86_64-linux DEPENDENCIES - async (~> 1.30.1) - async-http - async-websocket cyberarm_engine digest-crc ffi i18n ircparser launchy - net-ping rexml thread-local + websocket-client-simple win32-security BUNDLED WITH diff --git a/lib/api.rb b/lib/api.rb index a2027ce..e609103 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -4,15 +4,17 @@ class W3DHub API_TIMEOUT = 10 # seconds USER_AGENT = "Cyberarm's Linux Friendly W3D Hub Launcher v#{W3DHub::VERSION}".freeze - DEFAULT_HEADERS = [ - ["User-Agent", USER_AGENT], - ["Accept", "application/json"] - ].freeze - FORM_ENCODED_HEADERS = ( - DEFAULT_HEADERS + [["Content-Type", "application/x-www-form-urlencoded"]] - ).freeze + DEFAULT_HEADERS = { + "User-Agent": USER_AGENT, + "Accept": "application/json" + }.freeze + FORM_ENCODED_HEADERS = { + "User-Agent": USER_AGENT, + "Accept": "application/json", + "Content-Type": "application/x-www-form-urlencoded" + }.freeze - def self.on_fiber(method, *args, &callback) + def self.on_thread(method, *args, &callback) BackgroundWorker.job(-> { Api.send(method, *args) }, callback) end @@ -27,8 +29,6 @@ class W3DHub ENDPOINT = "https://secure.w3dhub.com".freeze def self.post(url, headers = DEFAULT_HEADERS, body = nil) - @client ||= Async::HTTP::Client.new(Async::HTTP::Endpoint.parse(ENDPOINT, protocol: Async::HTTP::Protocol::HTTP10)) - # TODO: Check if session has expired and attempt to refresh session before submitting request logger.debug(LOG_TAG) { "Fetching POST \"#{url}\"..." } @@ -37,17 +37,15 @@ class W3DHub if Store.account logger.debug(LOG_TAG) { " Injecting Authorization header..." } headers = headers.dup - headers << ["Authorization", "Bearer #{Store.account.access_token}"] + headers["Authorization"] = "Bearer #{Store.account.access_token}" end begin - Async::Task.current.with_timeout(API_TIMEOUT) do - @client.post(url, headers, body) - end - rescue Async::TimeoutError + Excon.post(url, headers: headers, body: body, tcp_nodelay: true, write_timeout: API_TIMEOUT, read_timeout: API_TIMEOUT, connection_timeout: API_TIMEOUT) + rescue Excon::Errors::Timeout logger.error(LOG_TAG) { "Connection to \"#{url}\" timed out after: #{API_TIMEOUT} seconds" } DummyResponse.new - rescue EOFError => e + rescue Excon::Socket::Error => e logger.error(LOG_TAG) { "Connection to \"#{url}\" errored:" } logger.error(LOG_TAG) { e } DummyResponse.new @@ -73,16 +71,16 @@ class W3DHub body = "data=#{JSON.dump({refreshToken: refresh_token})}" response = post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) - if response.success? - user_data = JSON.parse(response.read, symbolize_names: true) + if response.status == 200 + user_data = JSON.parse(response.body, symbolize_names: true) return false if user_data[:error] body = "data=#{JSON.dump({ id: user_data[:userid] })}" user_details = post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) - if user_details.success? - user_details_data = JSON.parse(user_details.read, symbolize_names: true) + if user_details.status == 200 + user_details_data = JSON.parse(user_details.body, symbolize_names: true) else logger.error(LOG_TAG) { "Failed to fetch refresh user details:" } logger.error(LOG_TAG) { user_details } @@ -101,16 +99,16 @@ class W3DHub body = "data=#{JSON.dump({username: username, password: password})}" response = post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) - if response.success? - user_data = JSON.parse(response.read, symbolize_names: true) + if response.status == 200 + user_data = JSON.parse(response.body, symbolize_names: true) return false if user_data[:error] body = "data=#{JSON.dump({ id: user_data[:userid] })}" user_details = post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) - if user_details.success? - user_details_data = JSON.parse(user_details.read, symbolize_names: true) + if user_details.status == 200 + user_details_data = JSON.parse(user_details.body, symbolize_names: true) else logger.error(LOG_TAG) { "Failed to fetch user details:" } logger.error(LOG_TAG) { user_details } @@ -137,8 +135,8 @@ class W3DHub def self.service_status response = post("#{ENDPOINT}/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS) - if response.success? - ServiceStatus.new(response.read) + if response.status == 200 + ServiceStatus.new(response.body) else logger.error(LOG_TAG) { "Failed to fetch service status:" } logger.error(LOG_TAG) { response } @@ -153,8 +151,8 @@ class W3DHub def self.applications response = post("#{ENDPOINT}/apis/launcher/1/get-applications") - if response.success? - Applications.new(response.read) + if response.status == 200 + Applications.new(response.body) else logger.error(LOG_TAG) { "Failed to fetch applications list:" } logger.error(LOG_TAG) { response } @@ -170,8 +168,8 @@ class W3DHub body = "data=#{JSON.dump({category: category})}" response = post("#{ENDPOINT}/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body) - if response.success? - News.new(response.read) + if response.status == 200 + News.new(response.body) else logger.error(LOG_TAG) { "Failed to fetch news for:" } logger.error(LOG_TAG) { category } @@ -188,8 +186,8 @@ class W3DHub body = URI.encode_www_form("data": JSON.dump({ packages: packages })) response = post("#{ENDPOINT}/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body) - if response.success? - hash = JSON.parse(response.read, symbolize_names: true) + if response.status == 200 + hash = JSON.parse(response.body, symbolize_names: true) hash[:packages].map { |pkg| Package.new(pkg) } else logger.error(LOG_TAG) { "Failed to fetch package details for:" } @@ -214,8 +212,8 @@ class W3DHub body = URI.encode_www_form("data": JSON.dump({ serverPath: app_id })) response = post("#{ENDPOINT}/apis/w3dhub/1/get-server-events", FORM_ENCODED_HEADERS, body) - if response.success? - array = JSON.parse(response.read, symbolize_names: true) + if response.status == 200 + array = JSON.parse(response.body, symbolize_names: true) array.map { |e| Event.new(e) } else false @@ -227,11 +225,11 @@ 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 ||= Excon.new(SERVER_LIST_ENDPOINT, persistent: true) logger.debug(LOG_TAG) { "Fetching GET \"#{url}\"..." } - @client.get(url, headers, body) + Excon.get(url, headers: headers, body: body, persistent: true) end # Method: GET @@ -254,8 +252,8 @@ class W3DHub 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) + if response.status == 200 + data = JSON.parse(response.body, symbolize_names: true) return data.map { |hash| ServerListServer.new(hash) } end @@ -276,8 +274,8 @@ class W3DHub 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) + if response.status == 200 + hash = JSON.parse(response.body, symbolize_names: true) return hash end diff --git a/lib/api/server_list_server.rb b/lib/api/server_list_server.rb index d72c0a7..1d22b98 100644 --- a/lib/api/server_list_server.rb +++ b/lib/api/server_list_server.rb @@ -42,6 +42,8 @@ class W3DHub end def send_ping(force_ping = false) + return + if force_ping || Gosu.milliseconds - @last_pinged >= @ping_interval @last_pinged = Gosu.milliseconds diff --git a/lib/api/server_list_updater.rb b/lib/api/server_list_updater.rb index 67e8082..3e78d87 100644 --- a/lib/api/server_list_updater.rb +++ b/lib/api/server_list_updater.rb @@ -3,65 +3,6 @@ class W3DHub class ServerListUpdater LOG_TAG = "W3DHub::Api::ServerListUpdater".freeze include CyberarmEngine::Common - - ##!!! When this breaks update from: https://github.com/socketry/async-websocket/blob/master/lib/async/websocket/connection.rb - # refinements preserves super... 😢 - class PatchedConnection < ::Protocol::WebSocket::Connection - include ::Protocol::WebSocket::Headers - - def self.call(framer, protocol = [], **options) - instance = self.new(framer, Array(protocol).first, **options) - - return instance unless block_given? - - begin - yield instance - ensure - instance.close - end - end - - def initialize(framer, protocol = nil, response: nil, **options) - super(framer, **options) - - @protocol = protocol - @response = response - end - - def close - super - - if @response - @response.finish - @response = nil - end - end - - attr :protocol - - def read - if (buffer = super) - buffer.split("\x1e").map { |json| parse(json) } - end - end - - def write(object) - super("#{dump(object)}\x1e") - end - - def parse(buffer) - JSON.parse(buffer, symbolize_names: true) - end - - def dump(object) - JSON.dump(object) - end - - def call - self.close - end - end - @@instance = nil def self.instance @@ -87,45 +28,36 @@ class W3DHub retry end end + + logger.debug(LOG_TAG) { "Cleaning up..." } + @@instance = nil end def connect - Async do |task| - internet = Async::HTTP::Internet.instance + auto_reconnect = false - 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) { "Requesting connection token..." } + response = Excon.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", headers: Api::DEFAULT_HEADERS, body: "") + data = JSON.parse(response.body, 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 = "https://gsh.w3dhub.com/listings/push/v2?id=#{id}" - 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) { "Connecting to websocket..." } + WebSocket::Client::Simple.connect(endpoint, headers: Api::DEFAULT_HEADERS) do |ws| + ws.on(:message) do |msg| + msg = msg.data.split("\x1e").first - 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 + hash = JSON.parse(msg, symbolize_names: true) - 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] + # Send PING(?) + if hash.empty? || hash[:type] == 6 + ws.send({ type: 6 }.to_json + "\x1e") + else + case hash[:type] + when 1 + if hash[:target] == "ServerStatusChanged" + id, data = hash[: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 @@ -133,10 +65,32 @@ class W3DHub end end end - ensure - logger.debug(LOG_TAG) { "Cleaning up..." } - @@instance = nil + + ws.on(:open) do + logger.debug(LOG_TAG) { "Requesting json protocol, v1..." } + ws.send({ protocol: "json", version: 1 }.to_json + "\x1e") + + 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] } + ws.send(out.to_json + "\x1e") + end + end + + ws.on(:close) do |e| + p e + auto_reconnect = true + end + + ws.on(:error) do |e| + p e + auto_reconnect = true + end end + + connect if auto_reconnect end end end diff --git a/lib/application_manager/task.rb b/lib/application_manager/task.rb index 7c3b8c6..ad45842 100644 --- a/lib/application_manager/task.rb +++ b/lib/application_manager/task.rb @@ -56,7 +56,7 @@ class W3DHub @task_state = :running Thread.new do - Sync do + # Sync do begin status = execute_task rescue FailFast @@ -78,7 +78,7 @@ class W3DHub hide_application_taskbar if @task_state == :failed send_message_dialog(:failure, "Task #{type.inspect} failed for #{@application.name}", @task_failure_reason) if @task_state == :failed && !@fail_silently - end + # end end end diff --git a/lib/background_worker.rb b/lib/background_worker.rb index a7a7142..1b0b601 100644 --- a/lib/background_worker.rb +++ b/lib/background_worker.rb @@ -9,13 +9,12 @@ class W3DHub logger.info(LOG_TAG) { "Starting background job worker..." } + @@thread = Thread.current @@alive = true @@run = true @@instance = self.new - Async do - @@instance.handle_jobs - end + @@instance.handle_jobs end def self.instance @@ -30,10 +29,18 @@ class W3DHub @@alive end + def self.busy? + instance&.busy? + end + def self.shutdown! @@run = false end + def self.kill! + @@thread.kill + end + def self.job(job, callback, error_handler = nil) @@instance.add_job(Job.new(job: job, callback: callback, error_handler: error_handler)) end @@ -43,6 +50,7 @@ class W3DHub end def initialize + @busy = false @jobs = [] end @@ -50,12 +58,16 @@ class W3DHub while BackgroundWorker.run? job = @jobs.shift + @busy = true + begin job&.do rescue => error job&.raise_error(error) end + @busy = !@jobs.empty? + sleep 0.1 end @@ -67,6 +79,10 @@ class W3DHub @jobs << job end + def busy? + @busy + end + class Job def initialize(job:, callback:, error_handler: nil, deliver_to_queue: false) @job = job diff --git a/lib/cache.rb b/lib/cache.rb index 5e286ce..57b0a7a 100644 --- a/lib/cache.rb +++ b/lib/cache.rb @@ -16,12 +16,12 @@ class W3DHub path elsif async BackgroundWorker.job( - -> { Async::HTTP::Internet.instance.get(uri, W3DHub::Api::DEFAULT_HEADERS) }, - ->(response) { response.save(path, "wb") if response.success? } + -> { Api.get(uri, W3DHub::Api::DEFAULT_HEADERS) }, + ->(response) { File.open(path, "wb") { |f| f.write response.body } if response.status == 200 } ) else - response = Async::HTTP::Internet.instance.get(uri, W3DHub::Api::DEFAULT_HEADERS) - response.save(path, "wb") if response.success? + response = Api.get(uri, W3DHub::Api::DEFAULT_HEADERS) + File.open(path, "wb") { |f| f.write response.body } if response.status == 200 end end @@ -82,7 +82,7 @@ class W3DHub block.call(chunk, remaining_bytes, total_bytes) end - response.success? + response.status == 200 ensure file&.close end diff --git a/lib/common.rb b/lib/common.rb index fe6a7b7..87ed8ae 100644 --- a/lib/common.rb +++ b/lib/common.rb @@ -40,6 +40,14 @@ class W3DHub end end + def self.commmand(command) + if windows? + + else + IO.popen(command) + end + end + def self.home_directory File.expand_path("~") end diff --git a/lib/states/boot.rb b/lib/states/boot.rb index 95e3510..831230b 100644 --- a/lib/states/boot.rb +++ b/lib/states/boot.rb @@ -78,7 +78,7 @@ class W3DHub logger.info(LOG_TAG) { "Refreshing user login..." } # TODO: Check without network - Api.on_fiber(:refresh_user_login, account.refresh_token) do |refreshed_account| + Api.on_thread(:refresh_user_login, account.refresh_token) do |refreshed_account| update_account_data(refreshed_account) end @@ -108,7 +108,7 @@ class W3DHub end def service_status - Api.on_fiber(:service_status) do |service_status| + Api.on_thread(:service_status) do |service_status| @service_status = service_status if @service_status @@ -132,7 +132,7 @@ class W3DHub def applications @status_label.value = I18n.t(:"boot.checking_for_updates") - Api.on_fiber(:applications) do |applications| + Api.on_thread(:applications) do |applications| if applications Store.applications = applications @@ -152,7 +152,7 @@ class W3DHub packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" } end - Api.on_fiber(:package_details, packages) do |package_details| + Api.on_thread(:package_details, packages) do |package_details| 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" @@ -180,7 +180,7 @@ class W3DHub def server_list @status_label.value = I18n.t(:"server_browser.fetching_server_list") - Api.on_fiber(:server_list, 2) do |list| + Api.on_thread(: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 diff --git a/lib/window.rb b/lib/window.rb index 47d1a7e..d195e06 100644 --- a/lib/window.rb +++ b/lib/window.rb @@ -35,6 +35,9 @@ class W3DHub while (block = Store.main_thread_queue.shift) block&.call end + + # Manually sleep main thread so that the BackgroundWorker thread can be scheduled + sleep(update_interval / 1000.0) if W3DHub::BackgroundWorker.busy? end def gain_focus diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb index 5d4b15d..e23bded 100644 --- a/w3d_hub_linux_launcher.rb +++ b/w3d_hub_linux_launcher.rb @@ -5,6 +5,7 @@ require "fileutils" require "digest" require "rexml" require "logger" +require "time" class W3DHub W3DHUB_DEBUG = ARGV.join.include?("--debug") @@ -22,7 +23,31 @@ end module Kernel def logger - W3DHub::LOGGER + @logger = W3DHub::LOGGER + end + + class W3DHubLogger + def initialize + end + + def level=(options) + end + + def info(tag, &block) + pp [tag, block&.call] + end + + def debug(tag, &block) + pp [tag, block&.call] + end + + def warn(tag, &block) + pp [tag, block&.call] + end + + def error(tag, &block) + pp [tag, block&.call] + end end end @@ -42,15 +67,7 @@ end require "i18n" require "launchy" - -require "async" -require "async/barrier" -require "async/semaphore" -require "async/http/internet/instance" -require "async/http/endpoint" -require "async/websocket/client" -require "protocol/websocket/connection" -require "net/ping" +require "websocket-client-simple" I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"] I18n.default_locale = :en @@ -124,14 +141,22 @@ Thread.new do W3DHub::BackgroundWorker.create end +until W3DHub::BackgroundWorker.alive? + sleep 0.1 +end + logger.info(W3DHub::LOG_TAG) { "Launching window..." } # W3DHub::Window.new(width: 980, height: 720, borderless: false, resizable: true).show unless defined?(Ocra) W3DHub::Window.new(width: 1280, height: 800, borderless: false, resizable: true).show unless defined?(Ocra) # W3DHub::Window.new(width: 1920, height: 1080, borderless: false, resizable: true).show unless defined?(Ocra) W3DHub::BackgroundWorker.shutdown! +worker_soft_halt = Gosu.milliseconds + # Wait for BackgroundWorker to return while W3DHub::BackgroundWorker.alive? + W3DHub::BackgroundWorker.kill! if Gosu.milliseconds - worker_soft_halt >= 1_000 + sleep 0.1 end