diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d1028c..6806370 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y cmake clang-format clang-tidy libsdl2-dev + sudo apt-get install -y cmake clang-format clang-tidy libsdl2-dev protobuf-compiler - name: Check Code Formatting run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f28891..326ad2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) enable_testing() - + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(Dependencies) diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 0c2b475..bce5c33 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -28,4 +28,7 @@ FetchContent_Declare( FetchContent_MakeAvailable(concurrentqueue) # 4. SDL2 (System installed) -find_package(SDL2 REQUIRED) \ No newline at end of file +find_package(SDL2 REQUIRED) + +# 5. Protobuf (System installed) +find_package(Protobuf REQUIRED) diff --git a/include/parser/Parser.hpp b/include/parser/Parser.hpp new file mode 100644 index 0000000..d19fb98 --- /dev/null +++ b/include/parser/Parser.hpp @@ -0,0 +1,28 @@ +#ifndef PARSER_HPP +#define PARSER_HPP + +#include +#include +#include + +class Scene; +class SceneObject; +class Component; + +namespace NeuronIDE { +class SceneObject; +class Component; +} // namespace NeuronIDE + +class Parser { + public: + Parser() = default; + + std::shared_ptr parse(const std::string& filePath); + + private: + static std::shared_ptr buildSceneObject(const NeuronIDE::SceneObject& protoObj); + static std::unique_ptr buildComponent(const NeuronIDE::Component& protoComp); +}; + +#endif // PARSER_HPP \ No newline at end of file diff --git a/include/scene/Scene.hpp b/include/scene/Scene.hpp new file mode 100644 index 0000000..77c6be5 --- /dev/null +++ b/include/scene/Scene.hpp @@ -0,0 +1,24 @@ +#ifndef SCENE_HPP +#define SCENE_HPP + +#include +#include +#include + +class SceneObject; + +class Scene { + private: + std::string experimentName; + std::vector> objects; + + public: + void setExperimentName(const std::string& name) { experimentName = name; } + + void addObject(std::shared_ptr obj) { objects.push_back(std::move(obj)); } + + const std::string& getExperimentName() const { return experimentName; } + const std::vector>& getObjects() const { return objects; } +}; + +#endif // SCENE_HPP \ No newline at end of file diff --git a/include/scene/SceneObject.hpp b/include/scene/SceneObject.hpp new file mode 100644 index 0000000..bf6ef92 --- /dev/null +++ b/include/scene/SceneObject.hpp @@ -0,0 +1,29 @@ +#ifndef SCENEOBJECT_HPP +#define SCENEOBJECT_HPP + +#include +#include +#include +#include + +class Component; + +class SceneObject { + public: + std::string name; + bool isVisible = true; + + struct Transform { + double posX = 0, posY = 0, width = 0, height = 0, rotation = 0; + } transform; + + std::vector> components; + + SceneObject(std::string n, bool visible = true); + + void setTransform(Transform t); + + void addComponent(std::unique_ptr comp); +}; + +#endif // SCENEOBJECT_HPP \ No newline at end of file diff --git a/include/scene/components/BlinkComponent.hpp b/include/scene/components/BlinkComponent.hpp new file mode 100644 index 0000000..59816b2 --- /dev/null +++ b/include/scene/components/BlinkComponent.hpp @@ -0,0 +1,27 @@ +#ifndef BLINKCOMPONENT_HPP +#define BLINKCOMPONENT_HPP + +#include +#include + +#include "Component.hpp" + +namespace NeuronIDE { +class Component; +} + +class BlinkComponent : public Component { + public: + BlinkComponent(double freq) : blinkFrequencyHz(freq) { + std::cout << " + [BlinkComponent] Utworzono z czestotliwoscia: " << blinkFrequencyHz + << "Hz\n"; + } + void setFrequency(double freq); + + static std::unique_ptr createBlinker(const NeuronIDE::Component& protoComp); + + private: + double blinkFrequencyHz = 0.0; +}; + +#endif // BLINKCOMPONENT_HPP \ No newline at end of file diff --git a/include/scene/components/Component.hpp b/include/scene/components/Component.hpp new file mode 100644 index 0000000..1b15b63 --- /dev/null +++ b/include/scene/components/Component.hpp @@ -0,0 +1,15 @@ +#ifndef COMPONENT_HPP +#define COMPONENT_HPP + +class Component { + public: + Component() = default; + virtual ~Component() = default; + + Component(const Component&) = default; + Component(Component&&) = default; + Component& operator=(const Component&) = default; + Component& operator=(Component&&) = default; +}; + +#endif // COMPONENT_HPP \ No newline at end of file diff --git a/include/scene/components/ComponentRegistry.hpp b/include/scene/components/ComponentRegistry.hpp new file mode 100644 index 0000000..da89f8a --- /dev/null +++ b/include/scene/components/ComponentRegistry.hpp @@ -0,0 +1,52 @@ +#ifndef COMPONENTREGISTRY_HPP +#define COMPONENTREGISTRY_HPP + +#include +#include +#include + +class Component; +namespace NeuronIDE { +class Component; +} + +using ComponentCreatorFunc = std::function(const NeuronIDE::Component&)>; + +class ComponentRegistry { + public: + ComponentRegistry(const ComponentRegistry&) = delete; + ComponentRegistry& operator=(const ComponentRegistry&) = delete; + + ComponentRegistry(ComponentRegistry&&) = delete; + ComponentRegistry& operator=(ComponentRegistry&&) = delete; + + static ComponentRegistry& instance() { + static ComponentRegistry instance; + return instance; + } + + void registerCreator(int typeId, ComponentCreatorFunc creator); + + std::unique_ptr build(const NeuronIDE::Component& protoComp); + + private: + ComponentRegistry() = default; + + std::unordered_map creators; +}; + +#define COMPONENT_REGISTRATION_CONCAT_IMPL(x, y) x##y +#define COMPONENT_REGISTRATION_CONCAT(x, y) COMPONENT_REGISTRATION_CONCAT_IMPL(x, y) + +#define REGISTER_COMPONENT(typeId, creatorFunc) \ + namespace { \ + struct COMPONENT_REGISTRATION_CONCAT(ComponentRegistrar_, __LINE__) { \ + COMPONENT_REGISTRATION_CONCAT(ComponentRegistrar_, __LINE__)() { \ + ComponentRegistry::instance().registerCreator(static_cast(typeId), creatorFunc); \ + } \ + }; \ + static COMPONENT_REGISTRATION_CONCAT(ComponentRegistrar_, __LINE__) \ + COMPONENT_REGISTRATION_CONCAT(global_registrar_, __LINE__); \ + } + +#endif // COMPONENTREGISTRY_HPP \ No newline at end of file diff --git a/protoFiles/neuronide.proto b/protoFiles/neuronide.proto new file mode 100644 index 0000000..24e15a3 --- /dev/null +++ b/protoFiles/neuronide.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +package NeuronIDE; + +message SpriteRenderer { + string texture_path = 1; + string img_path = 2; +} + +message TextRenderer { + string text = 1; + string font_path = 2; + uint32 font_size = 3; +} + +message BlinkComponent { + double blink_frequency_hz = 1; +} + +message ScriptComponent { + string script_path = 1; +} + +message Transform { + double x = 1; + double y = 2; + double width = 3; + double height = 4; + double rotation = 5; +} + +message Component { + oneof component_type { + SpriteRenderer renderer = 1; + TextRenderer text = 2; + BlinkComponent blinker = 3; + ScriptComponent script = 4; + } +} + +message SceneObject { + string name = 1; + bool is_visible = 2; + Transform transform = 3; + repeated Component components = 4; +} + +message Scene { + string project_name = 1; + repeated SceneObject scene_objects = 2; +} \ No newline at end of file diff --git a/protoFiles/tests/test_scene.pb b/protoFiles/tests/test_scene.pb new file mode 100644 index 0000000..ed6cd4c Binary files /dev/null and b/protoFiles/tests/test_scene.pb differ diff --git a/protoFiles/tests/test_scene.pbtxt b/protoFiles/tests/test_scene.pbtxt new file mode 100644 index 0000000..e2781cf --- /dev/null +++ b/protoFiles/tests/test_scene.pbtxt @@ -0,0 +1,39 @@ +project_name: "Moj Pierwszy Eksperyment" + +scene_objects { + name: "Gracz" + is_visible: true + + transform { + x: 100.0 + y: 200.0 + width: 64.0 + height: 64.0 + rotation: 0.0 + } + + components { + blinker { + blink_frequency_hz: 1.5 + } + } +} + +scene_objects { + name: "Napis Powitalny" + is_visible: true + + transform { + x: 400.0 + y: 50.0 + width: 300.0 + height: 100.0 + rotation: 0.0 + } + + components { + blinker { + blink_frequency_hz: 5 + } + } +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 854e2c8..ced725f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,12 +1,27 @@ -add_library(runtime_core Runtime.cpp) -target_include_directories(runtime_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include) +protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${CMAKE_CURRENT_SOURCE_DIR}/../protoFiles/neuronide.proto) -target_link_libraries(runtime_core PUBLIC +add_library(runtime_core OBJECT + Runtime.cpp + parser/Parser.cpp + scene/components/ComponentRegistry.cpp + scene/components/BlinkComponent.cpp + scene/SceneObject.cpp + ${PROTO_SRCS} + ${PROTO_HDRS} +) + +target_include_directories(runtime_core SYSTEM PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${CMAKE_CURRENT_BINARY_DIR} + ${SDL2_INCLUDE_DIRS} +) + +target_link_libraries(runtime_core PUBLIC lsl concurrentqueue + protobuf::libprotobuf ${SDL2_LIBRARIES} ) -target_include_directories(runtime_core PUBLIC ${SDL2_INCLUDE_DIRS}) add_executable(NeuronIDE main.cpp) target_link_libraries(NeuronIDE PRIVATE runtime_core) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1ddb7e1..1b85885 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include -#include int main(int argc, char* argv[]) { Runtime::start(); diff --git a/src/parser/Parser.cpp b/src/parser/Parser.cpp new file mode 100644 index 0000000..79de0bc --- /dev/null +++ b/src/parser/Parser.cpp @@ -0,0 +1,66 @@ +#include "parser/Parser.hpp" + +#include +#include +#include + +#include "neuronide.pb.h" +#include "scene/Scene.hpp" +#include "scene/SceneObject.hpp" +#include "scene/components/Component.hpp" +#include "scene/components/ComponentRegistry.hpp" + +std::shared_ptr Parser::parse(const std::string& filePath) { + NeuronIDE::Scene protoScene; + + std::ifstream file(filePath, std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Parser: cannot open file: " + filePath); + } + + if (!protoScene.ParseFromIstream(&file)) { + throw std::runtime_error("Parser: failed to parse protobuf from: " + filePath); + } + + auto scene = std::make_shared<::Scene>(); + scene->setExperimentName(protoScene.project_name()); + + for (const auto& protoObj : protoScene.scene_objects()) { + auto obj = buildSceneObject(protoObj); + scene->addObject(std::move(obj)); + } + + return scene; +} + +std::shared_ptr Parser::buildSceneObject(const NeuronIDE::SceneObject& protoObj) { + auto obj = std::make_shared(protoObj.name(), protoObj.is_visible()); + + if (protoObj.has_transform()) { + const auto& tra = protoObj.transform(); + obj->setTransform({tra.x(), tra.y(), tra.width(), tra.height(), tra.rotation()}); + } + + std::unordered_set seenComponentTypes; + + for (const auto& protoComp : protoObj.components()) { + int typeId = static_cast(protoComp.component_type_case()); + + if (seenComponentTypes.find(typeId) != seenComponentTypes.end()) { + throw std::runtime_error("Parser: duplicate component type in object '" + + protoObj.name() + "'."); + } + seenComponentTypes.insert(typeId); + + auto comp = buildComponent(protoComp); + if (comp) { + obj->addComponent(std::move(comp)); + } + } + + return obj; +} + +std::unique_ptr Parser::buildComponent(const NeuronIDE::Component& protoComp) { + return ComponentRegistry::instance().build(protoComp); +} \ No newline at end of file diff --git a/src/scene/SceneObject.cpp b/src/scene/SceneObject.cpp new file mode 100644 index 0000000..06d7dae --- /dev/null +++ b/src/scene/SceneObject.cpp @@ -0,0 +1,15 @@ +#include "scene/SceneObject.hpp" + +#include + +#include "scene/components/Component.hpp" + +SceneObject::SceneObject(std::string n, bool visible) : name(std::move(n)), isVisible(visible) { + std::cout << " [SceneObject] Utworzono obiekt: " << name << "\n"; +} + +void SceneObject::setTransform(Transform t) { transform = t; } + +void SceneObject::addComponent(std::unique_ptr comp) { + components.push_back(std::move(comp)); +} \ No newline at end of file diff --git a/src/scene/components/BlinkComponent.cpp b/src/scene/components/BlinkComponent.cpp new file mode 100644 index 0000000..3830efa --- /dev/null +++ b/src/scene/components/BlinkComponent.cpp @@ -0,0 +1,12 @@ +#include "scene/components/BlinkComponent.hpp" + +#include "neuronide.pb.h" +#include "scene/components/ComponentRegistry.hpp" + +void BlinkComponent::setFrequency(double freq) { blinkFrequencyHz = freq; } + +std::unique_ptr BlinkComponent::createBlinker(const NeuronIDE::Component& protoComp) { + return std::make_unique(protoComp.blinker().blink_frequency_hz()); +} + +REGISTER_COMPONENT(NeuronIDE::Component::kBlinker, BlinkComponent::createBlinker) \ No newline at end of file diff --git a/src/scene/components/ComponentRegistry.cpp b/src/scene/components/ComponentRegistry.cpp new file mode 100644 index 0000000..52faf24 --- /dev/null +++ b/src/scene/components/ComponentRegistry.cpp @@ -0,0 +1,27 @@ +#include "scene/components/ComponentRegistry.hpp" + +#include +#include + +#include "neuronide.pb.h" +#include "scene/components/Component.hpp" + +void ComponentRegistry::registerCreator(int typeId, ComponentCreatorFunc creator) { + if (creators.find(typeId) != creators.end()) { + throw std::runtime_error("Creator for this typeId is already registered."); + } + creators[typeId] = std::move(creator); +} + +std::unique_ptr ComponentRegistry::build(const NeuronIDE::Component& protoComp) { + auto activeCase = protoComp.component_type_case(); + + int typeId = static_cast(activeCase); + + auto it = creators.find(typeId); + if (it != creators.end()) { + return it->second(protoComp); + } + + return nullptr; +} \ No newline at end of file