7#include <emscripten/bind.h>
9#include "absl/strings/str_format.h"
13#include "nlohmann/json.hpp"
21editor::EditorManager* WasmSessionBridge::editor_manager_ =
nullptr;
22bool WasmSessionBridge::initialized_ =
false;
23SharedSessionState WasmSessionBridge::current_state_;
24std::mutex WasmSessionBridge::state_mutex_;
25std::vector<WasmSessionBridge::StateChangeCallback> WasmSessionBridge::state_callbacks_;
27std::string WasmSessionBridge::pending_command_;
28std::string WasmSessionBridge::pending_result_;
29bool WasmSessionBridge::command_pending_ =
false;
41 j[
"rom"][
"title"] = rom_title;
42 j[
"rom"][
"size"] = rom_size;
43 j[
"rom"][
"dirty"] = rom_dirty;
46 j[
"editor"][
"current"] = current_editor;
47 j[
"editor"][
"type"] = current_editor_type;
48 j[
"editor"][
"visible_cards"] = visible_cards;
51 j[
"session"][
"id"] = session_id;
52 j[
"session"][
"count"] = session_count;
53 j[
"session"][
"name"] = session_name;
56 j[
"flags"][
"save_all_palettes"] = flag_save_all_palettes;
57 j[
"flags"][
"save_gfx_groups"] = flag_save_gfx_groups;
58 j[
"flags"][
"save_overworld_maps"] = flag_save_overworld_maps;
59 j[
"flags"][
"load_custom_overworld"] = flag_load_custom_overworld;
60 j[
"flags"][
"apply_zscustom_asm"] = flag_apply_zscustom_asm;
63 j[
"project"][
"name"] = project_name;
64 j[
"project"][
"path"] = project_path;
65 j[
"project"][
"has_project"] = has_project;
68 j[
"z3ed"][
"last_command"] = last_z3ed_command;
69 j[
"z3ed"][
"last_result"] = last_z3ed_result;
70 j[
"z3ed"][
"command_pending"] = z3ed_command_pending;
76 SharedSessionState state;
79 auto j = nlohmann::json::parse(json);
82 if (j.contains(
"rom")) {
83 state.rom_loaded = j[
"rom"].value(
"loaded",
false);
84 state.rom_filename = j[
"rom"].value(
"filename",
"");
85 state.rom_title = j[
"rom"].value(
"title",
"");
86 state.rom_size = j[
"rom"].value(
"size", 0);
87 state.rom_dirty = j[
"rom"].value(
"dirty",
false);
91 if (j.contains(
"editor")) {
92 state.current_editor = j[
"editor"].value(
"current",
"");
93 state.current_editor_type = j[
"editor"].value(
"type", 0);
94 if (j[
"editor"].contains(
"visible_cards")) {
95 state.visible_cards = j[
"editor"][
"visible_cards"].get<std::vector<std::string>>();
100 if (j.contains(
"session")) {
101 state.session_id = j[
"session"].value(
"id", 0);
102 state.session_count = j[
"session"].value(
"count", 1);
103 state.session_name = j[
"session"].value(
"name",
"");
107 if (j.contains(
"flags")) {
108 state.flag_save_all_palettes = j[
"flags"].value(
"save_all_palettes",
false);
109 state.flag_save_gfx_groups = j[
"flags"].value(
"save_gfx_groups",
false);
110 state.flag_save_overworld_maps = j[
"flags"].value(
"save_overworld_maps",
true);
111 state.flag_load_custom_overworld = j[
"flags"].value(
"load_custom_overworld",
false);
112 state.flag_apply_zscustom_asm = j[
"flags"].value(
"apply_zscustom_asm",
false);
116 if (j.contains(
"project")) {
117 state.project_name = j[
"project"].value(
"name",
"");
118 state.project_path = j[
"project"].value(
"path",
"");
119 state.has_project = j[
"project"].value(
"has_project",
false);
122 }
catch (
const std::exception& e) {
123 LOG_ERROR(
"SharedSessionState",
"Failed to parse JSON: %s", e.what());
130 if (!manager)
return;
133 auto* rom = manager->GetCurrentRom();
134 if (rom && rom->is_loaded()) {
137 rom_title = rom->title();
138 rom_size = rom->size();
139 rom_dirty = rom->dirty();
149 auto* current = manager->GetCurrentEditor();
151 current_editor_type =
static_cast<int>(current->type());
152 if (current_editor_type >= 0 &&
159 session_id = manager->GetCurrentSessionIndex();
160 session_count = manager->GetActiveSessionCount();
164 flag_save_all_palettes = flags.kSaveAllPalettes;
165 flag_save_gfx_groups = flags.kSaveGfxGroups;
166 flag_save_overworld_maps = flags.overworld.kSaveOverworldMaps;
167 flag_load_custom_overworld = flags.overworld.kLoadCustomOverworld;
168 flag_apply_zscustom_asm = flags.overworld.kApplyZSCustomOverworldASM;
171absl::Status SharedSessionState::ApplyToEditor(editor::EditorManager* manager) {
173 return absl::InvalidArgumentError(
"EditorManager is null");
178 flags.kSaveAllPalettes = flag_save_all_palettes;
179 flags.kSaveGfxGroups = flag_save_gfx_groups;
180 flags.overworld.kSaveOverworldMaps = flag_save_overworld_maps;
181 flags.overworld.kLoadCustomOverworld = flag_load_custom_overworld;
182 flags.overworld.kApplyZSCustomOverworldASM = flag_apply_zscustom_asm;
185 if (!current_editor.empty()) {
194 return absl::OkStatus();
201EM_JS(
void, SetupYazeSessionApi, (), {
202 if (typeof Module ===
'undefined')
return;
210 window.yaze.session = {
212 getState: function() {
213 if (Module.sessionGetState) {
214 try {
return JSON.parse(Module.sessionGetState()); }
215 catch(e) {
return {error: e.message}; }
217 return {error:
"API not ready"};
220 setState: function(state) {
221 if (Module.sessionSetState) {
222 try {
return JSON.parse(Module.sessionSetState(JSON.stringify(state))); }
223 catch(e) {
return {error: e.message}; }
225 return {error:
"API not ready"};
228 getProperty: function(name) {
229 if (Module.sessionGetProperty) {
230 try {
return JSON.parse(Module.sessionGetProperty(name)); }
231 catch(e) {
return {error: e.message}; }
233 return {error:
"API not ready"};
236 setProperty: function(name, value) {
237 if (Module.sessionSetProperty) {
238 try {
return JSON.parse(Module.sessionSetProperty(name, JSON.stringify(value))); }
239 catch(e) {
return {error: e.message}; }
241 return {error:
"API not ready"};
244 refresh: function() {
245 if (Module.sessionRefreshState) {
246 try {
return JSON.parse(Module.sessionRefreshState()); }
247 catch(e) {
return {error: e.message}; }
249 return {error:
"API not ready"};
253 getFlags: function() {
254 if (Module.sessionGetFeatureFlags) {
255 try {
return JSON.parse(Module.sessionGetFeatureFlags()); }
256 catch(e) {
return {error: e.message}; }
258 return {error:
"API not ready"};
261 setFlag: function(name, value) {
262 if (Module.sessionSetFeatureFlag) {
263 try {
return JSON.parse(Module.sessionSetFeatureFlag(name, value)); }
264 catch(e) {
return {error: e.message}; }
266 return {error:
"API not ready"};
269 getAvailableFlags: function() {
270 if (Module.sessionGetAvailableFlags) {
271 try {
return JSON.parse(Module.sessionGetAvailableFlags()); }
272 catch(e) {
return {error: e.message}; }
274 return {error:
"API not ready"};
279 if (Module.sessionExecuteZ3edCommand) {
280 try {
return JSON.parse(Module.sessionExecuteZ3edCommand(command)); }
281 catch(e) {
return {error: e.message}; }
283 return {error:
"API not ready"};
286 getPendingCommand: function() {
287 if (Module.sessionGetPendingCommand) {
288 try {
return JSON.parse(Module.sessionGetPendingCommand()); }
289 catch(e) {
return {error: e.message}; }
291 return {error:
"API not ready"};
294 setCommandResult: function(result) {
295 if (Module.sessionSetCommandResult) {
296 try {
return JSON.parse(Module.sessionSetCommandResult(JSON.stringify(result))); }
297 catch(e) {
return {error: e.message}; }
299 return {error:
"API not ready"};
302 isReady: function() {
303 return Module.sessionIsReady ? Module.sessionIsReady() :
false;
307 console.log(
"[yaze] window.yaze.session API initialized");
314void WasmSessionBridge::Initialize(editor::EditorManager* editor_manager) {
315 editor_manager_ = editor_manager;
316 initialized_ = (editor_manager_ !=
nullptr);
319 SetupJavaScriptBindings();
322 std::lock_guard<std::mutex> lock(state_mutex_);
323 current_state_.UpdateFromEditor(editor_manager_);
325 LOG_INFO(
"WasmSessionBridge",
"Session bridge initialized");
329bool WasmSessionBridge::IsReady() {
330 return initialized_ && editor_manager_ !=
nullptr;
333void WasmSessionBridge::SetupJavaScriptBindings() {
334 SetupYazeSessionApi();
337std::string WasmSessionBridge::GetState() {
339 return R
"({"error": "Session bridge not initialized"})";
342 std::lock_guard<std::mutex> lock(state_mutex_);
343 current_state_.UpdateFromEditor(editor_manager_);
344 return current_state_.ToJson();
347std::string WasmSessionBridge::SetState(
const std::string& state_json) {
348 nlohmann::json result;
351 result[
"success"] =
false;
352 result[
"error"] =
"Session bridge not initialized";
353 return result.dump();
357 std::lock_guard<std::mutex> lock(state_mutex_);
358 auto new_state = SharedSessionState::FromJson(state_json);
359 auto status = new_state.ApplyToEditor(editor_manager_);
362 current_state_ = new_state;
363 result[
"success"] =
true;
365 result[
"success"] =
false;
366 result[
"error"] = status.ToString();
368 }
catch (
const std::exception& e) {
369 result[
"success"] =
false;
370 result[
"error"] = e.what();
373 return result.dump();
376std::string WasmSessionBridge::GetProperty(
const std::string& property_name) {
377 nlohmann::json result;
380 result[
"error"] =
"Session bridge not initialized";
381 return result.dump();
384 std::lock_guard<std::mutex> lock(state_mutex_);
385 current_state_.UpdateFromEditor(editor_manager_);
387 if (property_name ==
"rom.loaded") {
388 result[
"value"] = current_state_.rom_loaded;
389 }
else if (property_name ==
"rom.filename") {
390 result[
"value"] = current_state_.rom_filename;
391 }
else if (property_name ==
"rom.title") {
392 result[
"value"] = current_state_.rom_title;
393 }
else if (property_name ==
"rom.size") {
394 result[
"value"] = current_state_.rom_size;
395 }
else if (property_name ==
"rom.dirty") {
396 result[
"value"] = current_state_.rom_dirty;
397 }
else if (property_name ==
"editor.current") {
398 result[
"value"] = current_state_.current_editor;
399 }
else if (property_name ==
"session.id") {
400 result[
"value"] = current_state_.session_id;
401 }
else if (property_name ==
"session.count") {
402 result[
"value"] = current_state_.session_count;
404 result[
"error"] =
"Unknown property: " + property_name;
407 return result.dump();
410std::string WasmSessionBridge::SetProperty(
const std::string& property_name,
411 const std::string& value) {
412 nlohmann::json result;
415 result[
"success"] =
false;
416 result[
"error"] =
"Session bridge not initialized";
417 return result.dump();
422 if (property_name.find(
"flags.") == 0) {
423 std::string flag_name = property_name.substr(6);
425 bool flag_value = nlohmann::json::parse(value).get<
bool>();
426 return SetFeatureFlag(flag_name, flag_value);
427 }
catch (
const std::exception& e) {
428 result[
"success"] =
false;
429 result[
"error"] =
"Invalid boolean value";
432 result[
"success"] =
false;
433 result[
"error"] =
"Property is read-only: " + property_name;
436 return result.dump();
443std::string WasmSessionBridge::GetFeatureFlags() {
444 nlohmann::json result;
448 result[
"save_all_palettes"] = flags.kSaveAllPalettes;
449 result[
"save_gfx_groups"] = flags.kSaveGfxGroups;
450 result[
"save_with_change_queue"] = flags.kSaveWithChangeQueue;
451 result[
"save_dungeon_maps"] = flags.kSaveDungeonMaps;
452 result[
"save_graphics_sheet"] = flags.kSaveGraphicsSheet;
453 result[
"log_to_console"] = flags.kLogToConsole;
454 result[
"enable_performance_monitoring"] = flags.kEnablePerformanceMonitoring;
455 result[
"enable_tiered_gfx_architecture"] = flags.kEnableTieredGfxArchitecture;
456 result[
"use_native_file_dialog"] = flags.kUseNativeFileDialog;
459 result[
"overworld"][
"draw_sprites"] = flags.overworld.kDrawOverworldSprites;
460 result[
"overworld"][
"save_maps"] = flags.overworld.kSaveOverworldMaps;
461 result[
"overworld"][
"save_entrances"] = flags.overworld.kSaveOverworldEntrances;
462 result[
"overworld"][
"save_exits"] = flags.overworld.kSaveOverworldExits;
463 result[
"overworld"][
"save_items"] = flags.overworld.kSaveOverworldItems;
464 result[
"overworld"][
"save_properties"] = flags.overworld.kSaveOverworldProperties;
465 result[
"overworld"][
"load_custom"] = flags.overworld.kLoadCustomOverworld;
466 result[
"overworld"][
"apply_zscustom_asm"] = flags.overworld.kApplyZSCustomOverworldASM;
468 return result.dump();
471std::string WasmSessionBridge::SetFeatureFlag(
const std::string& flag_name,
bool value) {
472 nlohmann::json result;
477 if (flag_name ==
"save_all_palettes") {
478 flags.kSaveAllPalettes = value;
479 }
else if (flag_name ==
"save_gfx_groups") {
480 flags.kSaveGfxGroups = value;
481 }
else if (flag_name ==
"save_with_change_queue") {
482 flags.kSaveWithChangeQueue = value;
483 }
else if (flag_name ==
"save_dungeon_maps") {
484 flags.kSaveDungeonMaps = value;
485 }
else if (flag_name ==
"save_graphics_sheet") {
486 flags.kSaveGraphicsSheet = value;
487 }
else if (flag_name ==
"log_to_console") {
488 flags.kLogToConsole = value;
489 }
else if (flag_name ==
"enable_performance_monitoring") {
490 flags.kEnablePerformanceMonitoring = value;
491 }
else if (flag_name ==
"overworld.draw_sprites") {
492 flags.overworld.kDrawOverworldSprites = value;
493 }
else if (flag_name ==
"overworld.save_maps") {
494 flags.overworld.kSaveOverworldMaps = value;
495 }
else if (flag_name ==
"overworld.save_entrances") {
496 flags.overworld.kSaveOverworldEntrances = value;
497 }
else if (flag_name ==
"overworld.save_exits") {
498 flags.overworld.kSaveOverworldExits = value;
499 }
else if (flag_name ==
"overworld.save_items") {
500 flags.overworld.kSaveOverworldItems = value;
501 }
else if (flag_name ==
"overworld.save_properties") {
502 flags.overworld.kSaveOverworldProperties = value;
503 }
else if (flag_name ==
"overworld.load_custom") {
504 flags.overworld.kLoadCustomOverworld = value;
505 }
else if (flag_name ==
"overworld.apply_zscustom_asm") {
506 flags.overworld.kApplyZSCustomOverworldASM = value;
512 result[
"success"] =
true;
513 result[
"flag"] = flag_name;
514 result[
"value"] = value;
515 LOG_INFO(
"WasmSessionBridge",
"Set flag %s = %s", flag_name.c_str(), value ?
"true" :
"false");
517 result[
"success"] =
false;
518 result[
"error"] =
"Unknown flag: " + flag_name;
521 return result.dump();
524std::string WasmSessionBridge::GetAvailableFlags() {
525 nlohmann::json result = nlohmann::json::array();
527 result.push_back(
"save_all_palettes");
528 result.push_back(
"save_gfx_groups");
529 result.push_back(
"save_with_change_queue");
530 result.push_back(
"save_dungeon_maps");
531 result.push_back(
"save_graphics_sheet");
532 result.push_back(
"log_to_console");
533 result.push_back(
"enable_performance_monitoring");
534 result.push_back(
"enable_tiered_gfx_architecture");
535 result.push_back(
"overworld.draw_sprites");
536 result.push_back(
"overworld.save_maps");
537 result.push_back(
"overworld.save_entrances");
538 result.push_back(
"overworld.save_exits");
539 result.push_back(
"overworld.save_items");
540 result.push_back(
"overworld.save_properties");
541 result.push_back(
"overworld.load_custom");
542 result.push_back(
"overworld.apply_zscustom_asm");
544 return result.dump();
551std::string WasmSessionBridge::ExecuteZ3edCommand(
const std::string& command) {
552 nlohmann::json result;
555 result[
"success"] =
false;
556 result[
"error"] =
"Session bridge not initialized";
557 return result.dump();
561 if (command_handler_) {
562 std::string output = command_handler_(command);
563 result[
"success"] =
true;
564 result[
"output"] = output;
566 std::lock_guard<std::mutex> lock(state_mutex_);
567 current_state_.last_z3ed_command = command;
568 current_state_.last_z3ed_result = output;
569 return result.dump();
574 std::lock_guard<std::mutex> lock(state_mutex_);
575 pending_command_ = command;
576 command_pending_ =
true;
577 current_state_.z3ed_command_pending =
true;
578 current_state_.last_z3ed_command = command;
581 result[
"success"] =
true;
582 result[
"queued"] =
true;
583 result[
"command"] = command;
585 LOG_INFO(
"WasmSessionBridge",
"Queued z3ed command: %s", command.c_str());
586 return result.dump();
589std::string WasmSessionBridge::GetPendingCommand() {
590 nlohmann::json result;
592 std::lock_guard<std::mutex> lock(state_mutex_);
594 if (command_pending_) {
595 result[
"pending"] =
true;
596 result[
"command"] = pending_command_;
598 result[
"pending"] =
false;
601 return result.dump();
604std::string WasmSessionBridge::SetCommandResult(
const std::string& result_str) {
605 nlohmann::json result;
607 std::lock_guard<std::mutex> lock(state_mutex_);
609 pending_result_ = result_str;
610 command_pending_ =
false;
611 current_state_.z3ed_command_pending =
false;
612 current_state_.last_z3ed_result = result_str;
614 result[
"success"] =
true;
616 return result.dump();
619void WasmSessionBridge::SetCommandHandler(CommandCallback handler) {
620 command_handler_ = handler;
627void WasmSessionBridge::OnStateChange(StateChangeCallback callback) {
628 state_callbacks_.push_back(callback);
631void WasmSessionBridge::NotifyStateChange() {
632 std::lock_guard<std::mutex> lock(state_mutex_);
633 current_state_.UpdateFromEditor(editor_manager_);
635 for (
const auto& callback : state_callbacks_) {
637 callback(current_state_);
642std::string WasmSessionBridge::RefreshState() {
644 return R
"({"error": "Session bridge not initialized"})";
647 std::lock_guard<std::mutex> lock(state_mutex_);
648 current_state_.UpdateFromEditor(editor_manager_);
650 nlohmann::json result;
651 result["success"] =
true;
652 result[
"state"] = nlohmann::json::parse(current_state_.ToJson());
654 return result.dump();
662 emscripten::function(
"sessionIsReady", &WasmSessionBridge::IsReady);
663 emscripten::function(
"sessionGetState", &WasmSessionBridge::GetState);
664 emscripten::function(
"sessionSetState", &WasmSessionBridge::SetState);
665 emscripten::function(
"sessionGetProperty", &WasmSessionBridge::GetProperty);
666 emscripten::function(
"sessionSetProperty", &WasmSessionBridge::SetProperty);
667 emscripten::function(
"sessionGetFeatureFlags", &WasmSessionBridge::GetFeatureFlags);
668 emscripten::function(
"sessionSetFeatureFlag", &WasmSessionBridge::SetFeatureFlag);
669 emscripten::function(
"sessionGetAvailableFlags", &WasmSessionBridge::GetAvailableFlags);
670 emscripten::function(
"sessionExecuteZ3edCommand", &WasmSessionBridge::ExecuteZ3edCommand);
671 emscripten::function(
"sessionGetPendingCommand", &WasmSessionBridge::GetPendingCommand);
672 emscripten::function(
"sessionSetCommandResult", &WasmSessionBridge::SetCommandResult);
673 emscripten::function(
"sessionRefreshState", &WasmSessionBridge::RefreshState);
#define LOG_ERROR(category, format,...)
#define LOG_INFO(category, format,...)
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })
constexpr std::array< const char *, 14 > kEditorNames
std::string executeCommand(std::string command)
EMSCRIPTEN_BINDINGS(yaze_debug_inspector)