open-story-teller/story-editor/src/app/app_controller.cpp
2025-10-01 17:25:56 +02:00

944 lines
30 KiB
C++

// app_controller.cpp
#include "app_controller.h"
// Inclure les entêtes des dépendances de AppController nécessaires pour l'implémentation
#include <filesystem>
#include <fstream>
#include <chrono> // Pour std::this_thread::sleep_for
#include <thread> // Pour std::this_thread
#include <algorithm> // 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
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<uint8_t(chip32_ctx_t *, uint8_t)>::func = std::bind(&AppController::Syscall, this, std::placeholders::_1, std::placeholders::_2);
m_chip32_ctx.syscall = static_cast<syscall_t>(Callback<uint8_t(chip32_ctx_t *, uint8_t)>::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<OpenProjectEvent>(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<OpenFunctionEvent>(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<StoryProject>(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::CompileNodes(IStoryProject::Type type)
{
if (type == IStoryProject::Type::PROJECT_TYPE_STORY && m_story)
{
if (m_story->GenerateScript(m_storyAssembly))
{
// La GUI (DebuggerWindow) doit être notifiée de cette mise à jour.
// Au lieu de appeler m_debuggerWindow.SetScript(m_storyAssembly); 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
{
m_logger.Log("Failed to generate script for story.", true);
}
}
else if (type == IStoryProject::Type::PROJECT_TYPE_MODULE && m_module)
{
if (m_module->GenerateScript(m_storyAssembly))
{
m_logger.Log("Nodes script generated for module.");
}
else
{
m_logger.Log("Failed to generate script for module.", true);
}
}
}
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. FIXME
// m_debuggerWindow.ClearErrors();
if (m_story->GenerateBinary(m_storyAssembly, 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. FIXME
// m_debuggerWindow.AddError(err.line, err.message);
}
}
void AppController::BuildCode(std::shared_ptr<StoryProject> story, bool compileonly, bool force)
{
// Note: Dans le code original, BuildCode lisait m_externalSourceFileName.
// Il faut s'assurer que m_storyAssembly est bien défini avant d'appeler Build.
if (story) {
m_storyAssembly = SysLib::ReadFile(story->GetProjectFilePath()); // Simplifié pour l'exemple
// La GUI (DebuggerWindow) doit être notifiée pour SetScript.
// m_debuggerWindow.SetScript(m_storyAssembly);
Build(compileonly);
} else {
m_logger.Log("No story provided for BuildCode.", true);
}
}
void AppController::BuildCode(bool compileonly)
{
m_storyAssembly = SysLib::ReadFile(m_externalSourceFileName);
// m_debuggerWindow.SetScript(m_storyAssembly); // FIXME: GUI event
Build(compileonly);
}
void AppController::CompileNodes(bool compileonly)
{
if (m_story->GenerateScript(m_storyAssembly))
{
// m_debuggerWindow.SetScript(m_storyAssembly); // FIXME: GUI event
Build(compileonly);
}
}
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::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<std::string>();
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<std::string>();
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_emulatorDock.SetImage(imageFile); // Ceci est une dépendance GUI
}
else
{
// m_emulatorDock.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()
{
// FIXME !!
// 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
}
m_resources.Clear();
// 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
m_logger.Log("Project closed.");
}
void AppController::SaveProject()
{
if (m_story)
{
m_story->Save(m_resources);
m_libraryManager.Scan(); // Add new project to library
m_logger.Log("Project saved: " + m_story->GetProjectFilePath());
} else {
m_logger.Log("No project open to save.", true);
}
}
std::shared_ptr<StoryProject> AppController::NewModule()
{
m_module = m_nodesFactory.NewModule();
return m_module;
}
void AppController::SaveModule()
{
m_nodesFactory.SaveAllModules(m_resources);
m_logger.Log("Modules saved.");
}
std::shared_ptr<StoryProject> AppController::OpenModule(const std::string &uuid)
{
m_module = m_nodesFactory.GetModule(uuid);
if (!m_module)
{
m_eventBus.Emit(std::make_shared<GenericResultEvent>(false, "Cannot find module: " + uuid));
}
else if (m_module->Load(m_resources, m_nodesFactory))
{
m_eventBus.Emit(std::make_shared<GenericResultEvent>(true, "Open module success: " + uuid));
}
else
{
m_eventBus.Emit(std::make_shared<GenericResultEvent>(false, "Failed to open module: " + uuid));
}
return m_module;
}
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(m_logger, 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);
}
// Send event success
m_eventBus.Emit(std::make_shared<GenericResultEvent>(true, "Import successful"));
}
std::shared_ptr<IStoryProject> AppController::GetCurrentProject()
{
return m_story; // Retourne le projet actuel
}