Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Checks: 'bugprone-*,cppcoreguidelines-*,performance-*,readability-*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-bounds-array-to-pointer-decay'
WarningsAsErrors: ''
WarningsAsErrors: '*'
HeaderFilterRegex: '.*'
ExcludeHeaderFilterRegex: '(_deps|\.pb\.h|\.pb\.cc|/usr/)'
FormatStyle: 'file'
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

## Setup and Build Instructions

This project uses CMake and requires a modern C++17 compiler.
This project uses CMake (minimum version 3.25) and requires a modern C++20 compiler.

### 1. Clone repo and install required dependencies
This project uses GTest, LSL, SDL2, Clang-format, Clang-tidy, protobuf-compiler and cmake, but you don't need to install GTest and LSL because cmake will install it for you. Below is minimal linux setup.
This project uses GTest, LSL, SDL2, Clang-format, Clang-tidy-20, protobuf-compiler, gcovr and cmake, but you don't need to install GTest and LSL because cmake will install it for you. Below is minimal linux setup.

```bash
git clone git@github.com:KN-Neuron/Neuron-IDE-runtime.git

sudo apt update
sudo apt install cmake clang-format clang-tidy libsdl2-dev protobuf-compiler
sudo apt install cmake clang-format clang-tidy-20 libsdl2-dev protobuf-compiler gcovr
```

### 2. Build the Project
Expand Down Expand Up @@ -66,4 +66,34 @@ In case you want to create an example .pb file for testing or some other purpose

```bash
protoc --encode=NeuronIDE.Scene protoFiles/neuronide.proto < protoFiles/tests/test_scene.pbtxt > protoFiles/tests/test_scene.pb
```

### 6. Code Coverage

This project supports generating code coverage reports using `gcovr` to ensure that our tests thoroughly exercise the codebase.

**To build with coverage enabled:**
You need to pass the `NEURON_IDE_ENABLE_COVERAGE` flag during the CMake configuration step.

```bash
# Generate the build system with coverage instrumentation enabled
cmake -B build -DNEURON_IDE_ENABLE_COVERAGE=ON

# Compile the project
cmake --build build
```

**To run coverage and generate reports:**
Once built with the coverage flag, you can run the `coverage` target. This will automatically execute the test suite and then invoke `gcovr` to generate both a terminal summary and detailed HTML reports.

```bash
cmake --build build --target coverage
```

**To check the coverage reports:**
After running the coverage target, the generated HTML files will be located in the `build/coverage` directory. You can open `index.html` in your favorite web browser to explore detailed line-by-line coverage for each file.

```bash
# Open the main coverage report in your default web browser
xdg-open build/coverage/index.html
```
5 changes: 5 additions & 0 deletions include/Runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ class Runtime {
public:
Runtime() = default;
~Runtime() = default;

Runtime(const Runtime&) = delete;
Runtime& operator=(const Runtime&) = delete;
Runtime(Runtime&&) = delete;
Runtime& operator=(Runtime&&) = delete;
static void start();
};

Expand Down
2 changes: 1 addition & 1 deletion include/data_structures/Context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

struct Context {
double timestamp = 0.0;
std::vector<std::string>& markers;
std::vector<std::string>* markers = nullptr;
};

