From d77408484aaeef9278bf6089a70c0efc3a373f42 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Mon, 24 Aug 2026 23:01:27 -0500 Subject: [PATCH] Initial functioning work to get IPC working via UNIX sockets since ractors are still not a viable option :sad: --- lib/constants.rb | 1 + lib/gui_ext.rb | 6 ++ lib/pages/boot/initial_setup.rb | 2 +- lib/pages/games.rb | 2 +- lib/states/boot.rb | 2 +- lib/states/interface.rb | 2 +- lib/window.rb | 12 ++++ lib/worker.rb | 101 ++++++++++++++++++++++++-------- lib/worker/api.rb | 2 +- lib/worker/request.rb | 2 +- lib/worker/w3dhub_api.rb | 20 ++++++- w3d_hub_linux_launcher.rb | 31 ++++------ 12 files changed, 131 insertions(+), 52 deletions(-) diff --git a/lib/constants.rb b/lib/constants.rb index ca980b8..c321c3b 100644 --- a/lib/constants.rb +++ b/lib/constants.rb @@ -7,5 +7,6 @@ module W3DHubLauncher DEFAULT_PACKAGE_CACHE_PATH = "#{CACHE_PATH}/packages".freeze DEFAULT_APPLICATIONS_PATH = "#{Dir.home}/.local/share/#{DIRECTORY_NAME}/applications".freeze + IPC_PATH = "/tmp/w3dhub_linux_launcher.sock" USER_AGENT = "#{NAME} v#{VERSION}".freeze end diff --git a/lib/gui_ext.rb b/lib/gui_ext.rb index 53b67bf..97406f4 100644 --- a/lib/gui_ext.rb +++ b/lib/gui_ext.rb @@ -4,6 +4,8 @@ module W3DHubLauncher WHITE_IMAGE = Gosu.render(64, 64, retro: true) { Gosu.draw_rect(0, 0, 32, 32, Gosu::Color::WHITE) } 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) path = "#{ROOT_PATH}/media/default.png" @@ -21,5 +23,9 @@ module W3DHubLauncher circle.draw(0, 0, 1, 1, 1, 0xff_ffffff, :multiply) end end + + def on_main_thread(block) + window.add_to_queue(block) + end end end diff --git a/lib/pages/boot/initial_setup.rb b/lib/pages/boot/initial_setup.rb index d5b3481..f7403b7 100644 --- a/lib/pages/boot/initial_setup.rb +++ b/lib/pages/boot/initial_setup.rb @@ -51,7 +51,7 @@ module W3DHubLauncher Worker::Api.update_settings({}) do |result| if result.okay? - page(Page::Boot::StartUp) + on_main_thread(proc { page(Page::Boot::StartUp) }) else btn.enabled = true end diff --git a/lib/pages/games.rb b/lib/pages/games.rb index 2173dd7..b7abf7b 100644 --- a/lib/pages/games.rb +++ b/lib/pages/games.rb @@ -26,7 +26,7 @@ module W3DHubLauncher # game info container stack(width: 340, height: 1.0) do # logo - image safe_get_image("/run/media/cyberarm/Storage/W3DHub/Launcher/package-cache/games/apb/logo.png.package"), width: 1.0, max_height: 124 + image safe_get_image("#{ROOT_PATH}/media/default.png"), width: 1.0, max_height: 124 # web links stack(width: 1.0, fill: true, padding: 0, padding_top: LARGE_PADDING) do diff --git a/lib/states/boot.rb b/lib/states/boot.rb index a9ac8e6..18e927a 100644 --- a/lib/states/boot.rb +++ b/lib/states/boot.rb @@ -5,7 +5,7 @@ module W3DHubLauncher super # root container - background image - stack(width: 1.0, height: 1.0, background_image: safe_get_image("/run/media/cyberarm/Storage/W3DHub/Launcher/package-cache/games/apb/background.png.package"), background_image_mode: :fill) do + stack(width: 1.0, height: 1.0, background_image: safe_get_image("#{ROOT_PATH}/media/background.png"), background_image_mode: :fill) do # root container - background image tint flow(width: 1.0, height: 1.0, background: 0xaa_000000) do # content container diff --git a/lib/states/interface.rb b/lib/states/interface.rb index 787e766..b4a6b66 100644 --- a/lib/states/interface.rb +++ b/lib/states/interface.rb @@ -5,7 +5,7 @@ module W3DHubLauncher super # root container - background image - stack(width: 1.0, height: 1.0, background_image: safe_get_image("/run/media/cyberarm/Storage/W3DHub/Launcher/package-cache/games/apb/background.png.package"), background_image_mode: :fill) do + stack(width: 1.0, height: 1.0, background_image: safe_get_image("#{ROOT_PATH}/media/background.png"), background_image_mode: :fill) do # root container - background image tint stack(width: 1.0, height: 1.0, background: ALPHA_BLACK) do # content container diff --git a/lib/window.rb b/lib/window.rb index 02aaaf3..28ca957 100644 --- a/lib/window.rb +++ b/lib/window.rb @@ -4,6 +4,8 @@ module W3DHubLauncher self.show_cursor = true self.caption = format("%s | v%s (%s)", NAME, VERSION, VERSION_NAME) # "Cyberarm's W3D Hub Linux Launcher | v2.0.0 alpha" + @main_thread_queue = [] + push_state(States::Boot) # push_state(States::Interface) end @@ -13,9 +15,19 @@ module W3DHubLauncher end def update + while(block = @main_thread_queue.shift) + block.call + end + + WORKER.service + super sleep 0.001 end + + def add_to_queue(block) + @main_thread_queue << block + end end end diff --git a/lib/worker.rb b/lib/worker.rb index 07f82bb..dd07756 100644 --- a/lib/worker.rb +++ b/lib/worker.rb @@ -3,16 +3,28 @@ module W3DHubLauncher Response = Data.define(:status, :request_id, :data) def initialize + end + + def connect + puts :connect + + @socket = UNIXSocket.new(IPC_PATH) + end + + def connected? + @socket && !@socket.closed? + end + + def listen + init_server + end + + def init_server @threads = [] @requests = [] - @settings = # Settings.new + @settings = 0# Settings.new - # next available request_id to assign incoming requests - @request_id = 0 - - # listen for requests from frontend - listener = Thread.new { listen } # connect to and monitor GSH web service @threads << Thread.new { game_server_hub_websocket } # connect to and monitor Backend web service @@ -20,20 +32,26 @@ module W3DHubLauncher @w3dhub_api = W3DHubLauncher::W3DHubApi.new - listener.join - end + Async do |task| + UNIXServer.open(IPC_PATH) do |server| + while(socket = server.accept) + task.async do + data = socket.gets + json = JSON.parse(data) + query = Request::Query.new(type: json["type"].to_sym, request_id: json["request_id"], data: json["data"]) - def listen - loop do - query = Ractor.receive - pp query + pp [:server_incoming, data, query] - if respond_to?(query.type) - Async do - send(query.type, query) + if respond_to?(query.type) + response = send(query.type, query) + pp [:server_to_client, response] + payload = { status: response.status, request_id: response.request_id, data: response.data.data }.to_json + socket.puts(payload) + end + end end - else - raise "UNKNOWN REQUEST: #{query}" + ensure # manually delete "socket" + File.delete(IPC_PATH) end end end @@ -44,6 +62,45 @@ module W3DHubLauncher def backend_websocket end + # Send request to server + def request(query) + pp [:client_request, query] + payload = { type: query.type, request_id: query.request_id, data: query.data }.to_json + + if respond_to?(query.type) + @socket.puts(payload) + else + raise "UNKNOWN REQUEST: #{query}" + end + end + + def service + data = @socket.read_nonblock(1_048_576) # 1 MB + pp [:CLIENT, data] + json = JSON.parse(data) + response = Response.new(status: json["status"], request_id: json["request_id"], data: json["data"]) + request = W3DHubLauncher::Worker::Request.requests.find { |r| r.request_id == response.request_id } + + pp [json, response, request] + return unless request + + CyberarmEngine::Window.instance&.add_to_queue(proc { + request.handle_event( + response.status, + CyberarmEngine::Result.new(data: response.data, error: response.status.negative? ? true : nil) + ) + }) + + rescue Errno::EWOULDBLOCK + end + + def deliver_response(result, query) + response = Response.new(result.okay? ? Request::STATUS_COMPLETE : Request::STATUS_ERROR, query.request_id, result) + pp response + + response + end + # # ------------ query / request handlers ------------ # @@ -54,11 +111,9 @@ module W3DHubLauncher end def w3dhub_api_call(query) - result = @w3dhub_api.send(query.data[:call], *(query.data[:arguments] || [])) - pp result - response = Response.new(result.okay? ? Request::STATUS_COMPLETE : Request::STATUS_ERROR, query.request_id, result) + result = @w3dhub_api.send(query.data["call"], *(query.data["arguments"] || [])) - Ractor.main.send(response) + deliver_response(result, query) end def update_settings(query) @@ -67,9 +122,7 @@ module W3DHubLauncher FileUtils.mkdir_p(W3DHubLauncher::CONFIG_PATH) # FIXME: FAILABLE! File.write("#{W3DHubLauncher::CONFIG_PATH}/settings.json", query.data) # FIXME: FAILABLE! result.data = query.data - response = Response.new(result.okay? ? Request::STATUS_COMPLETE : Request::STATUS_ERROR, query.request_id, result) - - Ractor.main.send(response) + deliver_response(result, query) end def install_application(query) diff --git a/lib/worker/api.rb b/lib/worker/api.rb index 539b135..fc75761 100644 --- a/lib/worker/api.rb +++ b/lib/worker/api.rb @@ -1,4 +1,4 @@ -# Helper for launcher frontend to safely communicate with ractor (prevent deadlocks and concurrent access errors) +# Helper for launcher frontend to safely communicate with launcher backend module W3DHubLauncher class Worker diff --git a/lib/worker/request.rb b/lib/worker/request.rb index 7d3cc7c..f1f7f9d 100644 --- a/lib/worker/request.rb +++ b/lib/worker/request.rb @@ -47,7 +47,7 @@ module W3DHubLauncher def enqueue(type, id, data) Request.requests << self - W3DHubLauncher::WORKER.send(Query.new(type, id, data)) + W3DHubLauncher::WORKER.request(Query.new(type, id, data)) end # event from Worker received diff --git a/lib/worker/w3dhub_api.rb b/lib/worker/w3dhub_api.rb index 03c4ee5..52f066e 100644 --- a/lib/worker/w3dhub_api.rb +++ b/lib/worker/w3dhub_api.rb @@ -21,7 +21,7 @@ module W3DHubLauncher array << ["content-type", "application/x-www-form-urlencoded"] if form_encoded array << ["authorization", "Bearer #{@access_token}"] if @access_token - pp array + # pp array array end @@ -36,6 +36,7 @@ module W3DHubLauncher if response.success? result.data = response.read else + pp response result.error = true end end @@ -99,9 +100,24 @@ module W3DHubLauncher def fetch_applications result = CyberarmEngine::Result.new - primary_result = fetch("#{PRIMARY_W3DHUB_API_ENDPOINT}/apis/launcher/1/get-applications") + # If we're not signed in, then the primary backend will try to redirect us to the alternate backend. So skip it if we're not signed in. + primary_result = @access_token ? fetch("#{PRIMARY_W3DHUB_API_ENDPOINT}/apis/launcher/1/get-applications") : CyberarmEngine::Result.new(error: true) alternate_result = fetch("#{ALTERNATIVE_W3DHUB_API_ENDPOINT}/apis/launcher/1/get-applications") + # We've failed to retrieve data + if primary_result.error? && alternate_result.error? + return result + # We've only gotten data back from the primary backend + elsif primary_result.okay? && alternate_result.error? + result.data = primary_result.data + return result + # We've only gotten data back from the alternate backend + elsif primary_result.error? && alternate_result.okay? + result.data = alternate_result.data + return result + end + + # We've gotten data back from both backends, merge them. pp [primary_result, alternate_result] # FIXME: Merge primary and alternate results diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb index f90107e..ca40b8c 100644 --- a/w3d_hub_linux_launcher.rb +++ b/w3d_hub_linux_launcher.rb @@ -4,6 +4,7 @@ rescue LoadError require "cyberarm_engine" end +require "socket" require "rexml" require "base64" require "logger" @@ -44,30 +45,20 @@ require_relative "lib/worker/tasks/repair_application" require_relative "lib/worker/tasks/update_application" module W3DHubLauncher - WORKER = Ractor.new(name: "Parallel Worker") { W3DHubLauncher::Worker.new } -end - -# Hello, I exist because there presently exists no way to ask if there are pending -# messages in our ractors mailbox without making a blocking call which is a big no no -# for a GUI application. :| -# -# Keep an eye on: https://bugs.ruby-lang.org/issues/21930: "Add Ractor#empty? method to check for pending messages without blocking" -# -# NOTE: May need to mangle Window#update to do ruby-land sleep so thread gets time to process :( -Thread.new do - loop do - response = Ractor.receive - pp response - - request = W3DHubLauncher::Worker::Request.requests.find { |r| r.request_id == response.request_id } - request&.handle_event(response.status, response.data) + # UNIXServer + Thread.new do + W3DHubLauncher::Worker.new.listen end + + # UNIXSocket / client + WORKER = W3DHubLauncher::Worker.new + WORKER.connect end # 10.times do - W3DHubLauncher::Worker::Request.new(:w3dhub_api_call, { call: :fetch_applications }) do |result| - pp result - end + # W3DHubLauncher::Worker::Request.new(:w3dhub_api_call, { call: :fetch_applications }) do |result| + # pp [:CALLBACK, result] + # end # end window = W3DHubLauncher::Window.new(width: 1280, height: 800, resizable: true)