C++ Conversation
1. Install Dora CLI and Dependencies
Because Dora connects C++ to a high-performance Rust core, you need C++ build tools, the Rust toolchain, and Python (to run the Dora CLI orchestrator).
First, ensure you have the required system tools:
- C++ Compiler & CMake: (e.g.,
sudo apt install build-essential cmakeon Ubuntu) - Rust & Cargo: Install via rustup (
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh) - Python 3.11
Next, install uv to manage the Python environment. (Linux note: If pip install uv throws an externally-managed environment error, install it via their standalone script: curl -LsSf https://astral.sh/uv/install.sh | sh).
pip install uv
Then, create and activate a virtual environment:
uv venv --seed -p 3.11
source .venv/bin/activate # Linux/macOS
.venv\Scripts\activate # Windows
Finally, install the Dora CLI
pip install dora-rs-cli
2. Create a new dataflow
Create a C++ based dataflow and navigate into the project directory:
dora new conversation_cxx --lang cxx
cd conversation_cxx
This creates the following conversation_cxx directory structure. Notice that C++ projects rely heavily on CMakeLists.txt to orchestrate the build process.
├── CMakeLists.txt
├── dataflow.yml
├── listener_1
│ └── node.cc
├── talker_1
│ └── node.cc
└── talker_2
└── node.cc
3. Add another node (optional)
From inside the conversation_cxx project directory, you can add another C++ node to the workspace:
dora new --kind node talker --lang cxx
Now open the talker_1/node.cc file in your text editor.
4. How the default node works
Your C++ node is very bare bones right now. Below is an explanation of how it works by default and how it safely shares memory with the Rust daemon.
Importing and initializing the node
#include "dora-node-api.h"
#include <iostream>
#include <vector>
int main() {
auto dora_node = init_dora_node();
init_dora_node()registers your executable with the Dora Daemon and establishes the shared memory connections.
Handling input events and timers
for (int i = 0; i < 20; i++) {
auto event = dora_node.events->next();
auto ty = event_type(event);
if (ty == DoraEventType::AllInputsClosed) {
break;
} else if (ty == DoraEventType::Input) {
// Logic goes here
}
dora_node.events->next()blocks the loop completely until an event arrives.- In the case of this talker, it does not receive data from another node. Instead, the
dataflow.ymlassigns it a timer (dora/timer/millis/100), acting as an invisible metronome. The Rust daemon sends anInputevent exactly every 100ms to drive the loop forward.
Sending output via zero-copy memory
std::string message{"Hello World!"};
rust::Slice<const uint8_t> message_slice{reinterpret_cast<const uint8_t*>(message.c_str()), message.size()};
auto result = send_output(dora_node.send_output, "speech", message_slice);
- To achieve ultra-low latency, Dora uses zero-copy memory.
- We take our native
std::stringand usereinterpret_castto cast its raw memory pointer into arust::Slice. send_outputhands this slice to the"speech"channel, allowing downstream nodes to read the exact memory address instantly without copying the data.
5. Listener node breakdown
File: listener_1/node.cc
When receiving data, the listener must safely unpack the shared memory pointer back into a native C++ object.
else if (ty == DoraEventType::Input)
{
auto input = event_as_input(std::move(event));
auto input_id = input.id;
auto message = std::string(reinterpret_cast<const char*>(input.data.data()), input.data.size());
std::cout << "I heard " << message << " from " << std::string(input_id) << std::endl;
}
event_as_input(std::move(event))safely transfers ownership of the memory block from the generic event.input.data.data()retrieves the raw pointer to the payload, which wereinterpret_castback into a standard C++std::stringusinginput.data.size().
6. Running the dataflow
Unlike Python scripts, C++ nodes must be explicitly compiled into binaries using CMake before the Dora Daemon can run them. By default, CMake outputs these binaries to a build/ directory.
Edit dataflow.yml to ensure your node paths point to the build/ folder so Dora knows where to find your executables:
nodes:
- id: talker_1
path: build/talker_1
inputs:
tick: dora/timer/millis/100
outputs:
- speech
- id: talker_2
path: build/talker_2
inputs:
tick: dora/timer/secs/2
outputs:
- speech
- id: listener_1
path: build/listener_1
inputs:
speech-1: talker_1/speech
speech-2: talker_2/speech
Build and run the dataflow
First, compile your C++ project:
# Compile the C++ nodes
mkdir build
cd build
cmake ..
make
cd ..
Once compiled, run the dataflow from the root project directory:
# Run the dataflow
dora run dataflow.yml
You should see:
I heard Hello World! from speech
To stop the dataflow, press Ctrl+C.
7. Conclusion
Well done reaching the end of this tutorial.
You’ve compiled and run a Dora dataflow connecting a talker and listener node in C++, and learned how to navigate Rust bindings and zero-copy memory management.
From here, try experimenting with different data types or exploring Dora’s advanced features.