diff --git a/core/story-manager/interfaces/i_audio_event.h b/core/story-manager/interfaces/i_audio_event.h new file mode 100644 index 0000000..67cf96d --- /dev/null +++ b/core/story-manager/interfaces/i_audio_event.h @@ -0,0 +1,16 @@ +#ifndef I_AUDIO_EVENT_H +#define I_AUDIO_EVENT_H + +// Interface pour gérer les événements audio, comme la fin d'une piste. +// Ceci permet de découpler le lecteur audio de l'entité qui doit réagir à ces événements (par exemple, la VM). + +class IAudioEvent +{ +public: + virtual ~IAudioEvent() = default; + + // Appelé lorsque la lecture d'une piste audio est terminée. + virtual void EndOfAudio() = 0; +}; + +#endif // I_AUDIO_EVENT_H diff --git a/core/story-manager/interfaces/i_logger.h b/core/story-manager/interfaces/i_logger.h index 5a8fbcf..1151a2b 100644 --- a/core/story-manager/interfaces/i_logger.h +++ b/core/story-manager/interfaces/i_logger.h @@ -2,10 +2,16 @@ #include +class ILogSubject { +public: + virtual void LogEvent(const std::string &txt, bool critical = false) = 0; +}; + class ILogger { public: virtual void Log(const std::string &txt, bool critical = false) = 0; + virtual void RegisterSubject(std::shared_ptr subject) = 0; virtual ~ILogger() {} }; diff --git a/core/story-manager/interfaces/i_story_manager.h b/core/story-manager/interfaces/i_story_manager.h index 0f1bd75..9b1a143 100644 --- a/core/story-manager/interfaces/i_story_manager.h +++ b/core/story-manager/interfaces/i_story_manager.h @@ -42,14 +42,6 @@ public: // Modules virtual void OpenModule(const std::string &uuid) = 0; - // Resources management - virtual std::pair Images() = 0; - virtual std::pair Sounds() = 0; - virtual std::pair Resources() = 0; - virtual void AddResource(std::shared_ptr res) = 0; - virtual void ClearResources() = 0; - virtual void DeleteResource(FilterIterator &it) = 0; - // Node interaction virtual void BuildNodes(bool compileonly) = 0; virtual void BuildCode(bool compileonly) = 0; diff --git a/core/story-manager/lib/nodes_factory.h b/core/story-manager/src/nodes_factory.h similarity index 100% rename from core/story-manager/lib/nodes_factory.h rename to core/story-manager/src/nodes_factory.h diff --git a/core/story-manager/lib/resource.cpp b/core/story-manager/src/resource.cpp similarity index 100% rename from core/story-manager/lib/resource.cpp rename to core/story-manager/src/resource.cpp diff --git a/core/story-manager/lib/resource.h b/core/story-manager/src/resource.h similarity index 100% rename from core/story-manager/lib/resource.h rename to core/story-manager/src/resource.h diff --git a/shared/story_db.cpp b/core/story-manager/src/story_db.cpp similarity index 100% rename from shared/story_db.cpp rename to core/story-manager/src/story_db.cpp diff --git a/shared/story_db.h b/core/story-manager/src/story_db.h similarity index 100% rename from shared/story_db.h rename to core/story-manager/src/story_db.h diff --git a/core/story-manager/lib/story_options.h b/core/story-manager/src/story_options.h similarity index 100% rename from core/story-manager/lib/story_options.h rename to core/story-manager/src/story_options.h diff --git a/core/story-manager/lib/sys_lib.cpp b/core/story-manager/src/sys_lib.cpp similarity index 100% rename from core/story-manager/lib/sys_lib.cpp rename to core/story-manager/src/sys_lib.cpp diff --git a/core/story-manager/lib/sys_lib.h b/core/story-manager/src/sys_lib.h similarity index 100% rename from core/story-manager/lib/sys_lib.h rename to core/story-manager/src/sys_lib.h diff --git a/core/story-manager/lib/variable.h b/core/story-manager/src/variable.h similarity index 100% rename from core/story-manager/lib/variable.h rename to core/story-manager/src/variable.h diff --git a/shared/audio_player.h b/shared/audio_player.h index 9d1e016..9fbcd8d 100644 --- a/shared/audio_player.h +++ b/shared/audio_player.h @@ -9,21 +9,13 @@ #include #include #include "thread_safe_queue.h" - +#include "i_audio_event.h" struct AudioCommand { std::string order; std::string filename; }; -class IAudioEvent -{ -public: - virtual ~IAudioEvent() {} - - virtual void EndOfAudio() = 0; -}; - class AudioPlayer { diff --git a/story-editor/CMakeLists.txt b/story-editor/CMakeLists.txt index 45a45d2..09bb7f1 100644 --- a/story-editor/CMakeLists.txt +++ b/story-editor/CMakeLists.txt @@ -120,6 +120,8 @@ set(SRCS src/main.cpp src/main_window.cpp + src/app/app_controller.cpp + src/windows/window_base.cpp src/windows/console_window.cpp src/windows/library_window.cpp @@ -167,7 +169,6 @@ set(SRCS ../shared/resource_manager.cpp ../shared/library_manager.cpp ../shared/downloader.cpp - ../shared/story_db.cpp ../shared/miniz.c ../shared/zip.cpp ../shared/platform_folders.cpp @@ -187,10 +188,9 @@ set(SRCS ../core/story-manager/src/nodes/print_node.cpp ../core/story-manager/src/nodes/syscall_node.cpp ../core/story-manager/src/nodes/connection.cpp - - ../core/story-manager/lib/sys_lib.cpp - ../core/story-manager/lib/resource.cpp - + ../core/story-manager/src/story_db.cpp + ../core/story-manager/src/sys_lib.cpp + ../core/story-manager/src/resource.cpp ../core/chip32/chip32_assembler.cpp ../core/chip32/chip32_vm.c @@ -267,6 +267,8 @@ target_include_directories(${STORY_EDITOR_PROJECT} PUBLIC src/windows src/docks src/dialogs + src/app + src/events ../firmware/library ../core/chip32 @@ -274,7 +276,6 @@ target_include_directories(${STORY_EDITOR_PROJECT} PUBLIC ../core/story-manager/src ../core/story-manager/src/nodes ../core/story-manager/src/compiler - ../core/story-manager/lib ../core/story-manager/interfaces ) diff --git a/story-editor/imgui.ini b/story-editor/imgui.ini index 8af86e4..13acd2d 100644 --- a/story-editor/imgui.ini +++ b/story-editor/imgui.ini @@ -1,6 +1,6 @@ [Window][WindowOverViewport_11111111] Pos=60,26 -Size=1220,694 +Size=1220,962 Collapsed=0 [Window][Debug##Default] @@ -9,32 +9,32 @@ Size=400,400 Collapsed=0 [Window][Library Manager] -Pos=568,26 -Size=712,288 +Pos=878,26 +Size=402,403 Collapsed=0 DockId=0x00000003,0 [Window][Console] -Pos=60,467 +Pos=60,735 Size=610,253 Collapsed=0 DockId=0x00000004,0 [Window][Emulator] -Pos=568,26 -Size=712,288 +Pos=878,26 +Size=402,403 Collapsed=0 DockId=0x00000003,5 [Window][Code viewer] -Pos=568,26 -Size=712,288 +Pos=878,26 +Size=402,403 Collapsed=0 DockId=0x00000003,4 [Window][Resources] -Pos=568,26 -Size=712,288 +Pos=878,26 +Size=402,403 Collapsed=0 DockId=0x00000003,1 @@ -50,36 +50,36 @@ Size=150,42 Collapsed=0 [Window][Variables] -Pos=672,467 +Pos=672,735 Size=608,253 Collapsed=0 DockId=0x00000005,0 [Window][CPU] -Pos=568,26 -Size=712,288 +Pos=878,26 +Size=402,403 Collapsed=0 DockId=0x00000003,2 [Window][RAM view] -Pos=568,26 -Size=712,288 +Pos=878,26 +Size=402,403 Collapsed=0 DockId=0x00000003,3 [Window][Properties] -Pos=568,316 -Size=712,149 +Pos=878,431 +Size=402,302 Collapsed=0 DockId=0x00000006,0 [Window][ToolBar] Pos=0,26 -Size=60,694 +Size=60,962 Collapsed=0 [Window][QuitConfirm] -Pos=508,312 +Pos=508,446 Size=264,96 Collapsed=0 @@ -90,13 +90,13 @@ Collapsed=0 [Window][Module editor] Pos=60,26 -Size=506,439 +Size=816,707 Collapsed=0 DockId=0x00000001,1 [Window][Story editor] Pos=60,26 -Size=506,439 +Size=816,707 Collapsed=0 DockId=0x00000001,0 @@ -133,12 +133,12 @@ Column 1 Width=104 Column 2 Width=120 [Docking][Data] -DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=60,26 Size=1220,694 Split=Y +DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=60,26 Size=1220,962 Split=Y DockNode ID=0x00000007 Parent=0x08BD597D SizeRef=1220,439 Split=X - DockNode ID=0x00000001 Parent=0x00000007 SizeRef=506,694 CentralNode=1 Selected=0x93ADCAAB - DockNode ID=0x00000002 Parent=0x00000007 SizeRef=712,694 Split=Y Selected=0x52EB28B5 - DockNode ID=0x00000003 Parent=0x00000002 SizeRef=718,369 Selected=0x63869CAF - DockNode ID=0x00000006 Parent=0x00000002 SizeRef=718,191 Selected=0x8C72BEA8 + DockNode ID=0x00000001 Parent=0x00000007 SizeRef=816,694 CentralNode=1 Selected=0x93ADCAAB + DockNode ID=0x00000002 Parent=0x00000007 SizeRef=402,694 Split=Y Selected=0x52EB28B5 + DockNode ID=0x00000003 Parent=0x00000002 SizeRef=718,250 Selected=0x4B07C626 + DockNode ID=0x00000006 Parent=0x00000002 SizeRef=718,187 Selected=0x8C72BEA8 DockNode ID=0x00000008 Parent=0x08BD597D SizeRef=1220,253 Split=X Selected=0xEA83D666 DockNode ID=0x00000004 Parent=0x00000008 SizeRef=610,192 Selected=0xEA83D666 DockNode ID=0x00000005 Parent=0x00000008 SizeRef=608,192 Selected=0x6DE9B20C diff --git a/story-editor/src/app/app_controller.cpp b/story-editor/src/app/app_controller.cpp new file mode 100644 index 0000000..be1527c --- /dev/null +++ b/story-editor/src/app/app_controller.cpp @@ -0,0 +1,973 @@ +// app_controller.cpp +#include "app_controller.h" + +// Inclure les entêtes des dépendances de AppController nécessaires pour l'implémentation +#include +#include +#include // Pour std::this_thread::sleep_for +#include // Pour std::this_thread +#include // Pour std::find + +#include "platform_folders.h" // Pour pf::getConfigHome() +#include "uuid.h" // Pour la génération d'UUID +#include "sys_lib.h" // Pour SysLib::ReadFile, GetFileExtension, GetFileName +#include "pack_archive.h" // Pour PackArchive +#include "json.hpp" +#include "variable.h" // Pour Variable +#include "all_events.h" + +// Définitions des registres et événements CHIP-32 si non déjà dans chip32_vm.h +// Assurez-vous que ces définitions sont accessibles. +#ifndef R0 +#define R0 0 +#endif +#ifndef R1 +#define R1 1 +#endif +#ifndef R2 +#define R2 2 +#endif +#ifndef R3 +#define R3 3 +#endif +#ifndef R4 +#define R4 4 +#endif +#ifndef PC +#define PC 7 // Exemple de registre Program Counter +#endif + +#ifndef EV_MASK_OK_BUTTON +#define EV_MASK_OK_BUTTON 1 +#endif +#ifndef EV_MASK_HOME_BUTTON +#define EV_MASK_HOME_BUTTON 2 +#endif +#ifndef EV_MASK_PREVIOUS_BUTTON +#define EV_MASK_PREVIOUS_BUTTON 4 +#endif +#ifndef EV_MASK_NEXT_BUTTON +#define EV_MASK_NEXT_BUTTON 8 +#endif +#ifndef EV_MASK_END_OF_AUDIO +#define EV_MASK_END_OF_AUDIO 16 +#endif + +// Définitions pour les codes de retour de Syscall +#ifndef SYSCALL_RET_OK +#define SYSCALL_RET_OK 0 +#endif +#ifndef SYSCALL_RET_WAIT_EV +#define SYSCALL_RET_WAIT_EV 1 // Exemple: VM doit attendre un événement +#endif + + +// Implémentation du callback pour le syscall de la VM +// C'est un peu délicat avec une fonction membre. +// Une solution courante est d'utiliser un 'trampoline' ou std::function. +// Pour ce projet, nous allons faire une adaptation simple en passant 'this' +// via un mécanisme global ou un wrapper. L'approche originale de MainWindow +// utilisait une classe Callback statique, que nous allons adapter ici. + +// Classe trampoline pour les syscalls de la VM +// Ceci est une version simplifiée. Dans un système plus robuste, vous pourriez +// vouloir un registre de callbacks ou une approche plus souple. +class SyscallTrampoline +{ +public: + static AppController* s_instance; // Pointer vers l'instance de AppController + + static uint8_t Callback(chip32_ctx_t *ctx, uint8_t code) + { + if (s_instance) + { + return s_instance->Syscall(ctx, code); + } + return SYSCALL_RET_OK; // Ou gérer l'erreur + } +}; +AppController* SyscallTrampoline::s_instance = nullptr; + + +AppController::AppController(ILogger& logger, EventBus& eventBus) + : m_logger(logger) + , m_eventBus(eventBus) // m_eventBus pour les événements + , m_resources(logger) // m_resources a besoin d'un IStoryManager/IResourceSource + , m_nodesFactory(logger) // m_nodesFactory a besoin d'un IStoryManager/IResourceSource + , m_libraryManager(logger, m_nodesFactory) // m_libraryManager a besoin d'un IStoryManager/INodeFactory + , m_player(*this) // m_player a besoin d'un IAudioEvent + , m_webServer(m_libraryManager) +{ + // VM Initialize - Déplacé du constructeur de MainWindow + m_chip32_ctx.stack_size = 512; + + m_chip32_ctx.rom.mem = m_rom_data; + m_chip32_ctx.rom.addr = 0; + m_chip32_ctx.rom.size = sizeof(m_rom_data); + + m_chip32_ctx.ram.mem = m_ram_data; + m_chip32_ctx.ram.addr = sizeof(m_rom_data); + m_chip32_ctx.ram.size = sizeof(m_ram_data); + + // Initialise le trampoline de syscall avec cette instance + // SyscallTrampoline::s_instance = this; + // m_chip32_ctx.syscall = SyscallTrampoline::Callback; + + Callback::func = std::bind(&AppController::Syscall, this, std::placeholders::_1, std::placeholders::_2); + m_chip32_ctx.syscall = static_cast(Callback::callback); + + // Assurez-vous que ces fonctions existent ou sont implémentées ailleurs + // CloseProject() et CloseModule() étaient dans MainWindow + // Si elles gèrent des états VM/projet, elles doivent être ici. + CloseProject(); + CloseModule(); +} + +AppController::~AppController() +{ + SaveParams(); // Sauvegarde les paramètres à la destruction +} + +bool AppController::Initialize() +{ + LoadParams(); + m_nodesFactory.ScanModules(); + m_player.Initialize(); // Initialise l'audio + return true; // Supposons que l'initialisation de AppController est toujours un succès simple +} + +// --- Implémentations de IStoryManager --- + +void AppController::OpenProject(const std::string &uuid) +{ + CloseProject(); + + m_story = m_libraryManager.GetStory(uuid); + + // DEBUG CODE !!!!!!!!!!!!! Permet si décommenter de forcer l'import, permet de tester plus facilement l'algo en ouvrant le projet + // PackArchive arch(*this); + // std::string basePath = m_libraryManager.LibraryPath() + "/" + uuid; + // arch.ConvertJsonStudioToOst(basePath, uuid, m_libraryManager.LibraryPath()); + + if (!m_story) + { + Log("Cannot find story: " + uuid); + } + else if (m_story->Load(m_resources, m_nodesFactory)) + { + Log("Open project success"); + m_eventBus.Emit(std::make_shared(uuid)); + } +} + + +void AppController::Log(const std::string &txt, bool critical) +{ + m_logger.Log(txt, critical); +} + +void AppController::PlaySoundFile(const std::string &fileName) +{ + m_player.Play(fileName); +} + +std::string AppController::BuildFullAssetsPath(const std::string_view fileName) const +{ + if (m_story) + { + return m_story->BuildFullAssetsPath(fileName); + } + return std::string(); +} + +void AppController::OpenFunction(const std::string &uuid, const std::string &name) +{ + m_eventBus.Emit(std::make_shared(uuid, name)); +} + +void AppController::NewStory() +{ + CloseProject(); + m_story = m_libraryManager.NewProject(); + if (m_story) + { + SaveStory(); // Sauvegarde le nouveau projet + m_libraryManager.Scan(); // Ajoute le nouveau projet à la bibliothèque + OpenStory(m_story->GetUuid()); // Ouvre le nouveau projet + } +} + +void AppController::OpenStory(const std::string &uuid_or_path) +{ + CloseProject(); + + // Tente d'abord d'ouvrir par UUID via LibraryManager + m_story = m_libraryManager.GetStory(uuid_or_path); + + // Si non trouvé par UUID, essaie d'ouvrir par chemin direct (si uuid_or_path est un chemin) + if (!m_story && std::filesystem::exists(uuid_or_path)) { + // Crée un StoryProject à partir d'un chemin existant si nécessaire + // Cette logique est plus complexe et dépend de comment votre StoryProject est construit + // For now, assume GetStory can handle paths if it's not a UUID + // Or you might need a dedicated method like m_libraryManager.GetStoryByPath() + // For simplicity, if it's a path, try to find its UUID or create temp StoryProject + // This part needs careful design based on your LibraryManager/StoryProject logic. + // For this example, we assume GetStory() attempts to resolve paths if it's not a UUID. + m_logger.Log("Attempting to open story by path (not UUID): " + uuid_or_path); + // A more robust solution would involve LibraryManager::GetStory(path) or similar. + // For now, if GetStory(uuid) failed and it's a path, we might assume it's a direct project path. + // This is a simplification and might need adjustment based on your actual library logic. + // If your StoryProject can be loaded directly from a path: + // m_story = std::make_shared(uuid_or_path); // Example, constructor might need more args + // m_story->Load(m_resources, m_nodesFactory); + // Or, more likely, LibraryManager needs to handle this. + } + + + if (!m_story) + { + m_logger.Log("Cannot find story: " + uuid_or_path, true); + } + else if (m_story->Load(m_resources, m_nodesFactory)) + { + m_logger.Log("Open project success: " + m_story->GetProjectFilePath()); + + // Add to recent if not exists + const auto& proj_path = m_story->GetProjectFilePath(); + AddRecentProject(proj_path); + + // Notify GUI (or specific windows) to enable/load project data + // These calls would be replaced by events or direct calls to GUI objects in MainWindow + // For now, we'll keep the conceptual calls that AppController would trigger: + // m_nodeEditorWindow.Load(m_story); // This logic belongs to GUI, AppController notifies + // m_nodeEditorWindow.Enable(); // This logic belongs to GUI + // etc. + UpdateVmView(); // To refresh debugger/CPU views + } + else + { + m_logger.Log("Open project error for: " + uuid_or_path, true); + } +} + +void AppController::SaveStory(const std::string &path) +{ + if (m_story) + { + m_story->Save(m_resources); + m_logger.Log("Project saved: " + m_story->GetProjectFilePath()); + } else { + m_logger.Log("No project open to save.", true); + } +} + +void AppController::ExportStory(const std::string &filename) +{ + // Logique d'exportation de l'histoire + // Cette partie est manquante dans le snippet original, mais devrait être ici. + m_logger.Log("Export story to: " + filename); +} + +void AppController::BuildNodes(IStoryProject::Type type) +{ + if (type == IStoryProject::Type::PROJECT_TYPE_STORY && m_story) { + if (m_story->GenerateScript(m_currentCode)) + { + // La GUI (DebuggerWindow) doit être notifiée de cette mise à jour. + // Au lieu de appeler m_debuggerWindow.SetScript(m_currentCode); directement, + // AppController pourrait émettre un événement ou un callback. + // Pour l'instant, on suppose une notification ou que la GUI tire les données. + m_logger.Log("Nodes script generated for story."); + Build(true); // Compile seulement par défaut + } + } else if (type == IStoryProject::Type::PROJECT_TYPE_MODULE && m_module) { + // Logique de génération de script pour le module + // Similaire à BuildNodes pour le projet principal. + m_logger.Log("Nodes script generated for module."); + } +} + +void AppController::Build(bool compileonly) +{ + m_dbg.run_result = VM_FINISHED; + m_dbg.free_run = false; + m_dbg.m_breakpoints.clear(); // Clear breakpoints on new build + + if (!m_story) { + m_logger.Log("No story loaded to build.", true); + return; + } + + if (!compileonly) + { + // Convert all media to desired type format + auto options = m_story->GetOptions(); + m_resources.ConvertResources(m_story->AssetsPath(), "", options.image_format, options.sound_format); + } + + Chip32::Assembler::Error err; + // La GUI (DebuggerWindow) doit être notifiée pour effacer les erreurs. + // m_debuggerWindow.ClearErrors(); + + if (m_story->GenerateBinary(m_currentCode, err)) + { + m_result.Print(); // Imprime le résultat de l'assemblage (Debug uniquement) + + if (m_story->CopyProgramTo(m_rom_data, sizeof (m_rom_data))) + { + m_story->SaveBinary(); + chip32_initialize(&m_chip32_ctx); + m_dbg.run_result = VM_READY; + UpdateVmView(); // Notifie la GUI de mettre à jour la vue VM + m_logger.Log("Build successful. VM ready."); + } + else + { + m_logger.Log("Program too big. Expand ROM memory.", true); + } + } + else + { + m_logger.Log(err.ToString(), true); + // La GUI (DebuggerWindow) doit être notifiée pour ajouter l'erreur. + // m_debuggerWindow.AddError(err.line, err.message); + } +} + +void AppController::BuildCode(std::shared_ptr story, bool compileonly, bool force) +{ + // Note: Dans le code original, BuildCode lisait m_externalSourceFileName. + // Il faut s'assurer que m_currentCode est bien défini avant d'appeler Build. + if (story) { + m_currentCode = SysLib::ReadFile(story->GetProjectFilePath()); // Simplifié pour l'exemple + // La GUI (DebuggerWindow) doit être notifiée pour SetScript. + // m_debuggerWindow.SetScript(m_currentCode); + Build(compileonly); + } else { + m_logger.Log("No story provided for BuildCode.", true); + } +} + +void AppController::SetExternalSourceFile(const std::string &filename) +{ + m_externalSourceFileName = filename; + m_logger.Log("External source file set to: " + filename); +} + +void AppController::LoadBinaryStory(const std::string &filename) +{ + FILE *fp = fopen(filename.c_str(), "rb"); + if (fp != NULL) + { + fseek(fp, 0L, SEEK_END); + long int sz = ftell(fp); + fseek(fp, 0L, SEEK_SET); + + if (sz <= m_chip32_ctx.rom.size) + { + size_t sizeRead = fread(m_chip32_ctx.rom.mem, 1, sz, fp); // Corrected fread args + if (sizeRead == (size_t)sz) // Cast sz to size_t for comparison + { + m_dbg.run_result = VM_READY; + chip32_initialize(&m_chip32_ctx); + m_logger.Log("Loaded binary file: " + filename); + UpdateVmView(); + } + else + { + m_logger.Log("Failed to load binary file completely. Read " + std::to_string(sizeRead) + " of " + std::to_string(sz) + " bytes.", true); + } + } else { + m_logger.Log("Binary file is too large for ROM: " + std::to_string(sz) + " bytes, max " + std::to_string(m_chip32_ctx.rom.size) + " bytes.", true); + } + fclose(fp); + } else { + m_logger.Log("Failed to open binary file: " + filename, true); + } +} + +void AppController::ToggleBreakpoint(int line) +{ + if (m_dbg.m_breakpoints.contains(line)) + { + m_dbg.m_breakpoints.erase(line); + m_logger.Log("Removed breakpoint at line: " + std::to_string(line + 1)); + } + else + { + m_dbg.m_breakpoints.insert(line); + m_logger.Log("Set breakpoint at line: " + std::to_string(line + 1)); + } + // Notify debugger window to update breakpoint display +} + +uint32_t AppController::GetRegister(int reg) +{ + uint32_t regVal = 0; + if (reg >= 0 && reg < REGISTER_COUNT) // Assurez-vous que REGISTER_COUNT est défini + { + regVal = m_chip32_ctx.registers[reg]; + } + return regVal; +} + +void AppController::ScanVariable(const std::function element)>& operation) +{ + if (m_story) + { + m_story->ScanVariable(operation); + } +} + +void AppController::AddVariable() +{ + if (m_story) + { + m_story->AddVariable(); + m_logger.Log("Variable added to project: " + m_story->GetProjectFilePath()); + } else { + m_logger.Log("No project open to add a variable.", true); + } +} + +void AppController::DeleteVariable(int i) +{ + if (m_story) + { + m_story->DeleteVariable(i); + m_logger.Log("Variable deleted from project: " + m_story->GetProjectFilePath()); + } else { + m_logger.Log("No project open to delete a variable.", true); + } +} + + +void AppController::Play() +{ + if ((m_dbg.run_result == VM_READY) || (m_dbg.run_result == VM_FINISHED)) + { + m_dbg.free_run = true; + m_dbg.run_result = VM_OK; // actually starts the execution + m_logger.Log("VM: Play initiated."); + } +} + +void AppController::Step() +{ + m_eventQueue.push({VmEventType::EvStep}); + m_logger.Log("VM: Step event queued."); +} + +void AppController::Run() +{ + m_eventQueue.push({VmEventType::EvRun}); + m_logger.Log("VM: Run event queued."); +} + +void AppController::Ok() +{ + m_eventQueue.push({VmEventType::EvOkButton}); + m_logger.Log("VM: OK button event queued."); +} + +void AppController::Stop() +{ + m_dbg.run_result = VM_FINISHED; + m_dbg.free_run = false; + m_logger.Log("VM: Stopped."); +} + +void AppController::Pause() +{ + // Logic for pausing VM (if VM supports pausing execution mid-instruction) + // For now, if free_run is false and VM_OK, it effectively waits for event + m_dbg.free_run = false; + m_logger.Log("VM: Pause requested."); +} + +void AppController::Home() +{ + m_eventQueue.push({VmEventType::EvHomeButton}); + m_logger.Log("VM: Home button event queued."); +} + +void AppController::Next() +{ + m_logger.Log("VM: Next button event queued."); + m_eventQueue.push({VmEventType::EvNextButton}); +} + +void AppController::Previous() +{ + m_logger.Log("VM: Previous button event queued."); + m_eventQueue.push({VmEventType::EvPreviousButton}); +} + +std::string AppController::VmState() const +{ + std::string state = "Unknown"; + switch (m_dbg.run_result) + { + case VM_READY: + state = "VM Ready"; + break; + case VM_FINISHED: + state = "VM Finished"; + break; + case VM_SKIPED: + state = "VM Skipped"; // Typo in original, corrected to Skipped + break; + case VM_WAIT_EVENT: + state = "VM Wait Event"; + break; + case VM_OK: + state = "VM Ok"; + break; + default: + state = "VM Error"; + break; + } + return state; +} + +// --- Implémentations de IAudioEvent --- + +void AppController::EndOfAudio() +{ + m_logger.Log("End of audio track. Queuing event for VM."); + m_eventQueue.push({VmEventType::EvAudioFinished}); +} + +// --- Fonctions internes de AppController (logique métier) --- + +void AppController::SaveParams() +{ + nlohmann::json j; + j["recents"] = m_recentProjects; + j["library_path"] = m_libraryManager.LibraryPath(); + j["store_url"] = m_libraryManager.GetStoreUrl(); + + std::string loc = pf::getConfigHome() + "/ost_settings.json"; + std::ofstream o(loc); + o << std::setw(4) << j << std::endl; + + m_logger.Log("Saved settings to: " + loc); +} + +void AppController::LoadParams() +{ + try { + std::filesystem::path dlDir = std::filesystem::path(pf::getConfigHome()) / "ost_modules"; + std::filesystem::create_directories(dlDir); + m_nodesFactory.SetModulesRootDirectory(dlDir.string()); + + std::string loc = pf::getConfigHome() + "/ost_settings.json"; + std::ifstream i(loc); + nlohmann::json j; + i >> j; + + if (j.contains("recents")) { + for (auto& element : j["recents"]) { + std::string path_str = element.get(); + if (std::filesystem::exists(path_str)) + { + m_recentProjects.push_back(path_str); + } + } + } + + if (j.contains("library_path")) { + std::string lib_path = j["library_path"].get(); + if (std::filesystem::exists(lib_path)) + { + m_libraryManager.Initialize(lib_path); + } + } + + if (j.contains("store_url")) { + m_libraryManager.SetStoreUrl(j.value("store_url", "https://gist.githubusercontent.com/DantSu/3aea4c1fe15070bcf394a40b89aec33e/raw/stories.json")); + } else { + m_logger.Log("No 'store_url' found in settings, using default.", false); + } + + } + catch(const std::exception &e) + { + m_logger.Log("Error loading settings: " + std::string(e.what()), true); + } +} + +std::string AppController::GetStringFromMemory(uint32_t addr) +{ + // Buffer local pour la chaîne + // Assurez-vous qu'il est assez grand pour gérer les chaînes de votre VM + // et qu'il est terminé par null + char strBuf[256]; // Augmenté la taille pour plus de sécurité + + // Le bit le plus significatif indique si c'est de la RAM (0x80000000) ou ROM + bool isRam = (addr & 0x80000000) != 0; + addr &= 0xFFFF; // Masque pour obtenir l'adresse 16 bits + + // Vérification de l'adresse pour éviter les dépassements de buffer + if (isRam) { + if (addr < m_chip32_ctx.ram.size) { + strncpy(strBuf, (const char *)&m_chip32_ctx.ram.mem[addr], sizeof(strBuf) - 1); + strBuf[sizeof(strBuf) - 1] = '\0'; // S'assurer que c'est null-terminated + } else { + m_logger.Log("GetStringFromMemory: Invalid RAM address: 0x" + std::to_string(addr), true); + return ""; + } + } else { + if (addr < m_chip32_ctx.rom.size) { + strncpy(strBuf, (const char *)&m_chip32_ctx.rom.mem[addr], sizeof(strBuf) - 1); + strBuf[sizeof(strBuf) - 1] = '\0'; // S'assurer que c'est null-terminated + } else { + m_logger.Log("GetStringFromMemory: Invalid ROM address: 0x" + std::to_string(addr), true); + return ""; + } + } + return strBuf; +} + +void AppController::ProcessStory() +{ + if (m_dbg.run_result == VM_FINISHED || m_dbg.run_result > VM_OK) + return; + + VmEvent event; + if (m_eventQueue.try_pop(event)) + { + if (event.type == VmEventType::EvStep) + { + StepInstruction(); + m_dbg.run_result = VM_OK; // Correction : ceci écrase le code de retour, potentiellement indésirable + } + else if (event.type == VmEventType::EvRun) + { + m_dbg.free_run = true; + m_dbg.run_result = VM_OK; + } + else if (event.type == VmEventType::EvStop) + { + m_dbg.run_result = VM_FINISHED; + } + + // Events managed only if the code is in wait event state + if (m_dbg.run_result == VM_WAIT_EVENT) + { + if (event.type == VmEventType::EvOkButton) + { + if (m_dbg.IsValidEvent(EV_MASK_OK_BUTTON)) + { + m_chip32_ctx.registers[R0] = EV_MASK_OK_BUTTON; + m_dbg.run_result = VM_OK; + } + } + else if (event.type == VmEventType::EvPreviousButton) + { + if (m_dbg.IsValidEvent(EV_MASK_PREVIOUS_BUTTON)) + { + m_chip32_ctx.registers[R0] = EV_MASK_PREVIOUS_BUTTON; + m_dbg.run_result = VM_OK; + } + } + else if (event.type == VmEventType::EvNextButton) + { + if (m_dbg.IsValidEvent(EV_MASK_NEXT_BUTTON)) + { + m_chip32_ctx.registers[R0] = EV_MASK_NEXT_BUTTON; + m_dbg.run_result = VM_OK; + } + } + else if (event.type == VmEventType::EvAudioFinished) + { + if (m_dbg.IsValidEvent(EV_MASK_END_OF_AUDIO)) + { + m_chip32_ctx.registers[R0] = EV_MASK_END_OF_AUDIO; + m_dbg.run_result = VM_OK; + } + } + else if (event.type == VmEventType::EvHomeButton) + { + if (m_dbg.IsValidEvent(EV_MASK_HOME_BUTTON)) + { + m_chip32_ctx.registers[R0] = EV_MASK_HOME_BUTTON; + m_dbg.run_result = VM_OK; + } + } + } + } + + if (m_dbg.run_result == VM_OK) + { + if (m_dbg.m_breakpoints.contains(m_dbg.line)) + { + m_logger.Log("Breakpoint hit on line: " + std::to_string(m_dbg.line + 1)); + m_dbg.run_result = VM_WAIT_EVENT; + m_dbg.free_run = false; + } + + if (m_dbg.free_run) + { + StepInstruction(); + } + } + + if (m_dbg.run_result == VM_FINISHED) + { + m_dbg.free_run = false; + } + else if (m_dbg.run_result > VM_OK) + { + std::string error = "VM Error: "; + switch (m_dbg.run_result) + { + case VM_ERR_STACK_OVERFLOW: error += "Stack overflow"; break; + case VM_ERR_STACK_UNDERFLOW: error += "Stack underflow"; break; + case VM_ERR_INVALID_ADDRESS: error += "Invalid address"; break; + case VM_ERR_UNSUPPORTED_OPCODE: error += "Unsupported opcode"; break; + case VM_ERR_UNKNOWN_OPCODE: error += "Unknown opcode"; break; + case VM_ERR_UNHANDLED_INTERRUPT: error += "Unhandled interrupt"; break; + case VM_ERR_INVALID_REGISTER: error += "Invalid register"; break; + default: error += "Unknown error"; break; + } + error += " (line: " + std::to_string(m_dbg.line) + ")"; + m_logger.Log(error, true); + } + + // In this case, we wait for single step debugger + if ((m_dbg.run_result == VM_OK) && !m_dbg.free_run) + { + m_dbg.run_result = VM_WAIT_EVENT; + } +} + +void AppController::StepInstruction() +{ + m_dbg.run_result = chip32_step(&m_chip32_ctx); + UpdateVmView(); +} + +uint8_t AppController::Syscall(chip32_ctx_t *ctx, uint8_t code) +{ + uint8_t retCode = SYSCALL_RET_OK; + m_logger.Log("SYSCALL: " + std::to_string(code)); + + // Media + if (code == 1) // Execute media + { + // R0: image file name address, R1: sound file name address + if (ctx->registers[R0] != 0) + { + std::string imageFile = m_story->BuildFullAssetsPath(GetStringFromMemory(ctx->registers[R0])); + m_logger.Log("Image: " + imageFile); + // Ici, vous notifieriez la fenêtre de l'émulateur + // m_emulatorWindow.SetImage(imageFile); // Ceci est une dépendance GUI + } + else + { + // m_emulatorWindow.ClearImage(); // Dépendance GUI + } + + if (ctx->registers[R1] != 0) + { + std::string soundFile = m_story->BuildFullAssetsPath(GetStringFromMemory(ctx->registers[R1])); + m_logger.Log("Sound: " + soundFile); + m_player.Play(soundFile); + } + retCode = SYSCALL_RET_OK; + } + else if (code == 2) // Wait for event + { + m_eventQueue.clear(); + m_dbg.event_mask = ctx->registers[R0]; + // optional timeout is located in R1 + retCode = SYSCALL_RET_WAIT_EV; + } + else if (code == 3) + { + // FIXME: Unknown syscall 3, log it for debugging + m_logger.Log("Syscall 3 called (FIXME)", false); + } + else if (code == 4) // Printf (printf-like behavior) + { + std::string text = GetStringFromMemory(ctx->registers[R0]); + int arg_count = ctx->registers[R1]; + char working_buf[200] = {0}; + + // Simplified printf logic for logging + switch(arg_count){ + case 0: m_logger.Log(text); break; + case 1: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2]); m_logger.Log(working_buf); break; + case 2: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2], ctx->registers[R3]); m_logger.Log(working_buf); break; + case 3: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2], ctx->registers[R3], ctx->registers[R4]); m_logger.Log(working_buf); break; + default: m_logger.Log("Printf with unsupported arg_count: " + std::to_string(arg_count) + " text: " + text, true); break; + } + } + else if (code == 5) // WAIT (sleep) + { + // Sleep is in milliseconds, R0 contains the duration + std::this_thread::sleep_for(std::chrono::milliseconds(ctx->registers[R0])); + m_logger.Log("VM slept for " + std::to_string(ctx->registers[R0]) + " ms."); + } + else + { + m_logger.Log("Unknown syscall code: " + std::to_string(code), true); + } + + return retCode; +} + +void AppController::UpdateVmView() +{ + // C'est une fonction de notification pour la GUI. + // AppController ne devrait pas directement manipuler les vues GUI. + // Au lieu de cela, il émettrait un signal ou appellerait un observer. + uint32_t pcVal = m_chip32_ctx.registers[PC]; + + if (m_story && m_story->GetAssemblyLine(pcVal, m_dbg.line)) + { + m_logger.Log("Executing line: " + std::to_string(m_dbg.line + 1)); + // m_debuggerWindow.HighlightLine(m_dbg.line); // Dépendance GUI + } + else + { + m_logger.Log("Reached end or instruction not found (line: " + std::to_string(m_dbg.line) + ")", false); + } + // m_cpuWindow.updateRegistersView(m_chip32_ctx); // Dépendance GUI + // m_memoryEditor.DrawWindow("RAM view", m_chip32_ctx.ram.mem, m_chip32_ctx.ram.size); // Dépendance GUI +} + +void AppController::AddRecentProject(const std::string& projectPath) +{ + // Remove if already exists to move to front + m_recentProjects.erase( + std::remove(m_recentProjects.begin(), m_recentProjects.end(), projectPath), + m_recentProjects.end() + ); + + // Add to front + m_recentProjects.insert(m_recentProjects.begin(), projectPath); + + // Limit to 10 recent projects + if (m_recentProjects.size() > 10) { + m_recentProjects.resize(10); + } + + // Save recent projects on disk + SaveParams(); +} + +void AppController::CloseProject() +{ + if (m_story) + { + m_story->Clear(); // Clear story resources/data + m_story.reset(); // Release shared_ptr + } + + // Clear VM state + m_dbg.run_result = VM_FINISHED; + m_dbg.free_run = false; + m_dbg.m_breakpoints.clear(); + chip32_initialize(&m_chip32_ctx); // Reset VM context + + m_resources.Clear(); // Clear loaded resources + m_eventQueue.clear(); // Clear any pending VM events + + // Notify GUI (or specific windows) to clear/disable project-related data + // These would be events or direct calls from AppController to GUI objects + // m_nodeEditorWindow.Clear(); + // m_emulatorWindow.ClearImage(); + // m_consoleWindow.ClearLog(); + // m_debuggerWindow.ClearErrors(); + // m_debuggerWindow.SetScript(""); + // m_nodeEditorWindow.Disable(); + // m_emulatorWindow.Disable(); + // m_debuggerWindow.Disable(); + // m_resourcesWindow.Disable(); + // m_PropertiesWindow.Disable(); + // m_variablesWindow.Disable(); + // m_cpuWindow.Disable(); + + m_logger.Log("Project closed."); +} + +void AppController::NewModule() +{ + m_module = m_nodesFactory.NewModule(); + if (m_module) { + // Notify GUI (e.g., m_moduleEditorWindow.Load(m_module);) + // m_moduleEditorWindow.Enable(); + m_logger.Log("New module created."); + } +} + +void AppController::SaveModule() +{ + m_nodesFactory.SaveAllModules(m_resources); + m_logger.Log("Modules saved."); +} + +void AppController::OpenModule(const std::string &uuid) +{ + m_module = m_nodesFactory.GetModule(uuid); + if (!m_module) + { + m_logger.Log("Cannot find module: " + uuid, true); + } + else if (m_module->Load(m_resources, m_nodesFactory)) + { + m_logger.Log("Open module success: " + uuid); + // Notify GUI (e.g., m_moduleEditorWindow.Load(m_module);) + // m_moduleEditorWindow.Enable(); + } + else + { + m_logger.Log("Open module error: " + uuid, true); + } +} + +void AppController::CloseModule() +{ + if (m_module) { + m_module->Clear(); + m_module.reset(); + } + // Notify GUI (e.g., m_moduleEditorWindow.Clear(); m_moduleEditorWindow.Disable();) + m_logger.Log("Module closed."); +} + +void AppController::ImportProject(const std::string &filePathName, int format) +{ + (void) format; // Not used in the original snippet but kept for signature. + PackArchive archive(*this, m_nodesFactory); // PackArchive constructor might need an ILogger, adjust if needed + + auto ext = SysLib::GetFileExtension(filePathName); + auto filename = SysLib::GetFileName(filePathName); + + if ((ext == "pk") || (filename == "ni")) + { + archive.ImportCommercialFormat(filePathName, m_libraryManager.LibraryPath(), m_libraryManager.CommercialDbView()); + m_logger.Log("Imported commercial format: " + filePathName); + } + else if ((ext == "json") || (ext == "zip")) + { + archive.ImportStudioFormat(filePathName, m_libraryManager.LibraryPath()); + m_logger.Log("Imported studio format: " + filePathName); + } + else + { + m_logger.Log("Unknown file format for import: " + filePathName, true); + } +} + +std::pair AppController::Images() +{ + return m_resources.Images(); +} + +std::pair AppController::Sounds() +{ + return m_resources.Sounds(); +} diff --git a/story-editor/src/app/app_controller.h b/story-editor/src/app/app_controller.h new file mode 100644 index 0000000..673e004 --- /dev/null +++ b/story-editor/src/app/app_controller.h @@ -0,0 +1,149 @@ +// app_controller.h +#ifndef APP_CONTROLLER_H +#define APP_CONTROLLER_H + +#include +#include +#include // Pour std::shared_ptr +#include // Pour std::function +#include // Pour std::set + +// Inclure les entêtes des dépendances de AppController +#include "story_project.h" // Pour StoryProject +#include "chip32_assembler.h" // Pour Chip32::Assembler +#include "chip32_vm.h" // Pour chip32_ctx_t, Chip32::Result, chip32_result_t +#include "i_story_manager.h" // Interface implémentée par AppController +#include "i_audio_event.h" // Interface implémentée par AppController +#include "i_logger.h" // Interface implémentée par AppController (pour les logs métier) +#include "thread_safe_queue.h" // Pour ThreadSafeQueue +#include "audio_player.h" // Pour AudioPlayer +#include "library_manager.h" // Pour LibraryManager +#include "nodes_factory.h" // Pour NodesFactory +#include "resource_manager.h" // Pour ResourceManager (si elle est utilisée directement par AppController) +#include "web_server.h" // Pour WebServer +#include "story_machine.h" // Peut-être renommé en VmEvent ou Processus de VM +#include "variable.h" // Pour Variable (si géré par AppController) +#include "debug_context.h" // Pour DebugContext +#include "event_bus.h" + +// Forward declaration pour éviter les dépendances circulaires si le logger est une GUI +class ILogger; // Peut être implémenté par la console_window par exemple + + +class AppController : public IStoryManager, public IAudioEvent +{ +public: + enum VmEventType { EvNoEvent, EvStep, EvRun, EvOkButton, EvPreviousButton, EvNextButton, EvAudioFinished, EvStop, EvHomeButton}; + + struct VmEvent + { + VmEventType type; + }; + + // Le constructeur prend une référence vers une implémentation de ILogger + // pour que la logique métier puisse logger sans dépendre de la GUI. + AppController(ILogger& logger, EventBus& eventBus); + ~AppController(); + + // Initialisation de l'AppController + bool Initialize(); + + void OpenProject(const std::string &uuid); + void ImportProject(const std::string &fileName, int format); + void Log(const std::string &txt, bool critical = false) override; + void PlaySoundFile(const std::string &fileName); + std::string BuildFullAssetsPath(const std::string_view fileName) const; + void OpenFunction(const std::string &uuid, const std::string &name); + void NewStory(); + void CloseProject(); + void NewModule(); + void SaveModule(); + void CloseModule(); + void OpenModule(const std::string &uuid); + void OpenStory(const std::string &path = ""); + void SaveStory(const std::string &path = ""); + void ExportStory(const std::string &filename); + std::shared_ptr GetCurrentStory() const { return m_story; } + std::shared_ptr GetModuleStory() const { return m_module; } + void BuildNodes(IStoryProject::Type type); + void Build(bool compileonly); + void BuildCode(std::shared_ptr story, bool compileonly, bool force = false); + + // --- Fonctions de IStoryManager --- + virtual void SetExternalSourceFile(const std::string &filename) override; + virtual void LoadBinaryStory(const std::string &filename) override; + virtual void ToggleBreakpoint(int line) override; + virtual uint32_t GetRegister(int reg) override; + virtual void ScanVariable(const std::function element)>& operation) override; + virtual void AddVariable() override; + virtual void DeleteVariable(int i) override; + virtual void Play() override; + virtual void Step() override; + virtual void Run() override; + virtual void Ok() override; + virtual void Stop() override; + virtual void Pause() override; + virtual void Home() override; + virtual void Next() override; + virtual void Previous() override; + virtual std::string VmState() const override; + + // --- Fonctions de IAudioEvent --- + virtual void EndOfAudio() override; + + // --- Fonctions internes de AppController (logique métier) --- + void SaveParams(); + void LoadParams(); + + // Méthodes pour interagir avec la VM et le débogueur + chip32_ctx_t* GetChip32Context() { return &m_chip32_ctx; } + DebugContext* GetDebugContext() { return &m_dbg; } + std::string GetStringFromMemory(uint32_t addr); + void ProcessStory(); + void StepInstruction(); + + // Getters pour les managers gérés par AppController + ResourceManager& GetResourceManager() { return m_resources; } + LibraryManager& GetLibraryManager() { return m_libraryManager; } + NodesFactory& GetNodesFactory() { return m_nodesFactory; } + AudioPlayer& GetAudioPlayer() { return m_player; } + WebServer& GetWebServer() { return m_webServer; } + const std::vector& GetRecentProjects() const { return m_recentProjects; } + void AddRecentProject(const std::string& projectPath); + + // Fonction pour les syscalls (appel système) de la VM + uint8_t Syscall(chip32_ctx_t *ctx, uint8_t code); + + // Méthode pour mettre à jour l'état de la vue VM (peut être un événement notifié à la GUI) + void UpdateVmView(); + + virtual std::pair Images() override; + virtual std::pair Sounds() override; + +private: + ILogger& m_logger; // Référence au logger pour les messages métier + EventBus& m_eventBus; // Bus d'événements pour la communication entre composants + + std::shared_ptr m_story; + std::shared_ptr m_module; + uint8_t m_rom_data[16*1024]; + uint8_t m_ram_data[16*1024]; + chip32_ctx_t m_chip32_ctx; + Chip32::Result m_result; + DebugContext m_dbg; // Contexte de débogage + std::string m_currentCode; + std::string m_externalSourceFileName; + std::vector m_recentProjects; + + NodesFactory m_nodesFactory; + ResourceManager m_resources; // Gère les ressources (images, sons) + LibraryManager m_libraryManager; // Gère les bibliothèques + AudioPlayer m_player; // Gère la lecture audio + ThreadSafeQueue m_eventQueue; // File d'événements de la VM + WebServer m_webServer; // Serveur web intégré + + // Fonctions privées utilitaires pour la logique métier + void SetupVM(int start_line, uint32_t entry_point); +}; + +#endif // APP_CONTROLLER_H diff --git a/story-editor/src/app/debug_context.h b/story-editor/src/app/debug_context.h new file mode 100644 index 0000000..70c0d75 --- /dev/null +++ b/story-editor/src/app/debug_context.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "chip32_assembler.h" +#include "chip32_vm.h" + +struct DebugContext +{ + uint32_t event_mask{0}; + bool wait_event{0}; + bool free_run{false}; + uint32_t line{0}; + chip32_result_t run_result{VM_FINISHED}; + + std::set m_breakpoints; + + void Stop() { + run_result = VM_FINISHED; + } + + bool IsValidEvent(uint32_t event) { + return (event_mask & event) != 0; + } + + static void DumpCodeAssembler(Chip32::Assembler & assembler) { + + for (std::vector::const_iterator iter = assembler.Begin(); + iter != assembler.End(); ++iter) + { + if (iter->isRomCode() || iter->isRomData) + { + std::cout << "-------------------" << std::endl; + std::cout << "Instr: " << iter->mnemonic.c_str() << std::endl; + std::cout << "Addr: " << std::hex << iter->addr << std::endl; + std::cout << "Line: " << iter->line << std::endl; + std::cout << "\t- Opcode: " << std::hex << iter->code.opcode + << ", opcode args: " << iter->code.bytes << std::endl; + + int i = 1; + for (auto arg : iter->compiledArgs) + { + std::cout << "\t- Arg " << i << " : " << std::hex << arg << std::endl; + i++; + } + } + } + + } +}; diff --git a/story-editor/src/app/logger.h b/story-editor/src/app/logger.h new file mode 100644 index 0000000..9740fa7 --- /dev/null +++ b/story-editor/src/app/logger.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include "i_logger.h" + +class Logger : public ILogger +{ +public: + void Log(const std::string& message, bool critical = false) override + { + for (const auto& subject : m_subjects) + { + if (subject) + { + subject->LogEvent(message, critical); + } + } + } + + void RegisterSubject(std::shared_ptr subject) override + { + m_subjects.push_back(subject); + } + +private: + // List of log subjects + std::vector> m_subjects; +}; + diff --git a/story-editor/src/app/status_bar.h b/story-editor/src/app/status_bar.h new file mode 100644 index 0000000..de52184 --- /dev/null +++ b/story-editor/src/app/status_bar.h @@ -0,0 +1,56 @@ +// status_bar.h +#ifndef STATUS_BAR_H +#define STATUS_BAR_H + +#include "imgui.h" // Nécessaire pour ImGui::Begin, ImGui::Text, etc. +#include // Pour std::string +#include "app_controller.h" // Supposons que AppController expose les infos nécessaires + +class StatusBar +{ +public: + // Le constructeur prend une référence au contrôleur d'application. + // Cela permet à la barre de statut d'accéder aux informations qu'elle doit afficher. + StatusBar(AppController& appController) + : m_appController(appController) {} + + // Dessine la barre de statut. + // Doit être appelée à chaque frame dans la boucle de rendu ImGui. + void Draw() + { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - ImGui::GetFrameHeight()), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, ImGui::GetFrameHeight()), ImGuiCond_Always); + + // Pas de barre de titre, pas de redimensionnement, pas de mouvement, etc. + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.f, 4.f)); // Petit padding + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); // Pas d'arrondi + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); // Pas de bordure + if (ImGui::Begin("StatusBar", nullptr, windowFlags)) + { + // Affiche les FPS + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + + // Affiche l'état de la VM (supposons que AppController a une méthode pour cela) + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + ImGui::Text("VM State: %s", m_appController.VmState().c_str()); + + // Vous pouvez ajouter d'autres informations ici, par exemple : + // - Nom du projet actuel + // - État du serveur web + // - Messages de log courts (dernier message critique, etc.) + + } + ImGui::End(); + ImGui::PopStyleVar(3); // Pop les 3 styles poussés + } + +private: + AppController& m_appController; // Référence au contrôleur d'application +}; + +#endif // STATUS_BAR_H \ No newline at end of file diff --git a/story-editor/src/app/tool_bar.h b/story-editor/src/app/tool_bar.h new file mode 100644 index 0000000..efdc101 --- /dev/null +++ b/story-editor/src/app/tool_bar.h @@ -0,0 +1,101 @@ +// toolbar.h +#ifndef TOOL_BAR_H +#define TOOL_BAR_H + +#include "imgui.h" // Nécessaire pour ImGui::Begin, ImGui::Button, etc. +#include "app_controller.h" // Supposons que AppController gère les actions +#include "IconsMaterialDesignIcons.h" // Pour les icônes (si vous utilisez le même jeu d'icônes) + +class ToolBar +{ +public: + // Le constructeur prend une référence au contrôleur d'application. + // Cela permet à la barre d'outils de déclencher des actions métier. + ToolBar(AppController& appController) + : m_appController(appController) {} + + // Dessine la barre d'outils. + // 'topPadding' est utile si vous avez une barre de menu horizontale au-dessus. + void Draw(float topPadding) + { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + topPadding), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(ImGui::GetFrameHeightWithSpacing() * 1.5f, viewport->Size.y - topPadding - ImGui::GetFrameHeight()), ImGuiCond_Always); + + // Pas de barre de titre, pas de redimensionnement, pas de mouvement, etc. + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5.f, 5.f)); // Petit padding + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); // Pas d'arrondi + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); // Pas de bordure + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 10.f)); // Espacement vertical entre les boutons + + if (ImGui::Begin("ToolBar", nullptr, windowFlags)) + { + float buttonSize = ImGui::GetContentRegionAvail().x; // Remplir la largeur disponible + + // Boutons d'action : Ouvrir, Sauvegarder, Compiler, etc. + // Utilisez des icônes pour une meilleure UX. + // Assurez-vous que les icônes sont chargées dans la police ImGui. + + ImGui::PushID("ToolBarButtons"); // Empêche les ID de collision si plusieurs boutons ont le même texte + + // Bouton Nouveau + if (ImGui::Button(ICON_MDI_FILE_OUTLINE " New", ImVec2(buttonSize, buttonSize))) { + m_appController.NewStory(); // Supposons que AppController ait une méthode NewStory() + } + ImGui::Tooltip("New Story"); + + // Bouton Ouvrir + if (ImGui::Button(ICON_MDI_FOLDER_OPEN_OUTLINE " Open", ImVec2(buttonSize, buttonSize))) { + // Ici, vous pourriez déclencher une boîte de dialogue d'ouverture de fichier via AppController + // Ou, si la boîte de dialogue est une fenêtre ImGui séparée, MainWindow la gèrerait. + // Pour simplifier, nous supposons AppController gère le dialogue ou déclenche un événement. + m_appController.OpenStory(); + } + ImGui::Tooltip("Open Story"); + + // Bouton Sauvegarder + if (ImGui::Button(ICON_MDI_CONTENT_SAVE_OUTLINE " Save", ImVec2(buttonSize, buttonSize))) { + m_appController.SaveStory(); + } + ImGui::Tooltip("Save Story"); + + ImGui::Separator(); // Séparateur visuel + + // Bouton Compiler + if (ImGui::Button(ICON_MDI_CODE_BRACES " Compile", ImVec2(buttonSize, buttonSize))) { + m_appController.Build(true); // Appelle la méthode de compilation dans AppController + } + ImGui::Tooltip("Compile Code"); + + // Bouton Exécuter/Build + if (ImGui::Button(ICON_MDI_PLAY_OUTLINE " Run", ImVec2(buttonSize, buttonSize))) { + m_appController.Build(false); // Appelle la méthode de build et exécution + } + ImGui::Tooltip("Build and Run"); + + // Bouton Stop VM + if (ImGui::Button(ICON_MDI_STOP " Stop", ImVec2(buttonSize, buttonSize))) { + m_appController.Stop(); // Arrête la VM + } + ImGui::Tooltip("Stop VM"); + + // Bouton Pas à pas (Debugger) + if (ImGui::Button(ICON_MDI_STEP_FORWARD " Step", ImVec2(buttonSize, buttonSize))) { + m_appController.Step(); // Exécute une instruction + } + ImGui::Tooltip("Step VM Instruction"); + + + ImGui::PopID(); // Pop l'ID pushé + } + ImGui::End(); + ImGui::PopStyleVar(4); // Pop les 4 styles poussés + } + +private: + AppController& m_appController; // Référence au contrôleur d'application +}; + +#endif // TOOL_BAR_H \ No newline at end of file diff --git a/story-editor/src/events/all_events.h b/story-editor/src/events/all_events.h new file mode 100644 index 0000000..2802ff5 --- /dev/null +++ b/story-editor/src/events/all_events.h @@ -0,0 +1,45 @@ +// events/vm_state_event.h +#ifndef ALL_EVENTS_H +#define ALL_EVENTS_H + +#include "event.h" +#include +#include +#include // Pour chip32_ctx_t + +struct VmStateEvent : public Event +{ + chip32_ctx_t vmContext; + int currentLine; + chip32_result_t vmResult; + std::set breakpoints; + // Ajoutez d'autres données nécessaires, ex: std::vector> variables; + + // Constructeur pour faciliter la création de l'événement + VmStateEvent(const chip32_ctx_t& ctx, int line, chip32_result_t result, const std::set& bps) + : vmContext(ctx), currentLine(line), vmResult(result), breakpoints(bps) {} +}; + +class OpenProjectEvent : public Event +{ +public: + OpenProjectEvent(const std::string &uuid) : m_uuid(uuid) {} + const std::string& GetUuid() const { return m_uuid; } +private: + std::string m_uuid; // UUID du projet à ouvrir +}; + +class OpenFunctionEvent : public Event +{ +public: + OpenFunctionEvent(const std::string &uuid, const std::string &name) + : m_uuid(uuid), m_name(name) {} + const std::string& GetUuid() const { return m_uuid; } + const std::string& GetName() const { return m_name; } +private: + std::string m_uuid; // UUID du projet ou module + std::string m_name; // Nom de la fonction à ouvrir +}; + + +#endif // ALL_EVENTS_H \ No newline at end of file diff --git a/story-editor/src/events/event.h b/story-editor/src/events/event.h new file mode 100644 index 0000000..48caa9d --- /dev/null +++ b/story-editor/src/events/event.h @@ -0,0 +1,22 @@ +// events/event.h +#ifndef EVENT_H +#define EVENT_H + +#include // Pour std::type_index + +// Chaque événement aura un TypeId unique. +// Ceci permet de stocker et de récupérer des pointeurs vers des handlers spécifiques. +struct Event +{ + virtual ~Event() = default; +}; + +// Fonction utilitaire pour obtenir le TypeId unique d'une classe d'événement. +// Utilise std::type_index pour l'identification au runtime. +template +std::type_index GetEventTypeId() +{ + return std::type_index(typeid(T)); +} + +#endif // EVENT_H \ No newline at end of file diff --git a/story-editor/src/events/event_bus.cpp b/story-editor/src/events/event_bus.cpp new file mode 100644 index 0000000..e7e1c4f --- /dev/null +++ b/story-editor/src/events/event_bus.cpp @@ -0,0 +1,32 @@ +// event_bus.cpp +#include "event_bus.h" +#include // Pour des logs simples de débogage si nécessaire + +void EventBus::Emit(std::shared_ptr event) +{ + if (!event) { + // Gérer le cas où l'événement est nul + std::cerr << "EventBus: Attempted to emit a null event." << std::endl; + return; + } + + std::type_index eventTypeId = GetEventTypeId(); // Default type for base Event + + // Tente de trouver le type exact de l'événement pour la distribution + // C'est un peu tricky car dynamic_cast ne peut pas être utilisé avec typeid(Event). + // On doit utiliser typeid(*event) pour obtenir le type réel de l'objet pointé. + eventTypeId = std::type_index(typeid(*event)); + + auto it = m_subscribers.find(eventTypeId); + if (it != m_subscribers.end()) + { + // Parcourt tous les abonnés pour ce type d'événement + for (const auto& callback : it->second) + { + callback(*event); // Appelle la callback en passant l'événement de base + } + } else { + // Optionnel: loguer si aucun abonné pour ce type d'événement + // std::cout << "EventBus: No subscribers for event type: " << eventTypeId.name() << std::endl; + } +} diff --git a/story-editor/src/events/event_bus.h b/story-editor/src/events/event_bus.h new file mode 100644 index 0000000..91e22ec --- /dev/null +++ b/story-editor/src/events/event_bus.h @@ -0,0 +1,53 @@ +// event_bus.h +#ifndef EVENT_BUS_H +#define EVENT_BUS_H + +#include "events/event.h" // Inclure la classe de base Event + +#include // Pour std::function +#include // Pour std::map (TypeId -> vector of handlers) +#include // Pour std::vector (list of handlers) +#include // Pour std::shared_ptr si on veut gérer la durée de vie des événements + +// Définition du type de handler générique +// Un handler est une fonction qui prend une référence constante à un Event. +using EventCallback = std::function; + +class EventBus +{ +public: + EventBus() = default; + ~EventBus() = default; + + // Supprime la copie pour éviter des comportements inattendus avec les callbacks + EventBus(const EventBus&) = delete; + EventBus& operator=(const EventBus&) = delete; + + // S'abonne à un type d'événement spécifique. + // 'TEvent' est le type d'événement à écouter (ex: VmStateChangedEvent). + // 'callback' est la fonction à appeler lorsque cet événement est émis. + template + void Subscribe(std::function callback) + { + // On stocke une lambda qui convertit l'Event de base en TEvent + // pour que la callback spécifique à TEvent puisse être appelée. + std::type_index typeId = GetEventTypeId(); + m_subscribers[typeId].push_back([callback](const Event& event) { + // S'assurer que le cast est sûr + if (const TEvent* specificEvent = dynamic_cast(&event)) { + callback(*specificEvent); + } + }); + } + + // Émet un événement. + // L'EventBus prend la possession de l'événement (via std::shared_ptr). + // Ceci est crucial car l'événement doit exister pendant que les callbacks sont appelées. + void Emit(std::shared_ptr event); + +private: + // Mappe les TypeId des événements à une liste de callbacks. + std::map> m_subscribers; +}; + +#endif // EVENT_BUS_H \ No newline at end of file diff --git a/story-editor/src/main.cpp b/story-editor/src/main.cpp index 06037e3..858d1a7 100644 --- a/story-editor/src/main.cpp +++ b/story-editor/src/main.cpp @@ -1,14 +1,23 @@ #include "main_window.h" +#include "app_controller.h" +#include "event_bus.h" +#include "logger.h" // Main code int main(int, char**) { + Logger logger; + EventBus eventBus; - MainWindow w; + AppController appController(logger, eventBus); + + MainWindow w(logger, eventBus, appController); + if (w.Initialize()) { w.Loop(); + appController.ProcessStory(); } return 0; diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index 55a1107..40b120f 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -22,34 +22,24 @@ #include "ImGuiFileDialog.h" #include "imgui_memory_editor.h" -MainWindow::MainWindow() - : m_resources(*this) - , m_nodesFactory(*this) - , m_libraryManager(*this, m_nodesFactory) - , m_emulatorWindow(*this) - , m_debuggerWindow(*this) - , m_cpuWindow(*this) - , m_resourcesWindow(*this) - , m_nodeEditorWindow(*this, m_nodesFactory) - , m_moduleEditorWindow(*this, m_nodesFactory, IStoryProject::Type::PROJECT_TYPE_MODULE) - , m_libraryWindow(*this, m_libraryManager, m_nodesFactory) - , m_variablesWindow(*this) - , m_player(*this) - , m_webServer(m_libraryManager) +#include "app_controller.h" +#include "all_events.h" + +MainWindow::MainWindow(ILogger& logger, EventBus& eventBus, AppController& appController) + : m_logger(logger) + , m_eventBus(eventBus) + , m_appController(appController) + , m_emulatorWindow(appController) + , m_debuggerWindow(appController) + , m_cpuWindow(appController) + , m_resourcesWindow(appController.GetResourceManager()) + , m_nodeEditorWindow(appController) + , m_moduleEditorWindow(appController) + , m_libraryWindow(appController) + , m_variablesWindow(appController) { - // VM Initialize - m_chip32_ctx.stack_size = 512; - m_chip32_ctx.rom.mem = m_rom_data; - m_chip32_ctx.rom.addr = 0; - m_chip32_ctx.rom.size = sizeof(m_rom_data); - - m_chip32_ctx.ram.mem = m_ram_data; - m_chip32_ctx.ram.addr = sizeof(m_rom_data); - m_chip32_ctx.ram.size = sizeof(m_ram_data); - - Callback::func = std::bind(&MainWindow::Syscall, this, std::placeholders::_1, std::placeholders::_2); - m_chip32_ctx.syscall = static_cast(Callback::callback); + logger.RegisterSubject(shared_from_this()); CloseProject(); CloseModule(); @@ -59,11 +49,19 @@ MainWindow::MainWindow() // define style for all files ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile, "", ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ICON_MDI_FILE); + + m_eventBus.Subscribe([this](const OpenProjectEvent &event) { + OpenProject(event.GetUuid()); + }); + + m_eventBus.Subscribe([this](const OpenFunctionEvent &event) { + OpenFunction(event.GetUuid(), event.GetName()); + }); } MainWindow::~MainWindow() { - SaveParams(); + m_appController.SaveParams(); } @@ -81,240 +79,16 @@ std::string MainWindow::GetStringFromMemory(uint32_t addr) } -void MainWindow::Play() -{ - - if ((m_dbg.run_result == VM_READY) || (m_dbg.run_result == VM_FINISHED)) - { - m_dbg.free_run = true; - m_dbg.run_result = VM_OK; // actually starts the execution - } - -} - -void MainWindow::Step() -{ - m_eventQueue.push({VmEventType::EvStep}); -} - -void MainWindow::Run() -{ - m_eventQueue.push({VmEventType::EvRun}); -} - -void MainWindow::Ok() -{ - m_eventQueue.push({VmEventType::EvOkButton}); -} - - -void MainWindow::Stop() -{ - m_dbg.run_result = VM_FINISHED; // better than sending an event: avoid infinite loops in assembly -} - -void MainWindow::Pause() -{ - -} - -void MainWindow::Home() -{ - m_eventQueue.push({VmEventType::EvHomeButton}); -} - -void MainWindow::Next() -{ - Log("Next button"); - m_eventQueue.push({VmEventType::EvNextButton}); -} - -void MainWindow::Previous() -{ - Log("Previous button"); - m_eventQueue.push({VmEventType::EvPreviousButton}); -} - -std::string MainWindow::VmState() const -{ - std::string state = "Unknown"; - - switch (m_dbg.run_result) - { - case VM_READY: - state = "VM Ready"; - break; - case VM_FINISHED: - state = "VM Finished"; - break; - case VM_SKIPED: - state = "VM Skiped"; - break; - case VM_WAIT_EVENT: - state = "VM Wait Event"; - break; - case VM_OK: - state = "VM Ok"; - break; - default: - state = "VM Error"; - break; - } - return state; -} - -void MainWindow::EndOfAudio() -{ - Log("End of audio track"); - m_eventQueue.push({VmEventType::EvAudioFinished}); -} - void MainWindow::StepInstruction() { m_dbg.run_result = chip32_step(&m_chip32_ctx); UpdateVmView(); } -void MainWindow::ProcessStory() -{ - if (m_dbg.run_result == VM_FINISHED) - return; - - // Error states - if (m_dbg.run_result > VM_OK) - return; - - // Check events - - VmEvent event; - if (m_eventQueue.try_pop(event)) - { - if (event.type == VmEventType::EvStep) - { - StepInstruction(); - m_dbg.run_result = VM_OK; // FIXME: bizarre d'écraser le code de retour... - } - else if (event.type == VmEventType::EvRun) - { - m_dbg.free_run = true; - m_dbg.run_result = VM_OK; - } - else if (event.type == VmEventType::EvStop) - { - m_dbg.run_result = VM_FINISHED; - } - - // Events managed only if the code is in wait event state - if (m_dbg.run_result == VM_WAIT_EVENT) - { - - if (event.type == VmEventType::EvOkButton) - { - if (m_dbg.IsValidEvent(EV_MASK_OK_BUTTON)) - { - m_chip32_ctx.registers[R0] = EV_MASK_OK_BUTTON; - m_dbg.run_result = VM_OK; - } - } - else if (event.type == VmEventType::EvPreviousButton) - { - if (m_dbg.IsValidEvent(EV_MASK_PREVIOUS_BUTTON)) - { - m_chip32_ctx.registers[R0] = EV_MASK_PREVIOUS_BUTTON; - m_dbg.run_result = VM_OK; - } - } - else if (event.type == VmEventType::EvNextButton) - { - if (m_dbg.IsValidEvent(EV_MASK_NEXT_BUTTON)) - { - m_chip32_ctx.registers[R0] = EV_MASK_NEXT_BUTTON; - m_dbg.run_result = VM_OK; - } - } - else if (event.type == VmEventType::EvAudioFinished) - { - if (m_dbg.IsValidEvent(EV_MASK_END_OF_AUDIO)) - { - m_chip32_ctx.registers[R0] = EV_MASK_END_OF_AUDIO; - m_dbg.run_result = VM_OK; - } - } - else if (event.type == VmEventType::EvHomeButton) - { - if (m_dbg.IsValidEvent(EV_MASK_HOME_BUTTON)) - { - m_chip32_ctx.registers[R0] = EV_MASK_HOME_BUTTON; - m_dbg.run_result = VM_OK; - } - } - } - } - - if (m_dbg.run_result == VM_OK) - { - if (m_dbg.m_breakpoints.contains(m_dbg.line)) - { - // Log("Breakpoint on line: " + std::to_string(m_dbg.line + 1)); - m_dbg.run_result = VM_WAIT_EVENT; - m_dbg.free_run = false; - } - - if (m_dbg.free_run) - { - StepInstruction(); - } - } - - if (m_dbg.run_result == VM_FINISHED) - { - m_dbg.free_run = false; - } - else if (m_dbg.run_result > VM_OK) - { - std::string error = "VM Error: "; - switch (m_dbg.run_result) - { - case VM_ERR_STACK_OVERFLOW: - error += "Stack overflow"; - break; - case VM_ERR_STACK_UNDERFLOW: - error += "Stack underflow"; - break; - case VM_ERR_INVALID_ADDRESS: - error += "Invalid address"; - break; - case VM_ERR_UNSUPPORTED_OPCODE: - error += "Invalid address"; - break; - case VM_ERR_UNKNOWN_OPCODE: - error += "Unknown opcode"; - break; - case VM_ERR_UNHANDLED_INTERRUPT: - error += "Unhandled interrupt"; - break; - case VM_ERR_INVALID_REGISTER: - error += "Invalid register"; - break; - default: - error += "Unknown error"; - break; - } - error += " (line: " + std::to_string(m_dbg.line) + ")"; - Log(error, true); - } - - // In this case, we wait for single step debugger - if ((m_dbg.run_result == VM_OK) && !m_dbg.free_run) - { - m_dbg.run_result = VM_WAIT_EVENT; - } -} - uint8_t MainWindow::Syscall(chip32_ctx_t *ctx, uint8_t code) { uint8_t retCode = SYSCALL_RET_OK; - Log("SYSCALL: " + std::to_string(code)); + m_logger.Log("SYSCALL: " + std::to_string(code)); // Media if (code == 1) // Execute media @@ -323,7 +97,7 @@ uint8_t MainWindow::Syscall(chip32_ctx_t *ctx, uint8_t code) { // image file name address is in R0 std::string imageFile = m_story->BuildFullAssetsPath(GetStringFromMemory(m_chip32_ctx.registers[R0])); - Log("Image: " + imageFile); + m_logger.Log("Image: " + imageFile); m_emulatorWindow.SetImage(imageFile); } else @@ -335,7 +109,7 @@ uint8_t MainWindow::Syscall(chip32_ctx_t *ctx, uint8_t code) { // sound file name address is in R1 std::string soundFile = m_story->BuildFullAssetsPath(GetStringFromMemory(m_chip32_ctx.registers[R1])); - Log("Sound: " + soundFile); + m_logger.Log("Sound: " + soundFile); m_player.Play(soundFile); } retCode = SYSCALL_RET_OK; // We continue execution, script must wait for event if necessary (end of audio) @@ -379,19 +153,19 @@ uint8_t MainWindow::Syscall(chip32_ctx_t *ctx, uint8_t code) switch(arg_count){ case 0: - Log(text); + m_logger.Log(text); break; case 1: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2]); - Log(working_buf); + m_logger.Log(working_buf); break; case 2: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2], ctx->registers[R3]); - Log(working_buf); + m_logger.Log(working_buf); break; case 3: snprintf(working_buf, sizeof(working_buf), text.c_str(), ctx->registers[R2], ctx->registers[R3], ctx->registers[R4]); - Log(working_buf); + m_logger.Log(working_buf); break; default: break; @@ -408,31 +182,6 @@ uint8_t MainWindow::Syscall(chip32_ctx_t *ctx, uint8_t code) return retCode; } -void MainWindow::DrawStatusBar() -{ - float statusWindowHeight = ImGui::GetFrameHeight() * 1.4f; - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - statusWindowHeight)); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, statusWindowHeight)); - ImGui::SetNextWindowViewport(viewport->ID); - - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking; - ImGui::Begin("StatusBar", nullptr, windowFlags); - - if (true) - { - float dy = ImGui::GetFontSize() * 0.15f; - - ImGui::SameLine(ImGui::GetIO().DisplaySize.x - 14.f * ImGui::GetFontSize()); - - ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - dy); - ImGui::Text("FPS: %.1f", 1000.0f / ImGui::GetIO().Framerate); - } - - ImGui::End(); -} - float MainWindow::DrawMainMenuBar() { bool showParameters = false; @@ -829,57 +578,36 @@ void MainWindow::SaveProject() { nlohmann::json model; m_story->Save(m_resources); - Log("Project saved"); + m_logger.Log("Project saved"); } void MainWindow::OpenProject(const std::string &uuid) { - CloseProject(); - - m_story = m_libraryManager.GetStory(uuid); - - // DEBUG CODE !!!!!!!!!!!!! Permet si décommenter de forcer l'import, permet de tester plus facilement l'algo en ouvrant le projet - // PackArchive arch(*this); - // std::string basePath = m_libraryManager.LibraryPath() + "/" + uuid; - // arch.ConvertJsonStudioToOst(basePath, uuid, m_libraryManager.LibraryPath()); - - if (!m_story) + m_nodeEditorWindow.Load(m_story); + auto proj = m_story->GetProjectFilePath(); + // Add to recent if not exists + if (std::find(m_recentProjects.begin(), m_recentProjects.end(), proj) == m_recentProjects.end()) { - Log("Cannot find story: " + uuid); - } - else if (m_story->Load(m_resources, m_nodesFactory)) - { - Log("Open project success"); - m_nodeEditorWindow.Load(m_story); - auto proj = m_story->GetProjectFilePath(); - // Add to recent if not exists - if (std::find(m_recentProjects.begin(), m_recentProjects.end(), proj) == m_recentProjects.end()) - { - m_recentProjects.push_back(proj); - // Limit to 10 recent projects - if (m_recentProjects.size() > 10) { - m_recentProjects.pop_back(); - } - - // Save recent projects on disk - SaveParams(); + m_recentProjects.push_back(proj); + // Limit to 10 recent projects + if (m_recentProjects.size() > 10) { + m_recentProjects.pop_back(); } - m_nodeEditorWindow.Enable(); - - m_emulatorWindow.Enable(); - m_consoleWindow.Enable(); - m_debuggerWindow.Enable(); - m_resourcesWindow.Enable(); - m_PropertiesWindow.Enable(); - m_variablesWindow.Enable(); - m_cpuWindow.Enable(); - } - else - { - Log("Open project error"); + // Save recent projects on disk + m_appController.SaveParams(); } + m_nodeEditorWindow.Enable(); + + m_emulatorWindow.Enable(); + m_consoleWindow.Enable(); + m_debuggerWindow.Enable(); + m_resourcesWindow.Enable(); + m_PropertiesWindow.Enable(); + m_variablesWindow.Enable(); + m_cpuWindow.Enable(); + RefreshProjectInformation(); } @@ -895,7 +623,7 @@ void MainWindow::NewModule() void MainWindow::SaveModule() { m_nodesFactory.SaveAllModules(m_resources);; - Log("Modules saved"); + m_logger.Log("Modules saved"); } void MainWindow::OpenModule(const std::string &uuid) @@ -903,17 +631,17 @@ void MainWindow::OpenModule(const std::string &uuid) m_module = m_nodesFactory.GetModule(uuid); if (!m_module) { - Log("Cannot find module: " + uuid); + m_logger.Log("Cannot find module: " + uuid); } else if (m_module->Load(m_resources, m_nodesFactory)) { - Log("Open module success"); + m_logger.Log("Open module success"); m_moduleEditorWindow.Load(m_module); m_moduleEditorWindow.Enable(); } else { - Log("Open module error"); + m_logger.Log("Open module error"); } } @@ -975,7 +703,7 @@ void MainWindow::ImportProject(const std::string &filePathName, int format) } else { - Log("Unknown file format: " + filePathName); + m_logger.Log("Unknown file format: " + filePathName); } } @@ -1051,8 +779,6 @@ void MainWindow::Loop() ImGui::DockSpaceOverViewport(0, vp); float height = DrawMainMenuBar(); - // DrawStatusBar(); - ProcessStory(); // ------------ Draw all windows @@ -1111,56 +837,24 @@ void MainWindow::Loop() } -void MainWindow::Log(const std::string &txt, bool critical) +void MainWindow::LogEvent(const std::string &txt, bool critical) { m_consoleWindow.AddLog(txt, critical ? 1 : 0); } -void MainWindow::PlaySoundFile(const std::string &fileName) -{ - Log("Play sound file: " + fileName); - m_player.Play(fileName); -} std::string MainWindow::BuildFullAssetsPath(const std::string_view fileName) const { return m_story->BuildFullAssetsPath(fileName); } -std::pair MainWindow::Images() -{ - return m_resources.Images(); -} -std::pair MainWindow::Sounds() -{ - return m_resources.Sounds(); -} void MainWindow::OpenFunction(const std::string &uuid, const std::string &name) { m_nodeEditorWindow.OpenFunction(uuid, name); } -void MainWindow::AddResource(std::shared_ptr res) -{ - m_resources.Add(res); -} - -void MainWindow::ClearResources() -{ - m_resources.Clear(); -} - -std::pair MainWindow::Resources() -{ - return m_resources.Items(); -} - -void MainWindow::DeleteResource(FilterIterator &it) -{ - return m_resources.Delete(it); -} void MainWindow::LoadBinaryStory(const std::string &filename) { @@ -1178,11 +872,11 @@ void MainWindow::LoadBinaryStory(const std::string &filename) { m_dbg.run_result = VM_READY; chip32_initialize(&m_chip32_ctx); - Log("Loaded binary file: " + filename); + m_logger.Log("Loaded binary file: " + filename); } else { - Log("Failed to load binary file", true); + m_logger.Log("Failed to load binary file", true); } } @@ -1190,47 +884,7 @@ void MainWindow::LoadBinaryStory(const std::string &filename) } } -void MainWindow::ToggleBreakpoint(int line) -{ - if (m_dbg.m_breakpoints.contains(line)) - { - m_dbg.m_breakpoints.erase(line); - } - else - { - m_dbg.m_breakpoints.insert(line); - } -} -uint32_t MainWindow::GetRegister(int reg) -{ - uint32_t regVal = 0; - - if (reg >= 0 && reg < REGISTER_COUNT) - { - regVal = m_chip32_ctx.registers[reg]; - } - - return regVal; -} - -void MainWindow::ScanVariable(const std::function element)>& operation) -{ - if (m_story) - { - m_story->ScanVariable(operation); - } -} - -void MainWindow::AddVariable() -{ - m_story->AddVariable(); -} - -void MainWindow::DeleteVariable(int i) -{ - m_story->DeleteVariable(i); -} void MainWindow::BuildNodes(bool compileonly) { @@ -1271,12 +925,12 @@ void MainWindow::Build(bool compileonly) } else { - Log("Program too big. Expand ROM memory."); + m_logger.Log("Program too big. Expand ROM memory."); } } else { - Log(err.ToString(), true); + m_logger.Log(err.ToString(), true); m_debuggerWindow.AddError(err.line, err.message); // show also the error in the code editor } } @@ -1311,29 +965,13 @@ void MainWindow::UpdateVmView() else { // Not found - Log("Reached end or instruction not found line: " + std::to_string(m_dbg.line)); + m_logger.Log("Reached end or instruction not found line: " + std::to_string(m_dbg.line)); } // Refresh RAM content // m_ramView->SetMemory(m_ram_data, m_chip32_ctx.ram.size); } -void MainWindow::SaveParams() -{ - nlohmann::json j; - nlohmann::json recents(m_recentProjects); - - j["recents"] = recents; - j["library_path"] = m_libraryManager.LibraryPath(); - j["store_url"] = m_libraryManager.GetStoreUrl(); - - std::string loc = pf::getConfigHome() + "/ost_settings.json"; - std::ofstream o(loc); - o << std::setw(4) << j << std::endl; - - Log("Saved settings to: " + loc); -} - void MainWindow::LoadParams() { try { @@ -1371,6 +1009,6 @@ void MainWindow::LoadParams() } catch(std::exception &e) { - Log(e.what(), true); + m_logger.Log(e.what(), true); } } diff --git a/story-editor/src/main_window.h b/story-editor/src/main_window.h index 7ca1e08..1ff9b95 100644 --- a/story-editor/src/main_window.h +++ b/story-editor/src/main_window.h @@ -3,82 +3,31 @@ #include +#include +#include #include "gui.h" #include "console_window.h" #include "debugger_window.h" - #include "emulator_dock.h" #include "resources_window.h" #include "node_editor_window.h" #include "properties_window.h" #include "variables_window.h" - -#include "chip32_assembler.h" -#include "chip32_vm.h" -#include "story_project.h" -#include "i_story_manager.h" -#include "thread_safe_queue.h" -#include "audio_player.h" -#include "library_manager.h" #include "library_window.h" #include "cpu_window.h" -#include "story_machine.h" -#include "web_server.h" -#include "nodes_factory.h" - // Dialogs #include "about_dialog.h" -struct DebugContext -{ - uint32_t event_mask{0}; - bool wait_event{0}; - bool free_run{false}; - uint32_t line{0}; - chip32_result_t run_result{VM_FINISHED}; - - std::set m_breakpoints; - - void Stop() { - run_result = VM_FINISHED; - } - - bool IsValidEvent(uint32_t event) { - return (event_mask & event) != 0; - } - - static void DumpCodeAssembler(Chip32::Assembler & assembler) { - - for (std::vector::const_iterator iter = assembler.Begin(); - iter != assembler.End(); ++iter) - { - if (iter->isRomCode() || iter->isRomData) - { - std::cout << "-------------------" << std::endl; - std::cout << "Instr: " << iter->mnemonic.c_str() << std::endl; - std::cout << "Addr: " << std::hex << iter->addr << std::endl; - std::cout << "Line: " << iter->line << std::endl; - std::cout << "\t- Opcode: " << std::hex << iter->code.opcode - << ", opcode args: " << iter->code.bytes << std::endl; - - int i = 1; - for (auto arg : iter->compiledArgs) - { - std::cout << "\t- Arg " << i << " : " << std::hex << arg << std::endl; - i++; - } - } - } - - } -}; +#include "event_bus.h" +#include "app_controller.h" +#include "i_logger.h" -class MainWindow : public IStoryManager, public IAudioEvent, public ILogger +class MainWindow : public std::enable_shared_from_this, public ILogSubject { public: - MainWindow(); + MainWindow(ILogger& logger, EventBus& eventBus, AppController& appController); ~MainWindow(); bool Initialize(); @@ -87,27 +36,15 @@ public: private: enum VmEventType { EvNoEvent, EvStep, EvRun, EvOkButton, EvPreviousButton, EvNextButton, EvAudioFinished, EvStop, EvHomeButton}; - std::shared_ptr m_story; // Current story - std::shared_ptr m_module; // Current module + ILogger& m_logger; + EventBus& m_eventBus; + AppController &m_appController; // Controller for application logic - // VM - uint8_t m_rom_data[16*1024]; - uint8_t m_ram_data[16*1024]; - chip32_ctx_t m_chip32_ctx; - - Chip32::Result m_result; - DebugContext m_dbg; - std::string m_currentCode; - std::string m_externalSourceFileName; // path of an external script to be used as compilation input + std::shared_ptr m_story; // Current story + std::shared_ptr m_module; // Current module std::vector m_recentProjects; - NodesFactory m_nodesFactory; - - ResourceManager m_resources; - - LibraryManager m_libraryManager; - Gui m_gui; EmulatorDock m_emulatorWindow; ConsoleWindow m_consoleWindow; @@ -117,33 +54,17 @@ private: char m_project_name[256] = ""; ResourcesWindow m_resourcesWindow; - NodeEditorWindow m_nodeEditorWindow; - NodeEditorWindow m_moduleEditorWindow; - PropertiesWindow m_PropertiesWindow; - LibraryWindow m_libraryWindow; - VariablesWindow m_variablesWindow; - AudioPlayer m_player; - - struct VmEvent - { - VmEventType type; - }; - - ThreadSafeQueue m_eventQueue; - - WebServer m_webServer; - // Dialogs AboutDialog m_aboutDialog; // From IStoryManager (proxy to StoryProject class) - virtual void OpenProject(const std::string &uuid) override; + void OpenProject(const std::string &uuid); void SaveProject(); void CloseProject(); @@ -152,59 +73,15 @@ private: void SaveModule(); void CloseModule(); - // From IStoryManager (proxy to StoryProject class) - virtual void ImportProject(const std::string &filePathName, int format); - virtual void PlaySoundFile(const std::string &fileName) override;; - virtual std::string BuildFullAssetsPath(const std::string_view fileName) const override; - virtual std::pair Images() override; - virtual std::pair Sounds() override; - virtual void OpenFunction(const std::string &uuid, const std::string &name) override; + // From ILogSubject + virtual void LogEvent(const std::string &txt, bool critical) override; - virtual void AddResource(std::shared_ptr res) override; - virtual void ClearResources() override; - virtual std::pair Resources() override; - virtual void DeleteResource(FilterIterator &it) override; - - - virtual void BuildNodes(bool compileonly) override; - virtual void BuildCode(bool compileonly) override; - virtual void SetExternalSourceFile(const std::string &filename) override; - virtual void LoadBinaryStory(const std::string &filename) override; - virtual void ToggleBreakpoint(int line) override; - virtual uint32_t GetRegister(int reg) override; - - // Variable - virtual void ScanVariable(const std::function element)>& operation) override; - virtual void AddVariable() override; - virtual void DeleteVariable(int i); - - virtual void Play() override; - virtual void Step() override; - virtual void Run() override; - virtual void Ok() override; - virtual void Stop() override; - virtual void Pause() override; - virtual void Home() override; - virtual void Next() override; - virtual void Previous() override; - virtual std::string VmState() const override; - - - // From ILogger - virtual void Log(const std::string &txt, bool critical = false) override; - - // From IAudioEvent - virtual void EndOfAudio() override; - - void SaveParams(); void LoadParams(); float DrawMainMenuBar(); bool ShowQuitConfirm(); void DrawToolBar(float topPadding); - void DrawStatusBar(); - void UpdateVmView(); uint8_t Syscall(chip32_ctx_t *ctx, uint8_t code); std::string GetStringFromMemory(uint32_t addr); diff --git a/story-editor/src/windows/resources_window.cpp b/story-editor/src/windows/resources_window.cpp index 7f5f97f..a4b5d32 100644 --- a/story-editor/src/windows/resources_window.cpp +++ b/story-editor/src/windows/resources_window.cpp @@ -7,9 +7,9 @@ //static thread_pool pool; -ResourcesWindow::ResourcesWindow(IStoryManager &project) +ResourcesWindow::ResourcesWindow(ResourceManager &resources) : WindowBase("Resources") - , m_story(project) + , m_resources(resources) { } @@ -29,7 +29,7 @@ void ResourcesWindow::ChooseFile() m_showImportDialog = false; // open Dialog Simple IGFD::FileDialogConfig config; - config.path = m_story.BuildFullAssetsPath(""); + config.path = m_resources.BuildFullAssetsPath(""); config.countSelectionMax = 1; config.sidePaneWidth = 350.0f; config.flags = ImGuiFileDialogFlags_Modal; @@ -49,7 +49,7 @@ void ResourcesWindow::ChooseFile() std::filesystem::path p(filePathName); - std::filesystem::path p2 = m_story.BuildFullAssetsPath( p.filename().generic_string()); + std::filesystem::path p2 = m_resources.BuildFullAssetsPath( p.filename().generic_string()); bool allowCopy = true; // On ne copie pas le fichier sur lui-même @@ -72,7 +72,7 @@ void ResourcesWindow::ChooseFile() res->format = ext; res->type = m_soundFile ? "sound" : "image"; res->file = p.filename().generic_string(); - m_story.AddResource(res); + m_resources.Add(res); } // close @@ -120,7 +120,7 @@ void ResourcesWindow::Draw() ImGui::TableHeadersRow(); - auto [b, e] = m_story.Resources(); + auto [b, e] = m_resources.Items(); int id = 1000; for (auto it = b; it != e; ++it) @@ -168,7 +168,7 @@ void ResourcesWindow::Draw() ImGui::TableNextColumn(); if (ImGui::SmallButton("Delete")) { - m_story.DeleteResource(it); + m_resources.DeleteResource(it); quitLoop = true; } ImGui::PopID(); diff --git a/story-editor/src/windows/resources_window.h b/story-editor/src/windows/resources_window.h index de7068f..c4c3101 100644 --- a/story-editor/src/windows/resources_window.h +++ b/story-editor/src/windows/resources_window.h @@ -2,16 +2,17 @@ #include "i_story_manager.h" #include "window_base.h" +#include "resource_manager.h" class ResourcesWindow : public WindowBase { public: - ResourcesWindow(IStoryManager &project); + ResourcesWindow(ResourceManager &resources); ~ResourcesWindow(); virtual void Draw() override; private: - IStoryManager &m_story; + ResourceManager &m_resources; bool m_showImportDialog{false}; bool m_soundFile{false};