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;
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();
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;
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;
467 result[
"dungeon"][
"save_entrances"] = flags.dungeon.kSaveEntrances;
469 return result.dump();
472std::string WasmSessionBridge::SetFeatureFlag(
const std::string& flag_name,
bool value) {
473 nlohmann::json result;
478 if (flag_name ==
"save_all_palettes") {
479 flags.kSaveAllPalettes = value;
480 }
else if (flag_name ==
"save_gfx_groups") {
481 flags.kSaveGfxGroups = value;
482 }
else if (flag_name ==
"save_with_change_queue") {
483 flags.kSaveWithChangeQueue = value;
484 }
else if (flag_name ==
"save_dungeon_maps") {
485 flags.kSaveDungeonMaps = value;
486 }
else if (flag_name ==
"save_graphics_sheet") {
487 flags.kSaveGraphicsSheet = value;
488 }
else if (flag_name ==
"log_to_console") {
489 flags.kLogToConsole = value;
490 }
else if (flag_name ==
"enable_performance_monitoring") {
491 flags.kEnablePerformanceMonitoring = value;
492 }
else if (flag_name ==
"overworld.draw_sprites") {
493 flags.overworld.kDrawOverworldSprites = value;
494 }
else if (flag_name ==
"overworld.save_maps") {
495 flags.overworld.kSaveOverworldMaps = value;
496 }
else if (flag_name ==
"overworld.save_entrances") {
497 flags.overworld.kSaveOverworldEntrances = value;
498 }
else if (flag_name ==
"overworld.save_exits") {
499 flags.overworld.kSaveOverworldExits = value;
500 }
else if (flag_name ==
"overworld.save_items") {
501 flags.overworld.kSaveOverworldItems = value;
502 }
else if (flag_name ==
"overworld.save_properties") {
503 flags.overworld.kSaveOverworldProperties = value;
504 }
else if (flag_name ==
"overworld.load_custom") {
505 flags.overworld.kLoadCustomOverworld = value;
506 }
else if (flag_name ==
"overworld.apply_zscustom_asm") {
507 flags.overworld.kApplyZSCustomOverworldASM = value;
508 }
else if (flag_name ==
"dungeon.save_entrances") {
509 flags.dungeon.kSaveEntrances = value;
515 result[
"success"] =
true;
516 result[
"flag"] = flag_name;
517 result[
"value"] = value;
518 LOG_INFO(
"WasmSessionBridge",
"Set flag %s = %s", flag_name.c_str(), value ?
"true" :
"false");
520 result[
"success"] =
false;
521 result[
"error"] =
"Unknown flag: " + flag_name;
524 return result.dump();
527std::string WasmSessionBridge::GetAvailableFlags() {
528 nlohmann::json result = nlohmann::json::array();
530 result.push_back(
"save_all_palettes");
531 result.push_back(
"save_gfx_groups");
532 result.push_back(
"save_with_change_queue");
533 result.push_back(
"save_dungeon_maps");
534 result.push_back(
"save_graphics_sheet");
535 result.push_back(
"log_to_console");
536 result.push_back(
"enable_performance_monitoring");
537 result.push_back(
"enable_tiered_gfx_architecture");
538 result.push_back(
"overworld.draw_sprites");
539 result.push_back(
"overworld.save_maps");
540 result.push_back(
"overworld.save_entrances");
541 result.push_back(
"overworld.save_exits");
542 result.push_back(
"overworld.save_items");
543 result.push_back(
"overworld.save_properties");
544 result.push_back(
"overworld.load_custom");
545 result.push_back(
"overworld.apply_zscustom_asm");
546 result.push_back(
"dungeon.save_entrances");
548 return result.dump();
555std::string WasmSessionBridge::ExecuteZ3edCommand(
const std::string& command) {
556 nlohmann::json result;
559 result[
"success"] =
false;
560 result[
"error"] =
"Session bridge not initialized";
561 return result.dump();
565 if (command_handler_) {
566 std::string output = command_handler_(command);
567 result[
"success"] =
true;
568 result[
"output"] = output;
570 std::lock_guard<std::mutex> lock(state_mutex_);
571 current_state_.last_z3ed_command = command;
572 current_state_.last_z3ed_result = output;
573 return result.dump();
578 std::lock_guard<std::mutex> lock(state_mutex_);
579 pending_command_ = command;
580 command_pending_ =
true;
581 current_state_.z3ed_command_pending =
true;
582 current_state_.last_z3ed_command = command;
585 result[
"success"] =
true;
586 result[
"queued"] =
true;
587 result[
"command"] = command;
589 LOG_INFO(
"WasmSessionBridge",
"Queued z3ed command: %s", command.c_str());
590 return result.dump();
593std::string WasmSessionBridge::GetPendingCommand() {
594 nlohmann::json result;
596 std::lock_guard<std::mutex> lock(state_mutex_);
598 if (command_pending_) {
599 result[
"pending"] =
true;
600 result[
"command"] = pending_command_;
602 result[
"pending"] =
false;
605 return result.dump();
608std::string WasmSessionBridge::SetCommandResult(
const std::string& result_str) {
609 nlohmann::json result;
611 std::lock_guard<std::mutex> lock(state_mutex_);
613 pending_result_ = result_str;
614 command_pending_ =
false;
615 current_state_.z3ed_command_pending =
false;
616 current_state_.last_z3ed_result = result_str;
618 result[
"success"] =
true;
620 return result.dump();
623void WasmSessionBridge::SetCommandHandler(CommandCallback handler) {
624 command_handler_ = handler;
631void WasmSessionBridge::OnStateChange(StateChangeCallback callback) {
632 state_callbacks_.push_back(callback);
635void WasmSessionBridge::NotifyStateChange() {
636 std::lock_guard<std::mutex> lock(state_mutex_);
637 current_state_.UpdateFromEditor(editor_manager_);
639 for (
const auto& callback : state_callbacks_) {
641 callback(current_state_);
646std::string WasmSessionBridge::RefreshState() {
648 return R
"({"error": "Session bridge not initialized"})";
651 std::lock_guard<std::mutex> lock(state_mutex_);
652 current_state_.UpdateFromEditor(editor_manager_);
654 nlohmann::json result;
655 result["success"] =
true;
656 result[
"state"] = nlohmann::json::parse(current_state_.ToJson());
658 return result.dump();
666 emscripten::function(
"sessionIsReady", &WasmSessionBridge::IsReady);
667 emscripten::function(
"sessionGetState", &WasmSessionBridge::GetState);
668 emscripten::function(
"sessionSetState", &WasmSessionBridge::SetState);
669 emscripten::function(
"sessionGetProperty", &WasmSessionBridge::GetProperty);
670 emscripten::function(
"sessionSetProperty", &WasmSessionBridge::SetProperty);
671 emscripten::function(
"sessionGetFeatureFlags", &WasmSessionBridge::GetFeatureFlags);
672 emscripten::function(
"sessionSetFeatureFlag", &WasmSessionBridge::SetFeatureFlag);
673 emscripten::function(
"sessionGetAvailableFlags", &WasmSessionBridge::GetAvailableFlags);
674 emscripten::function(
"sessionExecuteZ3edCommand", &WasmSessionBridge::ExecuteZ3edCommand);
675 emscripten::function(
"sessionGetPendingCommand", &WasmSessionBridge::GetPendingCommand);
676 emscripten::function(
"sessionSetCommandResult", &WasmSessionBridge::SetCommandResult);
677 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");} })
Rom * rom()
Get the current ROM instance.
Editor * current_editor()
Get the currently active editor.
constexpr std::array< const char *, 14 > kEditorNames
std::string executeCommand(std::string command)
EMSCRIPTEN_BINDINGS(yaze_debug_inspector)