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