|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: Event basics |
| 4 | +groups: |
| 5 | + - {name: Home, url: ''} |
| 6 | + - {name: Tutorials , url: 'tutorials/'} |
| 7 | +--- |
| 8 | + |
| 9 | +- [Preface](#preface) |
| 10 | +- [Hello world](#hello-world) |
| 11 | +- [Merging event streams](#merging-event-streams) |
| 12 | +- [Processing events](#filtering-events) |
| 13 | +- [Changing multiple inputs](#changing-multiple-inputs) |
| 14 | + |
| 15 | +## Preface |
| 16 | + |
| 17 | +This tutorial covers the event basics. |
| 18 | +It's assumed that the previous tutorial on signals has been understood. |
| 19 | + |
| 20 | + |
| 21 | +## Hello World |
| 22 | + |
| 23 | +We start by creating an `EventSource` that can emit strings: |
| 24 | +{% highlight C++ %} |
| 25 | +#include "react/Domain.h" |
| 26 | +#include "react/Event.h" |
| 27 | + |
| 28 | +REACTIVE_DOMAIN(D, sequential) |
| 29 | +USING_REACTIVE_DOMAIN(D) |
| 30 | + |
| 31 | +EventSourceT<string> mySource = MakeEventSource<D,string>(); |
| 32 | +{% endhighlight %} |
| 33 | +`EventSource` and `Events` are the respective counterparts of `VarSignal` and `Signal`. |
| 34 | + |
| 35 | +Analogously to VarSignalT<S>, `EventSourceT<E>` is an alias for `EventSource<D,E>` defined by `USING_REACTIVE_DOMAIN`. |
| 36 | + |
| 37 | +Unlike signals, event streams are purely push-based. |
| 38 | +They forward values to be processed, but don't hold on to them. |
| 39 | +There is no equivalent to the `Value()` accessor of `Signals`. |
| 40 | + |
| 41 | +This means to do anything useful with `mySource`, we have to add an observer: |
| 42 | + |
| 43 | +{% highlight C++ %} |
| 44 | +Observe(mySource, [] (string s) { |
| 45 | + cout << s << endl; |
| 46 | +}); |
| 47 | +{% endhighlight %} |
| 48 | +{% highlight C++ %} |
| 49 | +mySource.Emit(string( "Hello world" )); |
| 50 | + |
| 51 | +// ... or with operator |
| 52 | +mySource << string( "Hello world" ); |
| 53 | +{% endhighlight %} |
| 54 | + |
| 55 | +Note that here we use the conventional stream operator `<<`, rather than `<<=`, which is used for signals. |
| 56 | +The reasoning behind this is that event input is propagation-only, whereas signal input is both assignment and propagation. |
| 57 | +In other words: Signals hold a value, event streams don't. The different operators are meant to symbolize that. |
| 58 | + |
| 59 | +There's a third syntactic alternative that treats `mySource` as a function object: |
| 60 | +{% highlight C++ %} |
| 61 | +mySource(string( "Hello world" )); |
| 62 | +{% endhighlight %} |
| 63 | + |
| 64 | +These syntactic alternatives allow to distingish between several use cases. |
| 65 | +For instance, if an event is used like a function triggering an action, function-style is appropriate. |
| 66 | +If it acts more like a data stream, we can use the stream syntax. |
| 67 | + |
| 68 | +It's not uncommon that the value type transported by an event stream is irrelevant and we are only interested in the fact that it occurred. |
| 69 | +For instance, when a button has been clicked. In this case, the value type can be omitted. |
| 70 | +We create a second version of the "Hello world" program to demonstrate this: |
| 71 | +{% highlight C++ %} |
| 72 | +D::EventSourceT<> helloWorldTrigger = MakeEventSource<D>(); |
| 73 | +{% endhighlight %} |
| 74 | +{% highlight C++ %} |
| 75 | +Observe(helloWorldTrigger, [] (Token) { |
| 76 | + cout << "Hello world" << endl; |
| 77 | +}); |
| 78 | +{% endhighlight %} |
| 79 | +{% highlight C++ %} |
| 80 | +helloWorldTrigger.Emit(); |
| 81 | + |
| 82 | +// Stream-style: |
| 83 | +helloWorldTrigger << Token::value; |
| 84 | + |
| 85 | +// Function-style: |
| 86 | +helloWorldTrigger(); |
| 87 | +{% endhighlight %} |
| 88 | + |
| 89 | +Internally, the value transported by token streams is of type `Token`, hence for the observer function, we added an unnamed argument of this type. |
| 90 | + |
| 91 | + |
| 92 | +## Merging event streams |
| 93 | + |
| 94 | +From what we've seen so far, event streams are little more than callback registries; |
| 95 | +but their true strength is composability. |
| 96 | + |
| 97 | +For example, lets say we have two event sources that represent different mouse buttons. |
| 98 | +We can easily merge them to a single stream that contains events from both buttons: |
| 99 | +{% highlight C++ %} |
| 100 | +EventSourceT<> leftClick = MakeEventSource<D>(); |
| 101 | +EventSourceT<> rightClick = MakeEventSource<D>(); |
| 102 | + |
| 103 | +EventsT<> anyClick = Merge(leftClick, rightClick); |
| 104 | +{% endhighlight %} |
| 105 | +{% highlight C++ %} |
| 106 | +Observe(anyClick, [] (Token) { |
| 107 | + cout << "button clicked" << endl; |
| 108 | +}); |
| 109 | +{% endhighlight %} |
| 110 | +{% highlight C++ %} |
| 111 | +leftClick.Emit(); // output: clicked |
| 112 | +rightClick.Emit(); // output: clicked |
| 113 | +{% endhighlight %} |
| 114 | +`Merge` takes a variable number of arguments, so more than two streams can be merged at once. |
| 115 | + |
| 116 | +An alternative is using the overloaded `|` for merging: |
| 117 | +{% highlight C++ %} |
| 118 | +EventsT<> anyClick = leftClick | rightClick; |
| 119 | +{% endhighlight %} |
| 120 | + |
| 121 | + |
| 122 | +## Processing events |
| 123 | + |
| 124 | +Besides merging events from multipe streams, we can also process the events themselves. |
| 125 | + |
| 126 | +First, let's demonstrate this by filtering a stream of numbers: |
| 127 | +{% highlight C++ %} |
| 128 | +EventSourceT<int> numbers = MakeEventSource<D,int>(); |
| 129 | + |
| 130 | +EventsT<int> greater10 = Filter(numbers, [] (int n) { |
| 131 | + return n > 10; |
| 132 | +}); |
| 133 | +{% endhighlight %} |
| 134 | +{% highlight C++ %} |
| 135 | +Observe(greater10, [] (int n) { |
| 136 | + cout << n << endl; |
| 137 | +}); |
| 138 | +{% endhighlight %} |
| 139 | +{% highlight C++ %} |
| 140 | +numbers << 5 << 11 << 7 << 100; // output: 11, 100 |
| 141 | +{% endhighlight %} |
| 142 | +If the filter predicate function returns true for the passed value, it will be forwarded. Otherwise, it's filtered out. |
| 143 | + |
| 144 | +Events can be transformed with `Transform`. |
| 145 | + |
| 146 | +For example, we can transform a stream of numbers into a `std::pair` of the number and a tag that indicates whether the former exceeded a certain threshold: |
| 147 | +{% highlight C++ %} |
| 148 | +enum Tag { normal, critical }; |
| 149 | + |
| 150 | +using TaggedNum = pair<Tag,int>; |
| 151 | + |
| 152 | +EventSourceT<int> numbers = MakeEventSource<D,int>(); |
| 153 | +EventsT<TaggedNum> tagged = Transform(numbers, [] (int n) { |
| 154 | + if (n > 10) |
| 155 | + return TaggedNum( critical, n ); |
| 156 | + else |
| 157 | + return TaggedNum( normal, n ); |
| 158 | +}); |
| 159 | +{% endhighlight %} |
| 160 | +{% highlight C++ %} |
| 161 | +Observe(tagged, [] (const TaggedNum& t) { |
| 162 | + if (t.first == critical) |
| 163 | + cout << "(critical) " << t.second << std::endl; |
| 164 | + else |
| 165 | + cout << "(normal) " << t.second << std::endl; |
| 166 | +}); |
| 167 | +{% endhighlight %} |
| 168 | +{% highlight C++ %} |
| 169 | +numbers << 5; |
| 170 | +// output: (normal) 5 |
| 171 | + |
| 172 | +numbers << 20; |
| 173 | +// output: (critical) 20 |
| 174 | +{% endhighlight %} |
| 175 | + |
| 176 | + |
| 177 | +## Changing multiple inputs |
| 178 | + |
| 179 | +Queing multiple inputs in a single turn works analogously to signals: |
| 180 | +{% highlight C++ %} |
| 181 | +DoTransaction<D>([] { |
| 182 | + src << 1 << 2 << 3; |
| 183 | + src << 4; |
| 184 | +}); |
| 185 | +{% endhighlight %} |
| 186 | +It's possible to mix signal and event input in the same transaction. |
| 187 | + |
| 188 | +Unlike signals, where only the last value change for each signal is used, event streams will forward all queued values: |
| 189 | +{% highlight C++ %} |
| 190 | +EventSourceT<int> src = MakeEventSource<D,int>(); |
| 191 | + |
| 192 | +Observe(src, [] (int v) { |
| 193 | + cout << v << endl; |
| 194 | +}); |
| 195 | +// Turn #1, output: 1, 2, 3, 4 |
| 196 | +{% endhighlight %} |
0 commit comments