Skip to content

Commit 5ea4f91

Browse files
committed
Misc documentation updates.
1 parent 6847fac commit 5ea4f91

9 files changed

Lines changed: 118 additions & 168 deletions

File tree

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,53 @@
11
---
22
layout: default
3-
title: Graph model
3+
title: Dataflow model
44
groups:
55
- {name: Home, url: ''}
66
- {name: Guides , url: 'guides/'}
77
---
88

9-
Reactive programming is a declarative paradigm.
10-
The programmer states _how_ to calculate something, for example in terms of a function, but the actual execution of these calculations is implicitly scheduled by a runtime engine.
9+
* [Graph model](#graph-model)
10+
* [Propagation model](#propagation-model)
1111

12-
From the dependency relations between reactive values, we can construct a graph that models the dataflow.
13-
Each reactive value is a node and directed edges denote data propagation paths.
1412

15-
To give an example, let `a`, `b` and `c` be arbitrary reactive values.
16-
`x` is another reactive value that combines the former through use a binary operation `+`, which can be applied to reactive operands:
13+
## Graph model
14+
15+
[Introduction to C++React]() presented several types of reactive values to model dataflow declarativly.
16+
17+
A pratical analogy is to think of this as a system of assembly lines that process arbitrary items.
18+
We declare the paths on which items should move through a series of work stations.
19+
Once we did that, the system is self-operational and the logistic details are handled automatically.
20+
21+
A more formal method to model the dataflow are directed acyclic graphs (DAGs), which can be constructed from the dependency relations between reactive values.
22+
Each entity is a node and directed edges denote data propagation paths.
23+
24+
To give an example, let `a`, `b` and `c` be arbitrary signals.
25+
`x` is another signal that is calculated based on the former.
26+
Instead of invoking `MakeSignal` explicitly, the overloaded `+` operator is used to achieve the same result.
1727
{% highlight C++ %}
18-
x = (a + b) + c;
28+
SignalT<S> x = (a + b) + c;
1929
{% endhighlight %}
2030
This is the matching dataflow graph:
2131
<br />
2232
<img src="{{ site.baseurl }}/media/signals1.png" alt="Drawing" width="500px"/>
2333

34+
A similar example could've been given for event streams.
35+
From a dataflow perspective, what kind of data is propagated and what exactly happens to it in each node are not relevant.
36+
2437
C++React does not expose the graph data structures directly to the user; instead, they are wrapped by lightweight proxies.
2538
Such a proxy is essentially a shared pointer to the heap-allocated node.
39+
Examples of proxy types are `Signal`, `Events`, `Observer`.
2640
The concrete type of the node is hidden behind the proxy.
2741

28-
We show this scheme with an arbirary proxy type `R`:
42+
We show this scheme for the previous example:
2943
{% highlight C++ %}
30-
R a = MakeR(...);
31-
R b = MakeR(...);
32-
R c = MakeR(...);
33-
34-
R x = (a + b) + c;
44+
SignalT<S> a = MakeVar(...);
45+
SignalT<S> b = MakeVar(...);
46+
SignalT<S> c = MakeVar(...);
47+
SignalT<S> x = (a + b) + c;
3548
{% endhighlight %}
3649

37-
The `MakeR` function is responsible for allocating the respective node and linking it to the returned proxy.
50+
The `MakeVar` function allocates the respective node and linking it to the returned proxy.
3851

3952
One observation made from the previous example is that not all nodes in the graph are bound to a proxy; the temporary sub-expression `a + b` results in a node as well.
4053
If a new node is created, it takes shared ownership of its dependencies, because it needs them to calculate its own value. This prevents the `a + b` node from disappearing.
@@ -47,43 +60,44 @@ Assuming the handles for `a`, `b` and `c` would go out of scope but `x` remains,
4760
Once that happens, the graph is deconstructed from the bottom up.
4861

4962

50-
## Input and output nodes
63+
### Input and output nodes
5164

65+
From now on, we refer to the set of inter-connected reactive values as a reactive system.
5266
A closed, self-contained reactive system would ultimately be useless, as there's no way to get information in or out.
53-
In other words, a reactive system needs to be able to
67+
In other words, mechanisms are required to
5468

55-
* react to external events; and
69+
* react to external input; and
5670
* propagate side effects to the outside.
5771

5872
The outside refers to the larger context of the program the reactive system is part of.
5973

60-
To address the first requirement, we introduce designated `input nodes` at the root of the graph.
74+
To address the first requirement, there exist designated `input nodes` at the root of the graph.
75+
We've already seen examples of those in the form of `VarSignal` and `EventSource`.
6176
They are the input interface of the reactive system and can be manipulated imperatively.
6277
This allows integration of a reactive system with an imperative program.
6378

64-
Propagating changes to the outside world could happen at any place, since C++ does not provide any means to enforce functional purity.
65-
However, since side effects have certain implications on thread-safety and our ability to reason about program behaviour, by convention we move them to designated `output nodes`.
79+
Propagating changes to the outside world could happen at any place through side effects, since C++ does not provide any means to enforce functional purity.
80+
However, since side effects have certain implications on thread-safety and our ability to reason about program behaviour, by convention they're moved them to designated `output nodes`.
6681
By definition, these nodes don't have any successors. Analogously to input nodes, they are the output interface of the reactive system.
82+
In [Introduction to C++React]() we've already introduced examples of output nodes in the form of observers.
6783

6884

69-
## Domains
85+
### Domains
7086

7187
Organizing all reactive values in a single graph would become increasingly difficult to manage.
7288
For this reason, we allow multiple graphs in the form of domains.
7389
Each domain is independent and groups related reactives.
7490
The implementation uses static type tags, so the compiler prevents combination of reactives from different domains at compile time.
91+
The downside is that the domain tag becomes part of the type, so
92+
`Signal<S>` becomes `Signal<D,S>`, where `D` is the domain name.
93+
To reduce the amount of typing, there exists a macro to define scoped aliases for a given
7594

76-
Domains can communicate with each other through their regular input and output interfaces, i.e. they can send messages from their output nodes to input nodes of other domains, including themselves.
95+
Domains can communicate with each other by sending asynchrounous messages from special output nodes called continuations to input nodes of other domains, including themselves.
7796
The following figure outlines this model:<br />
7897
<img src="{{ site.baseurl }}/media/domain1.png" alt="Drawing" width="500px"/>
7998

80-
In summary:
81-
82-
* Intra-domain dependency relations are formulated declaratively and structured as a DAG. Communication is handled implicitly and provides certain guarantees w.r.t. to ordering etc.
83-
* Inter-domain communciation uses asychronous messaging. Messages are dispatched imperatively without any constraints.
8499

85-
86-
## Cycles
100+
### Cycles
87101

88102
When creating a reactive value, all its dependencies have to be passed upon initialization.
89103
For this reason, graphs are acyclic by definition.
@@ -92,4 +106,15 @@ Creating cyclic graphs this way results in undefined behaviour.
92106

93107
For inter-domain communcation, cyclic dependencies between domains are allowed.
94108
This means that two domains could bounce messages off each other infinitely.
95-
It's up to the programmer to ensure that such loops terminate eventually.
109+
It's up to the programmer to ensure that such loops terminate eventually.
110+
111+
## Propagation model
112+
113+
114+
115+
## Conclusion
116+
117+
In summary:
118+
119+
* Intra-domain dependency relations are formulated declaratively and structured as a DAG. Communication is handled implicitly and provides certain guarantees w.r.t. to ordering.
120+
* Inter-domain communciation uses asychronous messaging. Messages are dispatched imperatively without any constraints.
Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,58 @@
11
---
22
layout: default
3-
title: Reactive types
3+
title: Introduction to C++React
44
groups:
55
- {name: Home, url: ''}
66
- {name: Guides , url: 'guides/'}
77
---
88

9-
<!--
10-
# Motivation
11-
The [graph model guide]() explained how reactive values form a graph, with data being propagated along its edges.
12-
It did not concretize what "value" or "data" actually means, because the dataflow perspective allows to abstract from such details.
9+
- [Motivation](#signals)
10+
- [Design outline](#signals)
11+
- [Signals](#signals)
12+
- [Event streams](#event-streams)
13+
- [Observers](#observers)
14+
- [Conclusion](#conclusion)
1315

14-
In this guide, the level of abstraction is lowered to introduce the core reactive types that make up this library.
15-
-->
1616

17-
The purpose of this guide is to state the [Motivation](#motivation) behind this library and to introduce the core reactive types it provides.
18-
Namely those are:
17+
## Motivation
1918

20-
- [Signals](#signals);
21-
- [Event streams](#event-streams);
22-
- [Observers](#observers).
23-
24-
The [Conclusion](#conclusion) explains how it all fits together.
25-
26-
# Motivation
27-
28-
Reacting to events is a common task in modern applications, for instance to respond to user input.
19+
Reacting to events is a common task in modern applications, for example to respond to user input.
2920
Often events themselves are defined and triggered by a framework, but handled in client code.
30-
Callback mechanisms are used to implement this. They allow clients to register and unregister functions at runtime,
31-
and have these functions called when specific events occur.
21+
Callback mechanisms are used to implement this.
22+
They allow clients to register and unregister functions at runtime, and have these functions called when specific events occur.
3223

33-
Conceptionally, there is nothing wrong with this approach, but problems manifest when attempting to distribute complex business logic across multiple callbacks.
24+
Conceptionally, there is nothing wrong with this approach, but problems can arise when distributing complex program logic across multiple callbacks.
3425
The three main reasons for this are:
3526

3627
- (1) The control flow is "scattered"; events may be occur at arbitrary times, callbacks may be added and removed on-the-fly, etc.
37-
- (2) Data is exchanged via shared state and side-effects.
28+
- (2) Data is exchanged via shared state and side effects.
3829
- (3) Callback execution is uncoordinated; callbacks may trigger other callbacks etc.
3930

40-
The combination of these points makes it increasingly difficult to reason about program behaviour and properties like correctness or algorithmic complexity.
41-
Further, it complicates debugging and when adding concurrency to the mix, the situation gets worse.
31+
In combination, these factors make it increasingly difficult to reason about program behaviour and properties like correctness or algorithmic complexity.
32+
Further, debugging is difficult and when adding concurrency to the mix, the situation gets worse.
33+
34+
Decentralized control flow is inherent to the creation of interative applications, but issues of shared state and uncoordinated execution can be addressed.
35+
This is what C++React - and reactive programming in general - does.
36+
37+
38+
## Design outline
39+
40+
The issue of uncoordinated callback execution is handled by adding another layer of intelligence between triggering and actual execution.
41+
This layer schedules callbacks which are ready to be executed, potentially using multiple threads, while guarenteeing certain safety and complexity properteries.
4242

43-
Decentralized control flow is inherent to the creation of interative applications, but usage of side-effects and uncoordinated execution can be addressed.
44-
This is what this library - and reactive programming in general - does.
43+
The aforementioned usage of shared state and side effects is employed due to a lack of alternatives to implement dataflow between callbacks.
44+
Thus, to improve the situation, proper abstractions to model dataflow explicitly are needed.
4545

46+
From a high-level perspective, this dataflow model consists of entities, which can emit and/or receive data, and pure functions to "wire" them together.
47+
Instead of using side effects, these functions pass data through arguments and return values, based on semantics of the connected entities.
48+
There exist multiple types of entities, representing different concepts like time changing values, event occurances or actions.
4649

47-
# Signals
50+
Essentially, this means that callbacks are chained and can pass data in different ways, all of which is done in a composable manner, backed by a clear semantical model.
51+
52+
The following sections will introduce the core abstractions in more detail.
53+
54+
55+
## Signals
4856

4957
Signals are used to model dependency relations between mutable values.
5058
A `SignalT<S>` instance represents a container holding a single value of type `S`, which will notify dependents when that value changes.
@@ -189,22 +197,17 @@ ObserverT obs =
189197
{% endhighlight %}
190198

191199

192-
# Conclusion
193-
194-
The strength of this design are as follows:
195-
196-
There are two key points:
197-
198-
- First-class objects
200+
## Conclusion
199201

200-
- Avoidance of side-effects
202+
The presented reactive types provide us with specialized tools to address problems that would otherwise be implemented in callbacks with side effects:
201203

202-
Compare this to representing events on API level, i.e. `RegisterLeftClickCallback` vs `EventsT<> LeftClick`.
204+
- Signals, as alternative to updating and propagating state changes manually.
205+
- Event streams, as an alternative to transfering data between event handlers explicitly, i.e. through shared message queues.
203206

204-
- Fine-grained ab
207+
For cases where callbacks with side effects are not just a means to an end, but what is actually intended, observers exist as an alternative to setting up registration mechanisms by hand.
205208

206209

207-
## Further reading
210+
### Further reading
208211

209-
The concept of signals and event streams are established concepts from reactive programming and not original to this library.
210-
An academic paper which describes them well and has been especially influential for the design of this library is [Deprecating the Observer Pattern](http://lamp.epfl.ch/~imaier/pub/DeprecatingObserversTR2010.pdf) by Maier et al.
212+
The concepts described in this article are well-established in reactive programming and not original to this library, though semantics may slightly differ between implementations.
213+
An academic paper which has been especially influential for the design of this library is [Deprecating the Observer Pattern](http://lamp.epfl.ch/~imaier/pub/DeprecatingObserversTR2010.pdf) by Maier et al.

guides/index.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ groups:
55
- {name: Home, url: ''}
66
---
77

8-
### [Graph model]({{ site.baseurl }}/guides/Graph-model.html)
9-
### [Reactive types]({{ site.baseurl }}/guides/Reactive-types.html)
10-
### [Propagation model]({{ site.baseurl }}/guides/Propagation-model.html)
8+
### [Introduction to C++React]({{ site.baseurl }}/guides/Introduction.html)
9+
10+
> States the motivation behind this library and introduces its key elements.
11+
12+
### [Dataflow model]({{ site.baseurl }}/guides/Dataflow-model.html)
13+
14+
### [Design rationale]({{ site.baseurl }}/guides/Design-rationale.html)

reference/Domain.h/REACTIVE_DOMAIN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ groups:
77
- {name: Reference , url: 'reference/'}
88
- {name: Domain.h, url: 'reference/Domain.h/'}
99
---
10+
11+
Defines a reactive domain.
12+
1013
## Syntax
1114
{% highlight C++ %}
1215
// (1)

reference/Domain.h/TransactionStatus.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ groups:
1010
This class allow to wait for a asynchrounous transactions started with [AsyncTransaction](AsyncTransaction.html).
1111

1212
An instance of `TransactionStatus` shares ownership of its internal state with any transactions it is monitoring.
13-
The instance itself is a move-only type.
13+
The instance itself is a move-only type, i.e. each transaction status manages a unique state.
1414
A single `TransactionState` can monitor multiple transactions and it can be re-used to avoid repeated state allocations.
1515

1616
## Synopsis

tutorials/Async.md

Lines changed: 1 addition & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,88 +6,4 @@ groups:
66
- {name: Tutorials , url: 'tutorials/'}
77
---
88

9-
- [Parallel updating](#parallel-updating)
10-
- [Concurrent input](#concurrent-input)
11-
- [Selecting a propagation engine](#selecting-a-propagation-engine)
12-
13-
## Parallel updating
14-
15-
Enabling parallel propagation of changes - or _parallel updating as we refer to it from now on - turns out to be straightforward;
16-
all we have to do is selecting the `parallel` policy in the domain definition:
17-
{% highlight C++ %}
18-
#include "react/Domain.h"
19-
#include "react/Signal.h"
20-
#include "react/Event.h"
21-
22-
REACTIVE_DOMAIN(D, parallel)
23-
USING_REACTIVE_DOMAIN(D)
24-
25-
int calcX(int);
26-
int calcY(int);
27-
int calcZ(int);
28-
29-
VarSignalT<int> Input = MakeVar<D>(0);
30-
SignalT<int> X = MakeSignal(Input, calcX);
31-
SignalT<int> Y = MakeSignal(Input, calcY);
32-
SignalT<int> Z = MakeSignal(With(X, Y), calcZ);
33-
{% endhighlight %}
34-
35-
Assuming `calcX` and `calcY` are computationally expensive, they could be re-calculated in parallel after `Input` has been changed.
36-
How exactly this is handled internally is up to the propagation engine; it's explained in detail [here].
37-
In summary, the propagation engine will
38-
39-
- use TBB tasks to parallelize upates, which in turn are mapped to a thread pool;
40-
- try to identify expensive computations at runtime to determine when parallelization is worthwhile;
41-
- agglomerate computations dynamically to reduce overhead.
42-
43-
44-
## Concurrent input
45-
46-
Parallel updating is _internal_ concurrency, as it may execute multiple updates of the same turn concurrently.
47-
But what we really want there is parallelism - that is, running them on multiple CPUs in parallel.
48-
49-
On the other hand, there's _external_ concurrency, which is refers to the ability of handling concurrent input.
50-
This is similar to how a concurrent queue supports push and pop operations from multiple threads at the same time.
51-
52-
To enable concurrent input, we use prefix the concurrency policy with the `_concurrent` suffix:
53-
{% highlight C++ %}
54-
REACTIVE_DOMAIN(D1, sequential_concurrent)
55-
REACTIVE_DOMAIN(D2, parallel_concurrent)
56-
57-
D1::EventSourceT<int> Source1 = MakeEventSource<D1,int>();
58-
D2::EventSourceT<int> Source2 = MakeEventSource<D1,int>();
59-
{% endhighlight %}
60-
61-
{% highlight C++ %}
62-
auto f = [&] {
63-
for (int i=0; i<K; i++)
64-
{
65-
Source1 << i;
66-
Source2 << i;
67-
}
68-
};
69-
70-
std::thread t1(f);
71-
std::thread t2(f);
72-
{% endhighlight %}
73-
74-
Both domains `D1` and `D2` now support safe concurrent input, which is independent of whether updates are parallelized or not.
75-
76-
77-
## Selecting a propagation engine
78-
79-
C++React supports multiple propagation engines which implement different propagation strategies.
80-
So far, we only selected the concurrency policy when defining a domain.
81-
This uses a default engine for that particular policy.
82-
83-
Selecting an engine explicitly is mostly just relevant for parallel updating, as there's just a single engine that supports the `sequential` policy.
84-
85-
For `parallel` (and `parallel_concurrent`), we have the following options:
86-
{% highlight C++ %}
87-
REACTIVE_DOMAIN(D1, parallel, ToposortEngine)
88-
REACTIVE_DOMAIN(D2, parallel, PulsecountEngine)
89-
REACTIVE_DOMAIN(D3, parallel, SubtreeEngine)
90-
{% endhighlight %}
91-
92-
How exactly each strategy works is explained [here]().
93-
Since exchanging them is trivial, it's easy to compare their performance and find the one that works best for a particular scenario.
9+
TODO

0 commit comments

Comments
 (0)