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
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,17 @@ EEG samples and the stimulus markers must share a **common clock domain** so tha
| Language | C++20 |
| Build system | CMake (≥ 3.25), Ninja-friendly |
| Experiment file format | Protocol Buffers (proto3) — see `protoFiles/neuronide.proto` |
| Device config format | JSON (`config.json`) parsed with [nlohmann/json](https://github.com/nlohmann/json) |
| EEG acquisition | [LSL — Lab Streaming Layer](https://github.com/sccn/liblsl) (`liblsl`) |
| Rendering / windowing | SDL2 (+ SDL2_image), with vsync |
| Inter-thread queues | [moodycamel ConcurrentQueue](https://github.com/cameron314/concurrentqueue) (lock-free) |
| Python scripting (planned)| pybind11 (`ScriptComponent`) |
| Testing | GoogleTest + CTest |
| Tooling | clang-format, clang-tidy, gcovr (coverage) |

`liblsl`, `concurrentqueue`, and `googletest` are fetched automatically by CMake
(`FetchContent`). SDL2 and Protobuf are expected to be installed on the system.
`liblsl`, `concurrentqueue`, `nlohmann/json`, and `googletest` are fetched
automatically by CMake (`FetchContent`). SDL2 and Protobuf are expected to be
installed on the system.

## 3. Architecture

Expand Down Expand Up @@ -198,9 +200,10 @@ stream rather than letting an exception terminate the process.
| `ComponentRegistry` | Implemented | proto-type → factory, macro-based self-registration |
| `specifiic components` | **Planned** | defined in `neuronide.proto`, not yet implemented in C++ |
| `Renderer` | Implemented | SDL + vsync, marker timestamping |
| `LSLReader` | Implemented | LSL inlet → `eegQueue`, clock-synced (see §4) |
| `LSLReader` | Implemented | LSL inlet → `eegQueue`, clock-synced (see §4); driven by `LSLConfig` |
| `ConfigParser` | Implemented | `config.json` → `ExperimentConfig` (incl. `LSLConfig`), nlohmann/json |
| `DataWriter` | Implemented | strategy-based; `CSVFormatStrategy` |
| `Runtime` orchestration | **Stub** | `Runtime::start()` currently only prints; wiring of Parser + the three threads is the next integration step |
| `Runtime` orchestration | **Stub** | currently does nothing |

The class diagram in older docs is partly aspirational; the table above reflects
the actual code.
Expand All @@ -217,6 +220,7 @@ Neuron-IDE-runtime/ # the C++ runtime (git repo)
│ └── tests/ # .pbtxt fixtures + compiled .pb
├── include/ # public headers, mirrored by src/
│ ├── data_structures/ # EEGData, Marker, Context
│ ├── config/ # ConfigParser + ExperimentConfig / LSLConfig / ChannelConfig
│ ├── parser/ # Parser
│ ├── scene/ # Scene, SceneObject, components/
│ ├── renderer/ # Renderer
Expand Down Expand Up @@ -249,9 +253,12 @@ sudo apt install cmake clang-format clang-tidy libsdl2-dev protobuf-compiler gco
```bash
cmake -B build
cmake --build build
./build/src/NeuronIDE # run the (currently stub) executable
./build/src/NeuronIDE config.json experiment.neuroz # parses the device config, parses scene, starts LSLReader, DataWriter, Renderer.
```

`NeuronIDE` takes the path to a device `config.json` (defaults to `config.json` in
the working directory).

### Tests

```bash
Expand Down Expand Up @@ -300,10 +307,3 @@ protoc --encode=NeuronIDE.Scene protoFiles/neuronide.proto \
`feat(parser): create Parser class`.
- **Types:** `feat`, `fix`, `style` (clang config), `test`, `ci` (`.github`).

## 10. Roadmap (next steps)

1. Implement `Runtime` orchestration: `Parser → Scene`, then run `Renderer`,
`LSLReader`, and `DataWriter` concurrently and shut them down cleanly.
2. Implement the remaining components: `SpriteRenderer`, `TextRenderer`,
`ScriptComponent` (pybind11), each self-registering with `ComponentRegistry`.
3. Validate `LSLReader` end-to-end against a real EEG headset.
4 changes: 3 additions & 1 deletion cmake/Coverage.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ if(NEURON_IDE_ENABLE_COVERAGE)
--exclude ".*protoFiles.*"
--exclude ".*pb.*"
--exclude ".*\\.hpp"
--fail-under-line 60
--exclude-throw-branches
--exclude-unreachable-branches
--fail-under-line 90
--print-summary
--html-details ${COVERAGE_DIR}/index.html
--xml ${COVERAGE_DIR}/coverage.xml
Expand Down
14 changes: 11 additions & 3 deletions cmake/Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ FetchContent_Declare(
SYSTEM
)

# 4. nlohmann/json (header-only) - device config parsing
FetchContent_Declare(
nlohmann_json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
SYSTEM
)
set(JSON_BuildTests OFF CACHE INTERNAL "")

# Suppress compiler warnings from third-party targets when compiling their source files
set(BACKUP_C_FLAGS "${CMAKE_C_FLAGS}")
set(BACKUP_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
Expand All @@ -38,14 +46,14 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /w")
endif()

FetchContent_MakeAvailable(googletest liblsl concurrentqueue)
FetchContent_MakeAvailable(googletest liblsl concurrentqueue nlohmann_json)

# Restore compiler flags for our own project code
set(CMAKE_C_FLAGS "${BACKUP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${BACKUP_CXX_FLAGS}")

# 4. SDL2 (System installed)
# 5. SDL2 (System installed)
find_package(SDL2 REQUIRED)

# 5. Protobuf (System installed)
# 6. Protobuf (System installed)
find_package(Protobuf REQUIRED)
14 changes: 14 additions & 0 deletions include/config/ChannelConfig.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef CHANNELCONFIG_HPP
#define CHANNELCONFIG_HPP

#include <string>

// Description of a single EEG channel as declared in the device config file.
struct ChannelConfig {
int index = 0;
std::string label;
bool enabled = true;
std::string unit;
};

#endif // CHANNELCONFIG_HPP
16 changes: 16 additions & 0 deletions include/config/ConfigParser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef CONFIGPARSER_HPP
#define CONFIGPARSER_HPP

#include <config/ExperimentConfig.hpp>
#include <istream>
#include <string>

class ConfigParser {
public:
ConfigParser() = default;

static ExperimentConfig parse(const std::string& filePath);
static ExperimentConfig parseStream(std::istream& stream);
};

#endif // CONFIGPARSER_HPP
32 changes: 32 additions & 0 deletions include/config/ExperimentConfig.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef EXPERIMENTCONFIG_HPP
#define EXPERIMENTCONFIG_HPP

#include <config/LSLConfig.hpp>
#include <string>

struct ReferenceConfig {
std::string label;
std::string scheme;
};

struct GroundConfig {
std::string label;
};

struct ImpedanceConfig {
bool supported = false;
double thresholdKohm = 0.0;
};

struct ExperimentConfig {
std::string configVersion;
std::string deviceName;
std::string montageStandard;
LSLConfig lsl;
ReferenceConfig reference;
GroundConfig ground;
ImpedanceConfig impedance;
// TODO: DataWriterConfig writer; // EEG output file format strategy
};

#endif // EXPERIMENTCONFIG_HPP
17 changes: 17 additions & 0 deletions include/config/LSLConfig.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef LSLCONFIG_HPP
#define LSLCONFIG_HPP

#include <config/ChannelConfig.hpp>
#include <string>
#include <vector>

struct LSLConfig {
std::string name; // lsl_stream.name
std::string type; // lsl_stream.type
std::string sourceId; // lsl_stream.source_id
int expectedChannelCount = 0;
double expectedSampleRateHz = 0.0;
std::vector<ChannelConfig> channels;
};

#endif // LSLCONFIG_HPP
33 changes: 33 additions & 0 deletions include/lslreader/LSLReader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef LSLREADER_HPP
#define LSLREADER_HPP

#include <concurrentqueue.h>

#include <config/LSLConfig.hpp>
#include <memory>
#include <thread>

struct EEGData;

class LSLReader {
public:
explicit LSLReader(LSLConfig config);
~LSLReader();

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

void start(std::shared_ptr<moodycamel::ConcurrentQueue<EEGData>> eegQueue);
void stop();

private:
void readLoop(const std::stop_token& stopToken);

LSLConfig config;
std::shared_ptr<moodycamel::ConcurrentQueue<EEGData>> eegQueue;
std::jthread readerThread;
};

#endif // LSLREADER_HPP
12 changes: 8 additions & 4 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ target_link_libraries(neuronide_proto PUBLIC protobuf::libprotobuf)

add_subdirectory(scene)
add_subdirectory(parser)
add_subdirectory(config)
add_subdirectory(renderer)
add_subdirectory(lslreader)
add_subdirectory(datawriter)

add_library(runtime_core STATIC
Runtime.cpp
)
target_include_directories(runtime_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include)
target_link_libraries(runtime_core PUBLIC
scene
parser
renderer
target_link_libraries(runtime_core PUBLIC
scene
parser
config
renderer
lslreader
datawriter
)

Expand Down
9 changes: 9 additions & 0 deletions src/config/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
add_library(config OBJECT
ConfigParser.cpp
)

target_include_directories(config PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../../include/config
)

target_link_libraries(config PRIVATE nlohmann_json::nlohmann_json)
Loading
Loading