yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
application.cc
Go to the documentation of this file.
1#include "app/application.h"
2
3#include <chrono>
4#include <cstdlib>
5#include <ctime>
6#include <memory>
7#include <string>
8#include <utility>
9#include "absl/status/status.h"
10#include "activity_file.h"
11#include "controller.h"
12#include "emu/emulator.h"
13#include "emu/i_emulator.h"
14
15#ifndef _WIN32
16#include <unistd.h> // getpid()
17#endif
18
19#include "absl/strings/str_cat.h"
20#include "absl/strings/str_format.h"
23#include "util/log.h"
24
25#ifdef YAZE_WITH_GRPC
31#endif
32
33#ifdef __EMSCRIPTEN__
34#include <emscripten.h>
37#endif
38
39namespace yaze {
40
42 static Application instance;
43 return instance;
44}
45
47 config_ = config;
48 LOG_INFO("App", "Initializing Application instance...");
49
50 controller_ = std::make_unique<Controller>();
51 if (controller_->editor_manager()) {
52 controller_->editor_manager()->SetStartupLoadHints(config_);
53 }
54
55 // Process pending ROM load if we have one (from flags/config - non-WASM only)
56 std::string start_path = config_.rom_file;
57
58#ifndef __EMSCRIPTEN__
59 if (!pending_rom_.empty()) {
60 // Pending ROM takes precedence over config (e.g. drag-drop before init)
61 start_path = pending_rom_;
62 pending_rom_.clear();
63 LOG_INFO("App", "Found pending ROM load: %s", start_path.c_str());
64 } else if (!start_path.empty()) {
65 LOG_INFO("App", "Using configured startup ROM: %s", start_path.c_str());
66 } else {
67 LOG_INFO("App", "No pending ROM, starting empty.");
68 }
69#else
70 LOG_INFO("App", "WASM build - ROM loading handled via wasm_bootstrap queue.");
71 // In WASM, start_path from config might be ignored if we rely on web uploads
72 // But we can still try to pass it if it's a server-hosted ROM
73#endif
74
75 // Always call OnEntry to initialize Window/Renderer, even with empty path
76 auto status = controller_->OnEntry(start_path);
77 if (!status.ok()) {
78 LOG_ERROR("App", "Failed to initialize controller: %s",
79 std::string(status.message()).c_str());
80 // Window/renderer/ImGui init failed. Do not fall through into the frame
81 // loop with uninitialized state: the controller stays inactive, so the
82 // main loop exits immediately and Shutdown() tears down anything created.
83 return;
84 } else {
85 LOG_INFO("App", "Controller initialized successfully. Active: %s",
86 controller_->IsActive() ? "Yes" : "No");
87
88#ifdef YAZE_WITH_GRPC
89 test::TestManager::Get().SetFailureScreenshotRequester(
90 [controller = controller_.get()](
91 const std::string& preferred_path,
92 test::TestManager::FailureScreenshotCallback callback) {
93 controller->RequestScreenshot({.preferred_path = preferred_path,
94 .reveal_to_user = false,
95 .callback = std::move(callback)});
96 });
97#endif
98
99 if (controller_->editor_manager()) {
100 controller_->editor_manager()->ApplyStartupVisibility(config_);
101 }
102
103 // If we successfully loaded a ROM at startup, run startup actions
104 if (!start_path.empty() && controller_->editor_manager()) {
106 }
107
108#ifdef YAZE_WITH_GRPC
109 // Initialize gRPC unified server
111 LOG_INFO("App", "Initializing Unified gRPC Server...");
112 canvas_automation_service_ =
113 std::make_unique<CanvasAutomationServiceImpl>();
114 grpc_server_ = std::make_unique<YazeGRPCServer>();
115
116 auto rom_getter = [this]() {
117 return controller_->GetCurrentRom();
118 };
119 auto rom_loader = [this](const std::string& path) -> bool {
120 if (!controller_ || !controller_->editor_manager())
121 return false;
122 auto status = controller_->editor_manager()->OpenRomOrProject(path);
123 return status.ok();
124 };
125
126 emu::IEmulator* emulator_interface = nullptr;
127
128 if (config_.backend == "mesen") {
129 LOG_INFO("App", "Using Mesen2 backend for emulator service");
130 emulator_backend_ =
131 std::make_unique<emu::mesen::MesenEmulatorAdapter>();
132 emulator_interface = emulator_backend_.get();
133 } else {
134 emu::Emulator* internal_emulator = nullptr;
135 if (controller_->editor_manager()) {
136 internal_emulator = &controller_->editor_manager()->emulator();
137 } else {
138 LOG_WARN("App",
139 "EditorManager not ready; internal emulator services may be "
140 "limited");
141 }
142
143 auto adapter =
144 std::make_unique<emu::InternalEmulatorAdapter>(internal_emulator);
145
146 // Set up internal helpers for the adapter
147 adapter->SetRomLoader([this](const std::string& path) -> bool {
148 if (!controller_ || !controller_->editor_manager())
149 return false;
150 auto status = controller_->editor_manager()->OpenRomOrProject(path);
151 return status.ok();
152 });
153
154 adapter->SetRomGetter(
155 [this]() { return controller_->GetCurrentRom(); });
156
157 emulator_backend_ = std::move(adapter);
158 emulator_interface = emulator_backend_.get();
159 }
160
161 // Initialize server with all services
162 auto status = grpc_server_->Initialize(
163 config_.test_harness_port, emulator_interface, rom_getter, rom_loader,
165 nullptr, // Version manager not ready
166 nullptr, // Approval manager not ready
167 canvas_automation_service_.get());
168
169 if (status.ok()) {
170 status = grpc_server_->StartAsync(); // Start in background thread
171 if (!status.ok()) {
172 LOG_ERROR("App", "Failed to start gRPC server: %s",
173 std::string(status.message()).c_str());
174 } else {
175 LOG_INFO("App", "Unified gRPC server started on port %d",
177 }
178 } else {
179 LOG_ERROR("App", "Failed to initialize gRPC server: %s",
180 std::string(status.message()).c_str());
181 }
182
183 // Connect services to controller/editor manager
184 if (canvas_automation_service_) {
185 controller_->SetCanvasAutomationService(
186 canvas_automation_service_.get());
187 }
188 }
189#endif
190 }
191
192#ifdef __EMSCRIPTEN__
193 // Register the ROM load handler now that controller is ready.
194 yaze::app::wasm::SetRomLoadHandler(
195 [](std::string path) { Application::Instance().LoadRom(path); });
196#else
197 // Create activity file for instance discovery (non-WASM only).
198 // Prefer $XDG_RUNTIME_DIR (0700 per-user) over world-readable /tmp.
199 auto pid = getpid();
200 const char* runtime_dir = std::getenv("XDG_RUNTIME_DIR");
201 const char* status_dir = (runtime_dir && *runtime_dir) ? runtime_dir : "/tmp";
202 activity_file_ = std::make_unique<app::ActivityFile>(
203 absl::StrFormat("%s/yaze-%d.status", status_dir, pid));
204 UpdateActivityStatus();
205 LOG_INFO("App", "Activity file created: %s",
206 activity_file_->GetPath().c_str());
207#endif
208}
209
210void Application::Tick() {
211 if (!controller_)
212 return;
213
214 // Calculate delta time
215 auto now = std::chrono::steady_clock::now();
216 if (first_frame_) {
217 delta_time_ = 0.016f; // Assume ~60fps for first frame
218 first_frame_ = false;
219 } else {
220 auto elapsed = std::chrono::duration<float>(now - last_frame_time_);
221 delta_time_ = elapsed.count();
222 }
223 last_frame_time_ = now;
224
225 // Publish FrameBeginEvent for pre-frame (non-ImGui) work
226 if (auto* bus = editor::ContentRegistry::Context::event_bus()) {
227 bus->Publish(editor::FrameBeginEvent::Create(delta_time_));
228 }
229
230#ifdef __EMSCRIPTEN__
231 auto& wasm_collab = app::platform::GetWasmCollaborationInstance();
232 wasm_collab.ProcessPendingChanges();
233#endif
234
235 controller_->OnInput();
236 auto status = controller_->OnLoad();
237 if (!status.ok()) {
238 LOG_ERROR("App", "Controller Load Error: %s",
239 std::string(status.message()).c_str());
240#ifdef __EMSCRIPTEN__
241 emscripten_cancel_main_loop();
242#endif
243 return;
244 }
245
246 if (!controller_->IsActive()) {
247 // Window closed
248 // LOG_INFO("App", "Controller became inactive");
249 }
250
251 controller_->DoRender();
252
253 // Publish FrameEndEvent for cleanup operations
254 if (auto* bus = editor::ContentRegistry::Context::event_bus()) {
255 bus->Publish(editor::FrameEndEvent::Create(delta_time_));
256 }
257}
258
259void Application::LoadRom(const std::string& path) {
260 LOG_INFO("App", "Requesting ROM load: %s", path.c_str());
261
262 if (!controller_) {
263#ifdef __EMSCRIPTEN__
264 yaze::app::wasm::TriggerRomLoad(path);
265 LOG_INFO("App",
266 "Forwarded to wasm_bootstrap queue (controller not ready): %s",
267 path.c_str());
268#else
269 pending_rom_ = path;
270 LOG_INFO("App", "Queued ROM load (controller not ready): %s", path.c_str());
271#endif
272 return;
273 }
274
275 // Controller exists.
276 absl::Status status;
277 if (!controller_->IsActive()) {
278 status = controller_->OnEntry(path);
279 } else {
280 status = controller_->editor_manager()->OpenRomOrProject(path);
281 }
282
283 if (!status.ok()) {
284 std::string error_msg =
285 absl::StrCat("Failed to load ROM: ", status.message());
286 LOG_ERROR("App", "%s", error_msg.c_str());
287
288#ifdef __EMSCRIPTEN__
289 EM_ASM(
290 {
291 var msg = UTF8ToString($0);
292 console.error(msg);
293 alert(msg);
294 },
295 error_msg.c_str());
296#endif
297 } else {
298 LOG_INFO("App", "ROM loaded successfully: %s", path.c_str());
299
300 // Run startup actions whenever a new ROM is loaded IF it matches our startup config
301 // (Optional: we might only want to run actions once at startup, but for CLI usage usually
302 // you load one ROM and want the actions applied to it).
303 // For now, we'll only run actions if this is the first load or if explicitly requested.
304 // Actually, simpler: just run them. The user can close cards if they want.
305 RunStartupActions();
306
307#ifndef __EMSCRIPTEN__
308 // Update activity file with new ROM path
309 UpdateActivityStatus();
310#endif
311
312#ifdef __EMSCRIPTEN__
313 EM_ASM(
314 { console.log("ROM loaded successfully: " + UTF8ToString($0)); },
315 path.c_str());
316#endif
317 }
318}
319
320void Application::RunStartupActions() {
321 if (!controller_ || !controller_->editor_manager())
322 return;
323
324 auto* manager = controller_->editor_manager();
325 manager->ProcessStartupActions(config_);
326}
327
328#ifndef __EMSCRIPTEN__
329void Application::UpdateActivityStatus() {
330 if (!activity_file_)
331 return;
332
333 app::ActivityStatus status;
334 status.pid = getpid();
336 status.start_timestamp = std::time(nullptr);
337
338 if (controller_ && controller_->editor_manager()) {
339 auto* rom = controller_->GetCurrentRom();
340 status.active_rom = rom ? rom->filename() : "";
341 }
342
343#ifdef YAZE_WITH_GRPC
344 if (config_.enable_test_harness && config_.test_harness_port > 0) {
345 status.socket_path =
346 absl::StrFormat("localhost:%d", config_.test_harness_port);
347 }
348#endif
349
350 activity_file_->Update(status);
351}
352#endif
353
354#ifdef __EMSCRIPTEN__
355extern "C" void SyncFilesystem();
356#endif
357
358void Application::Shutdown() {
359#ifdef __EMSCRIPTEN__
360 // Sync IDBFS to persist any changes before shutdown
361 LOG_INFO("App", "Syncing filesystem before shutdown...");
362 SyncFilesystem();
363#endif
364
365#ifdef YAZE_WITH_GRPC
366 if (grpc_server_) {
367 LOG_INFO("App", "Shutting down Unified gRPC Server...");
368 grpc_server_->Shutdown();
369 grpc_server_.reset();
370 }
371 canvas_automation_service_.reset();
372 test::TestManager::Get().SetFailureScreenshotRequester({});
373#endif
374
375#ifndef __EMSCRIPTEN__
376 // Clean up activity file for instance discovery
377 if (activity_file_) {
378 LOG_INFO("App", "Removing activity file: %s",
379 activity_file_->GetPath().c_str());
380 activity_file_.reset(); // Destructor deletes the file
381 }
382#endif
383
384 if (controller_) {
385 controller_->OnExit();
386 controller_.reset();
387 }
388}
389
390} // namespace yaze
Main application singleton managing lifecycle and global state.
Definition application.h:61
std::string pending_rom_
AppConfig config_
Definition application.h:98
static Application & Instance()
std::unique_ptr< Controller > controller_
Definition application.h:97
void LoadRom(const std::string &path)
auto filename() const
Definition rom.h:175
A class for emulating and debugging SNES games.
Definition emulator.h:41
Abstract interface for emulator backends (Internal vs Mesen2)
Definition i_emulator.h:23
static TestManager & Get()
#define YAZE_VERSION_STRING
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
Configuration options for the application startup.
Definition application.h:26
std::string rom_file
Definition application.h:28
bool enable_test_harness
Definition application.h:51
std::string backend
Definition application.h:54
Status information for an active YAZE instance.