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