Skip to content

Commit 14fcc97

Browse files
authored
Extend doc with extended example (#491)
1 parent 84e2b24 commit 14fcc97

4 files changed

Lines changed: 117 additions & 1 deletion

File tree

Readme.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Try out rpp on [godbolt.org](https://godbolt.org/z/48fh1hcbn)!
2121

2222
## Documentation:
2323

24-
Refer to [User Guide](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/md_docs_2readme.html) for better understanding of concepts of Reactive Programming and [API Reference of RPP](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/group__rpp.html) itself.
24+
Check [User Guide](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/md_docs_2readme.html) and [API Reference of RPP](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/group__rpp.html).
2525

2626
## Note about V2:
2727
Currently I'm working on RPP v2 (`v2` branch). RPP v2 follows [**"zero-overhead principle"**](https://en.cppreference.com/w/cpp/language/Zero-overhead_principle) and most of the operators are (and will) minimize overhead.
@@ -61,6 +61,65 @@ rpp::source::from_callable(&::getchar)
6161
6262
There we are creating observable which emits value via invoking of `getchar` function, then `repeat`s it infinite amount of time till termination event happes. It emits values while symbol is not equal to `0`, takes only not digits, maps them to upper case and then just prints to console.
6363
64+
## Why do you need it?
65+
66+
Check the [User Guide](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/md_docs_2readme.html) for a detailed overview of the Reactive Programming concept and RPP itself.
67+
68+
In short, RPP can help you build complex pipelines to distribute values over time, connect "some sources of data" without directly connecting them.
69+
70+
Take a look at the example code for using RPP with QT. Here, you can see how to connect a button to a label and update it based on the number of clicks.
71+
```cpp
72+
auto button = new QPushButton("Click me!");
73+
auto clicks_count_label = new QLabel();
74+
QObject::connect(button, &QPushButton::clicked, [&clicks_count_label, count = 0]() mutable {
75+
clicks_count_label->setText(QString{"Clicked %1 times!"}.arg(++count));
76+
});
77+
```
78+
79+
In this example, the button is directly connected to the label. What if you want to link another widget to the same button?
80+
81+
```cpp
82+
auto button = new QPushButton("Click me!");
83+
auto clicks_count_label = new QLabel();
84+
auto clicks_duration_label = new QLabel();
85+
QObject::connect(button, &QPushButton::clicked, [&clicks_count_label, count = 0]() mutable {
86+
clicks_count_label->setText(QString{"Clicked %1 times!"}.arg(++count));
87+
});
88+
QObject::connect(button, &QPushButton::clicked, [&clicks_duration_label, now = std::chrono::high_resolution_clock::now()]() mutable {
89+
const auto old = std::exchange(now, std::chrono::high_resolution_clock::now());
90+
clicks_duration_label->setText(QString{"MS since last click %1!"}.arg(std::chrono::duration_cast<std::chrono::milliseconds>(now-old).count()));
91+
});
92+
```
93+
Again directly connected... and it becomes a bit complex.. what if i want to accumulate two buttons at the same time? should i make a separate variable for this case? Build complex state to track it? Ideally it would be nice also to update "MS since last click %1!" at runtime each 1ms... So, looks like each label have to depend on multiple sources of data. It is not a trivial case. In this case it is nice opportunity to try RPP!
94+
```cpp
95+
auto button_1 = new QPushButton("Click me!");
96+
auto button_2 = new QPushButton("Click me!");
97+
auto clicks_count_label = new QLabel();
98+
auto clicks_duration_label = new QLabel();
99+
100+
const auto clicks_1 = rppqt::source::from_signal(*button_1, &QPushButton::clicked);
101+
const auto clicks_2 = rppqt::source::from_signal(*button_2, &QPushButton::clicked);
102+
const auto merged_clicks = clicks_1 | rpp::operators::merge_with(clicks_2);
103+
104+
const auto total_clicks = merged_clicks | rpp::operators::scan(0, [](int seed, auto) { return ++seed; });
105+
const auto click_times = merged_clicks | rpp::operators::map([](auto) { return std::chrono::high_resolution_clock::now(); });
106+
const auto time_since_click = rpp::source::interval(std::chrono::milliseconds{1}, rppqt::schedulers::main_thread_scheduler{})
107+
| rpp::operators::with_latest_from([](auto, const auto click_time) { return std::chrono::high_resolution_clock::now() - click_time; }, click_times);
108+
109+
// .....
110+
111+
total_clicks.subscribe([&clicks_count_label](int clicks)
112+
{
113+
clicks_count_label->setText(QString{"Clicked %1 times in total!"}.arg(clicks));
114+
});
115+
116+
time_since_click.subscribe([&clicks_duration_label](std::chrono::high_resolution_clock::duration ms) {
117+
clicks_duration_label->setText(QString{"MS since last click %1!"}.arg(std::chrono::duration_cast<std::chrono::milliseconds>(ms).count()));
118+
});
119+
```
120+
Now we have separate observables for separate sources of dynamic data like clicks itself, clicks count and time of clicks. As a result, we can combine them in any way we want. At the same time now observables and actions for events can be separated easily - we have "some observable of some clicks or any counted event" and "some observable of durations". How this observables was obtained - doesn't matter. Also we easily built a much more complex pipeline without any difficulties.
121+
122+
64123
# Useful links
65124
- [User Guide](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/md_docs_2readme.html)
66125
- [API Reference](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/group__rpp.html)

src/examples/rppqt/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# add_subdirectory(interactive_window)
2+
add_subdirectory(basic)
23
add_subdirectory(doxygen)
34
# add_subdirectory(multi_threaded)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
add_executable(basic_rppqt basic_rppqt.cpp)
2+
target_link_libraries(basic_rppqt PRIVATE rpp rppqt)
3+
set_target_properties(basic_rppqt PROPERTIES FOLDER Examples/rppqt)
4+
rpp_add_qt_support_to_executable(basic_rppqt)
5+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#include <rpp/rpp.hpp>
2+
#include <rppqt/rppqt.hpp>
3+
4+
#include <QApplication>
5+
#include <QMainWindow>
6+
#include <QPushButton>
7+
#include <QVBoxLayout>
8+
#include <QLabel>
9+
10+
int main(int argc, char* argv[])
11+
{
12+
QApplication app{argc, argv};
13+
14+
auto button_1 = new QPushButton("Click me!");
15+
auto button_2 = new QPushButton("Click me!");
16+
auto clicks_count_label = new QLabel();
17+
auto clicks_duration_label = new QLabel();
18+
19+
const auto clicks_1 = rppqt::source::from_signal(*button_1, &QPushButton::clicked);
20+
const auto clicks_2 = rppqt::source::from_signal(*button_2, &QPushButton::clicked);
21+
const auto merged_clicks = clicks_1 | rpp::operators::merge_with(clicks_2);
22+
23+
const auto total_clicks = merged_clicks | rpp::operators::scan(0, [](int seed, auto) { return ++seed; });
24+
const auto click_times = merged_clicks | rpp::operators::map([](auto) { return std::chrono::high_resolution_clock::now(); });
25+
const auto time_since_click = rpp::source::interval(std::chrono::milliseconds{1}, rppqt::schedulers::main_thread_scheduler{})
26+
| rpp::operators::with_latest_from([](auto, const auto click_time) { return std::chrono::high_resolution_clock::now() - click_time; }, click_times);
27+
28+
// .....
29+
30+
total_clicks.subscribe([&clicks_count_label](int clicks)
31+
{
32+
clicks_count_label->setText(QString{"Clicked %1 times in total!"}.arg(clicks));
33+
});
34+
35+
36+
time_since_click.subscribe([&clicks_duration_label](std::chrono::high_resolution_clock::duration ms) {
37+
clicks_duration_label->setText(QString{"MS since last click %1!"}.arg(std::chrono::duration_cast<std::chrono::milliseconds>(ms).count()));
38+
});
39+
40+
QMainWindow window{};
41+
auto vbox = new QVBoxLayout();
42+
vbox->addWidget(button_1);
43+
vbox->addWidget(button_2);
44+
vbox->addWidget(clicks_count_label);
45+
vbox->addWidget(clicks_duration_label);
46+
window.setCentralWidget(new QWidget);
47+
window.centralWidget()->setLayout(vbox);
48+
window.show();
49+
50+
return app.exec();
51+
}

0 commit comments

Comments
 (0)