diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..7ff3f3a --- /dev/null +++ b/core/README.md @@ -0,0 +1,10 @@ +# StoryTeller core files + +GUI agnostic implementation of the editor engine. + +Directories: +- interfaces: contains only pure virtual C++ interface classes +- lib: shared static classes utilities +- src: core implementation (private files) + + diff --git a/shared/i_logger.h b/core/interfaces/i_logger.h similarity index 83% rename from shared/i_logger.h rename to core/interfaces/i_logger.h index d3d0c1e..5a8fbcf 100644 --- a/shared/i_logger.h +++ b/core/interfaces/i_logger.h @@ -7,4 +7,5 @@ class ILogger public: virtual void Log(const std::string &txt, bool critical = false) = 0; + virtual ~ILogger() {} }; diff --git a/story-editor/src/node_engine/i_script_node.h b/core/interfaces/i_script_node.h similarity index 100% rename from story-editor/src/node_engine/i_script_node.h rename to core/interfaces/i_script_node.h diff --git a/story-editor/src/i_story_manager.h b/core/interfaces/i_story_manager.h similarity index 87% rename from story-editor/src/i_story_manager.h rename to core/interfaces/i_story_manager.h index d381f8c..d92cf15 100644 --- a/story-editor/src/i_story_manager.h +++ b/core/interfaces/i_story_manager.h @@ -49,10 +49,6 @@ public: // Node interaction virtual void BuildNodes(bool compileonly) = 0; virtual void BuildCode(bool compileonly) = 0; - virtual std::shared_ptr CreateNode(const std::string &type) = 0; - virtual void DeleteNode(const std::string &id) = 0; - virtual void DeleteLink(std::shared_ptr c) = 0; - virtual std::list> GetNodeConnections(const std::string &nodeId) = 0; virtual void LoadBinaryStory(const std::string &filename) = 0; virtual void ToggleBreakpoint(int line) = 0; virtual uint32_t GetRegister(int reg) = 0; diff --git a/core/interfaces/i_story_page.h b/core/interfaces/i_story_page.h new file mode 100644 index 0000000..bb358b7 --- /dev/null +++ b/core/interfaces/i_story_page.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "connection.h" + +class IStoryPage +{ +public: + virtual ~IStoryPage() {} + + virtual void GetNodeConnections(std::list> &c, const std::string &nodeId) = 0; +}; diff --git a/shared/i_story_project.h b/core/interfaces/i_story_project.h similarity index 65% rename from shared/i_story_project.h rename to core/interfaces/i_story_project.h index eb8604d..39f0b5f 100644 --- a/shared/i_story_project.h +++ b/core/interfaces/i_story_project.h @@ -3,15 +3,16 @@ #include #include #include "connection.h" +#include "story_options.h" class IStoryProject { public: virtual ~IStoryProject() {}; + virtual std::list> GetNodeConnections(const std::string &nodeId) = 0; virtual int OutputsCount(const std::string &nodeId) = 0; - virtual std::string ImageExtension(const std::string &filename) const = 0; - virtual std::string SoundExtension(const std::string &filename) const = 0; + virtual StoryOptions GetOptions() = 0; }; diff --git a/core/lib/resource.cpp b/core/lib/resource.cpp new file mode 100644 index 0000000..4269891 --- /dev/null +++ b/core/lib/resource.cpp @@ -0,0 +1,56 @@ + +#include "resource.h" +#include "sys_lib.h" + +std::string Resource::ImageFormatToString(ImageFormat format) +{ + std::string text = "SAME"; + switch (format) + { + case IMG_FORMAT_QOIF: + text = "QOIF"; + break; + } + return text; +} + +std::string Resource::SoundFormatToString(SoundFormat format) +{ + std::string text = "SAME"; + switch (format) + { + case SND_FORMAT_WAV: + text = "WAV"; + break; + case SND_FORMAT_QOAF: + text = "QOAF"; + break; + } + return text; +} + +std::string Resource::ImageExtension(const std::string &filename, Resource::ImageFormat prefered_format) +{ + std::string ext = SysLib::GetFileExtension(filename); + if (prefered_format == Resource::ImageFormat::IMG_FORMAT_QOIF) + { + return "qoi"; + } + + return ext; +} + +std::string Resource::SoundExtension(const std::string &filename, Resource::SoundFormat prefered_format) +{ + std::string ext = SysLib::GetFileExtension(filename); + if (prefered_format == Resource::SoundFormat::SND_FORMAT_QOAF) + { + return "qoa"; + } + else if (prefered_format == Resource::SoundFormat::SND_FORMAT_WAV) + { + return "wav"; + } + + return ext; +} diff --git a/shared/resource.h b/core/lib/resource.h similarity index 90% rename from shared/resource.h rename to core/lib/resource.h index bbde7ab..9b6aa5e 100644 --- a/shared/resource.h +++ b/core/lib/resource.h @@ -25,6 +25,8 @@ struct Resource static std::string ImageFormatToString(ImageFormat format); static std::string SoundFormatToString(SoundFormat format); + static std::string ImageExtension(const std::string &filename, Resource::ImageFormat prefered_format); + static std::string SoundExtension(const std::string &filename, Resource::SoundFormat prefered_format); }; // Itérateur pour parcourir les éléments filtrés @@ -82,12 +84,4 @@ private: }; -class IResource -{ -public: - virtual ~IResource() {} - -}; - - #endif // RESOURCE_H diff --git a/core/lib/story_options.h b/core/lib/story_options.h new file mode 100644 index 0000000..c12d429 --- /dev/null +++ b/core/lib/story_options.h @@ -0,0 +1,11 @@ +#pragma once + +#include "resource.h" + +struct StoryOptions +{ + Resource::ImageFormat image_format{Resource::IMG_SAME_FORMAT}; + Resource::SoundFormat sound_format{Resource::SND_SAME_FORMAT}; + int display_w{320}; + int display_h{240}; +}; diff --git a/shared/sys_lib.cpp b/core/lib/sys_lib.cpp similarity index 99% rename from shared/sys_lib.cpp rename to core/lib/sys_lib.cpp index 131b8b4..fd5d678 100644 --- a/shared/sys_lib.cpp +++ b/core/lib/sys_lib.cpp @@ -21,7 +21,6 @@ std::string SysLib::ToUpper(const std::string &input) } - std::string SysLib::GetFileExtension(const std::string &fileName) { auto idx = fileName.find_last_of("."); @@ -80,7 +79,6 @@ std::string SysLib::RemoveFileExtension(const std::string &filename) return filename.substr(0, dotPos); } - std::string SysLib::Normalize(const std::string &input) { std::string valid_file = input; diff --git a/shared/sys_lib.h b/core/lib/sys_lib.h similarity index 99% rename from shared/sys_lib.h rename to core/lib/sys_lib.h index 7e0039f..896f926 100644 --- a/shared/sys_lib.h +++ b/core/lib/sys_lib.h @@ -6,7 +6,6 @@ class SysLib { public: - static std::string GetFileExtension(const std::string &FileName); static std::string GetFileName(const std::string &path); static std::string RemoveFileExtension(const std::string &FileName); diff --git a/story-editor/src/node_engine/base_node.cpp b/core/src/base_node.cpp similarity index 100% rename from story-editor/src/node_engine/base_node.cpp rename to core/src/base_node.cpp diff --git a/story-editor/src/node_engine/base_node.h b/core/src/base_node.h similarity index 83% rename from story-editor/src/node_engine/base_node.h rename to core/src/base_node.h index 72cc4ea..61bd340 100644 --- a/story-editor/src/node_engine/base_node.h +++ b/core/src/base_node.h @@ -6,7 +6,8 @@ #include #include "json.hpp" -#include "i_story_project.h" +#include "i_story_page.h" +#include "story_options.h" class BaseNode { @@ -23,8 +24,8 @@ public: static std::string GetEntryLabel(const std::string &id); virtual void Initialize() = 0; - virtual std::string Build(IStoryProject &story, int nb_out_conns) = 0; - virtual std::string GenerateConstants(IStoryProject &story, int nb_out_conns) = 0; + virtual std::string Build(IStoryPage &page, const StoryOptions &options, int nb_out_conns) = 0; + virtual std::string GenerateConstants(IStoryPage &page, const StoryOptions &options, int nb_out_conns) = 0; void SetPosition(float x, float y); diff --git a/core/src/compiler.cpp b/core/src/compiler.cpp new file mode 100644 index 0000000..596c5ab --- /dev/null +++ b/core/src/compiler.cpp @@ -0,0 +1,9 @@ + +#include "compiler.h" + + +std::string Compiler::FileToConstant(const std::string &FileName, const std::string &extension) +{ + std::string f = SysLib::RemoveFileExtension(FileName); + return "$" + FileName + " DC8 \"" + FileName + "\", 8\r\n"; +} diff --git a/core/src/compiler.h b/core/src/compiler.h new file mode 100644 index 0000000..c624e68 --- /dev/null +++ b/core/src/compiler.h @@ -0,0 +1,15 @@ +#pragma once + +#include "sys_lib.h" + +class Compiler +{ + +public: + Compiler() = default; + ~Compiler() = default; + + static std::string FileToConstant(const std::string &FileName, const std::string &extension); + + +}; diff --git a/story-editor/src/node_engine/connection.cpp b/core/src/connection.cpp similarity index 100% rename from story-editor/src/node_engine/connection.cpp rename to core/src/connection.cpp diff --git a/story-editor/src/node_engine/connection.h b/core/src/connection.h similarity index 100% rename from story-editor/src/node_engine/connection.h rename to core/src/connection.h diff --git a/story-editor/src/node_engine/function_node.cpp b/core/src/function_node.cpp similarity index 76% rename from story-editor/src/node_engine/function_node.cpp rename to core/src/function_node.cpp index 3ae7065..448b609 100644 --- a/story-editor/src/node_engine/function_node.cpp +++ b/core/src/function_node.cpp @@ -27,12 +27,12 @@ void FunctionNode::Initialize() // m_sound = j["sound"].get(); } -std::string FunctionNode::Build(IStoryProject &story, int nb_out_conns) +std::string FunctionNode::Build(IStoryPage &page, const StoryOptions &options, int nb_out_conns) { return std::string(); } -std::string FunctionNode::GenerateConstants(IStoryProject &story, int nb_out_conns) +std::string FunctionNode::GenerateConstants(IStoryPage &page, const StoryOptions &options, int nb_out_conns) { std::string s; diff --git a/story-editor/src/node_engine/function_node.h b/core/src/function_node.h similarity index 58% rename from story-editor/src/node_engine/function_node.h rename to core/src/function_node.h index 64cb32a..9fc7393 100644 --- a/story-editor/src/node_engine/function_node.h +++ b/core/src/function_node.h @@ -12,8 +12,8 @@ public: FunctionNode(const std::string &type); virtual void Initialize() override; - virtual std::string Build(IStoryProject &story, int nb_out_conns) override; - virtual std::string GenerateConstants(IStoryProject &story, int nb_out_conns) override; + virtual std::string Build(IStoryPage &page, const StoryOptions &options, int nb_out_conns) override; + virtual std::string GenerateConstants(IStoryPage &page, const StoryOptions &options, int nb_out_conns) override; void StoreInternalData(); diff --git a/story-editor/src/node_engine/media_node.cpp b/core/src/media_node.cpp similarity index 84% rename from story-editor/src/node_engine/media_node.cpp rename to core/src/media_node.cpp index 905139f..10cb03b 100644 --- a/story-editor/src/node_engine/media_node.cpp +++ b/core/src/media_node.cpp @@ -2,6 +2,7 @@ #include "story_project.h" #include "connection.h" #include "sys_lib.h" +#include "compiler.h" static std::string ChoiceLabel(const std::string &id) { @@ -33,17 +34,17 @@ void MediaNode::Initialize() m_sound = j["sound"].get(); } -std::string MediaNode::GenerateConstants(IStoryProject &story, int nb_out_conns) +std::string MediaNode::GenerateConstants(IStoryPage &page, const StoryOptions &options, int nb_out_conns) { std::string s; if (m_image.size() > 0) { - s = StoryProject::FileToConstant(m_image, story.ImageExtension(m_image)); + s = Compiler::FileToConstant(m_image, Resource::ImageExtension(m_image, options.image_format)); } if (m_sound.size() > 0) { - s += StoryProject::FileToConstant(m_sound, story.SoundExtension(m_sound)); // FIXME: Generate the extension setup in user option of output format + s += Compiler::FileToConstant(m_sound, Resource::SoundExtension(m_sound, options.sound_format)); // FIXME: Generate the extension setup in user option of output format } // Generate choice table if needed (out ports > 1) @@ -53,7 +54,8 @@ std::string MediaNode::GenerateConstants(IStoryProject &story, int nb_out_conns) << " DC32, " << nb_out_conns << ", "; - std::list> conns = story.GetNodeConnections(GetId()); + std::list> conns; + page.GetNodeConnections(conns, GetId()); int i = 0; for (auto & c : conns) { @@ -100,7 +102,7 @@ std::string_view MediaNode::GetSound() const return m_sound; } -std::string MediaNode::Build(IStoryProject &story, int nb_out_conns) +std::string MediaNode::Build(IStoryPage &page, const StoryOptions &options, int nb_out_conns) { std::stringstream ss; @@ -148,8 +150,8 @@ std::string MediaNode::Build(IStoryProject &story, int nb_out_conns) } else if (nb_out_conns == 1) // it is a transition node { - std::list> conns = story.GetNodeConnections(GetId()); - + std::list> conns; + page.GetNodeConnections(conns, GetId()); for (auto c : conns) { diff --git a/story-editor/src/node_engine/media_node.h b/core/src/media_node.h similarity index 70% rename from story-editor/src/node_engine/media_node.h rename to core/src/media_node.h index 5743939..3b47a0b 100644 --- a/story-editor/src/node_engine/media_node.h +++ b/core/src/media_node.h @@ -12,8 +12,8 @@ public: MediaNode(const std::string &type); virtual void Initialize() override; - virtual std::string Build(IStoryProject &story, int nb_out_conns) override; - virtual std::string GenerateConstants(IStoryProject &story, int nb_out_conns) override; + virtual std::string Build(IStoryPage &page, const StoryOptions &options, int nb_out_conns) override; + virtual std::string GenerateConstants(IStoryPage &page, const StoryOptions &options, int nb_out_conns) override; void SetImage(const std::string &image); std::string_view GetImage() const; diff --git a/core/src/story_page.cpp b/core/src/story_page.cpp new file mode 100644 index 0000000..d730926 --- /dev/null +++ b/core/src/story_page.cpp @@ -0,0 +1,4 @@ + +#include "story_page.h" + + diff --git a/core/src/story_page.h b/core/src/story_page.h new file mode 100644 index 0000000..ac7dfd5 --- /dev/null +++ b/core/src/story_page.h @@ -0,0 +1,181 @@ +#pragma once + +#include +#include +#include + +#include "i_story_page.h" +#include "base_node.h" +#include "connection.h" + +class StoryPage : public IStoryPage +{ + +public: + StoryPage(const std::string &uuid) + : m_uuid(uuid) + { + } + ~StoryPage() { + m_links.clear(); + m_nodes.clear(); + std::cout << "StoryPage destructor" << std::endl; + } + + std::string_view Uuid() const { return m_uuid; } + + void AddNode(std::shared_ptr node) { + m_nodes.push_back(node); + } + + void AddLink(std::shared_ptr link) { + m_links.push_back(link); + } + + void PrintStats() const { + std::cout << "Nodes: " << m_nodes.size() << ", links: " << m_links.size() << std::endl; + } + + std::pair>::iterator, std::list>::iterator> Nodes() { + return std::make_pair(m_nodes.begin(), m_nodes.end()); + } + + std::pair>::iterator, std::list>::iterator> Links() { + return std::make_pair(m_links.begin(), m_links.end()); + } + + void Clear() { + m_links.clear(); + m_nodes.clear(); + } + + void Build(std::stringstream &code, const StoryOptions &options) + { + // First generate all constants + for (const auto & n : m_nodes) + { + code << n->GenerateConstants(*this, options, OutputsCount(n->GetId())) << "\n"; + } + + for (const auto & n : m_nodes) + { + code << n->Build(*this, options, OutputsCount(n->GetId())) << "\n"; + } + } + + virtual void GetNodeConnections(std::list> &c, const std::string &nodeId) override + { + for (const auto & l : m_links) + { + if (l->outNodeId == nodeId) + { + c.push_back(l); + } + } + } + + std::string FindFirstNode() const + { + std::string id; + + // First node is the one without connection on its input port + + for (const auto & n : m_nodes) + { + bool foundConnection = false; + + for (const auto& l : m_links) + { + if (l->inNodeId == n->GetId()) + { + foundConnection = true; + } + } + + if (!foundConnection) + { + id = n->GetId(); + std::cout << "First node is: " + id << std::endl; + break; + } + } + + return id; + } + + int OutputsCount(const std::string &nodeId) const +{ + int count = 0; + for (const auto & l : m_links) + { + if (l->outNodeId == nodeId) + { + count++; + } + } + return count; + } + + + nlohmann::json ToJson() const + { + nlohmann::json model; + model["uuid"] = m_uuid; + + nlohmann::json nodes = nlohmann::json::array(); + for (const auto & n : m_nodes) + { + nodes.push_back(n->ToJson()); + } + + model["nodes"] = nodes; + + // Save links + nlohmann::json connections = nlohmann::json::array(); + for (const auto& cnx : m_links) + { + nlohmann::json c(*cnx); + connections.push_back(c); + } + + model["connections"] = connections; + + return model; + } + + void DeleteLink(std::shared_ptr c) + { + auto it = std::find_if(m_links.begin(), + m_links.end(), + [&c](std::shared_ptr const &cnx) { + return *cnx == *c; + }); + + if ( it != m_links.end() ) + { + it->reset(); + m_links.erase(it); + } + } + + void DeleteNode(const std::string &id) + { + + auto it = std::find_if(m_nodes.begin(), + m_nodes.end(), + [&id](std::shared_ptr const &n) { return n->GetId() == id; }); + + if ( it != m_nodes.end() ) + { + it->reset(); + m_nodes.erase(it); + } + } + +private: + std::string m_uuid; + std::string m_name; + std::list> m_links; + std::list> m_nodes; + +}; diff --git a/shared/story_project.cpp b/core/src/story_project.cpp similarity index 68% rename from shared/story_project.cpp rename to core/src/story_project.cpp index 894cbd5..bac1361 100644 --- a/shared/story_project.cpp +++ b/core/src/story_project.cpp @@ -56,7 +56,7 @@ void StoryProject::CopyToDevice(const std::string &outputDir) std::filesystem::copy(BinaryFileName(), destRootDir, std::filesystem::copy_options::overwrite_existing); // Convert resources (if necessary) and copy them to destination assets - manager.ConvertResources(AssetsPath(), destAssetsDir, m_imageFormat, m_soundFormat); + manager.ConvertResources(AssetsPath(), destAssetsDir, m_storyOptions.image_format, m_storyOptions.sound_format); } } @@ -109,29 +109,22 @@ bool StoryProject::ParseStoryInformation(nlohmann::json &j) void StoryProject::ModelToJson(nlohmann::json &model) { - nlohmann::json nodes = nlohmann::json::array(); - for (const auto & n : m_nodes) + for (const auto & p : m_pages) { - nodes.push_back(n->ToJson()); + nlohmann::json page = p->ToJson(); + model.push_back(page); } - - model["nodes"] = nodes; - - // Save links - nlohmann::json connections = nlohmann::json::array(); - for (const auto& cnx : m_links) - { - nlohmann::json c(*cnx); - connections.push_back(c); - } - - model["connections"] = connections; } - -std::shared_ptr StoryProject::CreateNode(const std::string &type) +std::shared_ptr StoryProject::CreatePage(const std::string &uuid) { + auto newPage = std::make_shared(uuid); + m_pages.push_back(newPage); + return newPage; +} +std::shared_ptr StoryProject::CreateNode(const std::string_view &page_uuid, const std::string &type) +{ typename Registry::const_iterator i = m_registry.find(type); if (i == m_registry.end()) { throw std::invalid_argument(std::string(__PRETTY_FUNCTION__) + @@ -140,88 +133,132 @@ std::shared_ptr StoryProject::CreateNode(const std::string &type) else { auto n = i->second(type); - m_nodes.push_back(n); + + for (auto & p : m_pages) + { + if (p->Uuid() == page_uuid) + { + p->AddNode(n); + } + } return n; } } -void StoryProject::AddConnection(std::shared_ptr c) - { - m_links.push_back(c); -} - -void StoryProject::DeleteNode(const std::string &id) +void StoryProject::AddConnection(const std::string_view &page_uuid, std::shared_ptr c) { - auto it = std::find_if(m_nodes.begin(), - m_nodes.end(), - [&id](std::shared_ptr const &n) { return n->GetId() == id; }); - - if ( it != m_nodes.end() ) + for (const auto & p : m_pages) { - it->reset(); - m_nodes.erase(it); + if (p->Uuid() == page_uuid) + { + p->AddLink(c); + } } } -void StoryProject::DeleteLink(std::shared_ptr c) +void StoryProject::DeleteNode(const std::string_view &page_uuid, const std::string &id) { - auto it = std::find_if(m_links.begin(), - m_links.end(), - [&c](std::shared_ptr const &cnx) { - return *cnx == *c; - }); - - if ( it != m_links.end() ) + for (const auto & p : m_pages) { - it->reset(); - m_links.erase(it); + if (p->Uuid() == page_uuid) + { + p->DeleteNode(id); + } } } +void StoryProject::DeleteLink(const std::string_view &page_uuid, std::shared_ptr c) +{ + + for (const auto & p : m_pages) + { + if (p->Uuid() == page_uuid) + { + p->DeleteLink(c); + } + } +} + +std::pair>::iterator, std::list>::iterator> StoryProject::Nodes(const std::string_view &page_uuid) +{ + for (const auto & p : m_pages) + { + if (p->Uuid() == page_uuid) + { + return p->Nodes(); + } + } + + return std::pair>::iterator, std::list>::iterator>(); +} + +std::pair>::iterator, std::list>::iterator> StoryProject::Links(const std::string_view &page_uuid) +{ + for (const auto & p : m_pages) + { + if (p->Uuid() == page_uuid) + { + return p->Links(); + } + } + + return std::pair>::iterator, std::list>::iterator>(); +} + bool StoryProject::ModelFromJson(const nlohmann::json &model) { bool success = false; try { - nlohmann::json nodesJsonArray = model["nodes"]; + nlohmann::json pagesJsonArray = model["pages"]; + m_pages.clear(); - m_nodes.clear(); - m_links.clear(); - - for (auto& element : nodesJsonArray) { - - std::string type = element["type"].get(); - - auto n = CreateNode(type); - if (n) - { - n->FromJson(element); - n->Initialize(); - } - else - { - throw std::logic_error(std::string("No registered model with name ") + type); - } - } - - // std::cout << model.dump(4) << std::endl; - - // Ici on reste flexible sur les connexions, cela permet de créer éventuellement des - // projets sans fils (bon, l'élément devrait quand même exister dans le JSON) - if (model.contains("connections")) + for (auto& pageModel : pagesJsonArray) { - nlohmann::json connectionJsonArray = model["connections"]; + // 1. Create the page in memory + auto p = std::make_shared(pageModel["uuid"].get()); + m_pages.push_back(p); - // key: node UUID, value: output counts - std::map outputCounts; + // 2. Load the nodes + nlohmann::json nodesJsonArray = pageModel["nodes"]; + for (auto& element : nodesJsonArray) { + + std::string type = element["type"].get(); - for (auto& connection : connectionJsonArray) - { - m_links.push_back(std::make_shared(connection.get())); + auto n = CreateNode(p->Uuid(), type); + if (n) + { + n->FromJson(element); + n->Initialize(); + } + else + { + throw std::logic_error(std::string("No registered model with name ") + type); + } } + + // 3. Load the connections + // std::cout << model.dump(4) << std::endl; + + // Ici on reste flexible sur les connexions, cela permet de créer éventuellement des + // projets sans fils (bon, l'élément devrait quand même exister dans le JSON) + if (pageModel.contains("connections")) + { + nlohmann::json connectionJsonArray = pageModel["connections"]; + + // key: node UUID, value: output counts + std::map outputCounts; + + for (auto& connection : connectionJsonArray) + { + p->AddLink(std::make_shared(connection.get())); + } + } + + } - std::cout << "From model, loded nodes: " << m_nodes.size() << ", links: " << m_links.size() << std::endl; + success = true; } catch(nlohmann::json::exception &e) @@ -266,97 +303,37 @@ bool StoryProject::GetAssemblyLine(uint32_t pointer_counter, uint32_t &line) return success; } -int StoryProject::OutputsCount(const std::string &nodeId) -{ - int count = 0; - for (const auto & l : m_links) - { - if (l->outNodeId == nodeId) - { - count++; - } - } - return count; -} - -std::string StoryProject::ImageExtension(const std::string &filename) const -{ - std::string ext = SysLib::GetFileExtension(filename); - if (m_imageFormat == Resource::ImageFormat::IMG_FORMAT_QOIF) - { - return "qoi"; - } - - return ext; -} - -std::string StoryProject::SoundExtension(const std::string &filename) const -{ - std::string ext = SysLib::GetFileExtension(filename); - if (m_soundFormat == Resource::SoundFormat::SND_FORMAT_QOAF) - { - return "qoa"; - } - else if (m_soundFormat == Resource::SoundFormat::SND_FORMAT_WAV) - { - return "wav"; - } - - return ext; -} - std::list> StoryProject::GetNodeConnections(const std::string &nodeId) { std::list> c; - for (const auto & l : m_links) - { - if (l->outNodeId == nodeId) - { - c.push_back(l); - } + for (const auto &p : m_pages) + { + p->GetNodeConnections(c, nodeId); } return c; } - -std::string StoryProject::FindFirstNode() const +int StoryProject::OutputsCount(const std::string &nodeId) { - std::string id; - - // First node is the one without connection on its input port - - for (const auto & n : m_nodes) + for (const auto &p : m_pages) { - bool foundConnection = false; - - for (const auto& l : m_links) - { - if (l->inNodeId == n->GetId()) - { - foundConnection = true; - } - } - - if (!foundConnection) - { - id = n->GetId(); - std::cout << "First node is: " + id << std::endl; - break; - } + return p->OutputsCount(nodeId); } - - return id; + return 0; } - bool StoryProject::GenerateScript(std::string &codeStr) { std::stringstream code; std::stringstream chip32; + std::string firstNode; - std::string firstNode = FindFirstNode(); + for (const auto & p : m_pages) + { + firstNode = p->FindFirstNode(); + } if (firstNode == "") { @@ -365,16 +342,11 @@ bool StoryProject::GenerateScript(std::string &codeStr) } code << "\tjump " << BaseNode::GetEntryLabel(firstNode) << "\r\n"; - - // First generate all constants - for (const auto & n : m_nodes) + + // On build toutes les pages + for (const auto & p : m_pages) { - code << n->GenerateConstants(*this, OutputsCount(n->GetId())) << "\n"; - } - - for (const auto & n : m_nodes) - { - code << n->Build(*this, OutputsCount(n->GetId())) << "\n"; + p->Build(code, m_storyOptions); } codeStr = code.str(); @@ -448,9 +420,9 @@ bool StoryProject::Load(ResourceManager &manager) manager.Add(rData); } - if (j.contains("nodegraph")) + if (j.contains("pages")) { - ModelFromJson(j["nodegraph"]); + ModelFromJson(j["pages"]); m_initialized = true; } } @@ -487,7 +459,7 @@ void StoryProject::Save(ResourceManager &manager) nlohmann::json model; ModelToJson(model); - j["nodegraph"] = model; + j["pages"] = model; std::ofstream o(m_project_file_path); o << std::setw(4) << j << std::endl; @@ -533,8 +505,6 @@ void StoryProject::Clear() m_working_dir = ""; m_project_file_path = ""; m_initialized = false; - m_nodes.clear(); - m_links.clear(); } @@ -550,18 +520,18 @@ void StoryProject::SetTitleSound(const std::string &titleSound) void StoryProject::SetImageFormat(Resource::ImageFormat format) { - m_imageFormat = format; + m_storyOptions.image_format = format; } void StoryProject::SetSoundFormat(Resource::SoundFormat format) { - m_soundFormat = format; + m_storyOptions.sound_format = format; } void StoryProject::SetDisplayFormat(int w, int h) { - m_display_w = w; - m_display_h = h; + m_storyOptions.display_w = w; + m_storyOptions.display_h = h; } std::string StoryProject::GetProjectFilePath() const @@ -579,11 +549,6 @@ std::string StoryProject::BuildFullAssetsPath(const std::string_view fileName) c return (AssetsPath() / fileName).generic_string(); } -std::string StoryProject::FileToConstant(const std::string &FileName, const std::string &extension) -{ - std::string f = SysLib::RemoveFileExtension(FileName); - return "$" + FileName + " DC8 \"" + FileName + "\", 8\r\n"; -} diff --git a/shared/story_project.h b/core/src/story_project.h similarity index 79% rename from shared/story_project.h rename to core/src/story_project.h index 5921606..1082dd8 100644 --- a/shared/story_project.h +++ b/core/src/story_project.h @@ -12,6 +12,8 @@ #include "base_node.h" #include "i_story_project.h" #include "chip32_assembler.h" +#include "story_page.h" +#include "story_options.h" // FIXME : Structure très Lunii style, à utiliser pour la conversion peut-être ... struct StoryNode @@ -45,6 +47,7 @@ struct StoryNode }; + struct StoryProject : public IStoryProject { @@ -78,29 +81,17 @@ public: bool CopyProgramTo(uint8_t *memory, uint32_t size); -// returns >= 0 on success + // returns >= 0 on success bool GetAssemblyLine(uint32_t pointer_counter, uint32_t &line); - void CreateTree(); void Clear(); - std::pair>::iterator, std::list>::iterator> Nodes() { - return std::make_pair(m_nodes.begin(), m_nodes.end()); - } - - std::pair>::iterator, std::list>::iterator> Links() { - return std::make_pair(m_links.begin(), m_links.end()); - } - void Select(bool selected) { m_selected = selected; } bool IsSelected() const { return m_selected; } void SetImageFormat(Resource::ImageFormat format); void SetSoundFormat(Resource::SoundFormat format); - Resource::ImageFormat GetImageFormat() const { return m_imageFormat; } - Resource::SoundFormat GetSoundFormat() const { return m_soundFormat; } - void SetDisplayFormat(int w, int h); void SetName(const std::string &name) { m_name = name; } void SetUuid(const std::string &uuid) { m_uuid = uuid; } @@ -114,8 +105,6 @@ public: std::string BuildFullAssetsPath(const std::string_view fileName) const; - static std::string FileToConstant(const std::string &FileName, const std::string &extension); - std::filesystem::path AssetsPath() const { return m_assetsPath; } void SetTitleImage(const std::string &titleImage); @@ -131,15 +120,17 @@ public: // From IStoryProject virtual std::list> GetNodeConnections(const std::string &nodeId) override; - std::string FindFirstNode() const; virtual int OutputsCount(const std::string &nodeId) override; - virtual std::string ImageExtension(const std::string &filename) const override; - virtual std::string SoundExtension(const std::string &filename) const override; + virtual StoryOptions GetOptions() override { return m_storyOptions; } - std::shared_ptr CreateNode(const std::string& type); - void AddConnection(std::shared_ptr c); - void DeleteNode(const std::string &id); - void DeleteLink(std::shared_ptr c); + // Node interaction + std::shared_ptr CreatePage(const std::string &uuid); + std::shared_ptr CreateNode(const std::string_view &page, const std::string &type); + void AddConnection(const std::string_view &page, std::shared_ptr c); + void DeleteNode(const std::string_view &page, const std::string &id); + void DeleteLink(const std::string_view &page, std::shared_ptr c); + std::pair>::iterator, std::list>::iterator> Nodes(const std::string_view &page_uuid); + std::pair>::iterator, std::list>::iterator> Links(const std::string_view &page_uuid); std::vector GetNodeTypes() const { std::vector l; @@ -164,21 +155,15 @@ private: Chip32::Assembler m_assembler; std::vector m_program; - // Model in memory - std::list> m_links; - std::list> m_nodes; + std::list> m_pages; + + StoryOptions m_storyOptions; bool m_initialized{false}; std::filesystem::path m_working_dir; /// Temporary folder based on the uuid, where the archive is unzipped std::filesystem::path m_project_file_path; /// JSON project file - int m_display_w{320}; - int m_display_h{240}; - - Resource::ImageFormat m_imageFormat{Resource::IMG_SAME_FORMAT}; - Resource::SoundFormat m_soundFormat{Resource::SND_SAME_FORMAT}; - template struct Factory { static std::shared_ptr create_func(const std::string &type) { diff --git a/shared/resource.cpp b/shared/resource.cpp deleted file mode 100644 index 2679bab..0000000 --- a/shared/resource.cpp +++ /dev/null @@ -1,30 +0,0 @@ - -#include "resource.h" - -std::string Resource::ImageFormatToString(ImageFormat format) -{ - std::string text = "SAME"; - switch (format) - { - case IMG_FORMAT_QOIF: - text = "QOIF"; - break; - } - return text; -} - -std::string Resource::SoundFormatToString(SoundFormat format) -{ - std::string text = "SAME"; - switch (format) - { - case SND_FORMAT_WAV: - text = "WAV"; - break; - case SND_FORMAT_QOAF: - text = "QOAF"; - break; - } - return text; -} - diff --git a/story-editor/CMakeLists.txt b/story-editor/CMakeLists.txt index f541dd1..d4a4648 100644 --- a/story-editor/CMakeLists.txt +++ b/story-editor/CMakeLists.txt @@ -187,15 +187,6 @@ set(SRCS src/platform_folders.cpp src/platform_folders.h - src/node_engine/base_node.h - src/node_engine/base_node.cpp - src/node_engine/media_node.h - src/node_engine/media_node.cpp - src/node_engine/function_node.h - src/node_engine/function_node.cpp - src/node_engine/connection.cpp - src/node_engine/connection.h - src/node_editor/media_node_widget.h src/node_editor/media_node_widget.cpp src/node_editor/base_node_widget.h @@ -223,15 +214,12 @@ set(SRCS src/media_converter.cpp src/media_converter.h - src/i_story_manager.h - src/miniz.c src/zip.cpp src/importers/pack_archive.cpp src/importers/ni_parser.c - libs/ImGuiColorTextEdit/TextEditor.cpp libs/ImGuiColorTextEdit/TextEditor.h libs/imgui-node-editor/imgui_node_editor.cpp @@ -257,17 +245,25 @@ set(SRCS ../shared/miniaudio.h ../shared/stb_vorbis.c ../shared/uuid.h - ../shared/resource.h - ../shared/resource.cpp ../shared/resource_manager.h ../shared/resource_manager.cpp - ../shared/story_project.cpp - ../shared/story_project.h + ../shared/thread_safe_queue.h ../shared/library_manager.h ../shared/library_manager.cpp - ../shared/sys_lib.cpp - ../shared/sys_lib.h + + # Core engine files + + ../core/src/compiler.cpp + ../core/src/story_project.cpp + ../core/src/story_page.cpp + ../core/src/base_node.cpp + ../core/src/media_node.cpp + ../core/src/function_node.cpp + ../core/src/connection.cpp + + ../core/lib/sys_lib.cpp + ../core/lib/resource.cpp ) if(WIN32) @@ -307,7 +303,9 @@ target_include_directories(${STORY_EDITOR_PROJECT} PUBLIC ../firmware/library ../firmware/chip32 ../shared - + ../core/src + ../core/lib + ../core/interfaces ) add_definitions(-DIMGUI_USE_WCHAR32 -DVERSION_MAJOR=${PROJECT_VERSION_MAJOR} -DVERSION_MINOR=${PROJECT_VERSION_MINOR} -DVERSION_PATCH=${PROJECT_VERSION_PATCH}) diff --git a/story-editor/src/importers/pack_archive.cpp b/story-editor/src/importers/pack_archive.cpp index 7f1b9a0..85b940c 100644 --- a/story-editor/src/importers/pack_archive.cpp +++ b/story-editor/src/importers/pack_archive.cpp @@ -384,6 +384,8 @@ bool PackArchive::ConvertJsonStudioToOst(const std::string &basePath, const std: StoryProject proj(m_log); ResourceManager res(m_log); + std::shared_ptr page = proj.CreatePage("main"); + if (j.contains("title")) { proj.New(uuid, outputDir); @@ -414,7 +416,7 @@ bool PackArchive::ConvertJsonStudioToOst(const std::string &basePath, const std: for (const auto & n : j["stageNodes"]) { - auto node = proj.CreateNode("media-node"); + auto node = proj.CreateNode("main", "media-node"); if (node) { @@ -452,7 +454,7 @@ bool PackArchive::ConvertJsonStudioToOst(const std::string &basePath, const std: c->inPortIndex = 0; i++; - proj.AddConnection(c); + page->AddLink(c); } } else diff --git a/story-editor/src/main_window.cpp b/story-editor/src/main_window.cpp index a8d3695..a88cfdc 100644 --- a/story-editor/src/main_window.cpp +++ b/story-editor/src/main_window.cpp @@ -360,6 +360,7 @@ float MainWindow::DrawMainMenuBar() if (m_story) { SaveProject(); + m_libraryManager.Scan(); // Add new project to library OpenProject(m_story->GetUuid()); } } @@ -1005,11 +1006,6 @@ void MainWindow::DeleteResource(FilterIterator &it) return m_resources.Delete(it); } -std::shared_ptr MainWindow::CreateNode(const std::string &type) -{ - return m_story->CreateNode(type); -} - void MainWindow::LoadBinaryStory(const std::string &filename) { FILE *fp = fopen(filename.c_str(), "rb"); @@ -1072,7 +1068,8 @@ void MainWindow::Build(bool compileonly) if (!compileonly) { // 3. Convert all media to desired type format - m_resources.ConvertResources(m_story->AssetsPath(), "", m_story->GetImageFormat(), m_story->GetSoundFormat()); // pas de répertoire de destination + auto options = m_story->GetOptions(); + m_resources.ConvertResources(m_story->AssetsPath(), "", options.image_format, options.sound_format); // pas de répertoire de destination } Chip32::Assembler::Error err; @@ -1109,20 +1106,6 @@ void MainWindow::BuildCode(bool compileonly) Build(compileonly); } -void MainWindow::DeleteNode(const std::string &id) -{ - m_story->DeleteNode(id); -} - -void MainWindow::DeleteLink(std::shared_ptr c) -{ - m_story->DeleteLink(c); -} - -std::list> MainWindow::GetNodeConnections(const std::string &nodeId) -{ - return m_story->GetNodeConnections(nodeId); -} void MainWindow::UpdateVmView() { diff --git a/story-editor/src/main_window.h b/story-editor/src/main_window.h index fbe43c9..c0df585 100644 --- a/story-editor/src/main_window.h +++ b/story-editor/src/main_window.h @@ -137,12 +137,9 @@ private: virtual std::pair Resources() override; virtual void DeleteResource(FilterIterator &it) override; - virtual std::shared_ptr CreateNode(const std::string &type) override; + virtual void BuildNodes(bool compileonly) override; virtual void BuildCode(bool compileonly) override; - virtual void DeleteNode(const std::string &id) override; - virtual void DeleteLink(std::shared_ptr c) override; - virtual std::list> GetNodeConnections(const std::string &nodeId) override; virtual void LoadBinaryStory(const std::string &filename) override; virtual void ToggleBreakpoint(int line) override; virtual uint32_t GetRegister(int reg) override; diff --git a/story-editor/src/node_editor/node_editor_window.cpp b/story-editor/src/node_editor/node_editor_window.cpp index 2cbaf18..6156814 100644 --- a/story-editor/src/node_editor/node_editor_window.cpp +++ b/story-editor/src/node_editor/node_editor_window.cpp @@ -45,7 +45,8 @@ void NodeEditorWindow::Initialize() m_callStack.clear(); m_currentPage = std::make_shared(gMainUuid, "Main"); - m_pages.emplace_back(m_currentPage); + m_pages.push_back(m_currentPage); + m_callStack.push_back(m_currentPage); m_currentPage->Select(); } @@ -70,9 +71,9 @@ void NodeEditorWindow::LoadPage(const std::string &uuid, const std::string &name if (m_currentPage->Uuid() != uuid) { - m_callStack.push_back(m_currentPage); // save where we are m_currentPage = page; m_currentPage->Select(); + m_callStack.push_back(m_currentPage); // show current page in call stack } } @@ -97,7 +98,7 @@ void NodeEditorWindow::Load(std::shared_ptr story) BaseNodeWidget::InitId(); Initialize(); - auto [node_begin, node_end] = m_story->Nodes(); + auto [node_begin, node_end] = m_story->Nodes(m_currentPage->Uuid()); int i = 0; @@ -117,7 +118,7 @@ void NodeEditorWindow::Load(std::shared_ptr story) std::cout << "Created " << ++i << " node" << std::endl; } - auto [link_begin, link_end] = m_story->Links(); + auto [link_begin, link_end] = m_story->Links(m_currentPage->Uuid()); for (auto it = link_begin; it != link_end; ++it) { @@ -203,6 +204,8 @@ std::shared_ptr NodeEditorWindow::GetSelectedNode() void NodeEditorWindow::Draw() { + // Check if we need to load a new page + // Typically if we are in a function and we want to open a new one if (!m_newPageUuid.empty()) { LoadPage(m_newPageUuid, m_newPageName); @@ -251,7 +254,7 @@ void NodeEditorWindow::Draw() { if (FillConnection(c, endId)) { - m_story->AddConnection(c); + m_story->AddConnection(m_currentPage->Uuid(), c); CreateLink(c, startId, endId); @@ -282,7 +285,7 @@ void NodeEditorWindow::Draw() if (m_currentPage->GetNode(nodeId, node)) { // First delete model, then current entry - m_manager.DeleteNode(node->Base()->GetId()); + m_story->DeleteNode(m_currentPage->Uuid(), node->Base()->GetId()); m_currentPage->DeleteNode(nodeId); } } @@ -298,7 +301,7 @@ void NodeEditorWindow::Draw() std::shared_ptr model; if (m_currentPage->GetModel(deletedLinkId, model)) { - m_manager.DeleteLink(model); + m_story->DeleteLink(m_currentPage->Uuid(), model); m_currentPage->EraseLink(deletedLinkId); } } @@ -328,7 +331,7 @@ void NodeEditorWindow::Draw() { if (ImGui::MenuItem(type.c_str())) { - base = m_manager.CreateNode(type); + base = m_story->CreateNode(m_currentPage->Uuid(), type); if (base) { auto n = CreateNodeWidget(type, m_manager, base); @@ -390,12 +393,17 @@ void NodeEditorWindow::ToolbarUI() { if (ImGui::Button(page->Name().data())) { - // Erase all pages after this iterator - auto it = std::find(m_callStack.begin(), m_callStack.end(), page); - m_callStack.erase(it, m_callStack.end()); + if (page->Uuid() != m_currentPage->Uuid()) + { + + // Erase all pages after this iterator + auto it = std::find(m_callStack.begin(), m_callStack.end(), page); + m_callStack.erase(it, m_callStack.end()); - LoadPage(page->Uuid().data(), page->Name().data()); - break; + LoadPage(page->Uuid().data(), page->Name().data()); + break; + } + } ImGui::SameLine(); ImGui::Text(">"); diff --git a/story-editor/story-editor b/story-editor/story-editor new file mode 100755 index 0000000..89fb204 Binary files /dev/null and b/story-editor/story-editor differ