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 if (controller_->editor_manager()) {
89 controller_->editor_manager()->ApplyStartupVisibility(config_);
90 }
91
92 // If we successfully loaded a ROM at startup, run startup actions
93 if (!start_path.empty() && controller_->editor_manager()) {
95 }
96
97#ifdef YAZE_WITH_GRPC
98 // Initialize gRPC unified server
100 LOG_INFO("App", "Initializing Unified gRPC Server...");
101 canvas_automation_service_ =
102 std::make_unique<CanvasAutomationServiceImpl>();
103 grpc_server_ = std::make_unique<YazeGRPCServer>();
104
105 auto rom_getter = [this]() {
106 return controller_->GetCurrentRom();
107 };
108 auto rom_loader = [this](const std::string& path) -> bool {
109 if (!controller_ || !controller_->editor_manager())
110 return false;
111 auto status = controller_->editor_manager()->OpenRomOrProject(path);
112 return status.ok();
113 };
114
115 emu::IEmulator* emulator_interface = nullptr;
116
117 if (config_.backend == "mesen") {
118 LOG_INFO("App", "Using Mesen2 backend for emulator service");
119 emulator_backend_ =
120 std::make_unique<emu::mesen::MesenEmulatorAdapter>();
121 emulator_interface = emulator_backend_.get();
122 } else {
123 emu::Emulator* internal_emulator = nullptr;
124 if (controller_->editor_manager()) {
125 internal_emulator = &controller_->editor_manager()->emulator();
126 } else {
127 LOG_WARN("App",
128 "EditorManager not ready; internal emulator services may be "
129 "limited");
130 }
131
132 auto adapter =
133 std::make_unique<emu::InternalEmulatorAdapter>(internal_emulator);
134
135 // Set up internal helpers for the adapter
136 adapter->SetRomLoader([this](const std::string& path) -> bool {
137 if (!controller_ || !controller_->editor_manager())
138 return false;
139 auto status = controller_->editor_manager()->OpenRomOrProject(path);
140 return status.ok();
141 });
142
143 adapter->SetRomGetter(
144 [this]() { return controller_->GetCurrentRom(); });
145
146 emulator_backend_ = std::move(adapter);
147 emulator_interface = emulator_backend_.get();
148 }
149
150 // Initialize server with all services
151 auto status = grpc_server_->Initialize(
152 config_.test_harness_port, emulator_interface, rom_getter, rom_loader,
154 nullptr, // Version manager not ready
155 nullptr, // Approval manager not ready
156 canvas_automation_service_.get());
157
158 if (status.ok()) {
159 status = grpc_server_->StartAsync(); // Start in background thread
160 if (!status.ok()) {
161 LOG_ERROR("App", "Failed to start gRPC server: %s",
162 std::string(status.message()).c_str());
163 } else {
164 LOG_INFO("App", "Unified gRPC server started on port %d",
166 }
167 } else {
168 LOG_ERROR("App", "Failed to initialize gRPC server: %s",
169 std::string(status.message()).c_str());
170 }
171
172 // Connect services to controller/editor manager
173 if (canvas_automation_service_) {
174 controller_->SetCanvasAutomationService(
175 canvas_automation_service_.get());
176 }
177 }
178#endif
179 }
180
181#ifdef __EMSCRIPTEN__
182 // Register the ROM load handler now that controller is ready.
183 yaze::app::wasm::SetRomLoadHandler(
184 [](std::string path) { Application::Instance().LoadRom(path); });
185#else
186 // Create activity file for instance discovery (non-WASM only).
187 // Prefer $XDG_RUNTIME_DIR (0700 per-user) over world-readable /tmp.
188 auto pid = getpid();
189 const char* runtime_dir = std::getenv("XDG_RUNTIME_DIR");
190 const char* status_dir = (runtime_dir && *runtime_dir) ? runtime_dir : "/tmp";
191 activity_file_ = std::make_unique<app::ActivityFile>(
192 absl::StrFormat("%s/yaze-%d.status", status_dir, pid));
194 LOG_INFO("App", "Activity file created: %s",
195 activity_file_->GetPath().c_str());
196#endif
197}
198
200 if (!controller_)
201 return;
202
203 // Calculate delta time
204 auto now = std::chrono::steady_clock::now();
205 if (first_frame_) {
206 delta_time_ = 0.016f; // Assume ~60fps for first frame
207 first_frame_ = false;
208 } else {
209 auto elapsed = std::chrono::duration<float>(now - last_frame_time_);
210 delta_time_ = elapsed.count();
211 }
212 last_frame_time_ = now;
213
214 // Publish FrameBeginEvent for pre-frame (non-ImGui) work
217 }
218
219#ifdef __EMSCRIPTEN__
220 auto& wasm_collab = app::platform::GetWasmCollaborationInstance();
221 wasm_collab.ProcessPendingChanges();
222#endif
223
224 controller_->OnInput();
225 auto status = controller_->OnLoad();
226 if (!status.ok()) {
227 LOG_ERROR("App", "Controller Load Error: %s",
228 std::string(status.message()).c_str());
229#ifdef __EMSCRIPTEN__
230 emscripten_cancel_main_loop();
231#endif
232 return;
233 }
234
235 if (!controller_->IsActive()) {
236 // Window closed
237 // LOG_INFO("App", "Controller became inactive");
238 }
239
240 controller_->DoRender();
241
242 // Publish FrameEndEvent for cleanup operations
245 }
246}
247
248void Application::LoadRom(const std::string& path) {
249 LOG_INFO("App", "Requesting ROM load: %s", path.c_str());
250
251 if (!controller_) {
252#ifdef __EMSCRIPTEN__
253 yaze::app::wasm::TriggerRomLoad(path);
254 LOG_INFO("App",
255 "Forwarded to wasm_bootstrap queue (controller not ready): %s",
256 path.c_str());
257#else
258 pending_rom_ = path;
259 LOG_INFO("App", "Queued ROM load (controller not ready): %s", path.c_str());
260#endif
261 return;
262 }
263
264 // Controller exists.
265 absl::Status status;
266 if (!controller_->IsActive()) {
267 status = controller_->OnEntry(path);
268 } else {
269 status = controller_->editor_manager()->OpenRomOrProject(path);
270 }
271
272 if (!status.ok()) {
273 std::string error_msg =
274 absl::StrCat("Failed to load ROM: ", status.message());
275 LOG_ERROR("App", "%s", error_msg.c_str());
276
277#ifdef __EMSCRIPTEN__
278 EM_ASM(
279 {
280 var msg = UTF8ToString($0);
281 console.error(msg);
282 alert(msg);
283 },
284 error_msg.c_str());
285#endif
286 } else {
287 LOG_INFO("App", "ROM loaded successfully: %s", path.c_str());
288
289 // Run startup actions whenever a new ROM is loaded IF it matches our startup config
290 // (Optional: we might only want to run actions once at startup, but for CLI usage usually
291 // you load one ROM and want the actions applied to it).
292 // For now, we'll only run actions if this is the first load or if explicitly requested.
293 // Actually, simpler: just run them. The user can close cards if they want.
295
296#ifndef __EMSCRIPTEN__
297 // Update activity file with new ROM path
299#endif
300
301#ifdef __EMSCRIPTEN__
302 EM_ASM(
303 { console.log("ROM loaded successfully: " + UTF8ToString($0)); },
304 path.c_str());
305#endif
306 }
307}
308
310 if (!controller_ || !controller_->editor_manager())
311 return;
312
313 auto* manager = controller_->editor_manager();
314 manager->ProcessStartupActions(config_);
315}
316
317#ifndef __EMSCRIPTEN__
319 if (!activity_file_)
320 return;
321
322 app::ActivityStatus status;
323 status.pid = getpid();
325 status.start_timestamp = std::time(nullptr);
326
327 if (controller_ && controller_->editor_manager()) {
328 auto* rom = controller_->GetCurrentRom();
329 status.active_rom = rom ? rom->filename() : "";
330 }
331
332#ifdef YAZE_WITH_GRPC
334 status.socket_path =
335 absl::StrFormat("localhost:%d", config_.test_harness_port);
336 }
337#endif
338
339 activity_file_->Update(status);
340}
341#endif
342
343#ifdef __EMSCRIPTEN__
344extern "C" void SyncFilesystem();
345#endif
346
348#ifdef __EMSCRIPTEN__
349 // Sync IDBFS to persist any changes before shutdown
350 LOG_INFO("App", "Syncing filesystem before shutdown...");
351 SyncFilesystem();
352#endif
353
354#ifdef YAZE_WITH_GRPC
355 if (grpc_server_) {
356 LOG_INFO("App", "Shutting down Unified gRPC Server...");
357 grpc_server_->Shutdown();
358 grpc_server_.reset();
359 }
360 canvas_automation_service_.reset();
361#endif
362
363#ifndef __EMSCRIPTEN__
364 // Clean up activity file for instance discovery
365 if (activity_file_) {
366 LOG_INFO("App", "Removing activity file: %s",
367 activity_file_->GetPath().c_str());
368 activity_file_.reset(); // Destructor deletes the file
369 }
370#endif
371
372 if (controller_) {
373 controller_->OnExit();
374 controller_.reset();
375 }
376}
377
378} // 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 UpdateActivityStatus()
void LoadRom(const std::string &path)
std::chrono::steady_clock::time_point last_frame_time_
std::unique_ptr< app::ActivityFile > activity_file_
auto filename() const
Definition rom.h:157
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
::yaze::EventBus * event_bus()
Get the current EventBus instance.
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.
static FrameBeginEvent Create(float dt)
static FrameEndEvent Create(float dt)