Skip to content

Commit 0bf6dc0

Browse files
committed
Create Shapes demo
1 parent a571b49 commit 0bf6dc0

File tree

10 files changed

+476
-2
lines changed

10 files changed

+476
-2
lines changed

docs/DETAILS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,7 @@ Three System Architecture build projects exist:
21592159
* [system-architecture-no-deps](../example/sample-projects/system-architecture-no-deps/) - builds on Windows or Linux. No external libraries required.
21602160
* [databus](../example/sample-projects/databus/) - builds on Windows or Linux. Abstracted using the Data Bus. No external libraries required.
21612161
* [databus-multicast](../example/sample-projects/databus-multicast/) - builds on Windows or Linux. Demonstrates one-to-many distribution using the Data Bus and Multicast transport. No external libraries required.
2162+
* [databus-shapes](../example/sample-projects/databus-shapes/) - builds on Windows or Linux. Graphical TUI demonstration of DataBus features including Multicast and location transparency.
21622163
* [system-architecture-python](../example/sample-projects/system-architecture-python/) - Python client communicates with C++ server using MessagePack and ZeroMQ external libraries.
21632164
21642165
Follow the steps below to execute the first two projects. See `README.txt` within [system-architecture-python](../example/sample-projects/system-architecture-python/) for the Python example.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# DataBus Shapes Demo Example
2+
3+
The **Shapes Demo** is a graphical demonstration of the **DelegateMQ DataBus** capabilities. It provides a visual proof-of-concept for high-performance messaging, location transparency, and multicast distribution entirely within a modern Terminal User Interface (TUI).
4+
5+
<img src="ShapesDemo.png" alt="DelegateMQ Shapes Demo" style="max-width: 800px; width: 100%;">
6+
7+
## Key Features
8+
9+
- **Location Transparency**: The client renders shape movements calculated in a completely separate server process.
10+
- **UDP Multicast**: A single publisher (Server) can drive any number of graphical clients simultaneously with zero extra network overhead per client.
11+
- **Asynchronous Rendering**: Demonstrates thread-safe data transfer between a background network thread and the main UI rendering thread.
12+
- **TUI Graphics**: Uses the [FTXUI](https://github.com/ArthurSonzogni/FTXUI) library to draw high-resolution shapes inside a standard terminal.
13+
14+
## How to Run
15+
16+
1. **Configure and Build**:
17+
Use the `03_generate_samples.py` script in the root directory, or run CMake manually in both `client` and `server` folders:
18+
```bash
19+
cmake -B build .
20+
cmake --build build --config Release
21+
```
22+
23+
2. **Start the Server**:
24+
The server calculates shape trajectories and broadcasts them to the multicast group.
25+
```bash
26+
./server/build/Release/delegate_databus_shapes_server.exe
27+
```
28+
29+
3. **Start Multiple Clients**:
30+
You can run multiple instances of the client simultaneously. Because the system uses **UDP Multicast**, all clients will show the shapes moving in perfect synchronization.
31+
```bash
32+
./client/build/Release/delegate_databus_shapes_client.exe
33+
```
34+
35+
## DataBus Spy Integration
36+
37+
This project is fully integrated with the **DelegateMQ Spy Console**.
38+
39+
### Enabling the Spy Bridge
40+
The server-side bridge is enabled by default in the `CMakeLists.txt` via the `DMQ_DATABUS_SPY` option. To monitor the raw coordinate data flowing through the bus:
41+
42+
1. Start the **Spy Console** from the `DelegateMQ-Spy` project:
43+
```bash
44+
./dmq-spy 9999 --multicast 239.1.1.1
45+
```
46+
2. Run the Shapes Demo server.
47+
3. Observe the `Shape/Square` and `Shape/Circle` coordinate updates in real-time on the spy dashboard.
48+
49+
## Technical Details
50+
51+
- **Multicast Group**: `239.1.1.1:8000`
52+
- **TUI Refresh Rate**: 20 FPS (Optimized for terminal performance)
53+
- **Automatic Interface Detection**: The demo automatically identifies and binds to the primary physical network adapter, ensuring reliable multicast routing on Windows and Linux.
31.8 KB
Loading
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Specify the minimum CMake version required
2+
cmake_minimum_required(VERSION 3.16)
3+
4+
set(DELEGATE_APP "delegate_databus_shapes_client")
5+
project(DelegateDataBusShapesClient VERSION 1.0 LANGUAGES CXX)
6+
7+
# Set build options
8+
set(DMQ_ASSERTS "ON")
9+
set(DMQ_ALLOCATOR "OFF")
10+
set(DMQ_LOG "OFF")
11+
set(DMQ_UTIL "ON")
12+
set(DMQ_THREAD "DMQ_THREAD_STDLIB")
13+
set(DMQ_SERIALIZE "DMQ_SERIALIZE_SERIALIZE")
14+
set(DMQ_TRANSPORT "DMQ_TRANSPORT_WIN32_UDP")
15+
set(DMQ_DATABUS "ON")
16+
option(DMQ_DATABUS_SPY "Enable DataBus Spy support" OFF)
17+
18+
include("${CMAKE_SOURCE_DIR}/../../../../src/delegate-mq/DelegateMQ.cmake")
19+
20+
# FTXUI & spdlog integration (from peer workspace folders)
21+
set(WORKSPACE_DIR "${CMAKE_SOURCE_DIR}/../../../../..")
22+
set(FTXUI_DIR "${WORKSPACE_DIR}/ftxui")
23+
set(SPDLOG_DIR "${WORKSPACE_DIR}/spdlog")
24+
25+
if(EXISTS "${FTXUI_DIR}/CMakeLists.txt")
26+
add_subdirectory("${FTXUI_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/ftxui" EXCLUDE_FROM_ALL)
27+
endif()
28+
if(EXISTS "${SPDLOG_DIR}/CMakeLists.txt")
29+
add_subdirectory("${SPDLOG_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/spdlog" EXCLUDE_FROM_ALL)
30+
endif()
31+
32+
if (MSVC)
33+
add_compile_options("/utf-8")
34+
add_compile_definitions(_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING)
35+
endif()
36+
37+
# Collect all .cpp and *.h source files in this directory
38+
file(GLOB SOURCES "*.cpp" "*.h")
39+
40+
# Collect Common files to show them in VS Solution Explorer
41+
file(GLOB COMMON_SOURCES "${CMAKE_SOURCE_DIR}/../common/*.h" "${CMAKE_SOURCE_DIR}/../common/*.cpp")
42+
set_source_files_properties(${COMMON_SOURCES} PROPERTIES HEADER_FILE_ONLY TRUE)
43+
44+
# Collect DelegateMQ predef source files
45+
list(APPEND SOURCES ${DMQ_PREDEF_SOURCES})
46+
47+
# Organize source files within IDE (Visual Studio)
48+
source_group("Delegate Files" FILES ${DMQ_LIB_SOURCES})
49+
source_group("Predef Files" FILES ${DMQ_PREDEF_SOURCES})
50+
source_group("Common Files" FILES ${COMMON_SOURCES})
51+
52+
# Add subdirectories to include path
53+
include_directories(
54+
${CMAKE_SOURCE_DIR}/../common
55+
${DMQ_INCLUDE_DIR}
56+
"${SPDLOG_DIR}/include"
57+
)
58+
59+
# Add an executable target
60+
add_executable(${DELEGATE_APP} ${SOURCES} ${DMQ_LIB_SOURCES} ${COMMON_SOURCES})
61+
62+
target_link_libraries(${DELEGATE_APP} PRIVATE
63+
ftxui::screen
64+
ftxui::dom
65+
ftxui::component
66+
spdlog::spdlog
67+
)
68+
69+
# Link libraries based on platform
70+
find_package(Threads REQUIRED)
71+
if(WIN32)
72+
target_link_libraries(${DELEGATE_APP} PRIVATE ws2_32 Threads::Threads)
73+
else()
74+
target_link_libraries(${DELEGATE_APP} PRIVATE Threads::Threads)
75+
endif()
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// main.cpp
2+
// DataBus Shapes Demo Client (Subscriber).
3+
//
4+
// Receives shape positions via Multicast and renders them in a TUI using FTXUI.
5+
6+
#include <ftxui/dom/elements.hpp>
7+
#include <ftxui/screen/screen.hpp>
8+
#include <ftxui/component/component.hpp>
9+
#include <ftxui/component/screen_interactive.hpp>
10+
#include <ftxui/dom/canvas.hpp>
11+
12+
#include "DelegateMQ.h"
13+
#include "SystemMessages.h"
14+
#include "SystemIds.h"
15+
#include <iostream>
16+
#include <vector>
17+
#include <thread>
18+
#include <mutex>
19+
#include <map>
20+
#include <atomic>
21+
22+
#if defined(_WIN32) || defined(_WIN64)
23+
#include "predef/transport/win32-udp/MulticastTransport.h"
24+
#include "predef/util/WinsockConnect.h"
25+
#else
26+
#include "predef/transport/linux-udp/MulticastTransport.h"
27+
#endif
28+
29+
using namespace ftxui;
30+
31+
// State shared between DataBus callbacks and UI thread
32+
struct GlobalState {
33+
std::mutex mutex;
34+
std::map<std::string, ShapeMsg> shapes;
35+
} g_state;
36+
37+
int main() {
38+
#ifdef _WIN32
39+
WinsockContext winsock;
40+
std::string localIP = WinsockContext::GetLocalAddress();
41+
#else
42+
std::string localIP = "0.0.0.0";
43+
#endif
44+
45+
// 1. Initialize Multicast Transport (Group: 239.1.1.1, Port: 8000)
46+
MulticastTransport transport;
47+
if (transport.Create(MulticastTransport::Type::SUB, "239.1.1.1", 8000, localIP.c_str()) != 0) {
48+
std::cerr << "Failed to create Multicast transport" << std::endl;
49+
return -1;
50+
}
51+
52+
// 2. Setup Participant and Handlers
53+
auto group = std::make_shared<dmq::Participant>(transport);
54+
static Serializer<void(ShapeMsg)> serializer;
55+
56+
auto shapeHandler = [](const std::string& topic, ShapeMsg msg) {
57+
std::lock_guard<std::mutex> lock(g_state.mutex);
58+
g_state.shapes[topic] = msg;
59+
};
60+
61+
group->RegisterHandler<ShapeMsg>(SystemTopic::SquareId, serializer, [&](ShapeMsg m) { shapeHandler(SystemTopic::Square, m); });
62+
group->RegisterHandler<ShapeMsg>(SystemTopic::CircleId, serializer, [&](ShapeMsg m) { shapeHandler(SystemTopic::Circle, m); });
63+
group->RegisterHandler<ShapeMsg>(SystemTopic::TriangleId, serializer, [&](ShapeMsg m) { shapeHandler(SystemTopic::Triangle, m); });
64+
65+
// 3. Network processing thread
66+
std::atomic<bool> running{true};
67+
std::thread netThread([&]() {
68+
while (running) {
69+
group->ProcessIncoming();
70+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
71+
}
72+
});
73+
74+
// 4. FTXUI Render Loop
75+
auto screen = ScreenInteractive::Fullscreen();
76+
77+
auto renderer = Renderer([&] {
78+
// Create a canvas that fills the available space
79+
auto c = Canvas(200, 100);
80+
81+
{
82+
std::lock_guard<std::mutex> lock(g_state.mutex);
83+
for (auto const& [topic, shape] : g_state.shapes) {
84+
if (topic == SystemTopic::Square) {
85+
// Draw Square (using block coordinates)
86+
for(int x=0; x<10; ++x)
87+
for(int y=0; y<10; ++y) c.DrawBlock(shape.x * 2 + x, shape.y * 2 + y, true, Color::Blue);
88+
} else if (topic == SystemTopic::Circle) {
89+
// Draw Circle
90+
c.DrawBlockCircle(shape.x * 2, shape.y * 2, 10, Color::Red);
91+
}
92+
}
93+
}
94+
95+
return vbox({
96+
text("DelegateMQ DataBus - Multicast Shapes Demo") | bold | color(Color::Green) | center,
97+
text("Interface: " + localIP + " | Group: 239.1.1.1:8000") | dim | center,
98+
separator(),
99+
canvas(std::move(c)) | flex | border,
100+
hbox({
101+
filler(),
102+
text("Newest data received via DataBus Multicast | 'q' to quit") | dim,
103+
filler()
104+
})
105+
});
106+
});
107+
108+
// Handle 'q' to quit and sink all other events to prevent character artifacts
109+
auto component = CatchEvent(renderer, [&](Event event) {
110+
if (event == Event::Character('q')) {
111+
screen.Exit();
112+
return true;
113+
}
114+
return true; // Sink all events
115+
});
116+
117+
// Refresh UI at 20fps to reduce flickering
118+
std::thread refresher([&] {
119+
while (running) {
120+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
121+
screen.PostEvent(Event::Custom);
122+
}
123+
});
124+
125+
screen.Loop(component);
126+
127+
running = false;
128+
netThread.join();
129+
refresher.join();
130+
transport.Close();
131+
132+
std::cout << "\r" << std::flush;
133+
134+
return 0;
135+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef SYSTEM_IDS_H
2+
#define SYSTEM_IDS_H
3+
4+
#include "DelegateMQ.h"
5+
6+
namespace SystemTopic {
7+
// Topic names for different shapes
8+
inline constexpr const char* const Square = "Shape/Square";
9+
inline constexpr const char* const Circle = "Shape/Circle";
10+
inline constexpr const char* const Triangle = "Shape/Triangle";
11+
12+
// Remote IDs for Multicast distribution
13+
enum RemoteId : dmq::DelegateRemoteId {
14+
SquareId = 200,
15+
CircleId = 201,
16+
TriangleId = 202
17+
};
18+
}
19+
20+
#endif // SYSTEM_IDS_H
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#ifndef SYSTEM_MESSAGES_H
2+
#define SYSTEM_MESSAGES_H
3+
4+
#include "predef/serialize/serialize/msg_serialize.h"
5+
#include <string>
6+
7+
// Message representing a shape's position and color
8+
struct ShapeMsg : public serialize::I {
9+
int x = 0;
10+
int y = 0;
11+
int size = 10;
12+
int color = 0; // 0: Blue, 1: Red, 2: Green
13+
14+
virtual std::ostream& write(::serialize& ms, std::ostream& os) override {
15+
ms.write(os, x);
16+
ms.write(os, y);
17+
ms.write(os, size);
18+
return ms.write(os, color);
19+
}
20+
virtual std::istream& read(::serialize& ms, std::istream& is) override {
21+
ms.read(is, x);
22+
ms.read(is, y);
23+
ms.read(is, size);
24+
return ms.read(is, color);
25+
}
26+
};
27+
28+
#endif // SYSTEM_MESSAGES_H
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Specify the minimum CMake version required
2+
cmake_minimum_required(VERSION 3.16)
3+
4+
set(DELEGATE_APP "delegate_databus_shapes_server")
5+
project(DelegateDataBusShapesServer VERSION 1.0 LANGUAGES CXX)
6+
7+
# Set build options
8+
set(DMQ_ASSERTS "ON")
9+
set(DMQ_ALLOCATOR "OFF")
10+
set(DMQ_LOG "OFF")
11+
set(DMQ_UTIL "ON")
12+
set(DMQ_THREAD "DMQ_THREAD_STDLIB")
13+
set(DMQ_SERIALIZE "DMQ_SERIALIZE_SERIALIZE")
14+
set(DMQ_TRANSPORT "DMQ_TRANSPORT_WIN32_UDP")
15+
set(DMQ_DATABUS "ON")
16+
option(DMQ_DATABUS_SPY "Enable DataBus Spy support" ON)
17+
18+
include("${CMAKE_SOURCE_DIR}/../../../../src/delegate-mq/DelegateMQ.cmake")
19+
20+
if (MSVC)
21+
add_compile_options("/utf-8")
22+
add_compile_definitions(_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING)
23+
endif()
24+
25+
# Collect all .cpp and *.h source files in this directory
26+
file(GLOB SOURCES "*.cpp" "*.h")
27+
28+
# Collect Common files to show them in VS Solution Explorer
29+
file(GLOB COMMON_SOURCES "${CMAKE_SOURCE_DIR}/../common/*.h" "${CMAKE_SOURCE_DIR}/../common/*.cpp")
30+
set_source_files_properties(${COMMON_SOURCES} PROPERTIES HEADER_FILE_ONLY TRUE)
31+
32+
if (DMQ_DATABUS_SPY)
33+
add_compile_definitions(DMQ_DATABUS_SPY)
34+
set(SPY_BRIDGE_DIR "${CMAKE_SOURCE_DIR}/../../../../../DelegateMQ-Spy")
35+
set(SPY_BRIDGE_SOURCES
36+
"${SPY_BRIDGE_DIR}/bridge/SpyBridge.cpp"
37+
"${SPY_BRIDGE_DIR}/bridge/SpyBridge.h"
38+
)
39+
include_directories(
40+
"${CMAKE_SOURCE_DIR}/../../../../../bitsery/include"
41+
"${SPY_BRIDGE_DIR}/src"
42+
"${SPY_BRIDGE_DIR}/bridge"
43+
)
44+
list(APPEND SOURCES ${SPY_BRIDGE_SOURCES})
45+
endif()
46+
47+
# Collect DelegateMQ predef source files
48+
list(APPEND SOURCES ${DMQ_PREDEF_SOURCES})
49+
50+
# Organize source files within IDE (Visual Studio)
51+
source_group("Delegate Files" FILES ${DMQ_LIB_SOURCES})
52+
source_group("Predef Files" FILES ${DMQ_PREDEF_SOURCES})
53+
source_group("Common Files" FILES ${COMMON_SOURCES})
54+
55+
# Add subdirectories to include path
56+
include_directories(
57+
${CMAKE_SOURCE_DIR}/../common
58+
${DMQ_INCLUDE_DIR}
59+
)
60+
61+
# Add an executable target
62+
add_executable(${DELEGATE_APP} ${SOURCES} ${DMQ_LIB_SOURCES} ${COMMON_SOURCES})
63+
64+
# Link libraries based on platform
65+
find_package(Threads REQUIRED)
66+
if(WIN32)
67+
target_link_libraries(${DELEGATE_APP} PRIVATE ws2_32 Threads::Threads)
68+
else()
69+
target_link_libraries(${DELEGATE_APP} PRIVATE Threads::Threads)
70+
endif()

0 commit comments

Comments
 (0)