From baec1e1e180a879101752ae6a041e9ffac027378 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Wed, 2 Sep 2026 11:17:04 -0500 Subject: [PATCH] Added GSH game server data containing class, stub Account and Join Server dialogs, support 'lazy loading' remote images (downloading images in the background then calling a callback to replace temp image if host element is still in layout), misc. other changes. --- lib/dialogs/account.rb | 25 +++++++++++ lib/dialogs/server.rb | 38 +++++++++++++++++ lib/gui_ext.rb | 30 ++++++++++++- lib/pages/boot/start_up.rb | 6 +-- lib/pages/games.rb | 32 +++++++------- lib/states/interface.rb | 5 ++- lib/worker.rb | 48 +++++++++++++++++++++ lib/worker/api.rb | 3 +- lib/worker/api/application.rb | 2 +- lib/worker/api/game_server.rb | 79 +++++++++++++++++++++++++++++++++++ lib/worker/w3dhub_api.rb | 2 +- w3d_hub_linux_launcher.rb | 3 ++ 12 files changed, 248 insertions(+), 25 deletions(-) create mode 100644 lib/dialogs/account.rb create mode 100644 lib/dialogs/server.rb create mode 100644 lib/worker/api/game_server.rb diff --git a/lib/dialogs/account.rb b/lib/dialogs/account.rb new file mode 100644 index 0000000..5c70907 --- /dev/null +++ b/lib/dialogs/account.rb @@ -0,0 +1,25 @@ +module W3DHubLauncher + class Dialog + class Account < W3DHubLauncher::Dialog + def setup + super + + stack(width: 1.0, max_width: 600, height: 1.0, max_height: 600, h_align: :center, v_align: :center, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_BLACK) do + flow(width: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do + banner "Account", width: 1.0, text_align: :center + end + + stack(width: 1.0, fill: true, scroll: true, padding_left: PADDING, padding_right: PADDING) do + button "Sign In", tip: "https://sso.w3d.cyberarm.dev" do + # use SSO service + pop_state + end + end + + flow(width: 1.0, padding: PADDING, background_nine_slice: NINE_SLICE_ROUNDED_BOTTOM, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do + end + end + end + end + end +end diff --git a/lib/dialogs/server.rb b/lib/dialogs/server.rb new file mode 100644 index 0000000..af6f475 --- /dev/null +++ b/lib/dialogs/server.rb @@ -0,0 +1,38 @@ +module W3DHubLauncher + class Dialog + class Server < W3DHubLauncher::Dialog + def setup + super + + stack(width: 1.0, max_width: 600, height: 1.0, max_height: 600, h_align: :center, v_align: :center, background_nine_slice: NINE_SLICE_ROUNDED, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_BLACK) do + flow(width: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do + banner "Downloads", width: 1.0, text_align: :center + end + + stack(width: 1.0, fill: true, scroll: true, padding_left: PADDING, padding_right: PADDING) do + 50.times do + stack(width: 1.0, margin_top: HALF_PADDING, margin_bottom: HALF_PADDING) do + flow(width: 1.0) do + para "PACKAGE_NAME.mix" + flow(fill: true) + para ["Downloading...", "Pending...", "Done.", "Patching...", "Unpacking..."].sample + end + progress(width: 1.0, fraction: rand) + end + end + end + + flow(width: 1.0, padding: PADDING, background_nine_slice: NINE_SLICE_ROUNDED_BOTTOM, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: ALPHA_GRAY) do + flow(fill: true) + + button "Close" do + pop_state + end + + flow(fill: true) + end + end + end + end + end +end diff --git a/lib/gui_ext.rb b/lib/gui_ext.rb index 97406f4..6a4b7ae 100644 --- a/lib/gui_ext.rb +++ b/lib/gui_ext.rb @@ -6,7 +6,11 @@ module W3DHubLauncher def safe_get_image(path, retro: false) raise RuntimeError, "Images may only be loaded from the main thread!" unless Thread.current == Thread.main - return get_image(path, retro: retro) if File.exist?(path) + begin + return get_image(path, retro: retro) if File.exist?(path) + rescue RuntimeError => e + pp e + end path = "#{ROOT_PATH}/media/default.png" return get_image(path, retro: retro) if File.exist?(path) @@ -24,6 +28,30 @@ module W3DHubLauncher end end + # Downloads requested url and on success check if host element is still in the layout and call the block if so. + def remote_image(path, url:, element:, &block) + memcache_key = :"lock_net_download_#{element.style.tag}" + + if !File.exist?(path) && MemCache[memcache_key].nil? + MemCache[memcache_key] = true + + Worker::Api.download_url(url, path) do |result| + # ignore progress reports + next unless result.okay? && result.data == true + + MemCache.delete(memcache_key) + # does the target element still exist in the layout? + e = find_element_by_tag(element.root, element.style.tag) + + next unless e + + if result.okay? + block.call(element, path) + end + end + end + end + def on_main_thread(block) window.add_to_queue(block) end diff --git a/lib/pages/boot/start_up.rb b/lib/pages/boot/start_up.rb index a6728e9..2bd027f 100644 --- a/lib/pages/boot/start_up.rb +++ b/lib/pages/boot/start_up.rb @@ -57,11 +57,11 @@ module W3DHubLauncher after(3000) do puts "HELLO" W3DHubLauncher::Worker::Request.new(:w3dhub_api_call, { call: :fetch_applications }) do |result| - pp [:CALLBACK, result] + # pp [:CALLBACK, result] File.write("applications.json", result.data) hash = JSON.parse(result.data) - pp applications = hash["applications"]&.map { |app| W3DHubLauncher::Worker::Api::Application.new(app) } || [] - pp applications.to_json + applications = hash["applications"]&.map { |app| W3DHubLauncher::Worker::Api::Application.new(app) } || [] + # pp applications.to_json MemCache[:applications] = applications end diff --git a/lib/pages/games.rb b/lib/pages/games.rb index 25246f5..c0b7b92 100644 --- a/lib/pages/games.rb +++ b/lib/pages/games.rb @@ -39,6 +39,13 @@ module W3DHubLauncher end end + populate_game + end + + def populate_game(game = @current_app, channel = @current_channel) + @current_app = game + @current_channel = game&.channels&.first + populate_games_list populate_game_info populate_game_event @@ -49,24 +56,12 @@ module W3DHubLauncher @games_list_container.clear do @games.each_with_index do |game, i| if i.zero? - image(safe_get_image("#{ROOT_PATH}/data/cache/#{game.id}.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0x88_5e5c64, border_thickness_bottom: 3, border_color_bottom: 0xff_3584e4, tip: game.name)do - @current_app = game - @current_channel = game&.channels&.first - - populate_games_list - populate_game_info - populate_game_event - populate_game_news + image(safe_get_image("#{ROOT_PATH}/data/cache/#{game.id}.png"), tag: :"image_icon_#{game.id}", height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0x88_5e5c64, border_thickness_bottom: 3, border_color_bottom: 0xff_3584e4, tip: game.name)do + populate_game(game, game&.channels&.first) end else - image(safe_get_image("#{ROOT_PATH}/data/cache/#{game.id}.png"), height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: game.name)do - @current_app = game - @current_channel = game&.channels&.first - - populate_games_list - populate_game_info - populate_game_event - populate_game_news + image(safe_get_image("#{ROOT_PATH}/data/cache/#{game.id}.png"), tag: :"image_icon_#{game.id}", height: 1.0, padding: HALF_PADDING, background_nine_slice: NINE_SLICE_ROUNDED_TOP, background_nine_slice_from_edge: NINE_SLICE_EDGE, background_nine_slice_color: 0, tip: game.name)do + populate_game(game, game&.channels&.first) end end end @@ -76,7 +71,10 @@ module W3DHubLauncher def populate_game_info @game_info_container.clear do # logo - image safe_get_image("#{ROOT_PATH}/media/#{@current_app.id}.png"), width: 1.0, max_height: 124 + img = image safe_get_image("#{ROOT_PATH}/data/cache/logo_#{@current_app.id}.png"), tag: :"image_logo_#{@current_app.id}", width: 1.0, max_height: 124 + remote_image("#{ROOT_PATH}/data/cache/logo_#{@current_app.id}.png", url: "https://s3.w3d.cyberarm.dev/games/#{@current_app.id}/logo.png", element: img) do |e, path| + e&.value = safe_get_image(path) + end # web links stack(width: 1.0, fill: true, padding: 0, padding_top: LARGE_PADDING) do diff --git a/lib/states/interface.rb b/lib/states/interface.rb index b4a6b66..3c909e6 100644 --- a/lib/states/interface.rb +++ b/lib/states/interface.rb @@ -53,7 +53,10 @@ module W3DHubLauncher # self account container flow(width: 300, height: 80, margin_left: LARGE_PADDING) do # self avatar container - stack(width: 80, height: 1.0, background_image: rounded_avatar(safe_get_image("#{ROOT_PATH}/media/default.png"))) do + stack(width: 80, height: 1.0, background_image: rounded_avatar(safe_get_image("#{ROOT_PATH}/media/default.png"))) do |i| + i.subscribe(:clicked_left_mouse_button) do + dialog(Dialog::Account) + end # self online state container stack(width: 20, height: 20, v_align: :bottom, h_align: :right, background_image: safe_get_image("#{ROOT_PATH}/media/ui/circle_small.png"), background_image_color: 0xff_26a269) end diff --git a/lib/worker.rb b/lib/worker.rb index 6671b49..3694da8 100644 --- a/lib/worker.rb +++ b/lib/worker.rb @@ -1,5 +1,10 @@ module W3DHubLauncher class Worker + DEFAULT_HEADERS = [ + ["user-agent", W3DHubLauncher::USER_AGENT] + ].freeze + DEFAULT_NETWORK_TIMEOUT = 30 + Response = Data.define(:status, :request_id, :data) def initialize @@ -109,6 +114,49 @@ module W3DHubLauncher end def download_url(query) + result = CyberarmEngine::Result.new + + + method = query.data["method"] + url = query.data["url"] + path = query.data["path"] + headers = query.data["headers"] || DEFAULT_HEADERS + body = query.data["body"] + + Sync do |task| + task.with_timeout(DEFAULT_NETWORK_TIMEOUT) do + Async::HTTP::Internet.send(method, url, headers, body) do |response| + if response.success? + content_length = response.headers["content-length"] || 0 + + total_downloaded_bytes = 0 + File.open(path, "wb") do |file| + response.each do |chunk| + file.write(chunk) + downloaded_bytes = chunk.length + total_downloaded_bytes += downloaded_bytes + + progress_result = CyberarmEngine::Result.new(data: { + downloaded_bytes: downloaded_bytes, + total_downloaded_bytes: total_downloaded_bytes, + content_length: content_length + }) + # FIXME: Send intermediate response to requester + # send(query, Response.new(Request::STATUS_IN_PROGRESS, query.request_id progress_result)) + end + end + + result.data = true + end + # rescue StandardError => e + # result.error = e + end + # rescue Async::TimeoutError + # result.error = e + end + end + + deliver_response(result, query) end def w3dhub_api_call(query) diff --git a/lib/worker/api.rb b/lib/worker/api.rb index fc75761..3b3d5f0 100644 --- a/lib/worker/api.rb +++ b/lib/worker/api.rb @@ -8,7 +8,8 @@ module W3DHubLauncher end # downloads requested resource, periodically reporting progress until completion, returning path for file on disk - def self.download_url + def self.download_url(url, path, method = :get, headers = nil, body = nil, &block) + Worker::Request.new(:download_url, { url: url, path: path, method: method, headers: headers, body: body }, &block) end # returns user account data diff --git a/lib/worker/api/application.rb b/lib/worker/api/application.rb index f549556..351233b 100644 --- a/lib/worker/api/application.rb +++ b/lib/worker/api/application.rb @@ -5,7 +5,7 @@ module W3DHubLauncher attr_reader :id, :category, :name, :type, :channels, :extended_data, :web_links def initialize(data) - pp data + # pp data @id = data["id"] @category = data["category"] diff --git a/lib/worker/api/game_server.rb b/lib/worker/api/game_server.rb new file mode 100644 index 0000000..b14f8d7 --- /dev/null +++ b/lib/worker/api/game_server.rb @@ -0,0 +1,79 @@ +module W3DHubLauncher + class Worker + class Api + # Designed to work with cyberarm's Game Server Hub service data, may work with W3D Hub's service with degraded metadata. + class GameServer + attr_reader :id, :game, :channel, :address, :port, :region, :version + + def initialize(data) + # main data + @id = data["id"] + @game = data["game"] + @channel = data["channel"] + @address = data["address"] + @port = data["port"] + @region = data["region"] + + # status data + @name = data["status"]["name"] + @password = data["status"]["password"] || false + @current_map = data["status"]["map"] + @player_count = data["status"]["numplayers"] || 0 + @max_players = data["status"]["maxplayers"] + + @match_start_time = data["status"]["started"] + @estimated_end_time = data["status"]["estimatedEndTime"] + @match_remaining_time = data["status"]["remaining"] + + # status teams data + @teams = [] + data["status"]["teams"].each do |team_data| + @teams << Team.new(team_data) + end + + # status player data + data["status"]["players"].each do |player_data| + team = @teams.find { |t| t.id == player_data["id"] } + next unless team + + team.players << Player.new(player_data) + end + + # Cyberarm GSH extras + @version = data["version"] || "838" + @next_map = data["status"]["nextmap"] || "" + end + end + + class Team + attr_reader :id, :name, :score, :kills, :deaths, :players + + def initialize(data) + @id = data["id"] + @name = data["name"] + @score = data["score"] + @kills = data["kills"] + @deaths = data["deaths"] + + @players = [] + end + end + + class Player + attr_reader :name, :team, :score, :kills, :deaths, :ping, :time + + def initialize(data) + @name = data["nick"] + @team = data["team"] + @score = data["score"] + @kills = data["kills"] + @deaths = data["deaths"] + + # Cyberarm GSH extras + @ping = data["ping"] || 0 + @time = data["time"] || "00:00:00" + end + end + end + end +end diff --git a/lib/worker/w3dhub_api.rb b/lib/worker/w3dhub_api.rb index 52f066e..998b704 100644 --- a/lib/worker/w3dhub_api.rb +++ b/lib/worker/w3dhub_api.rb @@ -118,7 +118,7 @@ module W3DHubLauncher end # We've gotten data back from both backends, merge them. - pp [primary_result, alternate_result] + # pp [primary_result, alternate_result] # FIXME: Merge primary and alternate results result diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb index bbde029..a95dd36 100644 --- a/w3d_hub_linux_launcher.rb +++ b/w3d_hub_linux_launcher.rb @@ -30,6 +30,8 @@ require_relative "lib/pages/boot/initial_setup" require_relative "lib/pages/boot/start_up" require_relative "lib/dialogs/about" require_relative "lib/dialogs/downloads" +require_relative "lib/dialogs/server" +require_relative "lib/dialogs/account" require_relative "lib/states/boot" require_relative "lib/states/interface" require_relative "lib/window" @@ -37,6 +39,7 @@ require_relative "lib/window" require_relative "lib/worker" require_relative "lib/worker/api" require_relative "lib/worker/api/application" +require_relative "lib/worker/api/game_server" require_relative "lib/worker/request" require_relative "lib/worker/w3dhub_api" require_relative "lib/worker/task"