#endif // CONTEXT_HPP
5 changes: 5 additions & 0 deletions include/datawriter/CSVFormatStrategy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ class CSVFormatStrategy : public IDataFormatStrategy {
CSVFormatStrategy() = default;
~CSVFormatStrategy() override;

CSVFormatStrategy(const CSVFormatStrategy&) = delete;
CSVFormatStrategy& operator=(const CSVFormatStrategy&) = delete;
CSVFormatStrategy(CSVFormatStrategy&&) = delete;
CSVFormatStrategy& operator=(CSVFormatStrategy&&) = delete;

void open(const std::string& filepath) override;
void close() override;

Expand Down
4 changes: 2 additions & 2 deletions include/datawriter/DataWriter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

#include <datawriter/IDataFormatStrategy.hpp>

class EEGData;
class Marker;
struct EEGData;
struct Marker;

#include <memory>
#include <string>
Expand Down
2 changes: 1 addition & 1 deletion include/datawriter/IDataFormatStrategy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include <string>

class EEGData;
struct EEGData;
struct Marker;

class IDataFormatStrategy {
Expand Down
2 changes: 1 addition & 1 deletion include/parser/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Parser {
public:
Parser() = default;

std::shared_ptr<Scene> parse(const std::string& filePath);
static std::shared_ptr<Scene> parse(const std::string& filePath);

private:
static std::shared_ptr<SceneObject> buildSceneObject(const NeuronIDE::SceneObject& protoObj);
Expand Down
5 changes: 5 additions & 0 deletions include/renderer/Renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ class Renderer {
std::shared_ptr<moodycamel::ConcurrentQueue<Marker>> markerQueue);
~Renderer() = default;

Renderer(const Renderer&) = delete;
Renderer& operator=(const Renderer&) = delete;
Renderer(Renderer&&) = delete;
Renderer& operator=(Renderer&&) = delete;

void render(const std::stop_token& stoken);

private:
Expand Down
2 changes: 1 addition & 1 deletion include/scene/components/BlinkComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Component;

class BlinkComponent : public Component {
public:
BlinkComponent(std::shared_ptr<SceneObject> owner, double freq)
BlinkComponent(const std::shared_ptr<SceneObject>& owner, double freq)
: Component(owner), blinkFrequencyHz(freq) {}
void setFrequency(double freq);

Expand Down
3 changes: 2 additions & 1 deletion include/scene/components/Component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ struct Context;
class Component {
public:
Component() = delete;
Component(std::shared_ptr<SceneObject> owner) : owner(owner) {}
Component(const std::shared_ptr<SceneObject>& owner) : owner(owner) {}
virtual ~Component() = default;

Component(const Component&) = delete;
Expand All @@ -22,6 +22,7 @@ class Component {
virtual void render(SDL_Renderer* renderer) = 0;

protected:
// NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)
std::weak_ptr<SceneObject> owner;
};

Expand Down
5 changes: 4 additions & 1 deletion include/scene/components/ComponentRegistry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ComponentRegistry {

ComponentRegistry(ComponentRegistry&&) = delete;
ComponentRegistry& operator=(ComponentRegistry&&) = delete;
~ComponentRegistry() = default;

static ComponentRegistry& instance() {
static ComponentRegistry instance;
Expand All @@ -39,16 +40,18 @@ class ComponentRegistry {
};

#define COMPONENT_REGISTRATION_CONCAT_IMPL(x, y) x##y
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define COMPONENT_REGISTRATION_CONCAT(x, y) COMPONENT_REGISTRATION_CONCAT_IMPL(x, y)

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define REGISTER_COMPONENT(typeId, creatorFunc) \
namespace { \
struct COMPONENT_REGISTRATION_CONCAT(ComponentRegistrar_, __LINE__) { \
COMPONENT_REGISTRATION_CONCAT(ComponentRegistrar_, __LINE__)() { \
ComponentRegistry::instance().registerCreator(static_cast<int>(typeId), creatorFunc); \
} \
}; \
static COMPONENT_REGISTRATION_CONCAT(ComponentRegistrar_, __LINE__) \
static const COMPONENT_REGISTRATION_CONCAT(ComponentRegistrar_, __LINE__) \
COMPONENT_REGISTRATION_CONCAT(global_registrar_, __LINE__); \
}

Expand Down
2 changes: 1 addition & 1 deletion src/datawriter/CSVFormatStrategy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <datawriter/CSVFormatStrategy.hpp>
#include <stdexcept>

CSVFormatStrategy::~CSVFormatStrategy() { close(); }
CSVFormatStrategy::~CSVFormatStrategy() { CSVFormatStrategy::close(); }

void CSVFormatStrategy::open(const std::string& filepath) {
outputFile.open(filepath, std::ios::out | std::ios::trunc);
Expand Down
2 changes: 1 addition & 1 deletion src/datawriter/DataWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
constexpr auto kWriteLoopSleep = std::chrono::milliseconds(10);

template <typename QueueT, typename ItemT, typename WriteFn>
bool drainQueue(const std::shared_ptr<QueueT>& queue, WriteFn&& writeFn) {
bool drainQueue(const std::shared_ptr<QueueT>& queue, WriteFn writeFn) {
bool wroteData = false;

if (queue) {
Expand Down
2 changes: 2 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <Runtime.hpp>

int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
Runtime::start();
return 0;
}
2 changes: 1 addition & 1 deletion src/parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ std::shared_ptr<SceneObject> Parser::buildSceneObject(const NeuronIDE::SceneObje
for (const auto& protoComp : protoObj.components()) {
int typeId = static_cast<int>(protoComp.component_type_case());

if (seenComponentTypes.find(typeId) != seenComponentTypes.end()) {
if (seenComponentTypes.contains(typeId)) {
throw std::runtime_error("Parser: duplicate component type in object '" +
protoObj.name() + "'.");
}
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "scene/Scene.hpp"

void Renderer::SDLWindowDeleter::operator()(SDL_Window* window) const {
if (window) {
if (window != nullptr) {
SDL_DestroyWindow(window);
}
}
Expand All @@ -28,7 +28,7 @@ void Renderer::render(const std::stop_token& stoken) {
lastTime = currentTime;

currentFrameMarkers.clear();
Context ctx{deltaTime, currentFrameMarkers};
Context ctx{deltaTime, &currentFrameMarkers};

SDL_Event event;
while (SDL_PollEvent(&event) == 1) {
Expand Down
2 changes: 2 additions & 0 deletions src/scene/components/BlinkComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ std::unique_ptr<Component> BlinkComponent::createBlinker(
}

void BlinkComponent::update(const Context& context) {
(void)context;
// TODO: implement blinking logic based on blinkFrequencyHz and context.timestamp
}

void BlinkComponent::render(SDL_Renderer* renderer) {
(void)renderer;
// This component does not render anything itself, it only controls visibility of the owner
// object.
}
Expand Down
8 changes: 4 additions & 4 deletions src/scene/components/ComponentRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "scene/components/Component.hpp"

void ComponentRegistry::registerCreator(int typeId, ComponentCreatorFunc creator) {
if (creators.find(typeId) != creators.end()) {
if (creators.contains(typeId)) {
throw std::runtime_error("Creator for this typeId is already registered.");
}
creators[typeId] = std::move(creator);
Expand All @@ -19,9 +19,9 @@ std::unique_ptr<Component> ComponentRegistry::build(const NeuronIDE::Component&

int typeId = static_cast<int>(activeCase);

auto it = creators.find(typeId);
if (it != creators.end()) {
return it->second(protoComp, owner);
auto iter = creators.find(typeId);
if (iter != creators.end()) {
return iter->second(protoComp, owner);
}

return nullptr;
Expand Down
61 changes: 33 additions & 28 deletions tests/unit_tests/RendererTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ constexpr uint32_t kDummySurfaceFlags = 0;

class CustomComponent : public Component {
public:
CustomComponent(std::shared_ptr<SceneObject> owner, std::shared_ptr<std::atomic<int>> updates,
std::shared_ptr<std::atomic<int>> renders,
std::shared_ptr<std::stop_source> stopSource)
: Component(std::move(owner)),
CustomComponent(const std::shared_ptr<SceneObject>& owner,
std::shared_ptr<std::atomic<int>> updates,
std::shared_ptr<std::atomic<int>> renders,
std::shared_ptr<std::stop_source> stopSource)
: Component(owner),
updates(std::move(updates)),
renders(std::move(renders)),
stopSource(std::move(stopSource)) {}
Expand All @@ -47,12 +48,14 @@ class CustomComponent : public Component {

class MarkerComponent : public Component {
public:
MarkerComponent(std::shared_ptr<SceneObject> owner,
std::shared_ptr<std::stop_source> stopSource)
: Component(std::move(owner)), stopSource(std::move(stopSource)) {}
MarkerComponent(const std::shared_ptr<SceneObject>& owner,
std::shared_ptr<std::stop_source> stopSource)
: Component(owner), stopSource(std::move(stopSource)) {}

void update(const Context& context) override {
context.markers.push_back("test_marker");
if (context.markers != nullptr) {
context.markers->push_back("test_marker");
}
stopSource->request_stop();
}

Expand Down Expand Up @@ -81,14 +84,15 @@ TEST(RendererTest, RenderLoop_WhenComponentAdded_CallsUpdateExactlyOnceBeforeSto
SDL_CreateRGBSurfaceWithFormat(kDummySurfaceFlags, kDummySurfaceWidth, kDummySurfaceHeight,
kDummySurfaceDepth, SDL_PIXELFORMAT_RGBA32);
SDL_Renderer* sdlRenderer = SDL_CreateSoftwareRenderer(surface);
auto sharedRenderer = std::shared_ptr<SDL_Renderer>(sdlRenderer, [surface](SDL_Renderer* r) {
if (r) {
SDL_DestroyRenderer(r);
}
if (surface) {
SDL_FreeSurface(surface);
}
});
auto sharedRenderer =
std::shared_ptr<SDL_Renderer>(sdlRenderer, [surface](SDL_Renderer* renderer) {
if (renderer) {
SDL_DestroyRenderer(renderer);
}
if (surface) {
SDL_FreeSurface(surface);
}
});

auto markerQueue = std::make_shared<moodycamel::ConcurrentQueue<Marker>>();

Expand All @@ -115,26 +119,27 @@ TEST(RendererTest, RenderLoop_QueuesMarkersFromComponents) {
SDL_CreateRGBSurfaceWithFormat(kDummySurfaceFlags, kDummySurfaceWidth, kDummySurfaceHeight,
kDummySurfaceDepth, SDL_PIXELFORMAT_RGBA32);
SDL_Renderer* sdlRenderer = SDL_CreateSoftwareRenderer(surface);
auto sharedRenderer = std::shared_ptr<SDL_Renderer>(sdlRenderer, [surface](SDL_Renderer* r) {
if (r) {
SDL_DestroyRenderer(r);
}
if (surface) {
SDL_FreeSurface(surface);
}
});
auto sharedRenderer =
std::shared_ptr<SDL_Renderer>(sdlRenderer, [surface](SDL_Renderer* renderer) {
if (renderer) {
SDL_DestroyRenderer(renderer);
}
if (surface) {
SDL_FreeSurface(surface);
}
});

auto markerQueue = std::make_shared<moodycamel::ConcurrentQueue<Marker>>();

Renderer renderer(scene, sharedRenderer, markerQueue);
renderer.render(stop_source->get_token());

Marker m;
bool dequeued = markerQueue->try_dequeue(m);
Marker marker;
bool dequeued = markerQueue->try_dequeue(marker);
EXPECT_TRUE(dequeued);
if (dequeued) {
EXPECT_EQ(m.eventName, "test_marker");
EXPECT_FALSE(markerQueue->try_dequeue(m));
EXPECT_EQ(marker.eventName, "test_marker");
EXPECT_FALSE(markerQueue->try_dequeue(marker));
}

SDL_Quit();
Expand Down
Loading