├── .gitignore ├── Makefile ├── README.md ├── TODO ├── circle.yml ├── images ├── complex.png ├── disposed-let.png ├── dot.png ├── glitch.png └── let.png ├── notes └── about-gitches.txt └── src ├── CMakeLists.txt ├── examples ├── CMakeLists.txt ├── add.c ├── await.c ├── complex.c ├── copy.c ├── dot.c ├── filter.c ├── fold.c ├── futures.c ├── glitch.c ├── let.c ├── lift.c ├── map.c ├── merging.c ├── set.c └── temperature.c ├── lib ├── CMakeLists.txt ├── time.c └── time.h ├── performance ├── CMakeLists.txt ├── gen.py ├── observer-chain.c ├── observer-fan.c ├── reactive-chain.c └── reactive-fan.c ├── reactive-c ├── CMakeLists.txt ├── all-any.c ├── all-any.h ├── api.h ├── debug.c ├── debug.h ├── dot.c ├── dot.h ├── filter.c ├── filter.h ├── folding.c ├── folding.h ├── internals.h ├── iterator.h ├── lift.h ├── merge.c ├── merge.h ├── observable.c ├── observable.h ├── observables.c ├── observables.h ├── script.c ├── script.h ├── unknown.h ├── varargs.h ├── when.c └── when.h ├── test ├── CMakeLists.txt └── test_observables.c ├── unit ├── CMakeLists.txt ├── test.c └── test.h └── unknown ├── CMakeLists.txt ├── Makefile ├── README ├── demo.c ├── unknown.c └── unknown.h /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC_DIR = src 2 | BUILD_DIR = bin 3 | 4 | MKDIR = mkdir -p 5 | CD = cd 6 | CMAKE = cmake 7 | RM = rm -rf 8 | 9 | DOT=dot -Nfixedsize=False -Nfontname=Times-Roman 10 | 11 | TYPE=Release 12 | 13 | GCC=gcc-mp-4.6 14 | VALGRIND=valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all 15 | SCANBUILD=scan-build-mp-3.5 -o report 16 | 17 | all: clean test 18 | 19 | RED = \033[0;31m 20 | GREEN = \033[0;32m 21 | NC = \033[0m # NoColor 22 | 23 | verify: gcc valgrind scan-build 24 | 25 | gcc: 26 | @echo "${RED}*** Verifying GCC...${NC}" 27 | @printf "${GREEN}press any key to start..${NC}" 28 | @read 29 | @(CC=${GCC} make TYPE=Debug) 30 | 31 | valgrind: ${BUILD_DIR}/Makefile 32 | @(${CD} ${BUILD_DIR}; make await) 33 | @echo "${RED}*** Verifying memory aspects using Valgrind...${NC}" 34 | @printf "${GREEN}press any key to continue..${NC}" 35 | @read 36 | @(${VALGRIND} ${BUILD_DIR}/examples/await) 37 | 38 | scan-build: 39 | @echo "${RED}*** Verifying static code using scan-build...${NC}" 40 | @printf "${GREEN}press any key to continue..${NC}" 41 | @read 42 | @(${SCANBUILD} make) 43 | 44 | examples: cmake 45 | @(${CD} ${BUILD_DIR}; ${MAKE} examples) 46 | 47 | test: cmake 48 | @(${CD} ${BUILD_DIR}; ${MAKE} check) 49 | 50 | perf: cmake 51 | @(${CD} ${BUILD_DIR}; ${MAKE} perf) 52 | 53 | run_%: ${BUILD_DIR}/Makefile 54 | @(${CD} ${BUILD_DIR}/; make $@ ) 55 | 56 | cmake: ${BUILD_DIR}/Makefile 57 | 58 | ${BUILD_DIR}/Makefile: ${BUILD_DIR} 59 | @(${CD} $<; ${CMAKE} -DCMAKE_BUILD_TYPE=${TYPE} ../${SRC_DIR}) 60 | 61 | images/%.png: ${BUILD_DIR}/examples/%.dot 62 | @${MKDIR} images 63 | @${DOT} -Tpng -Gsize=5.5,5.5\! -Gdpi=100 -o $@ $< 64 | 65 | ${BUILD_DIR}/examples/%.dot: run_% 66 | @ # <- need a command so that make "thinks" it _can_ rebuild the file 67 | 68 | ${BUILD_DIR}: 69 | @${MKDIR} $@ 70 | 71 | clean: 72 | @${RM} ${BUILD_DIR} 73 | 74 | .PHONY: all build run clean 75 | .PRECIOUS: ${BUILD_DIR}/examples/%.dot 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reactive C [![Build Status](https://circleci.com/gh/christophevg/reactive-c.png)](https://circleci.com/gh/christophevg/reactive-c) 2 | 3 | An experiment on implementing a reactive programming (RP) API in pure C. 4 | Christophe VG () 5 | [https://github.com/christophevg/reactive-c](https://github.com/christophevg/reactive-c) 6 | 7 | ## Introduction 8 | 9 | The origin of this experiment is the paper titled "[Deprecating the Observer Pattern with Scala.React](http://infoscience.epfl.ch/record/176887/files/DeprecatingObservers2012.pdf)". The goal of this experiment is to implement a RP API in C, thus making it available to e.g. embedded development in C. 10 | 11 | ### What do I consider RP? 12 | 13 | The core of RP is an extended implementation of the observer pattern, or as I like to call it "_The Observer Pattern on Steroids_". There is no black magic in reactive programming, just a nice(r) API to construct and compose observers and observables. (IHMO) 14 | 15 | ### Why embedded? 16 | 17 | Although RP typically focuses on UI development and proposes a solution to its (so called) callback hell, it is not difficult to see a strong link to embedded development. In stead of user interaction, the events that require observation now are changes in (digital and analogue) signals on IO ports,... 18 | 19 | Being able to deal with these changes in a more natural way, might also prove to be beneficial to embedded development... if the incurred cost is acceptable. 20 | 21 | ### The Million $ Question 22 | 23 | The questions this experiment wants to answer are "_Can the concepts of RP be applied to pure C programming? And is it worth the hassle?_ " 24 | 25 | ### Approach 26 | 27 | Using different examples, some of the RP concepts are implemented in pure C. Given the nature of C, some compromises have to be made and certain aspects of RP require creative solutions. At the end, a more elaborate example will be implemented and evaluated against an implementation using "basic" observers. 28 | 29 | ### Disclaimer 30 | 31 | 1. This is an _experiment_, so the code is not to be considered stable ... until I state otherwise :-) 32 | 2. I probably violate many C best practices with respect to naming conventions, C idoms,... At this point the goal justifies the means and I'm trying to create some sort of internal DSL, using every macro-expansion trick I can find to achieve this. 33 | 3. The internet/Google is often my teacher, so if you think I used something you should be credited for, let me know, I have no intention to steal from you or disrespect your work in any way. I try to add references to my sources as much as I can. 34 | 35 | ## Getting Started 36 | 37 | Rule of thumb for now: Take a look at the examples, which are also discussed below. 38 | 39 | Everything starts with ... 40 | 41 | ```c 42 | #include "reactive-c/api.h" 43 | ``` 44 | 45 | ## Examples 46 | 47 | Each of the following examples introduces an aspect of RP and its implementation in the context of Reactive C. 48 | 49 | ### Observed Values (temperature.c) 50 | 51 | The first concept is that of **observed values**. Given a value (read: variable) representing e.g. a temperature, we can turn it in an observable using the `observe` constructor. As there is no black magic, updates to the underlying variable need to be triggered using `observe_update`. 52 | 53 | The following example demonstrates how to implement an observable temperature value. This can be implemented in a module of its own, hiding the internal temperature representation, only exposing the `observable_temp` observable. 54 | 55 | ```c 56 | double temp = 123; 57 | 58 | observable_t observable_temp; 59 | 60 | void temp_init() { 61 | observable_temp = observe(temp); 62 | } 63 | 64 | void temp_update(double update) { 65 | temp = update; 66 | observe_update(observable_temp); 67 | } 68 | ``` 69 | 70 | Given this basic observable, we can now observe it using an observer (function). We again use the `observe` constructor. This time we don't provide a (reference to) a variable, but provide it a list of observables, an observer function and the type of the output of that observer (function). 71 | 72 | **Compromise #1**: C is statically typed. This means that an observer function also needs to be statically typed. To allow any kind of arguments and result, we need to resort to `void*` (which we also define as `unknown_t`) and **explicit casts** whenever we want to access these values. 73 | 74 | An observer (function) has to adhere to the `observer_t` type, which is defined as `typedef void (*observer_t)(observation_t);`. Such an `observation_t` consists of components: 75 | 76 | 1. an array of `unknown_t` arguments, used by the observer (function) as input. 77 | 2. a pointer to a single `unknown_t` result variable to hold the output of the observer (function). 78 | 79 | **Compromise #2**: The output value also needs to be stored in memory somehow. Again, because C is statically typed and it provides no ways to access the type of a function through reflection, we also need to explicitly provide it to the `observe` constructor. 80 | 81 | ```c 82 | void c2f(observation_t ob) { 83 | (*(double*)(ob->observer)) = ( (*(double*)(ob->observeds[0])) * 1.8 ) + 32; 84 | } 85 | 86 | int main(void) { 87 | observable_temp_init(); 88 | 89 | observable_t temp_f = observe(just(observable_temp), c2f, double); 90 | ... 91 | } 92 | ``` 93 | Note that observing some observable value, using an observer (function) also implies the creation of a new observable. Whenever the observed value changes, the observer (function) will be executed, resulting in an updated value. This way it becomes an observable. So we now can also observe `temp_f`, which is the temperature converted to Fahrenheit. 94 | 95 | ```c 96 | void display(observable_t ob) { 97 | printf( "observable was updated to %fC/%fF\n", 98 | *(double*)(ob->observed[0]), *(double*)(ob->observed[1]) ); 99 | } 100 | observable_t displayer = observe(both(observable_temp, temp_f), display); 101 | ``` 102 | 103 | Again we apply the `observe` constructor, now to observe both the `observable_temp` and `temp_f` observables. Notice the use of the helper constructors `just` and `both` to turn arbitrary lists of arguments in an array (technically a linked list) and pass it as the first argument to the `observe` constructor. 104 | 105 | **Note**: Besides `just`, which accepts a single argument, and `both` which accepts two arguments, the generic `each` accepts a variable number of arguments. The former are macro's transforming them in calls to `each`. 106 | 107 | **Note**: The `observe` constructor is in fact a collection of macro's. Depending on the number of arguments, it actually calls different `observe` implementations. Given a single argument, the argument is treated as a _value_. Because _every_ value must be referenced and cast to `void*` or `unknown_t`, the macro expansion will take care of this, allowing a clean call to `observe` simply passing the variable name. 108 | With two arguments, the first argument is a _list of observables_ and the second an _observer_ (function) _that doesn't produce output_. Adding a third argument defines the _type_ of the output produced by the observer (function). Finally, a fourth argument can be used to turn the output _type_ in an array of the given _size_. 109 | 110 | When we update the initial temperature, the two observers will be triggered and will compute the temperature in Fahrenheit and display both values. 111 | 112 | ```c 113 | temp_update(19); 114 | // output: observable was updated to 19.000000C/66.200000F 115 | ``` 116 | 117 | Although simple at first sight, this construction already introduces an important problem with observers: **glitches**. To understand this problem, we need to introduce the concept of a **dependency graph**. Three observables depend on each other: `temp_f` depends on `observable_temp` and `display` on both of them. We can visualize these dependencies in graph: 118 | 119 | ``` 120 | level 121 | observable_temp 0 <-- update 122 | / \ 123 | temp_f | 1 124 | \ / 125 | displayer 2 --> printf 126 | 127 | ``` 128 | A glitch can appear if we don't take these dependencies into account when propagating an _update_ through the graph. The `displayer` observer can only be updated when both observables that it depends on are updated. 129 | 130 | To avoid such a glitch, the concept of **levels** is introduced, internally. A level is a numeric property of an observable, indicating its depth in the dependency graph. Updates are propagated through the dependency graph one level at a time, guaranteeing that all observed observables are consistent with respect to the update. 131 | 132 | One final basic operation is defined for observables: `dispose`. This allows the destruction of an observable, removing it from the dependency graph. 133 | 134 | ```c 135 | temp_update(19); // output: observable was updated to 19.000000C/66.200000F 136 | 137 | dispose(displayer); 138 | 139 | temp_update(22); // no output 140 | ``` 141 | 142 | ### Merging & co (merging.c, map.c, fold.c) 143 | 144 | Given the basic concept of observables, we can define operations to combine them. Using `merge` we can take several observables and combine them into a new observable that will propagate each change to those observables. 145 | 146 | ```c 147 | void display(observable_t ob) { 148 | printf("current value = value: %f\n", *(double*)(ob->observed[0])); 149 | } 150 | 151 | int main(void) { 152 | double _a, _b, _c; 153 | 154 | observable_t a = observe(_a); 155 | observable_t b = observe(_b); 156 | observable_t c = observe(_c); 157 | 158 | observable_t abc = merge(a, b, c); 159 | 160 | observe(just(abc), display, void); 161 | 162 | _a = 1; observe_update(a); // output: current value = value: 1.000000. 163 | _b = 2; observe_update(b); // output: current value = value: 2.000000. 164 | _c = 3; observe_update(c); // output: current value = value: 3.000000. 165 | ... 166 | } 167 | ``` 168 | 169 | A second operation is `map`. This takes an observable and a function and applies the function to every observed update. A typical example usage is (type) conversion. 170 | 171 | ```c 172 | void double2string(observation_t ob) { 173 | snprintf(((char*)ob->observer), 10, "%0.f", *(double*)(ob-observed[0])); 174 | } 175 | 176 | void display(observation_t ob) { 177 | printf("current value = value: %s.\n", (char*)(ob->observed[0])); 178 | } 179 | 180 | int main(void) { 181 | double _a; 182 | 183 | observable_t a = observe(_a); 184 | observable_t A = map(a, double2string, char, 10); 185 | observe(just(A), display, void); 186 | 187 | _a = 1; observe_update(a); // output: current value = value: 1. 188 | _a = 2; observe_update(a); // output: current value = value: 2. 189 | _a = 3; observe_update(a); // output: current value = value: 3. 190 | ... 191 | } 192 | ``` 193 | Notice that `map(a, double2string, char, 10);` is _merely_ a wrapper around the basic `observe` constructor and is expanded to `observe(just(a), double2string, char, 10);` 194 | 195 | Let's take a look at another mapping function: 196 | 197 | ```c 198 | void sum(observation_t ob) { 199 | *((int*)ob->observer) += *((int*)ob->observeds[0]); 200 | } 201 | ``` 202 | 203 | Up to now, we didn't use the _current_ value of an observable to compute the next value, but what's stopping us? `sum(observation_t)` applied using a `map` is what sometimes is called a `fold` function. In this case, it's yet another macro expansion. 204 | 205 | But, normally, when used in this context, an initial value is also applied. Support for this is also implemented: 206 | 207 | ```c 208 | void fold_sum(observation_t ob) { 209 | *((int*)ob->observer) += *((int*)ob->observeds[0]); 210 | } 211 | 212 | int main(void) { 213 | int _var1; 214 | observable_t var1 = observe(_var1); 215 | 216 | observable_t folded = fold(var1, fold_sum, int, 3); 217 | 218 | _var1 = 1; observe_update(var1); // folded = 4 219 | _var1 = 2; observe_update(var1); // folded = 6 220 | _var1 = 3; observe_update(var1); // folded = 9 221 | ... 222 | } 223 | ``` 224 | 225 | ### Lifting Functions (add.c, lift.c) 226 | 227 | Another important aspect of RP is the ability to apply functions to observables. Ideally this should be transparent, but here we also are bound to the static typing of C and need to introduce **compromise #3**: explicit **lifting**. 228 | 229 | Lifting takes a function and allows it to function on observables. Let's first look at how we can do this manually: We can't overload operators in C, but we can fall back on functions to perform these operations. Let's take `+` as an example and implement `add`. As we can't have the same function to deal with `int` and `double`, we also need separate functions for each of them, let's call them `addi` and `addd`. Implementing `addi` using Reactive C so far allows us to define a _lifted_ function as such: 230 | 231 | ```c 232 | void _addi(observation_t ob) { 233 | (*(int*)ob->observer) = (*(int*)(ob->observed[0])) + (*(int*)(ob->observed[1])); 234 | } 235 | 236 | observable_t addi(observable_t a, observable_t b) { 237 | return observe(each(a, b), _addi, int); 238 | } 239 | 240 | int main(void) { 241 | int _var1, _var2; 242 | 243 | observable_t var1 = observe(_var1); 244 | observable_t var2 = observe(_var2); 245 | 246 | observable_t var3 = addi(var1, var2); 247 | 248 | _var1 = 1; observe_update(var1); 249 | _var2 = 2; observe_update(var2); 250 | 251 | printf("%d + %d = %d\n", _var1, _var2, *(int*)observable_value(var3)); 252 | // output: 1 + 2 = 3 253 | ... 254 | } 255 | ``` 256 | **Note**: The current value of an observable can be extracted using the `observable_value` function. 257 | 258 | We can also add some more syntactic sugar and use some generic lifting macro to create the lifted function for us, given a basic function that doesn't know about Reactive C: 259 | 260 | ```c 261 | int add(int a, int b) { 262 | return a + b; 263 | } 264 | 265 | lift2(int, add) 266 | 267 | int main(void) { 268 | int _var1, _var2; 269 | 270 | observable_t var1 = observe(_var1); 271 | observable_t var2 = observe(_var2); 272 | 273 | observable_t var3 = observe(both(var1, var2), lifted(add), int); 274 | 275 | _var1 = 1; observe_update(var1); 276 | _var2 = 2; observe_update(var2); 277 | 278 | printf("%d + %d = %d\n", _var1, _var2, *(int*)observable_value(var3)); 279 | // output: 1 + 2 = 3 280 | ... 281 | } 282 | ``` 283 | 284 | Macro `lift2` will in fact expand in this case to: 285 | 286 | ```c 287 | void __lifted_add(observation_t ob) { 288 | *(int*)(ob->observer) = add((*(int*)(ob->observed[0])), (*(int*)(ob-served[1]))); 289 | } 290 | ``` 291 | 292 | The helper macro `lifted` applied to `add` will expand to the `__lifted_add`, hiding the internal expansions. 293 | 294 | ### Scripts (await.c) 295 | 296 | One of the objections made by RP is that of the inherent inversion of control effect of callbacks. To resolve this, an imperative, internal DSL is (often) proposed. Reactive C implements this concept using **scripts**. 297 | 298 | ```c 299 | int main(void) { 300 | int _a = 0, _b = 0; 301 | 302 | observable_t a = observe(_a); 303 | observable_t b = observe(_b); 304 | 305 | run( 306 | script( 307 | await(a), 308 | await(b), 309 | await(a) 310 | ) 311 | ); 312 | 313 | _b = 1; observe_update(b); // does nothing 314 | _a = 1; observe_update(a); // finalizes await(a) 315 | _a = 2; observe_update(a); // does nothing 316 | _b = 2; observe_update(b); // finalizes await(b) 317 | _b = 3; observe_update(b); // does nothing 318 | _a = 3; observe_update(a); // finalizes await(a) 319 | _a = 4; observe_update(a); // does nothing 320 | _b = 4; observe_update(b); // does nothing 321 | ... 322 | } 323 | ``` 324 | 325 | A script consists of **suspended observables**, representing a statement from the DSL. A script must be `run`, before it actually executes the defined statements, by activating the observables. 326 | 327 | The first example of such a observable is `await`, which takes another observable and _pauses_ the script until it observes a change to the observable. Once `await` has observed a change, the observer is disposed and the next observable is activated. 328 | 329 | `await` takes a single observable, so if we want to wait for activity from more than one observable, we need to combine them. We already encountered `merge`, which could be used since it observes several observables and outputs the observed changes in all of them. As soon as one observable is updated, this would be propagated to `await`, causing it to continue the script. 330 | 331 | But for this situation, the DSL also provides `any` and `all`, which propagate when one or all of the observables is updated. 332 | 333 | ```c 334 | script( 335 | await(a), 336 | await(b), 337 | await(delayed(all(a, b, c))), 338 | await(delayed(any(b, c))), 339 | await( all(a, b, c)) 340 | ); 341 | ``` 342 | 343 | Here we need to be careful and notice the subtlety of suspended and non-suspended observables. Let's take `await(a)` first: `a` is an observable, defined outside the scope of the script. It's active and updates its value, before, during and after that `await(a)` is active. In very much the same way, `await(all(a,b,c))` must operate. When constructing the script, this will create an observable `all(a,b,c)`, which is activated at creation time. The surrounding `await` observable is initially suspended. When updates happen to `a`, `b` or `c` `all(a,b,c)` will update and track the updates until all three observables have been updated. If this happens before `await(all(a,b,c))` is activated, it may very well nog work as intended. 344 | 345 | If we want to wait for `a`, `b` and `c`, starting at the point where `await(all(a,b,c))` is activated, we need to configure `all(a,b,c)` as such, using the `delayed` decorator. This causes `all(a,b,c)` to be marked **delayed**, therefore not processing updates until its parent (`await`) is activated. 346 | 347 | In the script above the fifth statement (`await(all(a,b,c))`), will never introduce additional delay, because before it is activated, the awaited observable has already been updated, causing the surrounding `await` observable to dispose itself. 348 | 349 | ### Intermezzo: Visualizing Dependency Graphs (dot.c) 350 | 351 | At this point, it becomes apparent that as soon as these dependency graphs grow beyond a few observables, it can be helpful to have a visual overview of tall these dependencies. 352 | 353 | Using `to_dot(observable_t)` function one can generate a dot-language representation of the dependency graph, starting at the provided observable. The function tries to trace every possible other observable that is connected to the initially provided observable. The following example started from `a`: 354 | 355 |

356 | 357 |

358 | 359 | Observables with a grey background are **suspended** or **delayed**. Full arrows indicate which observable observes what other observable. Dashed arrows represent the sequential order in which (suspended) observables become active (e.g. in a script). Observed values have a green background. 360 | 361 | By default the output is sent to **stdout**, but the function also accepts a second argument, a **file pointer**. 362 | 363 | ### Let's talk bits & bytes (copy.c, set.c) 364 | 365 | Observables deal with values in _generic_ way, using the `unknown_t` type, which is basically your catch-all `void*`. But they store some information about the type, more specifically its size. This allows us to implement `observable_value_copy` to copy the value of one observable to another one, without really knowing its exact type. 366 | 367 | ```c 368 | int _a = 3, 369 | _b = 0; 370 | observable_t a = observe(_a); 371 | observable_t b = observe(_b); 372 | 373 | observable_value_copy(a, b); // _b == 3 too now 374 | ``` 375 | 376 | The underlying principle (aka `memcpy`) also allows for the implementation of a `let` function, that updates a variable through an observable, triggering the effects in the dependency graph. 377 | 378 | ```c 379 | int _a = 0; 380 | observable_t a = observe(_a); 381 | set(a, 3); // _a == 3 now 382 | ``` 383 | 384 | ### Filtering (filter.c) 385 | 386 | A filter might seem as simple as an observer with a function that validates every update, but it couldn't be implemented without the copy functionality introduced in the previous paragraph. 387 | 388 | From the outside it is as simple as any other observer we have introduced so far: 389 | 390 | ```c 391 | bool odd(unknown_t value) { 392 | return *(int*)value % 2 != 0; 393 | } 394 | 395 | int main(void) { 396 | int _var1; 397 | observable_t var1 = observe(_var1); 398 | 399 | observable_t filtered = filter(int, var1, odd); 400 | 401 | set(var1, 1); // var1 == filtered == 1 402 | set(var1, 2); // var1 == 2 | filtered == 1 403 | set(var1, 3); // var1 == filtered == 3 404 | } 405 | ``` 406 | 407 | ### Fun fact: Higher Order Observers... 408 | 409 | If you ever wonder about higher order observers, consider this: you can make higher order observers using ... macro expansion :-) 410 | 411 | ```c 412 | #define filtered_odd_averaged(type, var) \ 413 | filter(type, average(type, var), odd) 414 | ``` 415 | 416 | ### Let observables be observables (let.c) 417 | 418 | What if we want to alter an existing observer? A more concrete example might be that we want to have a _normal_ value-observable, itself be an observer. 419 | 420 | ```c 421 | int main(void) { 422 | int _a = 0, _b = 0, _c = 0; 423 | 424 | observable_t a = observe(_a); 425 | observable_t b = observe(_b); 426 | observable_t c = observe(_c); 427 | 428 | let(a, merge(b, c)); 429 | ... 430 | } 431 | ``` 432 | The `let` function simply copies (overwrites) the `observeds` and the update behaviour. So after this call, the dependency graph looks like this: 433 | 434 |

435 | 436 |

437 | 438 | **TIP** - To avoid merge to be still present in the dependency graph, we can use `disposing`, which wraps an observable, returns it and disposes it. 439 | 440 | ```c 441 | ... 442 | let(a, disposing(merge(b, c))); 443 | empty_trash(); 444 | ... 445 | } 446 | ``` 447 |

448 | 449 |

450 | 451 | ### Intermezzo: fixing the propagation (part 1) 452 | 453 | While going through the paper about [Distributed REScala](http://www.guidosalvaneschi.com/attachments/papers/2014_Distributed_REScala_An_Update_Algorithm_for_Distributed_Reactive_Programming_pdf.pdf), I was caught by a comparison of different algorithms to propagate updates through the dependency graph. I was wondering hoe reactive-c would perform, so I made some modifications and added some features to the `dot` output facilities. Et voila... 454 | 455 |

456 | 457 |

458 | 459 | ``` 460 | observe_update done in 6 steps and 18 messages. 461 | ``` 462 | 463 | If you take a look at the paper, you might notice that their dependency graphs aren't correctly "levelled". 464 | 465 | **Confession 1**: The result as shown above wasn't the first result. I actually had to fix the code to include observers that weren't just one level above the observable that was being updated. 466 | 467 | **Confession 2**: In its current state, reactive-c still isn't glitch-free. The following example shows this: 468 | 469 | ```c 470 | int main(void) { 471 | int _a = 0; 472 | 473 | observable_t a = observe(_a); 474 | 475 | observable_t b = observe(just(a), add_one, int); 476 | observable_t c = observe(just(a), times_three, int); 477 | observable_t d = observe(each(b, c), sum, int); 478 | 479 | // propagate one change through the graph 480 | set(a, 5); 481 | 482 | printf("a=%d, b=%d, c=%d, d=%d\n", 483 | *(int*)observable_value(a), 484 | *(int*)observable_value(b), 485 | *(int*)observable_value(c), 486 | *(int*)observable_value(d)); 487 | ... 488 | ``` 489 | 490 | This creates a dependency graph that looks like this: 491 | 492 |

493 | 494 |

495 | 496 | And the output looks like... 497 | 498 | ``` 499 | a=5, b=6, c=15, d=21 500 | ``` 501 | 502 | _**But** that's correct, so what's the problem?._ Indeed, **but** there still is a glitch. When adding some debugging output to show the underlying propagation decisions... 503 | 504 | ``` 505 | a=5 is propagating change to b=0 => b=6 506 | b=6 is propagating change to d=0 => d=6 507 | a=5 is propagating change to c=0 => c=15 508 | c=15 is propagating change to d=6 => d=21 509 | a=5, b=6, c=15, d=21 510 | ``` 511 | ... we learn that when a is updated, it first propagates its change to b, which is updated from 0 to 6 (=a+1). In its turn, b propagates its change to d, which is updated from 0 to 6 (=b+c). At this point, there is a glitch, because d now holds a value that is considered wrong. While the change is propagated/pushed through the network, all values _should_ be stable at any point. Given this network, d should never hold the value 6. 512 | 513 | So what should have happened? Although that one of its observed values has changed, d should wait to update its own value, until all of its observed values have been updated, or at least are in sync up to their level. 514 | 515 | _**But**, in the end the values are okay, so what's the big deal?_ True, the values are okay in the end. But now imagine that d is a variable that is directly linked to the physical output of your program and it controls the button that sets of the launch of a nuclear missile?! Do you still think its a good idea that d holds an incorrect value for even a fraction of time? 516 | 517 | So without further ado... 518 | 519 | ### Not-So Intermezzo: fixing propagation (part 2) 520 | 521 | The (current) design of reactive-c gives observers a reference to the values of its observed observables. Propagation of changes through the dependency graph are calls to `_observe_update` with an observer as an argument. It actually triggers re-computation of its own value, given the references to its observed values. So fixing the (current) glitch-causing behaviour consists in delaying the updates until all observed observables have been updated. 522 | 523 | This can be achieved by not actually having observed observables calling `_observe_update` on all of their observers, but putting them in a list of observers that require an update. Updates then are only requested when no other observers, with a level lower than the ones that still need to be updated, are awaiting an update. This by itself can be implemented as a list of observables, ordered by their level and continuously sending update requests to the first observable in the list, until the list (aka update queue is empty). This also appears to be the algorithm used by Scala.React. According to the REScala paper, this is far from optimal, but it will do for now ;-) 524 | 525 | So with this redesigned level-based priority-queue in place, the debug output of the glitch example shows that d is no longer update until both b and c are: 526 | 527 | ``` 528 | updating _a @ level 0 529 | updating __each(1, a) @ level 1 530 | updating __each(1, a) @ level 1 531 | updating __each(2, b, c) @ level 2 532 | observe_update done using 4 messages. 533 | a=5, b=6, c=15, d=21 534 | ``` 535 | 536 | ### The Future(s) of Observables 537 | 538 | While reading material related to all of this, I of course came across `futures`, `promises`,... For example at [https://github.com/cujojs/when](https://github.com/cujojs/when): 539 | 540 | ```js 541 | var when = require('when'); 542 | var rest = require('rest'); 543 | 544 | when.reduce(when.map(getRemoteNumberList(), times10), sum) 545 | .done(function(result) { 546 | console.log(result); 547 | }); 548 | 549 | function getRemoteNumberList() { 550 | // Get a remote array [1, 2, 3, 4, 5] 551 | return rest('http://example.com/numbers').then(JSON.parse); 552 | } 553 | 554 | function sum(x, y) { return x + y; } 555 | function times10(x) {return x * 10; } 556 | ``` 557 | 558 | At first, I thought this could easily be implemented using Reactive C. And I was right :-) 559 | 560 | ```c 561 | #include 562 | #include "unit/test.h" 563 | #include "reactive-c/api.h" 564 | 565 | int sum(int a, int b) { return a + b; } lift_fold1(int, sum) 566 | int times10(int a) { return a * 10; } lift1(int, times10) 567 | 568 | void console_log(observable_t ob) { 569 | capture_printf("%d", *(int*)observable_value(ob)); 570 | } 571 | 572 | int main(void) { 573 | observable_t remote_number_list = observed(int); 574 | 575 | on_dispose( 576 | reduce(map(remote_number_list, lifted(times10), int), lifted(sum), int), 577 | console_log 578 | ); 579 | 580 | // simulate 5 integers arriving 581 | set(remote_number_list, 1); 582 | set(remote_number_list, 2); 583 | set(remote_number_list, 3); 584 | set(remote_number_list, 4); 585 | set(remote_number_list, 5); 586 | dispose(remote_number_list); 587 | 588 | assert_output_was("150"); 589 | 590 | exit(EXIT_SUCCESS); 591 | } 592 | ``` 593 | 594 | Notable small changes include: 595 | 596 | * introduction of `lift_fold1` and `lift1` to explicitly lift functions 597 | * introduction of `observed` constructor that creates an observable value without pre-existing storage, implemented as a macro. 598 | * `reduce` is just another name for `fold` without a predefined initial value 599 | * not visible, but an interesting underlying addition is the concept of `AUTO DISPOSING`, which is now applied to `map` and `reduce`. When these observables are informed of the disposal of their last observed observable, they will automatically dispose themselves. This allows for tracking the disposal of the `reduce` observable and respond to it being "done". 600 | 601 | The implementation deals with the the `remote_number_list` in a bit different way than the original example. In stead of receiving a list as a whole, it deals with each element in turn and notifies the end of this list using the `dispose`. In a way, this felt more natural and correct with Reactive C. Time will tell if this is a correct interpretation. Right now, it allowed for a complete, working and very small implementation, that adhered to the original example as much as possible. 602 | 603 | ### But... 604 | 605 | ... with a little support from two functions and some macro's we can even improve on this and actually introduce `when` and remove the up-front call back registration for the disposal handler... 606 | 607 | ```c 608 | when( 609 | reduce(map(remote_number_list, lifted(times10), int), lifted(sum), int), 610 | is(done), 611 | then(console_log) 612 | ); 613 | ``` 614 | 615 | _To be continued..._ 616 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | - structural 4 | - add more tests 5 | - document using Doxygen 6 | - check coverage 7 | - add profiling 8 | 9 | - functional 10 | - introduce emit() + fix script to be real observable 11 | - introduce script looping (e.g reintroduce resuspended observable) 12 | - introduce (push/)pull strategy 13 | - on whole graph or on more fine-grained basis (observable,links)? 14 | - can do_#type() trick be applied to more? (cfr fold) 15 | - implement loop-detection 16 | - probably only warning, loops are valid (e.g. controller/feedback loop) 17 | - introduce time, timeout 18 | - introduce sample (can this be a script?) 19 | - make execution of observe_update async -> really needed? 20 | 21 | - technical 22 | - fix clock support on Linux 23 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | pre: 3 | - sudo apt-get update; sudo apt-get install cmake 4 | 5 | test: 6 | override: 7 | - make 8 | -------------------------------------------------------------------------------- /images/complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophevg/reactive-c/a0864c7cf008d31fc0068bb6c26aec1ace41fb5b/images/complex.png -------------------------------------------------------------------------------- /images/disposed-let.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophevg/reactive-c/a0864c7cf008d31fc0068bb6c26aec1ace41fb5b/images/disposed-let.png -------------------------------------------------------------------------------- /images/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophevg/reactive-c/a0864c7cf008d31fc0068bb6c26aec1ace41fb5b/images/dot.png -------------------------------------------------------------------------------- /images/glitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophevg/reactive-c/a0864c7cf008d31fc0068bb6c26aec1ace41fb5b/images/glitch.png -------------------------------------------------------------------------------- /images/let.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophevg/reactive-c/a0864c7cf008d31fc0068bb6c26aec1ace41fb5b/images/let.png -------------------------------------------------------------------------------- /notes/about-gitches.txt: -------------------------------------------------------------------------------- 1 | About Glitches 2 | 3 | Glitches occur when changes haven't fully propagated to all observed entities. 4 | An example: 5 | 6 | Observable --> Observer A -> Observer B 7 | `---------------------^ 8 | 9 | Observer B should not trigger its functionality simply when Observable has been 10 | updated. Only when Observer A has processed the change of the Observable, 11 | Observer B should trigger its functionality using the latest value of both 12 | Observable and Observer A. 13 | 14 | We solve this problem by introducing the concept of levels. In this example 15 | Observable is at level 0, Observer A is at level 1 and Observer B is at level 16 | 2. The level can be determined based on the levels of all observed entities and 17 | is defined as the highest level of those plus 1. 18 | 19 | When pushing updates through this network, we notify each level in its turn. 20 | This way glitches are avoided. 21 | 22 | Levels are (re)computed when an observer is added or removed. 23 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project (reactive) 2 | cmake_minimum_required(VERSION 2.4) 3 | 4 | message(STATUS "Build Configuration Summary:") 5 | message(STATUS " CMAKE_BUILD_TYPE : ${CMAKE_BUILD_TYPE}") 6 | message(STATUS " CMAKE_CXX_FLAGS_DEBUG : ${CMAKE_CXX_FLAGS_DEBUG}") 7 | message(STATUS " CMAKE_CXX_FLAGS_RELEASE : ${CMAKE_CXX_FLAGS_RELEASE}") 8 | 9 | # ok for clang and gcc, good enough for now 10 | add_compile_options(-std=c99) 11 | add_compile_options(-Wall) 12 | add_compile_options(-pedantic) 13 | 14 | # we're using GLib for its test harness 15 | find_package(PkgConfig REQUIRED) 16 | pkg_check_modules(GLIB2 REQUIRED glib-2.0) 17 | include_directories(${GLIB2_INCLUDE_DIRS}) 18 | link_directories(${GLIB2_LIBRARY_DIRS}) 19 | 20 | include_directories(.) 21 | 22 | add_subdirectory(reactive-c) 23 | add_subdirectory(lib) 24 | add_subdirectory(unit) 25 | add_subdirectory(test) 26 | add_subdirectory(examples) 27 | add_subdirectory(performance) 28 | -------------------------------------------------------------------------------- /src/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # EXAMPLES 2 | 3 | cmake_policy(SET CMP0003 OLD) 4 | 5 | # top-level run-target, run-targets for each executable are added below 6 | add_custom_target(examples) 7 | 8 | # create executables of all example files in the current directory 9 | file(GLOB EXAMPLES *.c) 10 | foreach(example ${EXAMPLES}) 11 | message(STATUS "Adding example ${example}") 12 | get_filename_component(name ${example} NAME_WE) 13 | add_executable(${name} ${example}) 14 | target_link_libraries(${name} LINK_PUBLIC reactive-c) 15 | target_link_libraries(${name} LINK_PUBLIC unit) 16 | add_custom_target(run_${name} 17 | DEPENDS ${name} 18 | COMMENT "Running ${name}..." 19 | COMMAND ${name} 20 | WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} 21 | ) 22 | # and make it a dependency of the generic run target 23 | add_dependencies(examples "run_${name}") 24 | endforeach() 25 | -------------------------------------------------------------------------------- /src/examples/add.c: -------------------------------------------------------------------------------- 1 | // demo for "add" 2 | 3 | #include 4 | #include "reactive-c/api.h" 5 | 6 | // too bad we can't do operator overloading in C ;-( 7 | int add(int a, int b) { return a + b; } 8 | 9 | // (basic) explicit lifting of non-lifted function ;-( 10 | lift2(int, add) 11 | 12 | int main(void) { 13 | // the primitive values 14 | int _a, _b, _c; 15 | 16 | // the observing counterparts 17 | observable_t a = observe(_a); 18 | observable_t b = observe(_b); 19 | observable_t c = observe(_c); 20 | 21 | // a = b + c 22 | let(a, be(add, int, b, c)); 23 | 24 | // too bad we can't do operator overloading in C ;-( 25 | _b = 10; 26 | _c = 42; 27 | 28 | // trigger observation manually ;-( 29 | observe_update(c); 30 | 31 | printf("_a = _b + _c = %d + %d = %d\n", _b, _c, _a); 32 | 33 | exit(EXIT_SUCCESS); 34 | } 35 | -------------------------------------------------------------------------------- /src/examples/await.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "unit/test.h" 4 | 5 | #include "reactive-c/api.h" 6 | 7 | // helper functionality to track steps of the script 8 | int steps = 0; 9 | void count_steps(observable_t _) { steps++; } 10 | void add_step_counter(observable_t observable) { 11 | on_dispose(observable, count_steps); 12 | } 13 | 14 | int main(void) { 15 | int _a = 0, _b = 0, _c = 0; 16 | 17 | observable_t a = observe(_a); 18 | observable_t b = observe(_b); 19 | observable_t c = observe(_c); 20 | 21 | observable_t s = 22 | script( 23 | await(a), 24 | await(b), 25 | await(delayed(all(a, b, c))), 26 | await(delayed(any(b, c))), 27 | await( all(a, b, c)) 28 | ); 29 | 30 | run( on_activation(s, add_step_counter) ); 31 | 32 | // await(a) 33 | assert_zero(steps, "Expected no steps, observed %d.\n", steps); 34 | 35 | _b = 1; observe_update(b); // does nothing 36 | assert_zero(steps, "Expected no steps, observed %d.\n", steps); 37 | 38 | _a = 1; observe_update(a); // finalizes await(a) 39 | assert_equal(steps, 1, "Expected 1 step, observed %d.\n", steps); 40 | 41 | // await(b) 42 | _a = 2; observe_update(a); // does nothing 43 | assert_equal(steps, 1, "Expected 1 step, observed %d.\n", steps); 44 | 45 | _b = 2; observe_update(b); // finalizes await(b) 46 | assert_equal(steps, 2, "Expected 2 steps, observed %d.\n", steps); 47 | 48 | // await(delayed(all(a,b,c))) 49 | _b = 3; observe_update(b); // does nothing 50 | assert_equal(steps, 2, "Expected 2 steps, observed %d.\n", steps); 51 | 52 | _a = 3; observe_update(a); // does nothing 53 | assert_equal(steps, 2, "Expected 2 steps, observed %d.\n", steps); 54 | 55 | _c = 3; observe_update(c); // finalizes await(delayed(all(a,b,c))) 56 | // but also the already started all(a,b,c) 57 | // but not its await step 58 | assert_equal(steps, 3, "Expected 3 steps, observed %d.\n", steps); 59 | 60 | // await(delayed(any(b,c))) 61 | _a = 4; observe_update(a); // does nothing 62 | assert_equal(steps, 3, "Expected 3 steps, observed %d.\n", steps); 63 | 64 | _c = 4; observe_update(c); // finalizes await(delayed(any(b,c))) 65 | // this also moves the script forward to the last 66 | // await(all(a,b,c)), of which the all() 67 | // observable has already finished 68 | assert_equal(steps, 5, "Expected 5 steps, observed %d.\n", steps); 69 | 70 | // script done, nothing triggered anymore 71 | _a = 5; observe_update(a); // does nothing 72 | assert_equal(steps, 5, "Expected 5 steps, observed %d.\n", steps); 73 | 74 | _b = 5; observe_update(b); // does nothing 75 | assert_equal(steps, 5, "Expected 5 steps, observed %d.\n", steps); 76 | 77 | _c = 5; observe_update(c); // does nothing 78 | assert_equal(steps, 5, "Expected 5 steps, observed %d.\n", steps); 79 | 80 | // clean up to validate memory leaks ;-) 81 | dispose(s); 82 | dispose(a); 83 | dispose(b); 84 | dispose(c); 85 | 86 | stop_observing(); 87 | 88 | exit(EXIT_SUCCESS); 89 | } 90 | -------------------------------------------------------------------------------- /src/examples/complex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "reactive-c/api.h" 5 | 6 | #include "internals.h" 7 | 8 | void mark(observation_t ob) { 9 | _mark(ob->self); 10 | } 11 | 12 | int main(void) { 13 | int _a = 0, _b = 0, _c = 0, _d = 0; 14 | 15 | observable_t a = observe(_a); 16 | observable_t b = observe(_b); 17 | observable_t c = observe(_c); 18 | observable_t d = observe(_d); 19 | 20 | observable_t l11 = observe(just(b), mark, int); 21 | observable_t l12 = observe(just(c), mark, int); 22 | observable_t l13 = observe(each(c,d), mark, int); 23 | observable_t l14 = observe(just(d), mark, int); 24 | 25 | observable_t l21 = observe(each(a, l11), mark, int); 26 | observable_t l22 = observe(just(l11), mark, int); 27 | observable_t l23 = observe(each(b, l12), mark, int); 28 | observable_t l24 = observe(just(l14), mark, int); 29 | 30 | observable_t l31 = observe(each(a, l22), mark, int); 31 | observable_t l32 = observe(just(l23), mark, int); 32 | observable_t l33 = observe(each(l12, l13), mark, int); 33 | observable_t l34 = observe(each(d, l24), mark, int); 34 | 35 | observable_t l41 = observe(just(l31),mark, int); 36 | observable_t l42 = observe(each(l31,c),mark, int); 37 | observable_t l43 = observe(just(l32),mark, int); 38 | observable_t l44 = observe(just(l34),mark, int); 39 | observable_t l45 = observe(each(l34, l23),mark, int); 40 | 41 | observable_t l51 = observe(just(l41),mark, int); 42 | observable_t l52 = observe(each(l42, l44),mark, int); 43 | 44 | observable_t l61 = observe(just(l51),mark, int); 45 | observable_t l62 = observe(just(l42),mark, int); 46 | observable_t l63 = observe(each(l52, l44, l45),mark, int); 47 | 48 | observable_t l71 = observe(each(l51, l42, l62),mark, int); 49 | observable_t l72 = observe(each(l62, l33), mark, int); 50 | 51 | // to avoid warnings about unused variables 52 | l63 = NULL; l43 = NULL; l61 = NULL; l21 = NULL; l72 = NULL; 53 | 54 | // propagate one change through the graph 55 | observe_update(c); 56 | 57 | // dump the graph with markings 58 | FILE *fp = fopen("complex.dot", "w"); 59 | to_dot(l71, fp, DOT_MESSAGE_STYLE | DOT_HIDE_LABELS | DOT_SMALL_RANK_SEP 60 | | DOT_SHAPE_IS_CIRCLE | DOT_SHOW_MARKED); 61 | fclose(fp); 62 | 63 | exit(EXIT_SUCCESS); 64 | } 65 | -------------------------------------------------------------------------------- /src/examples/copy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "unit/test.h" 5 | 6 | #include "reactive-c/api.h" 7 | 8 | int main(void) { 9 | int _a = 0, _b = 0; 10 | 11 | observable_t a = observe(_a); 12 | observable_t b = observe(_b); 13 | 14 | _a = 3; 15 | 16 | assert_equal(_a, 3, "a should be 3\n"); 17 | assert_equal(_b, 0, "b should be 0\n"); 18 | 19 | observable_value_copy(a, b); 20 | 21 | assert_equal(_a, 3, "a should be 3\n"); 22 | assert_equal(_b, 3, "b should be 3\n"); 23 | 24 | exit(EXIT_SUCCESS); 25 | } 26 | -------------------------------------------------------------------------------- /src/examples/dot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "reactive-c/api.h" 5 | 6 | int main(void) { 7 | int _a = 0, _b = 0, _c = 0; 8 | 9 | observable_t a = observe(_a); 10 | observable_t b = observe(_b); 11 | observable_t c = observe(_c); 12 | 13 | script( 14 | await(a), 15 | await(b), 16 | await(delayed(all(a, b, c))), 17 | await(delayed(any(b, c))), 18 | await( all(a, b, c)) 19 | ); 20 | 21 | FILE *fp = fopen("dot.dot", "w"); 22 | to_dot(a, fp); 23 | fclose(fp); 24 | 25 | exit(EXIT_SUCCESS); 26 | } 27 | -------------------------------------------------------------------------------- /src/examples/filter.c: -------------------------------------------------------------------------------- 1 | // demo for "filter" 2 | 3 | #include 4 | #include 5 | 6 | #include "unit/test.h" 7 | 8 | #include "reactive-c/api.h" 9 | 10 | bool odd(unknown_t value) { 11 | return *(int*)value % 2 != 0; 12 | } 13 | 14 | int main(void) { 15 | int _var1; 16 | observable_t var1 = observe(_var1); 17 | 18 | observable_t filtered = filter(int, var1, odd); 19 | 20 | set(var1, 1); 21 | 22 | assert_equal(*(int*)observable_value(var1), 1, "Expected value = 1\n"); 23 | assert_equal(*(int*)observable_value(filtered), 1, "Expected value = 1\n"); 24 | 25 | set(var1, 2); 26 | 27 | assert_equal(*(int*)observable_value(var1), 2, "Expected value = 2\n"); 28 | assert_equal(*(int*)observable_value(filtered), 1, "Expected value = 1\n"); 29 | 30 | set(var1, 3); 31 | 32 | assert_equal(*(int*)observable_value(var1), 3, "Expected value = 3\n"); 33 | assert_equal(*(int*)observable_value(filtered), 3, "Expected value = 3\n"); 34 | 35 | exit(EXIT_SUCCESS); 36 | } 37 | -------------------------------------------------------------------------------- /src/examples/fold.c: -------------------------------------------------------------------------------- 1 | // demo for "fold" 2 | 3 | #include 4 | #include 5 | 6 | #include "unit/test.h" 7 | 8 | #include "reactive-c/api.h" 9 | 10 | void fold_sum(observation_t ob) { 11 | *((int*)ob->observer) += *((int*)ob->observeds[0]); 12 | } 13 | 14 | int main(void) { 15 | int _var1; 16 | observable_t var1 = observe(_var1); 17 | 18 | observable_t folded = fold(var1, fold_sum, int, 3); 19 | 20 | _var1 = 1; observe_update(var1); 21 | 22 | assert_equal(*(int*)observable_value(folded), 4, "Expected value = 4\n"); 23 | 24 | _var1 = 2; observe_update(var1); 25 | 26 | assert_equal(*(int*)observable_value(folded), 6, "Expected value = 6\n"); 27 | 28 | _var1 = 3; observe_update(var1); 29 | 30 | assert_equal(*(int*)observable_value(folded), 9, "Expected value = 9\n"); 31 | 32 | exit(EXIT_SUCCESS); 33 | } 34 | -------------------------------------------------------------------------------- /src/examples/futures.c: -------------------------------------------------------------------------------- 1 | // an implementation of a futures/promises example from 2 | // https://github.com/cujojs/when 3 | 4 | #include 5 | 6 | #include "unit/test.h" 7 | 8 | #include "reactive-c/api.h" 9 | 10 | int sum(int a, int b) { return a + b; } lift_fold1(int, sum) 11 | int times10(int a) { return a * 10; } lift1(int, times10) 12 | 13 | void console_log(observable_t ob) { 14 | capture_printf("%d", *(int*)observable_value(ob)); 15 | } 16 | 17 | int main(void) { 18 | observable_t remote_number_list = observed(int); 19 | 20 | when( 21 | reduce(map(remote_number_list, lifted(times10), int), lifted(sum), int), 22 | is(done), 23 | then(console_log) 24 | ); 25 | 26 | // simulate 5 integers arriving 27 | set(remote_number_list, 1); 28 | set(remote_number_list, 2); 29 | set(remote_number_list, 3); 30 | set(remote_number_list, 4); 31 | set(remote_number_list, 5); 32 | dispose(remote_number_list); 33 | 34 | assert_output_was("150"); 35 | 36 | exit(EXIT_SUCCESS); 37 | } 38 | -------------------------------------------------------------------------------- /src/examples/glitch.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "reactive-c/api.h" 5 | 6 | #include "internals.h" 7 | 8 | void add_one(observation_t ob) { 9 | (*(int*)ob->observer) = (*(int*)(ob->observeds[0])) + 1; 10 | } 11 | 12 | void times_three(observation_t ob) { 13 | (*(int*)ob->observer) = (*(int*)(ob->observeds[0])) * 3; 14 | } 15 | 16 | void sum(observation_t ob) { 17 | (*(int*)ob->observer) = 18 | (*(int*)(ob->observeds[0])) + (*(int*)(ob->observeds[1])); 19 | } 20 | 21 | // this example shows what is considered a (possible) glitch 22 | 23 | // b (=a+1) 24 | // / \ 25 | // a d (=b+c) 26 | // \ / 27 | // c (=a*3) 28 | 29 | // if a=5 => b=6, c=15, d=61 30 | 31 | int main(void) { 32 | int _a = 0; 33 | 34 | observable_t a = observe(_a); 35 | 36 | observable_t b = observe(just(a), add_one, int); 37 | observable_t c = observe(just(a), times_three, int); 38 | observable_t d = observe(each(b, c), sum, int); 39 | 40 | // propagate one change through the graph 41 | set(a, 5); 42 | 43 | printf("a=%d, b=%d, c=%d, d=%d\n", 44 | *(int*)observable_value(a), 45 | *(int*)observable_value(b), 46 | *(int*)observable_value(c), 47 | *(int*)observable_value(d)); 48 | 49 | FILE *fp = fopen("glitch.dot", "w"); 50 | to_dot(a, fp); 51 | fclose(fp); 52 | 53 | exit(EXIT_SUCCESS); 54 | } 55 | -------------------------------------------------------------------------------- /src/examples/let.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "unit/test.h" 4 | 5 | #include "reactive-c/api.h" 6 | 7 | int main(void) { 8 | int _a = 0, _b = 0, _c = 0; 9 | 10 | observable_t a = observe(_a); 11 | observable_t b = observe(_b); 12 | observable_t c = observe(_c); 13 | 14 | // we might want to do... 15 | // observable_t a = merge(b,c); 16 | // but... observable a already exists, so we want to change it. 17 | // let copies all info from one observable to the other 18 | 19 | let(a, disposing(merge(b, c))); 20 | 21 | FILE *fp = fopen("let.dot", "w"); 22 | to_dot(a, fp); 23 | fclose(fp); 24 | 25 | empty_trash(); 26 | 27 | fp = fopen("disposed-let.dot", "w"); 28 | to_dot(a, fp); 29 | fclose(fp); 30 | 31 | set(a, 1); 32 | 33 | assert_equal(_a, 1, "a should be 1\n"); 34 | assert_equal(_b, 0, "b should be 0\n"); 35 | assert_equal(_c, 0, "c should be 0\n"); 36 | 37 | set(b, 2); 38 | 39 | assert_equal(_a, 2, "a should be 2\n"); 40 | assert_equal(_b, 2, "b should be 2\n"); 41 | assert_equal(_c, 0, "c should be 0\n"); 42 | 43 | set(c, 3); 44 | 45 | assert_equal(_a, 3, "a should be 3\n"); 46 | assert_equal(_b, 2, "b should be 2\n"); 47 | assert_equal(_c, 3, "c should be 3\n"); 48 | 49 | exit(EXIT_SUCCESS); 50 | } 51 | -------------------------------------------------------------------------------- /src/examples/lift.c: -------------------------------------------------------------------------------- 1 | // demo of "lifting" 2 | 3 | #include 4 | 5 | #include "unit/test.h" 6 | 7 | #include "reactive-c/api.h" 8 | 9 | // "a" function taking two integers and returning an int 10 | int add(int a, int b) { 11 | return a + b; 12 | } 13 | 14 | lift2(int, add) 15 | 16 | // expands to... 17 | // void lifted_add(observation_t ob) { 18 | // *(int*)(ob->observer) = 19 | // add((*(int*)(ob->observeds[0])), (*(int*)(ob->observeds[1]))); 20 | // } 21 | 22 | int main(void) { 23 | int _var1, _var2; 24 | 25 | // create two observable values 26 | observable_t var1 = observe(_var1); 27 | observable_t var2 = observe(_var2); 28 | 29 | // and one observable combination (aka behavior) 30 | observable_t var3 = observe(both(var1, var2), lifted(add), int); 31 | 32 | // simulate some events on all 33 | _var1 = 1; observe_update(var1); 34 | _var2 = 2; observe_update(var2); 35 | 36 | // at this point var3 is already updated, let's check that: 37 | 38 | capture_printf("%d + %d = %d\n", _var1, _var2, *(int*)observable_value(var3)); 39 | 40 | assert_output_was( "1 + 2 = 3\n" ); 41 | clear_output(); 42 | 43 | _var1 = 3; observe_update(var1); 44 | _var2 = 5; observe_update(var2); 45 | 46 | // at this point var3 is already (again) updated, let's check that (again): 47 | 48 | capture_printf("%d + %d = %d\n", _var1, _var2, *(int*)observable_value(var3)); 49 | 50 | assert_output_was( "3 + 5 = 8\n" ); 51 | 52 | exit(EXIT_SUCCESS); 53 | } 54 | -------------------------------------------------------------------------------- /src/examples/map.c: -------------------------------------------------------------------------------- 1 | // demo for map 2 | 3 | #include 4 | #include 5 | 6 | #include "unit/test.h" 7 | 8 | #include "reactive-c/api.h" 9 | 10 | void display(observation_t ob) { 11 | capture_printf("current value = value: %s.\n", (char*)(ob->observeds[0])); 12 | } 13 | 14 | // converts double to string representation 15 | void double2string(observation_t ob) { 16 | snprintf(((char*)ob->observer), 10, "%0.f", *(double*)(ob->observeds[0])); 17 | } 18 | 19 | int main(void) { 20 | double _a; 21 | 22 | // observer a value 23 | observable_t a = observe(_a); 24 | 25 | // map the observed value to something else 26 | observable_t A = map(a, double2string, char, 10); 27 | // which is basically a wrapper for... 28 | // observable_t A = observe(just(a), double2string, char, 10); 29 | 30 | observe(just(A), display, void*); 31 | 32 | _a = 1; observe_update(a); 33 | _a = 2; observe_update(a); 34 | _a = 3; observe_update(a); 35 | 36 | assert_output_was( 37 | "current value = value: 1.\n" 38 | "current value = value: 2.\n" 39 | "current value = value: 3.\n" 40 | ); 41 | 42 | exit(EXIT_SUCCESS); 43 | } 44 | -------------------------------------------------------------------------------- /src/examples/merging.c: -------------------------------------------------------------------------------- 1 | // demo for merging of observables 2 | 3 | #include 4 | #include 5 | 6 | #include "unit/test.h" 7 | 8 | #include "reactive-c/api.h" 9 | 10 | void display(observation_t ob) { 11 | capture_printf("current value = value: %f\n", *(double*)(ob->observeds[0])); 12 | } 13 | 14 | int main(void) { 15 | double _a, _b, _c; 16 | 17 | // create three basic observers 18 | observable_t a = observe(_a); 19 | observable_t b = observe(_b); 20 | observable_t c = observe(_c); 21 | 22 | // create one that merges all three 23 | observable_t abc = merge(a, b, c); 24 | 25 | // observe all (three) updates in one go 26 | observe(just(abc), display, void*); 27 | 28 | _a = 1; observe_update(a); 29 | 30 | assert_output_was( 31 | "current value = value: 1.000000\n" 32 | ); 33 | 34 | _b = 2; observe_update(b); 35 | 36 | assert_output_was( 37 | "current value = value: 1.000000\n" 38 | "current value = value: 2.000000\n" 39 | ); 40 | 41 | _c = 3; observe_update(c); 42 | 43 | assert_output_was( 44 | "current value = value: 1.000000\n" 45 | "current value = value: 2.000000\n" 46 | "current value = value: 3.000000\n" 47 | ); 48 | 49 | exit(EXIT_SUCCESS); 50 | } 51 | -------------------------------------------------------------------------------- /src/examples/set.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "unit/test.h" 5 | 6 | #include "reactive-c/api.h" 7 | 8 | int main(void) { 9 | int _a = 0; 10 | double _b = 0; 11 | 12 | observable_t a = observe(_a); 13 | observable_t b = observe(_b); 14 | 15 | assert_equal(_a, 0, "a should be 0\n"); 16 | assert_equal(_b, 0, "b should be 0\n"); 17 | 18 | set(a, 3); 19 | 20 | assert_equal(_a, 3, "a should be 3\n"); 21 | assert_equal(_b, 0, "b should be 0\n"); 22 | 23 | set(b, 3.0); 24 | 25 | assert_equal(_a, 3, "a should be 3\n"); 26 | assert_equal(_b, 3, "b should be 3\n"); 27 | 28 | exit(EXIT_SUCCESS); 29 | } 30 | -------------------------------------------------------------------------------- /src/examples/temperature.c: -------------------------------------------------------------------------------- 1 | // implementation of the temperature conversion behavior from the reactive 2 | // programming survey ... 3 | 4 | // we have a behavior temp that continuously presents the current temperature 5 | // in degrees Celcius 6 | 7 | #include 8 | 9 | #include "unit/test.h" 10 | 11 | #include "reactive-c/api.h" 12 | 13 | // to observe a value/variable... 14 | double temp = 123; 15 | 16 | // ...we create an observable... 17 | observable_t observable_temp; 18 | 19 | // ... and link it to the value/variable. 20 | void observable_temp_init() { 21 | observable_temp = observe(temp); 22 | } 23 | 24 | // when the value/variable is updated, we trigger the RP functionality 25 | void temp_update(double update) { 26 | temp = update; 27 | // trigger update propagation 28 | observe_update(observable_temp); 29 | } 30 | 31 | // a user defined convertion between Celcius and Farenheit 32 | void c2f(observation_t ob) { 33 | (*(double*)(ob->observer)) = ( (*(double*)(ob->observeds[0])) * 1.8 ) + 32; 34 | } 35 | 36 | // a user defined display function to display both C and F values 37 | void display(observation_t ob) { 38 | capture_printf( "observable was updated to %fC/%fF\n", 39 | *(double*)(ob->observeds[0]), *(double*)(ob->observeds[1]) ); 40 | } 41 | 42 | /* 43 | level 44 | observable_temp 0 <-- update 45 | / \ 46 | temp_f | 1 47 | \ / 48 | displayer 2 --> printf 49 | */ 50 | 51 | int main(void) { 52 | // init temp behavior 53 | observable_temp_init(); 54 | 55 | // create a new behviour that convers C to F 56 | observable_t temp_f = observe(just(observable_temp), c2f, double); 57 | 58 | // simulate changes to temp 59 | temp_update(16); 60 | temp_update(17); 61 | temp_update(18); 62 | 63 | assert_no_output(); 64 | 65 | assert_equal( 66 | *(double*)observable_value(observable_temp), 67 | 18, 68 | "Expected temperature to be 18C. Observed %f.\n", 69 | *(double*)observable_value(observable_temp) 70 | ); 71 | assert_equal( 72 | *(double*)observable_value(temp_f), 73 | 18 * 1.8 + 32, 74 | "Expected temperature to be %f. Observed %f.\n", 75 | 18 * 1.8 + 32, 76 | *(double*)observable_value(temp_f) 77 | ); 78 | 79 | // let's add an observer that displays the updates from now on using our 80 | // display observer function 81 | observable_t displayer = observe(both(observable_temp, temp_f), display, void*); 82 | 83 | temp_update(19); 84 | temp_update(20); 85 | temp_update(21); 86 | 87 | assert_output_was( 88 | "observable was updated to 19.000000C/66.200000F\n" 89 | "observable was updated to 20.000000C/68.000000F\n" 90 | "observable was updated to 21.000000C/69.800000F\n" 91 | ); 92 | 93 | clear_output(); 94 | 95 | dispose(displayer); 96 | 97 | temp_update(22); 98 | temp_update(23); 99 | temp_update(24); 100 | 101 | assert_no_output(); 102 | 103 | assert_equal( 104 | *(double*)observable_value(observable_temp), 105 | 24, 106 | "Expected temperature to be 18C. Observed %f.\n", 107 | *(double*)observable_value(observable_temp) 108 | ); 109 | assert_equal( 110 | *(double*)observable_value(temp_f), 111 | 24 * 1.8 + 32, 112 | "Expected temperature to be %f. Observed %f.\n", 113 | 24 * 1.8 + 32, 114 | *(double*)observable_value(temp_f) 115 | ); 116 | dispose(temp_f); 117 | 118 | exit(EXIT_SUCCESS); 119 | } 120 | -------------------------------------------------------------------------------- /src/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB SRCS *.c) 2 | 3 | add_library(reactive-lib ${SRCS}) 4 | 5 | target_include_directories(reactive-lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 6 | -------------------------------------------------------------------------------- /src/lib/time.c: -------------------------------------------------------------------------------- 1 | #include "time.h" 2 | 3 | #ifdef __MACH__ 4 | #include 5 | #include 6 | #endif 7 | 8 | // via: http://stackoverflow.com/questions/5167269/ 9 | 10 | struct timespec now() { 11 | struct timespec ts; 12 | #ifdef __MACH__ 13 | clock_serv_t cclock; 14 | mach_timespec_t mts; 15 | host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); 16 | clock_get_time(cclock, &mts); 17 | mach_port_deallocate(mach_task_self(), cclock); 18 | ts.tv_sec = mts.tv_sec; 19 | ts.tv_nsec = mts.tv_nsec; 20 | #else 21 | clock_gettime(CLOCK_REALTIME, &ts); 22 | #endif 23 | return ts; 24 | } 25 | 26 | double diff(struct timespec start, struct timespec end) { 27 | struct timespec temp; 28 | if((end.tv_nsec - start.tv_nsec) < 0) { 29 | temp.tv_sec = end.tv_sec - start.tv_sec - 1; 30 | temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec; 31 | } else { 32 | temp.tv_sec = end.tv_sec - start.tv_sec; 33 | temp.tv_nsec = end.tv_nsec - start.tv_nsec; 34 | } 35 | return temp.tv_sec + temp.tv_nsec / 1000000000.0; 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/time.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIB_TIME_H 2 | #define __LIB_TIME_H 3 | 4 | #include 5 | #include 6 | 7 | struct timespec now(); 8 | double diff(struct timespec, struct timespec); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/performance/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # PERFORMANCE 2 | 3 | add_custom_target(perf) 4 | 5 | file(GLOB PERF_TESTS *.c) 6 | foreach(perf ${PERF_TESTS}) 7 | message(STATUS "Adding performance test ${perf}") 8 | get_filename_component(name ${perf} NAME_WE) 9 | add_executable(${name} ${perf}) 10 | target_link_libraries(${name} LINK_PUBLIC reactive-c) 11 | target_link_libraries(${name} LINK_PUBLIC reactive-lib) 12 | add_custom_target(run_${name} 13 | DEPENDS ${name} 14 | COMMENT "Running performance test ${name}..." 15 | COMMAND ${name} 16 | WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} 17 | ) 18 | # and make it a dependency of the generic run target 19 | add_dependencies(perf "run_${name}") 20 | endforeach() 21 | -------------------------------------------------------------------------------- /src/performance/gen.py: -------------------------------------------------------------------------------- 1 | # small script to generate 100 individual observers 2 | 3 | # common 4 | for i in xrange(1,101): 5 | print "int f%d_value; observable f%d_ob;" % (i,i) 6 | print "void f%d(observable o) { f%d_value = o.value; notify(f%d_ob); }" % (i,i,i) 7 | 8 | # observer-fan 9 | for i in xrange(1,101): 10 | print "add(&source, f%d);" % i 11 | 12 | # observer-chain 13 | for i in xrange(1,101): 14 | print "f%d_ob = new_observable();" % i 15 | 16 | print "add(&source, f1);" 17 | for i in xrange(1,100): 18 | print "add(&f%d_ob, f%d);" %(i,i+1) 19 | -------------------------------------------------------------------------------- /src/performance/observer-chain.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "lib/time.h" 6 | 7 | // a minimalistic observer pattern implementation 8 | 9 | typedef struct observable observable; 10 | 11 | typedef void (*observing_function)(observable); 12 | 13 | typedef struct observer { 14 | observing_function f; 15 | struct observer *next; 16 | } observer; 17 | 18 | struct observable { 19 | int value; 20 | observer *observer; 21 | }; 22 | 23 | observable new_observable() { 24 | observable o; 25 | o.value = 0; 26 | o.observer = NULL; 27 | return o; 28 | } 29 | 30 | void add(observable *o, observing_function f) { 31 | observer *n = malloc(sizeof(struct observer)); 32 | n->next = o->observer; 33 | n->f = f; 34 | o->observer = n; 35 | } 36 | 37 | // timed range 38 | 39 | void notify(observable o) { 40 | int c = 0; 41 | observer *i = o.observer; 42 | while(i) { 43 | c++; 44 | i->f(o); 45 | i = i->next; 46 | } 47 | } 48 | 49 | void update(observable o, int value) { 50 | o.value = value; 51 | notify(o); 52 | } 53 | 54 | #define FAN_WIDTH 100 55 | #define UPDATES 5000000 56 | #define ITERATIONS 5 57 | 58 | int f1_value; observable f1_ob; 59 | void f1(observable o) { f1_value = o.value; notify(f1_ob); } 60 | int f2_value; observable f2_ob; 61 | void f2(observable o) { f2_value = o.value; notify(f2_ob); } 62 | int f3_value; observable f3_ob; 63 | void f3(observable o) { f3_value = o.value; notify(f3_ob); } 64 | int f4_value; observable f4_ob; 65 | void f4(observable o) { f4_value = o.value; notify(f4_ob); } 66 | int f5_value; observable f5_ob; 67 | void f5(observable o) { f5_value = o.value; notify(f5_ob); } 68 | int f6_value; observable f6_ob; 69 | void f6(observable o) { f6_value = o.value; notify(f6_ob); } 70 | int f7_value; observable f7_ob; 71 | void f7(observable o) { f7_value = o.value; notify(f7_ob); } 72 | int f8_value; observable f8_ob; 73 | void f8(observable o) { f8_value = o.value; notify(f8_ob); } 74 | int f9_value; observable f9_ob; 75 | void f9(observable o) { f9_value = o.value; notify(f9_ob); } 76 | int f10_value; observable f10_ob; 77 | void f10(observable o) { f10_value = o.value; notify(f10_ob); } 78 | int f11_value; observable f11_ob; 79 | void f11(observable o) { f11_value = o.value; notify(f11_ob); } 80 | int f12_value; observable f12_ob; 81 | void f12(observable o) { f12_value = o.value; notify(f12_ob); } 82 | int f13_value; observable f13_ob; 83 | void f13(observable o) { f13_value = o.value; notify(f13_ob); } 84 | int f14_value; observable f14_ob; 85 | void f14(observable o) { f14_value = o.value; notify(f14_ob); } 86 | int f15_value; observable f15_ob; 87 | void f15(observable o) { f15_value = o.value; notify(f15_ob); } 88 | int f16_value; observable f16_ob; 89 | void f16(observable o) { f16_value = o.value; notify(f16_ob); } 90 | int f17_value; observable f17_ob; 91 | void f17(observable o) { f17_value = o.value; notify(f17_ob); } 92 | int f18_value; observable f18_ob; 93 | void f18(observable o) { f18_value = o.value; notify(f18_ob); } 94 | int f19_value; observable f19_ob; 95 | void f19(observable o) { f19_value = o.value; notify(f19_ob); } 96 | int f20_value; observable f20_ob; 97 | void f20(observable o) { f20_value = o.value; notify(f20_ob); } 98 | int f21_value; observable f21_ob; 99 | void f21(observable o) { f21_value = o.value; notify(f21_ob); } 100 | int f22_value; observable f22_ob; 101 | void f22(observable o) { f22_value = o.value; notify(f22_ob); } 102 | int f23_value; observable f23_ob; 103 | void f23(observable o) { f23_value = o.value; notify(f23_ob); } 104 | int f24_value; observable f24_ob; 105 | void f24(observable o) { f24_value = o.value; notify(f24_ob); } 106 | int f25_value; observable f25_ob; 107 | void f25(observable o) { f25_value = o.value; notify(f25_ob); } 108 | int f26_value; observable f26_ob; 109 | void f26(observable o) { f26_value = o.value; notify(f26_ob); } 110 | int f27_value; observable f27_ob; 111 | void f27(observable o) { f27_value = o.value; notify(f27_ob); } 112 | int f28_value; observable f28_ob; 113 | void f28(observable o) { f28_value = o.value; notify(f28_ob); } 114 | int f29_value; observable f29_ob; 115 | void f29(observable o) { f29_value = o.value; notify(f29_ob); } 116 | int f30_value; observable f30_ob; 117 | void f30(observable o) { f30_value = o.value; notify(f30_ob); } 118 | int f31_value; observable f31_ob; 119 | void f31(observable o) { f31_value = o.value; notify(f31_ob); } 120 | int f32_value; observable f32_ob; 121 | void f32(observable o) { f32_value = o.value; notify(f32_ob); } 122 | int f33_value; observable f33_ob; 123 | void f33(observable o) { f33_value = o.value; notify(f33_ob); } 124 | int f34_value; observable f34_ob; 125 | void f34(observable o) { f34_value = o.value; notify(f34_ob); } 126 | int f35_value; observable f35_ob; 127 | void f35(observable o) { f35_value = o.value; notify(f35_ob); } 128 | int f36_value; observable f36_ob; 129 | void f36(observable o) { f36_value = o.value; notify(f36_ob); } 130 | int f37_value; observable f37_ob; 131 | void f37(observable o) { f37_value = o.value; notify(f37_ob); } 132 | int f38_value; observable f38_ob; 133 | void f38(observable o) { f38_value = o.value; notify(f38_ob); } 134 | int f39_value; observable f39_ob; 135 | void f39(observable o) { f39_value = o.value; notify(f39_ob); } 136 | int f40_value; observable f40_ob; 137 | void f40(observable o) { f40_value = o.value; notify(f40_ob); } 138 | int f41_value; observable f41_ob; 139 | void f41(observable o) { f41_value = o.value; notify(f41_ob); } 140 | int f42_value; observable f42_ob; 141 | void f42(observable o) { f42_value = o.value; notify(f42_ob); } 142 | int f43_value; observable f43_ob; 143 | void f43(observable o) { f43_value = o.value; notify(f43_ob); } 144 | int f44_value; observable f44_ob; 145 | void f44(observable o) { f44_value = o.value; notify(f44_ob); } 146 | int f45_value; observable f45_ob; 147 | void f45(observable o) { f45_value = o.value; notify(f45_ob); } 148 | int f46_value; observable f46_ob; 149 | void f46(observable o) { f46_value = o.value; notify(f46_ob); } 150 | int f47_value; observable f47_ob; 151 | void f47(observable o) { f47_value = o.value; notify(f47_ob); } 152 | int f48_value; observable f48_ob; 153 | void f48(observable o) { f48_value = o.value; notify(f48_ob); } 154 | int f49_value; observable f49_ob; 155 | void f49(observable o) { f49_value = o.value; notify(f49_ob); } 156 | int f50_value; observable f50_ob; 157 | void f50(observable o) { f50_value = o.value; notify(f50_ob); } 158 | int f51_value; observable f51_ob; 159 | void f51(observable o) { f51_value = o.value; notify(f51_ob); } 160 | int f52_value; observable f52_ob; 161 | void f52(observable o) { f52_value = o.value; notify(f52_ob); } 162 | int f53_value; observable f53_ob; 163 | void f53(observable o) { f53_value = o.value; notify(f53_ob); } 164 | int f54_value; observable f54_ob; 165 | void f54(observable o) { f54_value = o.value; notify(f54_ob); } 166 | int f55_value; observable f55_ob; 167 | void f55(observable o) { f55_value = o.value; notify(f55_ob); } 168 | int f56_value; observable f56_ob; 169 | void f56(observable o) { f56_value = o.value; notify(f56_ob); } 170 | int f57_value; observable f57_ob; 171 | void f57(observable o) { f57_value = o.value; notify(f57_ob); } 172 | int f58_value; observable f58_ob; 173 | void f58(observable o) { f58_value = o.value; notify(f58_ob); } 174 | int f59_value; observable f59_ob; 175 | void f59(observable o) { f59_value = o.value; notify(f59_ob); } 176 | int f60_value; observable f60_ob; 177 | void f60(observable o) { f60_value = o.value; notify(f60_ob); } 178 | int f61_value; observable f61_ob; 179 | void f61(observable o) { f61_value = o.value; notify(f61_ob); } 180 | int f62_value; observable f62_ob; 181 | void f62(observable o) { f62_value = o.value; notify(f62_ob); } 182 | int f63_value; observable f63_ob; 183 | void f63(observable o) { f63_value = o.value; notify(f63_ob); } 184 | int f64_value; observable f64_ob; 185 | void f64(observable o) { f64_value = o.value; notify(f64_ob); } 186 | int f65_value; observable f65_ob; 187 | void f65(observable o) { f65_value = o.value; notify(f65_ob); } 188 | int f66_value; observable f66_ob; 189 | void f66(observable o) { f66_value = o.value; notify(f66_ob); } 190 | int f67_value; observable f67_ob; 191 | void f67(observable o) { f67_value = o.value; notify(f67_ob); } 192 | int f68_value; observable f68_ob; 193 | void f68(observable o) { f68_value = o.value; notify(f68_ob); } 194 | int f69_value; observable f69_ob; 195 | void f69(observable o) { f69_value = o.value; notify(f69_ob); } 196 | int f70_value; observable f70_ob; 197 | void f70(observable o) { f70_value = o.value; notify(f70_ob); } 198 | int f71_value; observable f71_ob; 199 | void f71(observable o) { f71_value = o.value; notify(f71_ob); } 200 | int f72_value; observable f72_ob; 201 | void f72(observable o) { f72_value = o.value; notify(f72_ob); } 202 | int f73_value; observable f73_ob; 203 | void f73(observable o) { f73_value = o.value; notify(f73_ob); } 204 | int f74_value; observable f74_ob; 205 | void f74(observable o) { f74_value = o.value; notify(f74_ob); } 206 | int f75_value; observable f75_ob; 207 | void f75(observable o) { f75_value = o.value; notify(f75_ob); } 208 | int f76_value; observable f76_ob; 209 | void f76(observable o) { f76_value = o.value; notify(f76_ob); } 210 | int f77_value; observable f77_ob; 211 | void f77(observable o) { f77_value = o.value; notify(f77_ob); } 212 | int f78_value; observable f78_ob; 213 | void f78(observable o) { f78_value = o.value; notify(f78_ob); } 214 | int f79_value; observable f79_ob; 215 | void f79(observable o) { f79_value = o.value; notify(f79_ob); } 216 | int f80_value; observable f80_ob; 217 | void f80(observable o) { f80_value = o.value; notify(f80_ob); } 218 | int f81_value; observable f81_ob; 219 | void f81(observable o) { f81_value = o.value; notify(f81_ob); } 220 | int f82_value; observable f82_ob; 221 | void f82(observable o) { f82_value = o.value; notify(f82_ob); } 222 | int f83_value; observable f83_ob; 223 | void f83(observable o) { f83_value = o.value; notify(f83_ob); } 224 | int f84_value; observable f84_ob; 225 | void f84(observable o) { f84_value = o.value; notify(f84_ob); } 226 | int f85_value; observable f85_ob; 227 | void f85(observable o) { f85_value = o.value; notify(f85_ob); } 228 | int f86_value; observable f86_ob; 229 | void f86(observable o) { f86_value = o.value; notify(f86_ob); } 230 | int f87_value; observable f87_ob; 231 | void f87(observable o) { f87_value = o.value; notify(f87_ob); } 232 | int f88_value; observable f88_ob; 233 | void f88(observable o) { f88_value = o.value; notify(f88_ob); } 234 | int f89_value; observable f89_ob; 235 | void f89(observable o) { f89_value = o.value; notify(f89_ob); } 236 | int f90_value; observable f90_ob; 237 | void f90(observable o) { f90_value = o.value; notify(f90_ob); } 238 | int f91_value; observable f91_ob; 239 | void f91(observable o) { f91_value = o.value; notify(f91_ob); } 240 | int f92_value; observable f92_ob; 241 | void f92(observable o) { f92_value = o.value; notify(f92_ob); } 242 | int f93_value; observable f93_ob; 243 | void f93(observable o) { f93_value = o.value; notify(f93_ob); } 244 | int f94_value; observable f94_ob; 245 | void f94(observable o) { f94_value = o.value; notify(f94_ob); } 246 | int f95_value; observable f95_ob; 247 | void f95(observable o) { f95_value = o.value; notify(f95_ob); } 248 | int f96_value; observable f96_ob; 249 | void f96(observable o) { f96_value = o.value; notify(f96_ob); } 250 | int f97_value; observable f97_ob; 251 | void f97(observable o) { f97_value = o.value; notify(f97_ob); } 252 | int f98_value; observable f98_ob; 253 | void f98(observable o) { f98_value = o.value; notify(f98_ob); } 254 | int f99_value; observable f99_ob; 255 | void f99(observable o) { f99_value = o.value; notify(f99_ob); } 256 | int f100_value; observable f100_ob; 257 | void f100(observable o) { f100_value = o.value; notify(f100_ob); } 258 | 259 | int main(void) { 260 | // source 261 | observable source = new_observable(); 262 | 263 | f1_ob = new_observable(); 264 | f2_ob = new_observable(); 265 | f3_ob = new_observable(); 266 | f4_ob = new_observable(); 267 | f5_ob = new_observable(); 268 | f6_ob = new_observable(); 269 | f7_ob = new_observable(); 270 | f8_ob = new_observable(); 271 | f9_ob = new_observable(); 272 | f10_ob = new_observable(); 273 | f11_ob = new_observable(); 274 | f12_ob = new_observable(); 275 | f13_ob = new_observable(); 276 | f14_ob = new_observable(); 277 | f15_ob = new_observable(); 278 | f16_ob = new_observable(); 279 | f17_ob = new_observable(); 280 | f18_ob = new_observable(); 281 | f19_ob = new_observable(); 282 | f20_ob = new_observable(); 283 | f21_ob = new_observable(); 284 | f22_ob = new_observable(); 285 | f23_ob = new_observable(); 286 | f24_ob = new_observable(); 287 | f25_ob = new_observable(); 288 | f26_ob = new_observable(); 289 | f27_ob = new_observable(); 290 | f28_ob = new_observable(); 291 | f29_ob = new_observable(); 292 | f30_ob = new_observable(); 293 | f31_ob = new_observable(); 294 | f32_ob = new_observable(); 295 | f33_ob = new_observable(); 296 | f34_ob = new_observable(); 297 | f35_ob = new_observable(); 298 | f36_ob = new_observable(); 299 | f37_ob = new_observable(); 300 | f38_ob = new_observable(); 301 | f39_ob = new_observable(); 302 | f40_ob = new_observable(); 303 | f41_ob = new_observable(); 304 | f42_ob = new_observable(); 305 | f43_ob = new_observable(); 306 | f44_ob = new_observable(); 307 | f45_ob = new_observable(); 308 | f46_ob = new_observable(); 309 | f47_ob = new_observable(); 310 | f48_ob = new_observable(); 311 | f49_ob = new_observable(); 312 | f50_ob = new_observable(); 313 | f51_ob = new_observable(); 314 | f52_ob = new_observable(); 315 | f53_ob = new_observable(); 316 | f54_ob = new_observable(); 317 | f55_ob = new_observable(); 318 | f56_ob = new_observable(); 319 | f57_ob = new_observable(); 320 | f58_ob = new_observable(); 321 | f59_ob = new_observable(); 322 | f60_ob = new_observable(); 323 | f61_ob = new_observable(); 324 | f62_ob = new_observable(); 325 | f63_ob = new_observable(); 326 | f64_ob = new_observable(); 327 | f65_ob = new_observable(); 328 | f66_ob = new_observable(); 329 | f67_ob = new_observable(); 330 | f68_ob = new_observable(); 331 | f69_ob = new_observable(); 332 | f70_ob = new_observable(); 333 | f71_ob = new_observable(); 334 | f72_ob = new_observable(); 335 | f73_ob = new_observable(); 336 | f74_ob = new_observable(); 337 | f75_ob = new_observable(); 338 | f76_ob = new_observable(); 339 | f77_ob = new_observable(); 340 | f78_ob = new_observable(); 341 | f79_ob = new_observable(); 342 | f80_ob = new_observable(); 343 | f81_ob = new_observable(); 344 | f82_ob = new_observable(); 345 | f83_ob = new_observable(); 346 | f84_ob = new_observable(); 347 | f85_ob = new_observable(); 348 | f86_ob = new_observable(); 349 | f87_ob = new_observable(); 350 | f88_ob = new_observable(); 351 | f89_ob = new_observable(); 352 | f90_ob = new_observable(); 353 | f91_ob = new_observable(); 354 | f92_ob = new_observable(); 355 | f93_ob = new_observable(); 356 | f94_ob = new_observable(); 357 | f95_ob = new_observable(); 358 | f96_ob = new_observable(); 359 | f97_ob = new_observable(); 360 | f98_ob = new_observable(); 361 | f99_ob = new_observable(); 362 | f100_ob = new_observable(); 363 | 364 | add(&source, f1); 365 | add(&f1_ob, f2); 366 | add(&f2_ob, f3); 367 | add(&f3_ob, f4); 368 | add(&f4_ob, f5); 369 | add(&f5_ob, f6); 370 | add(&f6_ob, f7); 371 | add(&f7_ob, f8); 372 | add(&f8_ob, f9); 373 | add(&f9_ob, f10); 374 | add(&f10_ob, f11); 375 | add(&f11_ob, f12); 376 | add(&f12_ob, f13); 377 | add(&f13_ob, f14); 378 | add(&f14_ob, f15); 379 | add(&f15_ob, f16); 380 | add(&f16_ob, f17); 381 | add(&f17_ob, f18); 382 | add(&f18_ob, f19); 383 | add(&f19_ob, f20); 384 | add(&f20_ob, f21); 385 | add(&f21_ob, f22); 386 | add(&f22_ob, f23); 387 | add(&f23_ob, f24); 388 | add(&f24_ob, f25); 389 | add(&f25_ob, f26); 390 | add(&f26_ob, f27); 391 | add(&f27_ob, f28); 392 | add(&f28_ob, f29); 393 | add(&f29_ob, f30); 394 | add(&f30_ob, f31); 395 | add(&f31_ob, f32); 396 | add(&f32_ob, f33); 397 | add(&f33_ob, f34); 398 | add(&f34_ob, f35); 399 | add(&f35_ob, f36); 400 | add(&f36_ob, f37); 401 | add(&f37_ob, f38); 402 | add(&f38_ob, f39); 403 | add(&f39_ob, f40); 404 | add(&f40_ob, f41); 405 | add(&f41_ob, f42); 406 | add(&f42_ob, f43); 407 | add(&f43_ob, f44); 408 | add(&f44_ob, f45); 409 | add(&f45_ob, f46); 410 | add(&f46_ob, f47); 411 | add(&f47_ob, f48); 412 | add(&f48_ob, f49); 413 | add(&f49_ob, f50); 414 | add(&f50_ob, f51); 415 | add(&f51_ob, f52); 416 | add(&f52_ob, f53); 417 | add(&f53_ob, f54); 418 | add(&f54_ob, f55); 419 | add(&f55_ob, f56); 420 | add(&f56_ob, f57); 421 | add(&f57_ob, f58); 422 | add(&f58_ob, f59); 423 | add(&f59_ob, f60); 424 | add(&f60_ob, f61); 425 | add(&f61_ob, f62); 426 | add(&f62_ob, f63); 427 | add(&f63_ob, f64); 428 | add(&f64_ob, f65); 429 | add(&f65_ob, f66); 430 | add(&f66_ob, f67); 431 | add(&f67_ob, f68); 432 | add(&f68_ob, f69); 433 | add(&f69_ob, f70); 434 | add(&f70_ob, f71); 435 | add(&f71_ob, f72); 436 | add(&f72_ob, f73); 437 | add(&f73_ob, f74); 438 | add(&f74_ob, f75); 439 | add(&f75_ob, f76); 440 | add(&f76_ob, f77); 441 | add(&f77_ob, f78); 442 | add(&f78_ob, f79); 443 | add(&f79_ob, f80); 444 | add(&f80_ob, f81); 445 | add(&f81_ob, f82); 446 | add(&f82_ob, f83); 447 | add(&f83_ob, f84); 448 | add(&f84_ob, f85); 449 | add(&f85_ob, f86); 450 | add(&f86_ob, f87); 451 | add(&f87_ob, f88); 452 | add(&f88_ob, f89); 453 | add(&f89_ob, f90); 454 | add(&f90_ob, f91); 455 | add(&f91_ob, f92); 456 | add(&f92_ob, f93); 457 | add(&f93_ob, f94); 458 | add(&f94_ob, f95); 459 | add(&f95_ob, f96); 460 | add(&f96_ob, f97); 461 | add(&f97_ob, f98); 462 | add(&f98_ob, f99); 463 | add(&f99_ob, f100); 464 | 465 | int total = 0; 466 | 467 | for(int iteration=0; iteration 2 | #include 3 | #include 4 | 5 | #include "lib/time.h" 6 | 7 | // a minimalistic observer pattern implementation 8 | 9 | typedef struct observable observable; 10 | 11 | typedef void (*observing_function)(observable); 12 | 13 | typedef struct observer { 14 | observing_function f; 15 | struct observer *next; 16 | } observer; 17 | 18 | struct observable { 19 | int value; 20 | observer *observer; 21 | }; 22 | 23 | observable new_observable() { 24 | observable o; 25 | o.value = 0; 26 | o.observer = NULL; 27 | return o; 28 | } 29 | 30 | void add(observable *o, observing_function f) { 31 | observer *n = malloc(sizeof(struct observer)); 32 | n->next = o->observer; 33 | n->f = f; 34 | o->observer = n; 35 | } 36 | 37 | void notify(observable o) { 38 | int c = 0; 39 | observer *i = o.observer; 40 | while(i) { 41 | c++; 42 | i->f(o); 43 | i = i->next; 44 | } 45 | } 46 | 47 | void update(observable o, int value) { 48 | o.value = value; 49 | notify(o); 50 | } 51 | 52 | #define FAN_WIDTH 100 53 | #define UPDATES 5000000 54 | #define ITERATIONS 5 55 | 56 | int f1_value; observable f1_ob; 57 | void f1(observable o) { f1_value = o.value; notify(f1_ob); } 58 | int f2_value; observable f2_ob; 59 | void f2(observable o) { f2_value = o.value; notify(f2_ob); } 60 | int f3_value; observable f3_ob; 61 | void f3(observable o) { f3_value = o.value; notify(f3_ob); } 62 | int f4_value; observable f4_ob; 63 | void f4(observable o) { f4_value = o.value; notify(f4_ob); } 64 | int f5_value; observable f5_ob; 65 | void f5(observable o) { f5_value = o.value; notify(f5_ob); } 66 | int f6_value; observable f6_ob; 67 | void f6(observable o) { f6_value = o.value; notify(f6_ob); } 68 | int f7_value; observable f7_ob; 69 | void f7(observable o) { f7_value = o.value; notify(f7_ob); } 70 | int f8_value; observable f8_ob; 71 | void f8(observable o) { f8_value = o.value; notify(f8_ob); } 72 | int f9_value; observable f9_ob; 73 | void f9(observable o) { f9_value = o.value; notify(f9_ob); } 74 | int f10_value; observable f10_ob; 75 | void f10(observable o) { f10_value = o.value; notify(f10_ob); } 76 | int f11_value; observable f11_ob; 77 | void f11(observable o) { f11_value = o.value; notify(f11_ob); } 78 | int f12_value; observable f12_ob; 79 | void f12(observable o) { f12_value = o.value; notify(f12_ob); } 80 | int f13_value; observable f13_ob; 81 | void f13(observable o) { f13_value = o.value; notify(f13_ob); } 82 | int f14_value; observable f14_ob; 83 | void f14(observable o) { f14_value = o.value; notify(f14_ob); } 84 | int f15_value; observable f15_ob; 85 | void f15(observable o) { f15_value = o.value; notify(f15_ob); } 86 | int f16_value; observable f16_ob; 87 | void f16(observable o) { f16_value = o.value; notify(f16_ob); } 88 | int f17_value; observable f17_ob; 89 | void f17(observable o) { f17_value = o.value; notify(f17_ob); } 90 | int f18_value; observable f18_ob; 91 | void f18(observable o) { f18_value = o.value; notify(f18_ob); } 92 | int f19_value; observable f19_ob; 93 | void f19(observable o) { f19_value = o.value; notify(f19_ob); } 94 | int f20_value; observable f20_ob; 95 | void f20(observable o) { f20_value = o.value; notify(f20_ob); } 96 | int f21_value; observable f21_ob; 97 | void f21(observable o) { f21_value = o.value; notify(f21_ob); } 98 | int f22_value; observable f22_ob; 99 | void f22(observable o) { f22_value = o.value; notify(f22_ob); } 100 | int f23_value; observable f23_ob; 101 | void f23(observable o) { f23_value = o.value; notify(f23_ob); } 102 | int f24_value; observable f24_ob; 103 | void f24(observable o) { f24_value = o.value; notify(f24_ob); } 104 | int f25_value; observable f25_ob; 105 | void f25(observable o) { f25_value = o.value; notify(f25_ob); } 106 | int f26_value; observable f26_ob; 107 | void f26(observable o) { f26_value = o.value; notify(f26_ob); } 108 | int f27_value; observable f27_ob; 109 | void f27(observable o) { f27_value = o.value; notify(f27_ob); } 110 | int f28_value; observable f28_ob; 111 | void f28(observable o) { f28_value = o.value; notify(f28_ob); } 112 | int f29_value; observable f29_ob; 113 | void f29(observable o) { f29_value = o.value; notify(f29_ob); } 114 | int f30_value; observable f30_ob; 115 | void f30(observable o) { f30_value = o.value; notify(f30_ob); } 116 | int f31_value; observable f31_ob; 117 | void f31(observable o) { f31_value = o.value; notify(f31_ob); } 118 | int f32_value; observable f32_ob; 119 | void f32(observable o) { f32_value = o.value; notify(f32_ob); } 120 | int f33_value; observable f33_ob; 121 | void f33(observable o) { f33_value = o.value; notify(f33_ob); } 122 | int f34_value; observable f34_ob; 123 | void f34(observable o) { f34_value = o.value; notify(f34_ob); } 124 | int f35_value; observable f35_ob; 125 | void f35(observable o) { f35_value = o.value; notify(f35_ob); } 126 | int f36_value; observable f36_ob; 127 | void f36(observable o) { f36_value = o.value; notify(f36_ob); } 128 | int f37_value; observable f37_ob; 129 | void f37(observable o) { f37_value = o.value; notify(f37_ob); } 130 | int f38_value; observable f38_ob; 131 | void f38(observable o) { f38_value = o.value; notify(f38_ob); } 132 | int f39_value; observable f39_ob; 133 | void f39(observable o) { f39_value = o.value; notify(f39_ob); } 134 | int f40_value; observable f40_ob; 135 | void f40(observable o) { f40_value = o.value; notify(f40_ob); } 136 | int f41_value; observable f41_ob; 137 | void f41(observable o) { f41_value = o.value; notify(f41_ob); } 138 | int f42_value; observable f42_ob; 139 | void f42(observable o) { f42_value = o.value; notify(f42_ob); } 140 | int f43_value; observable f43_ob; 141 | void f43(observable o) { f43_value = o.value; notify(f43_ob); } 142 | int f44_value; observable f44_ob; 143 | void f44(observable o) { f44_value = o.value; notify(f44_ob); } 144 | int f45_value; observable f45_ob; 145 | void f45(observable o) { f45_value = o.value; notify(f45_ob); } 146 | int f46_value; observable f46_ob; 147 | void f46(observable o) { f46_value = o.value; notify(f46_ob); } 148 | int f47_value; observable f47_ob; 149 | void f47(observable o) { f47_value = o.value; notify(f47_ob); } 150 | int f48_value; observable f48_ob; 151 | void f48(observable o) { f48_value = o.value; notify(f48_ob); } 152 | int f49_value; observable f49_ob; 153 | void f49(observable o) { f49_value = o.value; notify(f49_ob); } 154 | int f50_value; observable f50_ob; 155 | void f50(observable o) { f50_value = o.value; notify(f50_ob); } 156 | int f51_value; observable f51_ob; 157 | void f51(observable o) { f51_value = o.value; notify(f51_ob); } 158 | int f52_value; observable f52_ob; 159 | void f52(observable o) { f52_value = o.value; notify(f52_ob); } 160 | int f53_value; observable f53_ob; 161 | void f53(observable o) { f53_value = o.value; notify(f53_ob); } 162 | int f54_value; observable f54_ob; 163 | void f54(observable o) { f54_value = o.value; notify(f54_ob); } 164 | int f55_value; observable f55_ob; 165 | void f55(observable o) { f55_value = o.value; notify(f55_ob); } 166 | int f56_value; observable f56_ob; 167 | void f56(observable o) { f56_value = o.value; notify(f56_ob); } 168 | int f57_value; observable f57_ob; 169 | void f57(observable o) { f57_value = o.value; notify(f57_ob); } 170 | int f58_value; observable f58_ob; 171 | void f58(observable o) { f58_value = o.value; notify(f58_ob); } 172 | int f59_value; observable f59_ob; 173 | void f59(observable o) { f59_value = o.value; notify(f59_ob); } 174 | int f60_value; observable f60_ob; 175 | void f60(observable o) { f60_value = o.value; notify(f60_ob); } 176 | int f61_value; observable f61_ob; 177 | void f61(observable o) { f61_value = o.value; notify(f61_ob); } 178 | int f62_value; observable f62_ob; 179 | void f62(observable o) { f62_value = o.value; notify(f62_ob); } 180 | int f63_value; observable f63_ob; 181 | void f63(observable o) { f63_value = o.value; notify(f63_ob); } 182 | int f64_value; observable f64_ob; 183 | void f64(observable o) { f64_value = o.value; notify(f64_ob); } 184 | int f65_value; observable f65_ob; 185 | void f65(observable o) { f65_value = o.value; notify(f65_ob); } 186 | int f66_value; observable f66_ob; 187 | void f66(observable o) { f66_value = o.value; notify(f66_ob); } 188 | int f67_value; observable f67_ob; 189 | void f67(observable o) { f67_value = o.value; notify(f67_ob); } 190 | int f68_value; observable f68_ob; 191 | void f68(observable o) { f68_value = o.value; notify(f68_ob); } 192 | int f69_value; observable f69_ob; 193 | void f69(observable o) { f69_value = o.value; notify(f69_ob); } 194 | int f70_value; observable f70_ob; 195 | void f70(observable o) { f70_value = o.value; notify(f70_ob); } 196 | int f71_value; observable f71_ob; 197 | void f71(observable o) { f71_value = o.value; notify(f71_ob); } 198 | int f72_value; observable f72_ob; 199 | void f72(observable o) { f72_value = o.value; notify(f72_ob); } 200 | int f73_value; observable f73_ob; 201 | void f73(observable o) { f73_value = o.value; notify(f73_ob); } 202 | int f74_value; observable f74_ob; 203 | void f74(observable o) { f74_value = o.value; notify(f74_ob); } 204 | int f75_value; observable f75_ob; 205 | void f75(observable o) { f75_value = o.value; notify(f75_ob); } 206 | int f76_value; observable f76_ob; 207 | void f76(observable o) { f76_value = o.value; notify(f76_ob); } 208 | int f77_value; observable f77_ob; 209 | void f77(observable o) { f77_value = o.value; notify(f77_ob); } 210 | int f78_value; observable f78_ob; 211 | void f78(observable o) { f78_value = o.value; notify(f78_ob); } 212 | int f79_value; observable f79_ob; 213 | void f79(observable o) { f79_value = o.value; notify(f79_ob); } 214 | int f80_value; observable f80_ob; 215 | void f80(observable o) { f80_value = o.value; notify(f80_ob); } 216 | int f81_value; observable f81_ob; 217 | void f81(observable o) { f81_value = o.value; notify(f81_ob); } 218 | int f82_value; observable f82_ob; 219 | void f82(observable o) { f82_value = o.value; notify(f82_ob); } 220 | int f83_value; observable f83_ob; 221 | void f83(observable o) { f83_value = o.value; notify(f83_ob); } 222 | int f84_value; observable f84_ob; 223 | void f84(observable o) { f84_value = o.value; notify(f84_ob); } 224 | int f85_value; observable f85_ob; 225 | void f85(observable o) { f85_value = o.value; notify(f85_ob); } 226 | int f86_value; observable f86_ob; 227 | void f86(observable o) { f86_value = o.value; notify(f86_ob); } 228 | int f87_value; observable f87_ob; 229 | void f87(observable o) { f87_value = o.value; notify(f87_ob); } 230 | int f88_value; observable f88_ob; 231 | void f88(observable o) { f88_value = o.value; notify(f88_ob); } 232 | int f89_value; observable f89_ob; 233 | void f89(observable o) { f89_value = o.value; notify(f89_ob); } 234 | int f90_value; observable f90_ob; 235 | void f90(observable o) { f90_value = o.value; notify(f90_ob); } 236 | int f91_value; observable f91_ob; 237 | void f91(observable o) { f91_value = o.value; notify(f91_ob); } 238 | int f92_value; observable f92_ob; 239 | void f92(observable o) { f92_value = o.value; notify(f92_ob); } 240 | int f93_value; observable f93_ob; 241 | void f93(observable o) { f93_value = o.value; notify(f93_ob); } 242 | int f94_value; observable f94_ob; 243 | void f94(observable o) { f94_value = o.value; notify(f94_ob); } 244 | int f95_value; observable f95_ob; 245 | void f95(observable o) { f95_value = o.value; notify(f95_ob); } 246 | int f96_value; observable f96_ob; 247 | void f96(observable o) { f96_value = o.value; notify(f96_ob); } 248 | int f97_value; observable f97_ob; 249 | void f97(observable o) { f97_value = o.value; notify(f97_ob); } 250 | int f98_value; observable f98_ob; 251 | void f98(observable o) { f98_value = o.value; notify(f98_ob); } 252 | int f99_value; observable f99_ob; 253 | void f99(observable o) { f99_value = o.value; notify(f99_ob); } 254 | int f100_value; observable f100_ob; 255 | void f100(observable o) { f100_value = o.value; notify(f100_ob); } 256 | 257 | int main(void) { 258 | // source 259 | observable source = new_observable(); 260 | 261 | add(&source, f1); 262 | add(&source, f2); 263 | add(&source, f3); 264 | add(&source, f4); 265 | add(&source, f5); 266 | add(&source, f6); 267 | add(&source, f7); 268 | add(&source, f8); 269 | add(&source, f9); 270 | add(&source, f10); 271 | add(&source, f11); 272 | add(&source, f12); 273 | add(&source, f13); 274 | add(&source, f14); 275 | add(&source, f15); 276 | add(&source, f16); 277 | add(&source, f17); 278 | add(&source, f18); 279 | add(&source, f19); 280 | add(&source, f20); 281 | add(&source, f21); 282 | add(&source, f22); 283 | add(&source, f23); 284 | add(&source, f24); 285 | add(&source, f25); 286 | add(&source, f26); 287 | add(&source, f27); 288 | add(&source, f28); 289 | add(&source, f29); 290 | add(&source, f30); 291 | add(&source, f31); 292 | add(&source, f32); 293 | add(&source, f33); 294 | add(&source, f34); 295 | add(&source, f35); 296 | add(&source, f36); 297 | add(&source, f37); 298 | add(&source, f38); 299 | add(&source, f39); 300 | add(&source, f40); 301 | add(&source, f41); 302 | add(&source, f42); 303 | add(&source, f43); 304 | add(&source, f44); 305 | add(&source, f45); 306 | add(&source, f46); 307 | add(&source, f47); 308 | add(&source, f48); 309 | add(&source, f49); 310 | add(&source, f50); 311 | add(&source, f51); 312 | add(&source, f52); 313 | add(&source, f53); 314 | add(&source, f54); 315 | add(&source, f55); 316 | add(&source, f56); 317 | add(&source, f57); 318 | add(&source, f58); 319 | add(&source, f59); 320 | add(&source, f60); 321 | add(&source, f61); 322 | add(&source, f62); 323 | add(&source, f63); 324 | add(&source, f64); 325 | add(&source, f65); 326 | add(&source, f66); 327 | add(&source, f67); 328 | add(&source, f68); 329 | add(&source, f69); 330 | add(&source, f70); 331 | add(&source, f71); 332 | add(&source, f72); 333 | add(&source, f73); 334 | add(&source, f74); 335 | add(&source, f75); 336 | add(&source, f76); 337 | add(&source, f77); 338 | add(&source, f78); 339 | add(&source, f79); 340 | add(&source, f80); 341 | add(&source, f81); 342 | add(&source, f82); 343 | add(&source, f83); 344 | add(&source, f84); 345 | add(&source, f85); 346 | add(&source, f86); 347 | add(&source, f87); 348 | add(&source, f88); 349 | add(&source, f89); 350 | add(&source, f90); 351 | add(&source, f91); 352 | add(&source, f92); 353 | add(&source, f93); 354 | add(&source, f94); 355 | add(&source, f95); 356 | add(&source, f96); 357 | add(&source, f97); 358 | add(&source, f98); 359 | add(&source, f99); 360 | add(&source, f100); 361 | 362 | int total = 0; 363 | 364 | for(int iteration=0; iteration 2 | #include 3 | 4 | #include "lib/time.h" 5 | 6 | #include "reactive-c/api.h" 7 | 8 | void id(observation_t ob) { 9 | *((int*)ob->observer) = *((int*)ob->observeds[0]); 10 | } 11 | 12 | #define CHAIN_LENGTH 100 13 | #define UPDATES 5000000 14 | #define ITERATIONS 5 15 | 16 | int main(void) { 17 | // source 18 | int _a = 0; 19 | observable_t source = observe(_a); 20 | 21 | // now chain 100 observers 22 | observable_t tail = source; 23 | for(int i=0; i 2 | #include 3 | 4 | #include "lib/time.h" 5 | 6 | #include "reactive-c/api.h" 7 | 8 | void id(observation_t ob) { 9 | *((int*)ob->observer) = *((int*)ob->observeds[0]); 10 | } 11 | 12 | #define FAN_WIDTH 100 13 | #define UPDATES 5000000 14 | #define ITERATIONS 5 15 | 16 | int main(void) { 17 | // source 18 | int _a = 0; 19 | observable_t source = observe(_a); 20 | 21 | // now fan to 100 observers 22 | for(int i=0; iself)) { return; } 11 | 12 | // track updates by marking their link as observed 13 | int count = 0; 14 | int observed = 0; 15 | foreach(observable_li_t, iter, ob->self->observeds) { 16 | if(iter->ob == ob->source) { 17 | _debug("ALL OBSERVED UPDATE", ob->source); 18 | _mark_observed(iter); 19 | } 20 | // keep stats 21 | count++; 22 | if(_is_observed(iter)) { observed++; } 23 | } 24 | 25 | // have we observed all items emit updates? 26 | if(observed == count) { 27 | _propagate(ob->self); 28 | _debug("FINALIZED ALL", ob->self); 29 | dispose(ob->self); 30 | } 31 | } 32 | 33 | observable_t __all(char *label, observables_t observeds) { 34 | observable_t observer = _combine(label, observeds, _all_handler); 35 | _debug("ALL", observer); 36 | return observer; 37 | } 38 | 39 | void _any_handler(observation_t ob) { 40 | if(_is_delayed(ob->self)) { return; } 41 | // as soon as we are "touched", we propagate again 42 | _propagate(ob->self); 43 | 44 | _debug("FINALIZED ANY", ob->self); 45 | 46 | // any hit is ok, we dispose ourselves and will be cleaned up by one of our 47 | // observeds when its done with us. in the meantime, we'll ignore more updates 48 | dispose(ob->self); 49 | } 50 | 51 | observable_t __any(char *label, observables_t observeds) { 52 | observable_t observer = _combine(label, observeds, _any_handler); 53 | _debug("ANY", observer); 54 | return observer; 55 | } 56 | -------------------------------------------------------------------------------- /src/reactive-c/all-any.h: -------------------------------------------------------------------------------- 1 | #ifndef __ALL_ANY_H 2 | #define __ALL_ANY_H 3 | 4 | // ... emits true when all observables have emitted at least once 5 | observable_t __all(char*, observables_t); 6 | 7 | // ... emits true when at least one observable has emitted at least once 8 | observable_t __any(char*, observables_t); 9 | 10 | // actual public API 11 | #define all(...) __all("all(" #__VA_ARGS__ ")", each(__VA_ARGS__)) 12 | #define any(...) __any("any(" #__VA_ARGS__ ")", each(__VA_ARGS__)) 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/reactive-c/api.h: -------------------------------------------------------------------------------- 1 | #ifndef __API_H 2 | #define __API_H 3 | 4 | // this header includes all public headers in one go 5 | 6 | #include "observable.h" 7 | #include "observables.h" 8 | #include "all-any.h" 9 | #include "folding.h" 10 | #include "lift.h" 11 | #include "merge.h" 12 | #include "filter.h" 13 | #include "script.h" 14 | #include "dot.h" 15 | #include "when.h" 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/reactive-c/debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "observable.h" 4 | #include "observables.h" 5 | #include "internals.h" 6 | 7 | #include "iterator.h" 8 | 9 | void _debug_level(char* title, observable_t this, int level) { 10 | printf("%*s: %s (%p) prop:", level, title, this->label, (void*)this); 11 | if(_is_disposed(this)) { printf(" disposed"); } 12 | if(_is_suspended(this)) { printf(" suspended"); } 13 | if(_is_delayed(this)) { printf(" delayed"); } 14 | if(!_is_propagating(this)) { printf(" no_propagation"); } 15 | printf("\n"); 16 | if(this->observeds && this->observeds) { 17 | printf("%*.s observing:\n", level, ""); 18 | foreach(observable_li_t, iter, this->observeds) { 19 | _debug_level(" - ", iter->ob, level+3); 20 | } 21 | } 22 | if(_is_script(this)) { 23 | printf("steps:\n"); 24 | observable_t step = this->next; 25 | int c = 1; 26 | while(step) { 27 | printf("%d)\n", c++); 28 | _debug_level("", step, level + 3); 29 | step = step->next; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/reactive-c/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef __DEBUG_H 2 | #define __DEBUG_H 3 | 4 | // debugging functionality 5 | #ifndef NDEBUG 6 | void _debug_level(char*, observable_t, int); 7 | #define _debug(t,o) _debug_level(t,o,0) 8 | #define _debug_printf(s,...) printf(s,__VA_ARGS__) 9 | #else 10 | #define _debug(t,o) ((void)0) 11 | #define _debug_printf(s,...) ((void)0) 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/reactive-c/dot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "observable.h" 5 | #include "observables.h" 6 | #include "internals.h" 7 | 8 | #include "iterator.h" 9 | 10 | #include "dot.h" 11 | 12 | void __to_dot(observable_t this, FILE *fp, int flags, bool preamble) { 13 | if(_is_exported(this)) { return; } 14 | _mark_exported(this); 15 | 16 | bool show_messages = flags & DOT_MESSAGE_STYLE; 17 | bool show_memory = flags & DOT_SHOW_MEMORY; 18 | bool show_label = !(flags & DOT_HIDE_LABELS); 19 | bool small_rank_sep = flags & DOT_SMALL_RANK_SEP; 20 | bool shape_is_circle = flags & DOT_SHAPE_IS_CIRCLE; 21 | bool show_level = flags & DOT_SHOW_LEVEL; 22 | 23 | if(preamble) { 24 | fprintf(fp, 25 | "digraph {\n" 26 | " ordering=out;\n" 27 | " ranksep=.%d;\n" 28 | " rankdir = %s;\n" 29 | " node [shape=%s, fixedsize=true, fontsize=11, " 30 | " fontname=\"Courier\", width=.25, height=.25];\n" 31 | " edge [arrowsize=.6]\n", 32 | small_rank_sep ? 2 : 4, 33 | show_messages ? "TB" : "BT", 34 | shape_is_circle ? "circle" : "rectangle" 35 | ); 36 | } 37 | 38 | // self node 39 | fprintf(fp, "subgraph level%d { rank=same; \"%p\" [label=\"%s", 40 | _is_script_part(this) ? 666 : this->level, (void*)this, 41 | show_label ? this->label : ""); 42 | if(show_level) { fprintf(fp, " @ %d", this->level); } 43 | if(show_memory) { fprintf(fp, "\n%p",(void*)this); } 44 | fprintf(fp, "\""); 45 | 46 | // delayed/suspended observables are grey 47 | if(_is_delayed(this) || _is_suspended(this)) { 48 | fprintf(fp, " color=\"grey\", style=\"filled\""); 49 | } 50 | // values are green 51 | if(_is_value(this)) { 52 | fprintf(fp, " color=\"green\", style=\"filled\""); 53 | } 54 | 55 | // values are green 56 | if(_is_marked(this)) { 57 | fprintf(fp, " color=\"red\", style=\"filled\""); 58 | } 59 | 60 | fprintf(fp, "] }\n"); 61 | 62 | // observeds 63 | foreach(observable_li_t, iter, this->observeds) { 64 | // only generate links for observed ... not also for observers 65 | if(!_is_exported(iter)) { 66 | _mark_exported(iter); 67 | fprintf(fp, "\"%p\" -> \"%p\"%s\n", 68 | (void*)this, (void*)iter->ob, show_messages ? " [dir=back]": "" ); 69 | } 70 | // recurse 71 | __to_dot(iter->ob, fp, flags, false); 72 | } 73 | 74 | // sequential relationships (e.g. scripts' steps) 75 | if(this->next) { 76 | fprintf(fp, "\"%p\" -> \"%p\" [style=\"dotted\"]\n", 77 | (void*)this, (void*)this->next); 78 | fprintf(fp, "{ rank = same; \"%p\" \"%p\" }", 79 | (void*)this, (void*)this->next); 80 | } 81 | 82 | // recurse observers 83 | foreach(observable_li_t, iter, this->observers) { 84 | __to_dot(iter->ob, fp, flags, false); 85 | } 86 | 87 | // recurse parent 88 | if(this->parent) { 89 | __to_dot(this->parent, fp, flags, false); 90 | } 91 | 92 | if(preamble) { fprintf(fp, "}\n"); } 93 | } 94 | -------------------------------------------------------------------------------- /src/reactive-c/dot.h: -------------------------------------------------------------------------------- 1 | #ifndef __DOT_H 2 | #define __DOT_H 3 | 4 | #include 5 | #include 6 | 7 | #include "observable.h" 8 | 9 | // dumps the given observable (recursively) in dot format 10 | void __to_dot(observable_t, FILE*, int, bool); 11 | 12 | #define DOT_DEFAULT 0 13 | #define DOT_SHOW_MEMORY 1 14 | #define DOT_MESSAGE_STYLE 2 15 | #define DOT_HIDE_LABELS 4 16 | #define DOT_SMALL_RANK_SEP 8 17 | #define DOT_SHAPE_IS_CIRCLE 16 18 | #define DOT_SHOW_MARKED 32 19 | #define DOT_SHOW_LEVEL 64 20 | 21 | #define __d1(x) __to_dot(x, stdout, DOT_DEFAULT, true) 22 | #define __d2(x, f) __to_dot(x, f, DOT_DEFAULT, true) 23 | #define __d3(x, f, s) __to_dot(x, f, s, true) 24 | 25 | #define __dx(_1, _2, _3, NAME,...) NAME 26 | 27 | // actual public API 28 | #define to_dot(...) __dx(__VA_ARGS__, __d3, __d2, __d1, 0)(__VA_ARGS__) 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/reactive-c/filter.c: -------------------------------------------------------------------------------- 1 | #include "observable.h" 2 | #include "internals.h" 3 | 4 | // filter support 5 | void _copy_value(observation_t ob) { 6 | // redirect value to the value of the emitting merged observable 7 | observable_value_copy(ob->source, ob->self); 8 | 9 | // cached args are out of date, because we're modifying the pointer itself 10 | // force refresh cache on all observers of merged_ob 11 | _update_observers_args(ob->self); 12 | } 13 | 14 | observable_t __filter(int size, observable_t observable, validator_t validator) { 15 | observable_t filter = observe(just(observable), _copy_value, size); 16 | filter->validate = validator; 17 | return filter; 18 | } 19 | -------------------------------------------------------------------------------- /src/reactive-c/filter.h: -------------------------------------------------------------------------------- 1 | #ifndef __FILTER_H 2 | #define __FILTER_H 3 | 4 | #include 5 | 6 | #include "observable.h" 7 | 8 | observable_t __filter(int size, observable_t, validator_t); 9 | 10 | // actual public API 11 | #define filter(t,o,v) __filter(sizeof(t),o,v) 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/reactive-c/folding.c: -------------------------------------------------------------------------------- 1 | #include "observable.h" 2 | #include "observables.h" 3 | #include "internals.h" 4 | 5 | observable_t __fold_int(observable_t ob, observer_t folder, int init) { 6 | observable_t folded = observe(just(ob), folder, sizeof(int)); 7 | *((int*)folded->value) = init; 8 | return folded; 9 | } 10 | 11 | observable_t __fold_double(observable_t ob, observer_t folder, double init) { 12 | observable_t folded = observe(just(ob), folder, sizeof(double)); 13 | *((double*)folded->value) = init; 14 | return folded; 15 | } 16 | -------------------------------------------------------------------------------- /src/reactive-c/folding.h: -------------------------------------------------------------------------------- 1 | #ifndef __FOLDING_H 2 | #define __FOLDING_H 3 | 4 | observable_t __fold_int(observable_t, observer_t, int); 5 | observable_t __fold_double(observable_t, observer_t, double); 6 | 7 | #define __f3(o,f,t) observe(just(o),f,t) 8 | #define __f4(o,f,t,i) __fold_##t(o,f,i) 9 | 10 | #define __fx(_1,_2,_3,_4,NAME,...) NAME 11 | 12 | // actual public API 13 | #define fold(...) __fx(__VA_ARGS__, __f4, __f3, __f2, __f1, 0)(__VA_ARGS__) 14 | 15 | // reduce is actually a fold, which takes the first value as its initialization 16 | // in case that is behaves the same as starting with 0, we can reuse the default 17 | // fold implementation 18 | 19 | #define reduce(o,f,t) auto_dispose(fold(o,f,t)) 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/reactive-c/internals.h: -------------------------------------------------------------------------------- 1 | #ifndef __OBSERVABLE_INTERNALS_H 2 | #define __OBSERVABLE_INTERNALS_H 3 | 4 | // detailed structure of an observable 5 | // WARNING: this header should _ONLY_ be included by Reactive C's own modules 6 | 7 | // observables can have several properties, which influences the internal 8 | // workings. 9 | enum properties { 10 | UNKNOWN = 0, 11 | VALUE = 1, 12 | OBSERVER = 2, 13 | DISPOSED = 4, // used to mark an observable as ready to be removed 14 | SUSPENDED = 8, 15 | DELAYED = 16, 16 | STOP_PROP = 32, 17 | EXPORTED = 64, 18 | OBSERVED = 128, 19 | MARKED = 256, 20 | AUTO_DISPOSE = 512, 21 | }; 22 | 23 | // macro's to hide underlying bitwise operations (hey, I like "readable code") 24 | #define _is_value(o) (o->prop & VALUE) 25 | #define _is_disposed(o) (o->prop & DISPOSED) 26 | #define _is_suspended(o) (o->prop & SUSPENDED) 27 | #define _is_delayed(o) (o->prop & DELAYED) 28 | #define _mark_observed(o) (o->prop |= OBSERVED) 29 | #define _propagate(o) (o->prop &= ~STOP_PROP) 30 | #define _is_propagating(o) (!(o->prop & STOP_PROP)) 31 | #define _dont_propagate(o) (o->prop |= STOP_PROP) 32 | #define _mark_exported(o) (o->prop |= EXPORTED) 33 | #define _is_exported(o) (o->prop & EXPORTED) 34 | #define _is_observed(o) (o->prop & OBSERVED) 35 | #define _is_script(o) (o->next && o->parent == NULL) 36 | #define _has_parent(o) (o->parent != NULL) 37 | #define _is_script_part(o) (_is_script(o) || _has_parent(o)) 38 | #define _mark(o) (o->prop |= MARKED) 39 | #define _is_marked(o) (o->prop & MARKED) 40 | #define _is_active(o) ! _is_disposed(target) && \ 41 | ! _is_delayed(target) && \ 42 | ! _is_suspended(target) 43 | #define _auto_dispose(o) (o->prop & AUTO_DISPOSE) 44 | 45 | typedef struct observable { 46 | char *label; // textual representation 47 | int prop; // internal properties 48 | unknown_t value; // cached or pointer to observed value <---+ 49 | int type_size; // sizeof(typeof(value)) 50 | observer_t process; // function that given input produces _____| 51 | observables_t observeds; // first of observed observables 52 | observables_t observers; // first of observers 53 | unknown_t *args; // cached array of ptrs to values of observeds 54 | int level; // the level in the dependecy graph 55 | validator_t validate; // validate if an update is propagated 56 | observable_t parent; // functional ref to "creating" observable 57 | observable_t next; // functional ref to "next" observable 58 | // events 59 | observable_callback_t on_dispose; 60 | observable_callback_t on_activation; 61 | } observable; 62 | 63 | // a generic type for handlers dealing with an observable 64 | typedef void (*observable_handler_t)(observable_t); 65 | 66 | // observable 67 | 68 | observable_t _new(char*); 69 | void _free(observable_t); 70 | observable_t _update_level(observable_t); 71 | void _set_observeds(observable_t, observables_t); 72 | void _update_observers_args(observable_t); 73 | observable_t _update_args(observable_t); 74 | observable_t _clear_observers(observable_t); 75 | observable_t _clear_observeds(observable_t); 76 | 77 | void _on_update(observable_handler_t); 78 | 79 | // observables 80 | 81 | // linked list item (LI) for observables. "next" can't be added to observable 82 | // because each observable can be grouped with other observers or observed 83 | // observables. 84 | typedef struct observable_li { 85 | observable_t ob; 86 | int prop; 87 | struct observable_li *next; 88 | } *observable_li_t; 89 | 90 | // the list is also packed in a struct, containing the root/first list item and 91 | // a reference to the last, for faster additions. 92 | struct observables { 93 | observable_li_t first; 94 | observable_li_t last; 95 | } observables; 96 | 97 | observable_t _combine(char*, observables_t, observer_t); 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /src/reactive-c/iterator.h: -------------------------------------------------------------------------------- 1 | // macro for iterating a linked list adhering to a few conventions: 2 | // - single linked list of 3 | // - list can be NULL 4 | // - list contains pointer to ->first item 5 | // - next item is accessed through ->next 6 | // - NULL terminated 7 | // the implementation is protected against removal of the iterated item 8 | 9 | #include 10 | 11 | #define foreach(type, item, list) \ 12 | for(type item = (list? list->first: NULL), \ 13 | _next = (item? item->next : NULL); \ 14 | item != NULL; \ 15 | item = _next, _next = (item? item->next : NULL) \ 16 | ) 17 | -------------------------------------------------------------------------------- /src/reactive-c/lift.h: -------------------------------------------------------------------------------- 1 | #ifndef __LIFT_H 2 | #define __LIFT_H 3 | 4 | // demo for lifting through macro-expansion 5 | #define lift2(type, fun) \ 6 | void __lifted_##fun(observation_t ob) { \ 7 | *(type*)(ob->observer) = fun((*(type*)(ob->observeds[0])), (*(type*)(ob->observeds[1]))); \ 8 | } 9 | 10 | // demo for lifting through macro-expansion 11 | #define lift1(type, fun) \ 12 | void __lifted_##fun(observation_t ob) { \ 13 | *(type*)(ob->observer) = fun((*(type*)(ob->observeds[0]))); \ 14 | } 15 | 16 | // demo for lifting a folding function 17 | #define lift_fold1(type, fun) \ 18 | void __lifted_##fun(observation_t ob) { \ 19 | *(type*)(ob->observer) = fun( (*(type*)(ob->observer)), (*(type*)(ob->observeds[0])) ); \ 20 | } 21 | 22 | #define lifted(x) __lifted_##x 23 | 24 | #define be(f, t, ...) observe(each(__VA_ARGS__), lifted(f), t) 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/reactive-c/merge.c: -------------------------------------------------------------------------------- 1 | #include "observable.h" 2 | #include "observables.h" 3 | #include "internals.h" 4 | 5 | #include "debug.h" 6 | 7 | // internal observer function to merge value updates to multiple observables 8 | // into one "merged" observable observer. 9 | void _merge_handler(observation_t ob) { 10 | if(_is_delayed(ob->self)) { return; } 11 | // make sure we propagate once we're active 12 | _propagate(ob->self); 13 | 14 | // if there is no source, ... there is nothing to handle 15 | if(ob->source) { 16 | if(_is_value(ob->self)) { 17 | // we can't redirect the pointer, but can copy the value 18 | observable_value_copy(ob->source, ob->self); 19 | } else { 20 | // redirect value to the value of the emitting merged observable 21 | ob->self->value = ob->source->value; 22 | } 23 | 24 | // cached args are out of date, because we're modifying the pointer itself 25 | // force refresh cache on all observers of merged_ob 26 | _update_observers_args(ob->self); 27 | 28 | // in _observe_update, after the call to this handler, the observers are 29 | // triggered, who now will get updated args 30 | } 31 | } 32 | 33 | // create a single observable observer from a list of observed observables. 34 | observable_t __merge(char *label, observables_t observeds) { 35 | observable_t observer = _combine(label, observeds, _merge_handler); 36 | _debug("MERGE", observer); 37 | return observer; 38 | } 39 | -------------------------------------------------------------------------------- /src/reactive-c/merge.h: -------------------------------------------------------------------------------- 1 | #ifndef __MERGE_H 2 | #define __MERGE_H 3 | 4 | #include "observable.h" 5 | #include "observables.h" 6 | 7 | // create a single observable observer from a list of observed observables. 8 | observable_t __merge(char*, observables_t); 9 | 10 | // actual public API 11 | #define merge(...) __merge("merge(" #__VA_ARGS__ ")", each(__VA_ARGS__)) 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/reactive-c/observable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "observable.h" 9 | #include "observables.h" 10 | #include "internals.h" 11 | 12 | #include "iterator.h" 13 | 14 | #include "debug.h" 15 | 16 | // constructor for empty observable 17 | observable_t _new(char *label) { 18 | observable_t this = malloc(sizeof(struct observable)); 19 | this->label = label; 20 | this->prop = UNKNOWN; 21 | this->value = NULL; 22 | this->type_size = 0; 23 | this->process = NULL; 24 | this->observeds = NULL; // provided by external allocation (__each) 25 | this->args = NULL; 26 | this->level = 0; 27 | this->observers = observables_new(); 28 | this->validate = NULL; 29 | this->parent = NULL; 30 | this->next = NULL; 31 | this->on_dispose = NULL; 32 | this->on_activation = NULL; 33 | return this; 34 | } 35 | 36 | // actually free the entire observable structure 37 | void _free(observable_t this) { 38 | if(this == NULL) { return; } 39 | _debug("FREEING", this); 40 | _clear_observers(this); free(this->observers); 41 | if(this->observeds) { _clear_observeds(this); free(this->observeds); } 42 | if(this->args) { free(this->args); this->args = NULL; } 43 | if(!_is_value(this) && this->value) { free(this->value); this->value = NULL; } 44 | free(this); 45 | } 46 | 47 | // recompute the level for this observable. do this by computing the maximum 48 | // level for all observed observables + 1 49 | observable_t _update_level(observable_t this) { 50 | int level = 0; 51 | foreach(observable_li_t, iter, this->observeds) { 52 | if(iter->ob->level > level) { level = iter->ob->level; } 53 | } 54 | this->level = level + 1; 55 | return this; 56 | } 57 | 58 | // update observers' arguments - probably because this' value changed location 59 | void _update_observers_args(observable_t this) { 60 | foreach(observable_li_t, iter, this->observers) { 61 | _update_args(iter->ob); 62 | } 63 | } 64 | 65 | // recompute the arguments pointer list for this observable. do this by storing 66 | // all value pointers of our own observed observables in an array. 67 | observable_t _update_args(observable_t this) { 68 | // optionally free existing list 69 | if(this->args) { free(this->args); } 70 | 71 | // allocate memory to hold cached list of pointers to observed values 72 | this->args = malloc(sizeof(unknown_t) * observables_count(this->observeds)); 73 | 74 | // copy pointers to values 75 | int i = 0; 76 | foreach(observable_li_t, iter, this->observeds) { 77 | this->args[i++] = iter->ob->value; 78 | } 79 | 80 | return this; 81 | } 82 | 83 | // remove all links to observers 84 | observable_t _clear_observers(observable_t this) { 85 | // remove back-links from our observers 86 | foreach(observable_li_t, iter, this->observers) { 87 | observables_remove(iter->ob->observeds, this); 88 | } 89 | observables_clear(this->observers); 90 | _debug("CLEARED OBSERVERS", this); 91 | return this; 92 | } 93 | 94 | // remove all links to observed observables 95 | observable_t _clear_observeds(observable_t this) { 96 | // remove back-links from our observeds 97 | foreach(observable_li_t, iter, this->observeds) { 98 | observables_remove(iter->ob->observers, this); 99 | } 100 | observables_clear(this->observeds); 101 | _debug("CLEARED OBSERVEDS", this); 102 | return this; 103 | } 104 | 105 | // the trash is another list of observables 106 | struct observables _bin = { NULL, NULL }; 107 | observables_t bin = &_bin; 108 | 109 | // add an observable to the trash 110 | #define _trash(o) observables_add(bin, o) 111 | 112 | // actually free all observables in the bin 113 | void empty_trash() { 114 | foreach(observable_li_t, item, bin) { 115 | observable_t observable = item->ob; 116 | observables_remove(bin, observable); 117 | _free(observable); 118 | } 119 | } 120 | 121 | /////////// 122 | 123 | // an ExternalValueObservable simply stores a pointer to some value in memory, 124 | // which is managed externally. when this value is updated, the observer_update 125 | // function should be called to activate the reactive behaviour associated with 126 | // it through this observable. 127 | observable_t __observing_value(char *label, unknown_t value, int size) { 128 | observable_t this = _new(label); 129 | this->prop = VALUE; 130 | this->value = value; 131 | this->type_size = size; 132 | return this; 133 | } 134 | 135 | // create an observable observer (function), storing the resulting value in a 136 | // memory location of size. 137 | observable_t __observing(char *label, observables_t observeds, 138 | observer_t observer, int size) 139 | { 140 | // create an observing observer with the fiven observing handler 141 | observable_t this = _new(label); 142 | this->prop = OBSERVER; 143 | this->value = size ? (unknown_t)malloc(size) : NULL; 144 | this->type_size = size; 145 | this->process = observer; 146 | 147 | // set its observed observables 148 | _set_observeds(this, observeds); 149 | 150 | return this; 151 | } 152 | 153 | void _set_observeds(observable_t this, observables_t observeds) { 154 | // step 1: set the obseveds 155 | this->observeds = observeds; // this is already partial in the graph 156 | // but cannot be used, no back-links 157 | 158 | // step 2: update the arguments list and our level in the dependency graph 159 | _update_args(this); 160 | _update_level(this); 161 | 162 | // step 3: add a back-link to the observer to all observeds 163 | foreach(observable_li_t, iter, this->observeds) { 164 | observables_add(iter->ob->observers, this); 165 | } 166 | } 167 | 168 | // public interface 169 | 170 | observable_t suspend(observable_t this) { 171 | if(!_is_suspended(this)) { 172 | this->prop |= SUSPENDED; 173 | _dont_propagate(this); 174 | _debug("SUSPEND", this); 175 | } 176 | return this; 177 | } 178 | 179 | observable_t unsuspend(observable_t this) { 180 | if(_is_suspended(this)) { 181 | this->prop &= ~SUSPENDED; 182 | _debug("UNSUSPEND", this); 183 | } 184 | return this; 185 | } 186 | 187 | observable_t delay(observable_t this) { 188 | this->prop |= DELAYED; 189 | _dont_propagate(this); 190 | _debug("DELAY", this); 191 | return this; 192 | } 193 | 194 | observable_t undelay(observable_t this) { 195 | this->prop &= ~DELAYED; 196 | _debug("UNDELAY", this); 197 | return this; 198 | } 199 | 200 | observable_t auto_dispose(observable_t this) { 201 | this->prop |= AUTO_DISPOSE; 202 | _debug("AUTO DISPOSE", this); 203 | return this; 204 | } 205 | 206 | // actually activate the observable in the dependecy graph 207 | observable_t start(observable_t this) { 208 | _debug("STARTING", this); 209 | // step 1: cancel suspension 210 | unsuspend(this); 211 | // step 2: if we have delayed observeds, un-delay them 212 | foreach(observable_li_t, iter, this->observeds) { 213 | undelay(iter->ob); 214 | } 215 | _debug("STARTED", this); 216 | return this; 217 | } 218 | 219 | // add a callback to the observable, triggered when it is disposed 220 | observable_t on_dispose(observable_t this, observable_callback_t callback) { 221 | this->on_dispose = callback; 222 | return this; 223 | } 224 | 225 | // add a callback to the observable, triggered when it is activated 226 | observable_t on_activation(observable_t this, observable_callback_t callback) { 227 | this->on_activation = callback; 228 | return this; 229 | } 230 | 231 | void _handle_disposal(observable_t this, observable_t observed) { 232 | // AUTO_DISPOSE implementation 233 | // if the only observed observable is disposed, dispose ourself 234 | if(_auto_dispose(this)) { 235 | if(this->observeds->first->ob == this->observeds->last->ob) { 236 | dispose(this); 237 | } 238 | } 239 | } 240 | 241 | // marks an observable for disposing, which is honored when an update-push is 242 | // executed on it. 243 | void dispose(observable_t this) { 244 | if(this == NULL) { return; } 245 | this->prop |= DISPOSED; 246 | _debug("DISPOSE", this); 247 | if(this->on_dispose) { 248 | this->on_dispose(this); 249 | } 250 | // notify observers of disposal of observed 251 | foreach(observable_li_t, iter, this->observers) { 252 | _handle_disposal(iter->ob, this); 253 | } 254 | _trash(this); 255 | } 256 | 257 | observable_t disposing(observable_t this) { 258 | dispose(this); 259 | return this; 260 | } 261 | 262 | // counter to track the number of messages an update triggers 263 | int messages = 0; 264 | 265 | // a list of observers that need to be updated, but have to wait until all 266 | // observers with a lower level are updated 267 | observables_t waiting = NULL; 268 | 269 | // TODO: clean this up, seems a bit hackish 270 | struct observable_handler_li { 271 | observable_handler_t handler; 272 | struct observable_handler_li *next; 273 | }; 274 | 275 | struct observable_handler_li *update_handlers = NULL; 276 | 277 | void _on_update(observable_handler_t handler) { 278 | struct observable_handler_li *item = 279 | malloc(sizeof(struct observable_handler_li)); 280 | item->handler = handler; 281 | item->next = update_handlers; 282 | update_handlers = item; 283 | } 284 | 285 | // public method can only trigger update, not be a source 286 | // the actual method that does the heavy lifting is 287 | void _observe_update(observable_t, observable_t); 288 | void observe_update(observable_t this) { 289 | _debug("UPDATE", this); 290 | 291 | // reset message count, a message is a call to _observe_update 292 | // we set it to -1, because this very call is not a message, but the trigger 293 | // for the other messages. we add "this" to the work-queue, which will 294 | // increment this counter at least once. 295 | messages = -1; 296 | 297 | // reset or create a waiting list 298 | // observables that need to be updated due to changes to this or dependent 299 | // observables, are put in a priority queue and processed until this queue 300 | // is empty 301 | if(waiting == NULL) { 302 | waiting = observables_new(); // lazy init 303 | } else { 304 | observables_clear(waiting); // reset 305 | } 306 | 307 | // add initial observable that requires update 308 | observables_add(waiting, this); 309 | 310 | // as long as there are observables waiting/delaying their update 311 | // restart the process for each of these 312 | while( ! observables_is_empty(waiting) ) { 313 | // the first in the list is always the next one to handle 314 | observable_t target = observables_first(waiting); 315 | // TODO: is the source always "this"? and not more specific the actual 316 | // observed observable that put this observer in the queue? 317 | if( _is_active(target) ) { _observe_update(target, this); } 318 | // remove it from the waiting list 319 | observables_remove(waiting, target); 320 | 321 | // call all registered handlers to perform post-_observe_update tasks 322 | // e.g. scripts that need to proceed 323 | for(struct observable_handler_li *item = update_handlers; item; 324 | item = item->next) 325 | { 326 | item->handler(this); 327 | } 328 | } 329 | 330 | // empty the trash 331 | empty_trash(); 332 | 333 | // report on steps/messages 334 | _debug_printf("observe_update done using %d messages.\n", messages); 335 | } 336 | 337 | // actual trigger to update due to changes in observed observables 338 | void _observe_update(observable_t this, observable_t source) { 339 | // every call to the internal observe_update is like a message sent between 340 | // distributed nodes (node in the dependency graph) 341 | messages++; 342 | 343 | _debug_printf("updating %s @ level %d\n", this->label, this->level); 344 | 345 | // if we have a filter, check if we want to propagate 346 | if(this->validate && source){ 347 | if(! this->validate(source->value) ) { 348 | _dont_propagate(this); 349 | return; 350 | } else { 351 | _propagate(this); 352 | } 353 | } 354 | 355 | // if we have a process execute it (IF WE'RE NOT OUR OWN SOURCE) 356 | if(this->process && this != source) { 357 | struct observation ob = { 358 | .self = this, 359 | .source = source, 360 | .observeds = this->args, 361 | .observer = this->value 362 | }; 363 | this->process(&ob); 364 | } 365 | 366 | // the logic in the process might have marked us for disposal, still we allow 367 | // the event flow to continue down to our observers one more time. 368 | 369 | // unless we don't want to propagate an update... 370 | if(_is_propagating(this)) { 371 | // put all of our observers in the waiting list 372 | foreach(observable_li_t, iter, this->observers) { 373 | if( ! _is_disposed(iter) ) { // if the link to it hasn't been disposed 374 | if( ! _is_suspended(iter->ob) && ! _is_delayed(iter->ob)) { 375 | observables_insert_unique_by_level(waiting, iter->ob); 376 | } 377 | } 378 | } 379 | } 380 | } 381 | 382 | void stop_observing(void) { 383 | empty_trash(); 384 | free(update_handlers); 385 | free(waiting); 386 | } 387 | 388 | // extract current value from this observable 389 | unknown_t observable_value(observable_t observable) { 390 | return observable->value; 391 | } 392 | 393 | // value manipulation support 394 | 395 | void observable_value_copy(observable_t src, observable_t trg) { 396 | // we don't know what we're copying, but we know the size ;-) 397 | // only copy when internal memory space is the same 398 | assert(src->type_size == trg->type_size); 399 | memcpy(trg->value, src->value, src->type_size); 400 | } 401 | 402 | void __set(observable_t this, void* value, int size) { 403 | assert(this->type_size == size); 404 | memcpy(this->value, value, size); 405 | observe_update(this); 406 | } 407 | 408 | // modification support 409 | 410 | void let(observable_t target, observable_t source) { 411 | // copy observeds 412 | if(source->observeds) { 413 | _set_observeds(target, observables_dup(source->observeds)); 414 | } 415 | // copy handler 416 | if(source->process) { 417 | target->process = source->process; 418 | } 419 | } 420 | 421 | observable_t force_level(observable_t observable, int level) { 422 | observable->level = level; 423 | return observable; 424 | } 425 | -------------------------------------------------------------------------------- /src/reactive-c/observable.h: -------------------------------------------------------------------------------- 1 | #ifndef __OBSERVABLE_H 2 | #define __OBSERVABLE_H 3 | 4 | #include "unknown.h" 5 | #include "observable.h" 6 | 7 | typedef struct observable *observable_t; 8 | 9 | #include "observables.h" 10 | 11 | // an observation consists of: 12 | // 1. an array of pointers to the values of the observed observables. 13 | // 2. a pointer to the location of the resulting observing value 14 | typedef struct observation { 15 | observable_t source, self; 16 | unknown_t *observeds; 17 | unknown_t observer; 18 | } *observation_t; 19 | 20 | // an observer handles an observation 21 | typedef void (*observer_t)(observation_t); 22 | 23 | // retrieve the current value of the observable 24 | unknown_t observable_value(observable_t); 25 | 26 | // adds observer to a list of observables, providing memory space for its value, 27 | // based on its size 28 | observable_t __observing(char*, observables_t, observer_t, int); 29 | 30 | // construct an observable from a (pointer to a) value 31 | observable_t __observing_value(char*, unknown_t, int); 32 | 33 | #define observed(t) __observing(#t, NULL, NULL, sizeof(t)) 34 | 35 | // actually starts an observable 36 | observable_t start(observable_t); 37 | 38 | // suspends a started observable 39 | observable_t suspend(observable_t); 40 | 41 | // delay an observable until its scripting parent is activated 42 | observable_t delay(observable_t); 43 | 44 | observable_t auto_dispose(observable_t); 45 | 46 | // add a callback to the observable, triggered when it is disposed 47 | typedef void (*observable_callback_t)(observable_t); 48 | observable_t on_dispose(observable_t, observable_callback_t); 49 | observable_t on_activation(observable_t, observable_callback_t); 50 | 51 | // remove an observer from all observeds and releases it entirely 52 | void dispose(observable_t); 53 | 54 | observable_t disposing(observable_t); 55 | 56 | // trigger gargage collection 57 | void empty_trash(void); 58 | 59 | // trigger and observable to be updated 60 | void observe_update(observable_t observable); 61 | 62 | // value manipulation support 63 | void observable_value_copy(observable_t, observable_t); 64 | 65 | void __set(observable_t, void*, int); 66 | 67 | typedef bool(*validator_t)(unknown_t); 68 | 69 | void stop_observing(void); 70 | 71 | // overloaded constructor for creating observables 72 | #define __o1(v) __observing_value(#v,(void*)&v,sizeof(v)) 73 | #define __o2(t,v) __observing_value(#v,(void*)&v,sizeof(t)) 74 | #define __o3(l,o,t) __observing(#l,l,o,sizeof(t)) 75 | #define __o4(l,o,t,s) __observing(#l,l,o,sizeof(t)*s) 76 | #define __ox(_1,_2,_3,_4,NAME,...) NAME 77 | #define observing(...) __ox(__VA_ARGS__, __o4, __o3, __o2, __o1, 0)(__VA_ARGS__) 78 | 79 | // helper macro to combine creation and activation 80 | #define observe(...) start(observing(__VA_ARGS__)) 81 | 82 | // sometimes its nicer to write... e.g. return suspended(this) 83 | #define suspended(x) suspend(x) 84 | #define started(x) start(x) 85 | #define delayed(x) delay(x) 86 | 87 | // set support 88 | #define set(o,v) { __typeof__ (v) _v = (v); __set(o, &_v, sizeof(v)); } 89 | 90 | // map support 91 | #define __m3(o,f,t) observe(just(o),f,t) 92 | #define __m4(o,f,t,s) observe(just(o),f,t,s) 93 | #define __mx(_1, _2, _3, _4,NAME,...) NAME 94 | #define map(...) auto_dispose(__mx(__VA_ARGS__, __m4, __m3, __m2, __m1)(__VA_ARGS__)) 95 | 96 | // modification support 97 | void let(observable_t, observable_t); 98 | 99 | // forces a level to a given value -> OVERWRITTEN BY FRAMEWORK WHEN NEEDED 100 | observable_t force_level(observable_t, int); 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /src/reactive-c/observables.c: -------------------------------------------------------------------------------- 1 | // observables 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "observable.h" 8 | #include "observables.h" 9 | #include "internals.h" 10 | 11 | #include "iterator.h" 12 | 13 | // turns a variadic list of observables into an linked list. this is a helper 14 | // function to allow calling observe(each(...) and pass a variable list of 15 | // observables) 16 | observables_t __each(int count, ...) { 17 | va_list ap; 18 | observables_t list = observables_new(); 19 | 20 | // import observables 21 | va_start(ap, count); 22 | for(int i=0; ifirst = NULL; 35 | list->last = NULL; 36 | return list; 37 | } 38 | 39 | observable_li_t _new_observable_li(observable_t observable) { 40 | observable_li_t item = malloc(sizeof(struct observable_li)); 41 | item->ob = observable; 42 | item->next = NULL; 43 | item->prop = 0; 44 | return item; 45 | } 46 | 47 | void observables_add(observables_t list, observable_t observable) { 48 | observable_li_t item = _new_observable_li(observable); 49 | if(list->last != NULL) { list->last->next = item; } 50 | list->last = item; 51 | if(list->first == NULL) { list->first = item; } 52 | } 53 | 54 | // inserts an observable in the list, keeping the observables ordered based on 55 | // their level. 56 | void observables_insert_unique_by_level(observables_t list, 57 | observable_t observable) 58 | { 59 | // find the insertion point, which is the address of the insertion point 60 | observable_li_t* point = &list->first; 61 | // we insert at the end of observables of the same level, to eliminate dups 62 | // and keep logical processing order 63 | while(*point && (*point)->ob->level <= observable->level) { 64 | // if we encounter the observable => abort 65 | if((*point)->ob == observable) { return; } // it's already in there 66 | point = & (*point)->next; 67 | } 68 | 69 | // insert new item 70 | observable_li_t item = _new_observable_li(observable); 71 | item->next = *point; 72 | if(*point == NULL) { list->last = item; } // keep tracking last item in list 73 | *point = item; 74 | } 75 | 76 | observables_t observables_dup(observables_t originals) { 77 | observables_t list = observables_new(); 78 | foreach(observable_li_t, original, originals) { 79 | observables_add(list, original->ob); 80 | } 81 | return list; 82 | } 83 | 84 | void observables_remove(observables_t list, observable_t observable) { 85 | if(observables_is_empty(list)) { 86 | assert(list->last == NULL); 87 | return; // empty list 88 | } 89 | observable_li_t head = list->first; 90 | if(head->ob == observable) { 91 | // first in the list, remove the head 92 | list->first = head->next; 93 | free(head); 94 | if(observables_is_empty(list)) { list->last = NULL; } // it was also the only item 95 | return; 96 | } 97 | // look further down the list 98 | while(head->next && head->next->ob != observable) { head = head->next; } 99 | if(head->next) { 100 | observable_li_t hit = head->next; 101 | head->next = hit->next; 102 | free(hit); 103 | if(head->next == NULL) { list->last = head; } // it was also the last item 104 | } 105 | } 106 | 107 | void observables_clear(observables_t list) { 108 | if(list == NULL) { return; } 109 | foreach(observable_li_t, item, list) { 110 | free(item); 111 | } 112 | list->first = NULL; 113 | list->last = list->first; 114 | } 115 | 116 | bool observables_contains(observables_t list, observable_t observable) { 117 | foreach(observable_li_t, item, list) { 118 | if(item->ob == observable) { return true; } 119 | } 120 | return false; 121 | } 122 | 123 | bool observables_is_empty(observables_t list) { 124 | return list->first == NULL; 125 | } 126 | 127 | int observables_count(observables_t list) { 128 | int count = 0; 129 | foreach(observable_li_t, _, list) { count++; } 130 | return count; 131 | } 132 | 133 | // generic constructor for observables that observe a set of observables 134 | observable_t _combine(char *label, observables_t observeds, observer_t handler) { 135 | observable_t combination = start(__observing(label, observeds, handler, 0)); 136 | return combination; 137 | } 138 | 139 | observable_t observables_first(observables_t list) { 140 | return list->first ? list->first->ob : NULL; 141 | } 142 | -------------------------------------------------------------------------------- /src/reactive-c/observables.h: -------------------------------------------------------------------------------- 1 | #ifndef __OBSERVABLES_H 2 | #define __OBSERVABLES_H 3 | 4 | #include 5 | 6 | #include "varargs.h" 7 | #include "observable.h" 8 | 9 | // and a (linked) list for observables. use each() & co to create the list. 10 | typedef struct observables *observables_t; 11 | 12 | // turns a variadic list of observables into a linked list of observables 13 | observables_t __each(int,...); 14 | 15 | // actual public API 16 | #define each(...) __each(_NARG(__VA_ARGS__), __VA_ARGS__) 17 | #define just(x) __each(1, x) 18 | #define both(x, y) __each(2, x, y) 19 | 20 | observables_t observables_new(void); 21 | void observables_add(observables_t, observable_t); 22 | void observables_insert_unique_by_level(observables_t, observable_t); 23 | observables_t observables_dup(observables_t); 24 | 25 | void observables_remove(observables_t, observable_t); 26 | 27 | void observables_clear(observables_t); 28 | 29 | bool observables_is_empty(observables_t list); 30 | bool observables_contains(observables_t, observable_t); 31 | 32 | int observables_count(observables_t); 33 | 34 | observable_t observables_first(observables_t); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/reactive-c/script.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "observable.h" 6 | #include "observables.h" 7 | #include "internals.h" 8 | 9 | #include "iterator.h" 10 | 11 | #include "debug.h" 12 | 13 | observable_t _step(observable_t script) { 14 | observable_t step = script->next; 15 | if(step == NULL) { return NULL; } // end of script 16 | _debug("STEP TO", step); 17 | 18 | // link new step to script 19 | step->parent = script->parent ? script->parent : script; 20 | 21 | // prepare for next step 22 | script->next = step->next; 23 | 24 | // start 25 | start(step); 26 | // if the parent/script has an on_activation handler, execute it on the step 27 | if(step->parent && step->parent->on_activation) { 28 | step->parent->on_activation(step); 29 | } 30 | 31 | // do we (still) have anything to do? our observeds might already be done. 32 | while(step && step->observeds && observables_count(step->observeds) == 0) { 33 | dispose(step); 34 | step = _step(script); 35 | } 36 | 37 | return step; 38 | } 39 | 40 | // constructor for a script, consisting of inactive observables 41 | observable_t __script(int count, ...) { 42 | if(count<1) { return NULL; } 43 | 44 | observable_t script = _new("script"); 45 | 46 | va_list ap; 47 | va_start(ap, count); 48 | 49 | // first due to count >= 1 50 | script->next = va_arg(ap, observable_t); 51 | observable_t step = script->next; 52 | step->parent = script; 53 | 54 | // remaining steps 55 | for(int i=1; inext = va_arg(ap, observable_t); 57 | step = step->next; 58 | step->parent = script; 59 | } 60 | va_end(ap); 61 | 62 | _debug("SCRIPT", script); 63 | 64 | return script; 65 | } 66 | 67 | observable_t run(observable_t script) { 68 | return _step(script); // activates the first step in a consistent way 69 | } 70 | 71 | // a list of script waiting to proceed 72 | struct observables _scripts_waiting = { NULL, NULL }; 73 | observables_t scripts_waiting = &_scripts_waiting; 74 | 75 | void _proceed_scripts_waiting(observable_t _) { 76 | // During the propagation of the update, scripts might have finalized a step, 77 | // and are waiting to proceed. This cannot be done safely otherwise (for now) 78 | // because an update triggering a step in the script might also trigger the 79 | // next step, which might not what is intended. 80 | // At the end of an observation propagation, we proceed all scripts that are 81 | // marked as such. 82 | foreach(observable_li_t, iter, scripts_waiting) { 83 | _step(iter->ob); 84 | } 85 | observables_clear(scripts_waiting); 86 | } 87 | 88 | observable_t _proceed(observable_t script) { 89 | // make sure that on the next observation, our handler is registered... ONCE 90 | static bool registered = false; 91 | if(!registered) { 92 | _on_update(_proceed_scripts_waiting); 93 | registered = true; 94 | } 95 | 96 | // add the script to the list of script that is ready to proceed, it will be 97 | // moved ahead once the top-level observation has been processed. 98 | // TODO: is this fine-grained enough? come up with scenario to invalidate this 99 | if(!observables_contains(scripts_waiting, script)) { 100 | observables_add(scripts_waiting, script); 101 | } 102 | return script; 103 | } 104 | 105 | void _finalize_await(observation_t ob) { 106 | _debug("FINALIZE AWAIT", ob->self); 107 | 108 | if(_is_suspended(ob->self)) { return; } 109 | 110 | // dispose ourselves 111 | dispose(ob->self); 112 | 113 | // tell the script to move to the next step 114 | _proceed(ob->self->parent); 115 | } 116 | 117 | // constructor for observer that wait until another observer emits 118 | observable_t await(observable_t observable) { 119 | observable_t this = 120 | __observing("await", just(observable), _finalize_await, 0); 121 | _debug("AWAIT", this); 122 | return suspended(this); 123 | } 124 | -------------------------------------------------------------------------------- /src/reactive-c/script.h: -------------------------------------------------------------------------------- 1 | #ifndef __SCRIPT_H 2 | #define __SCRIPT_H 3 | 4 | #include "observable.h" 5 | 6 | // script constructor takes a variable amount of (inactive) observables 7 | observable_t __script(int, ...); 8 | 9 | // explictly start a script 10 | observable_t run(observable_t); 11 | 12 | // morphing constructor to wait until an observable emits an update 13 | observable_t await(observable_t); 14 | 15 | // actual public API 16 | #define script(...) __script(_NARG(__VA_ARGS__), __VA_ARGS__) 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/reactive-c/unknown.h: -------------------------------------------------------------------------------- 1 | #ifndef __UNKNOWN_H 2 | #define __UNKNOWN_H 3 | 4 | // let's give the unknown types a name 5 | typedef void* unknown_t; 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/reactive-c/varargs.h: -------------------------------------------------------------------------------- 1 | #ifndef __VARARGS_H 2 | #define __VARARGS_H 3 | 4 | // marco's to count __VA_ARGS__ 5 | // via: https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s 6 | 7 | #define _NARG(...) \ 8 | _NARG_(__VA_ARGS__, _RSEQ_N()) 9 | #define _NARG_(...) \ 10 | _ARG_N(__VA_ARGS__) 11 | #define _ARG_N( \ 12 | _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ 13 | _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ 14 | _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ 15 | _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ 16 | _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ 17 | _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ 18 | _61,_62,_63,N,...) N 19 | #define _RSEQ_N() \ 20 | 63,62,61,60, \ 21 | 59,58,57,56,55,54,53,52,51,50, \ 22 | 49,48,47,46,45,44,43,42,41,40, \ 23 | 39,38,37,36,35,34,33,32,31,30, \ 24 | 29,28,27,26,25,24,23,22,21,20, \ 25 | 19,18,17,16,15,14,13,12,11,10, \ 26 | 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/reactive-c/when.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "observable.h" 5 | #include "when.h" 6 | 7 | #include "internals.h" 8 | 9 | void _dup(observation_t ob) { 10 | __set(ob->self, ob->observeds[0], ob->self->type_size); 11 | } 12 | 13 | observable_t when(observable_t ob, event_t ev, observable_callback_t ac) { 14 | observable_t watcher = __observing("watcher", just(ob), _dup, ob->type_size); 15 | if(ev & IS_DONE) { 16 | on_dispose( auto_dispose(watcher), ac ); 17 | } 18 | return watcher; 19 | } 20 | -------------------------------------------------------------------------------- /src/reactive-c/when.h: -------------------------------------------------------------------------------- 1 | // when.h 2 | 3 | enum { 4 | NEVER = 0, 5 | IS_DONE = 1, 6 | }; 7 | 8 | typedef int event_t; 9 | 10 | #define is(e) is_##e 11 | #define is_done IS_DONE 12 | 13 | #define then(a) a 14 | 15 | observable_t when(observable_t, event_t, observable_callback_t); 16 | -------------------------------------------------------------------------------- /src/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # TESTING 2 | 3 | enable_testing() 4 | 5 | cmake_policy(SET CMP0003 OLD) 6 | cmake_policy(SET CMP0023 OLD) 7 | 8 | add_custom_target(check 9 | COMMAND ${CMAKE_CTEST_COMMAND} --force-new-ctest-process --verbose 10 | ) 11 | SET(ENV{CTEST_OUTPUT_ON_FAILURE} TRUE) 12 | 13 | # create executables of all example files in the current directory 14 | file(GLOB TESTS *.c) 15 | foreach(test ${TESTS}) 16 | message(STATUS "Adding test ${test}") 17 | get_filename_component(name ${test} NAME_WE) 18 | add_executable(${name} ${test}) 19 | target_link_libraries(${name} LINK_PUBLIC reactive-c) 20 | target_link_libraries(${name} ${GLIB2_LIBRARIES}) 21 | add_test(NAME ${name} COMMAND ${name}) 22 | add_dependencies(check ${name}) 23 | endforeach() 24 | 25 | -------------------------------------------------------------------------------- /src/test/test_observables.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "reactive-c/api.h" 5 | 6 | typedef struct { 7 | observables_t list; 8 | int _a, _b, _c; 9 | observable_t a, b, c; 10 | } observables_f; 11 | 12 | void setup_empty_list(observables_f *ob, gconstpointer _) { 13 | ob->list = observables_new(); 14 | ob->a = NULL; 15 | ob->b = NULL; 16 | ob->c = NULL; 17 | } 18 | 19 | void teardown(observables_f *ob, gconstpointer _) { 20 | free(ob->list); 21 | if(ob->a) { free(ob->a); } 22 | if(ob->b) { free(ob->b); } 23 | if(ob->c) { free(ob->c); } 24 | } 25 | 26 | void test_add(observables_f *ob, gconstpointer _) { 27 | g_assert(observables_is_empty(ob->list)); 28 | int _d = 123; 29 | observable_t dummy = observe(_d); 30 | observables_add(ob->list, dummy); 31 | g_assert(! observables_is_empty(ob->list)); 32 | g_assert(observables_count(ob->list) == 1); 33 | } 34 | 35 | void setup_list_with_three_observable(observables_f *ob, gconstpointer _) { 36 | ob->list = observables_new(); 37 | 38 | ob->_a = 1; 39 | ob->_b = 2; 40 | ob->_c = 3; 41 | 42 | ob->a = observe(ob->_a); 43 | ob->b = observe(ob->_b); 44 | ob->c = observe(ob->_c); 45 | 46 | observables_add(ob->list, ob->a); 47 | observables_add(ob->list, ob->b); 48 | observables_add(ob->list, ob->c); 49 | } 50 | 51 | void test_remove(observables_f *ob, gconstpointer _) { 52 | observables_remove(ob->list, ob->b); 53 | g_assert(!observables_is_empty(ob->list)); 54 | g_assert(observables_count(ob->list) == 2); 55 | 56 | observables_remove(ob->list, ob->c); 57 | g_assert(!observables_is_empty(ob->list)); 58 | g_assert(observables_count(ob->list) == 1); 59 | 60 | observables_remove(ob->list, ob->a); 61 | g_assert(observables_is_empty(ob->list)); 62 | g_assert(observables_count(ob->list) == 0); 63 | } 64 | 65 | void test_clear(observables_f *ob, gconstpointer _) { 66 | observables_clear(ob->list); 67 | g_assert(observables_is_empty(ob->list)); 68 | } 69 | 70 | void test_contains(observables_f *ob, gconstpointer _) { 71 | g_assert(observables_contains(ob->list, ob->a)); 72 | g_assert(observables_contains(ob->list, ob->b)); 73 | g_assert(observables_contains(ob->list, ob->c)); 74 | int _u = 123; 75 | observable_t unknown = observe(_u); 76 | g_assert( ! observables_contains(ob->list, unknown)); 77 | } 78 | 79 | void test_dup(observables_f *ob, gconstpointer _) { 80 | observables_t dup = observables_dup(ob->list); 81 | g_assert(observables_contains(dup, ob->a)); 82 | g_assert(observables_contains(dup, ob->b)); 83 | g_assert(observables_contains(dup, ob->c)); 84 | } 85 | 86 | void test_insert_by_level(observables_f *ob, gconstpointer _) { 87 | g_assert(observables_is_empty(ob->list)); 88 | int value = 0; 89 | observable_t o1 = force_level(observe(value), 1); 90 | observable_t o2 = force_level(observe(value), 2); 91 | observable_t o3 = force_level(observe(value), 3); 92 | observables_insert_unique_by_level(ob->list, o2); 93 | observables_insert_unique_by_level(ob->list, o3); 94 | observables_insert_unique_by_level(ob->list, o1); 95 | 96 | g_assert(observables_count(ob->list) == 3); 97 | 98 | observable_t o; 99 | 100 | o = observables_first(ob->list); 101 | g_assert(o == o1); 102 | observables_remove(ob->list, o); 103 | 104 | o = observables_first(ob->list); 105 | g_assert(o == o2); 106 | observables_remove(ob->list, o); 107 | 108 | o = observables_first(ob->list); 109 | g_assert(o == o3); 110 | observables_remove(ob->list, o); 111 | } 112 | 113 | void test_insert_unique(observables_f *ob, gconstpointer _) { 114 | g_assert(observables_is_empty(ob->list)); 115 | int value = 0; 116 | observable_t o1 = force_level(observe(value), 1); 117 | observable_t o2 = force_level(observe(value), 2); 118 | observable_t o3 = force_level(observe(value), 3); 119 | 120 | // this simulates the glitch scenario 121 | // 0 adds 1 and 2 122 | observables_insert_unique_by_level(ob->list, o1); 123 | observables_insert_unique_by_level(ob->list, o2); 124 | 125 | g_assert(observables_count(ob->list) == 2); 126 | 127 | // 1 gets updated 128 | observables_remove(ob->list, o1); 129 | observables_insert_unique_by_level(ob->list, o3); 130 | 131 | g_assert(observables_count(ob->list) == 2); 132 | 133 | // 2 gets updated 134 | observables_remove(ob->list, o2); 135 | observables_insert_unique_by_level(ob->list, o3); 136 | 137 | // only 3 should still be in the list 138 | g_assert(observables_count(ob->list) == 1); 139 | } 140 | 141 | int main(int argc, char **argv) { 142 | g_test_init(&argc, &argv, NULL); 143 | 144 | g_test_add("/observables/add", observables_f, NULL, 145 | setup_empty_list, test_add, teardown ); 146 | g_test_add("/observables/remove", observables_f, NULL, 147 | setup_list_with_three_observable, test_remove, teardown ); 148 | g_test_add("/observables/clear", observables_f, NULL, 149 | setup_list_with_three_observable, test_clear, teardown ); 150 | g_test_add("/observables/contains", observables_f, NULL, 151 | setup_list_with_three_observable, test_contains, teardown ); 152 | g_test_add("/observables/dup", observables_f, NULL, 153 | setup_list_with_three_observable, test_dup, teardown ); 154 | g_test_add("/observables/insert_by_level", observables_f, NULL, 155 | setup_empty_list, test_insert_by_level, teardown ); 156 | g_test_add("/observables/insert_unique", observables_f, NULL, 157 | setup_empty_list, test_insert_unique, teardown ); 158 | return g_test_run(); 159 | } 160 | -------------------------------------------------------------------------------- /src/unit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB SRCS *.c) 2 | 3 | add_library(unit ${SRCS}) 4 | 5 | target_include_directories(unit PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 6 | -------------------------------------------------------------------------------- /src/unit/test.c: -------------------------------------------------------------------------------- 1 | // helper functions for basic unit testing 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "test.h" 11 | 12 | char _stdout[4096] = ""; 13 | 14 | // a replacement for printf to capture the output 15 | void capture_printf(char *format, ...) { 16 | va_list args; 17 | // create the formatted string 18 | char string[256] = ""; 19 | va_start(args, format); 20 | vsnprintf(string, 256, format, args); 21 | va_end(args); 22 | // concatenate it to the internal stdout buffer 23 | strcat(_stdout, string); 24 | } 25 | 26 | // assertion function to allow more elaborated assertion error messages 27 | void assert_true(bool asserted, char *message, ...) { 28 | if(asserted) { return; } 29 | va_list args; 30 | fprintf(stderr, "Assertion failed: "); 31 | va_start(args, message); 32 | vfprintf(stderr, message, args); 33 | va_end(args); 34 | abort(); 35 | } 36 | 37 | // assertion functions 38 | 39 | void assert_no_output(void) { 40 | assert_true( 41 | strcmp(_stdout, "") == 0, 42 | "Output produced, while none expected:\n" 43 | "output =\n%s", _stdout 44 | ); 45 | } 46 | 47 | void assert_output(void) { 48 | assert_true( 49 | strcmp(_stdout, "") != 0, 50 | "No output produced, while expected." 51 | ); 52 | } 53 | 54 | void assert_output_was(char* expected) { 55 | assert_true( 56 | (strlen(_stdout) == strlen(expected)) && 57 | (strncmp(_stdout, expected, strlen(expected)) == 0), 58 | "Output differs from expected:\n" 59 | "actual =\n%s\nexpected =\n%s\n", _stdout, expected 60 | ); 61 | } 62 | 63 | void clear_output(void) { 64 | _stdout[0] = '\0'; 65 | } 66 | -------------------------------------------------------------------------------- /src/unit/test.h: -------------------------------------------------------------------------------- 1 | // helper functions for basic unit testing 2 | 3 | #ifndef __TEST_H 4 | #define __TEST_H 5 | 6 | #include 7 | #include 8 | 9 | // a replacement for printf to capture the output 10 | void capture_printf(char *, ...); 11 | 12 | // assertion function to allow more elaborated assertion error messages 13 | void assert_true(bool, char *, ...); 14 | 15 | #define assert_false(a,...) assert_true(!a, __VA_ARGS__) 16 | #define assert_zero(v,...) assert_true(v == 0, __VA_ARGS__) 17 | #define assert_equal(x, y, ...) assert_true(x == y, __VA_ARGS__) 18 | 19 | void assert_no_output(void); 20 | void assert_output(void); 21 | void assert_output_was(char*); 22 | 23 | void clear_output(void); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/unknown/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project (unknown) 2 | cmake_minimum_required(VERSION 2.4) 3 | 4 | add_executable(demo demo.c unknown.c ../unit/test.c) 5 | 6 | add_custom_target(run 7 | DEPENDS demo 8 | COMMENT "Running demo..." 9 | COMMAND demo 10 | WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} 11 | ) 12 | -------------------------------------------------------------------------------- /src/unknown/Makefile: -------------------------------------------------------------------------------- 1 | SRC_DIR = . 2 | BUILD_DIR = bin 3 | 4 | MKDIR = mkdir -p 5 | CD = cd 6 | CMAKE = cmake 7 | RM = rm -rf 8 | 9 | all: clean run 10 | 11 | ${BUILD_DIR}/Makefile: ${BUILD_DIR} 12 | @(${CD} $<; ${CMAKE} -DCMAKE_BUILD_TYPE=Debug ../${SRC_DIR}) 13 | 14 | build: ${BUILD_DIR}/Makefile 15 | @(${CD} ${BUILD_DIR}; ${MAKE}) 16 | 17 | run: build 18 | @(${CD} ${BUILD_DIR}; ${MAKE} run) 19 | 20 | ${BUILD_DIR}: 21 | @${MKDIR} $@ 22 | 23 | clean: 24 | @${RM} ${BUILD_DIR} 25 | 26 | .PHONY: all build run clean 27 | -------------------------------------------------------------------------------- /src/unknown/README: -------------------------------------------------------------------------------- 1 | This module contains an experiment to implement an unknown type. The goal is to 2 | be able to wrap existing values and provide semi-dynamic conversions,... 3 | 4 | The goal is to replace the typedef (void*) unknown_t from the parent project, to 5 | remove the remaining casting and abuse of (void*). 6 | -------------------------------------------------------------------------------- /src/unknown/demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "unknown.h" 5 | 6 | #include "../unit/test.h" 7 | 8 | void display(unknown_t data) { 9 | capture_printf("%d = %f = %s\n", 10 | as(int, data), 11 | as(double, data), 12 | as(string, data)); 13 | } 14 | 15 | int main(void) { 16 | int i = 123; 17 | unknown_t value1 = new(int, i); 18 | display(value1); 19 | 20 | assert_output_was("123 = 123.000000 = 123\n"); clear_output(); 21 | 22 | double d = 123.123; 23 | unknown_t value2 = new(double, d); 24 | display(value2); 25 | 26 | assert_output_was("123 = 123.123000 = 123.123000\n"); clear_output(); 27 | 28 | char *s = "123.123"; 29 | unknown_t value3 = new(string, s); 30 | display(value3); 31 | 32 | assert_output_was("123 = 123.123000 = 123.123\n"); clear_output(); 33 | 34 | // let support 35 | 36 | double d2 = 456.789; 37 | let(value1, new(double, d2)); 38 | 39 | assert_equal(as(int, value1), 457, "Expected 456.789 as int to be 457.\n"); 40 | 41 | let(value1, double, 123.123); 42 | 43 | assert_equal(as(int, value1), 123, "Expected 123.123 as int to be 123.\n"); 44 | 45 | val(int, value1) = (int)987.123; 46 | 47 | assert_equal(as(int, value1), 987, "Expected 987 as int to be 987 .\n"); 48 | 49 | char *s2 = "hello world"; 50 | unknown_t str = new(string, s2); 51 | val(string, str) = "updated world"; 52 | 53 | assert_true(strcmp(as(string, str), "updated world") == 0, 54 | "Expected updated string"); 55 | 56 | assert_true(is(int, value1), "Expected value1 to be an integer."); 57 | assert_false(is(double, value1), "Expected value1 NOT to be a double."); 58 | assert_true(is(string, str), "Expected str to be a string."); 59 | 60 | exit(EXIT_SUCCESS); 61 | } 62 | -------------------------------------------------------------------------------- /src/unknown/unknown.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "unknown.h" 7 | 8 | typedef struct internals { 9 | union { 10 | int* i; 11 | double* d; 12 | char** s; 13 | } value; 14 | enum { INTEGER, DOUBLE, STRING } type; 15 | char *buffer; // used for conversion to string, allows for better freeing 16 | } internals; 17 | 18 | // int -> 19 | char *i2s(unknown_t data) { 20 | if(data->private->buffer) { free(data->private->buffer); } 21 | data->private->buffer = malloc(sizeof(char)*16); 22 | assert(data->private->buffer != NULL); 23 | snprintf(data->private->buffer, 15,"%d", *(data->private->value.i)); 24 | return data->private->buffer; 25 | } 26 | int i2i(unknown_t data) { return *data->private->value.i; } 27 | double i2d(unknown_t data) { return (double)*(data->private->value.i); } 28 | 29 | // double -> 30 | char *d2s(unknown_t data) { 31 | if(data->private->buffer) { free(data->private->buffer); } 32 | data->private->buffer = malloc(sizeof(char)*16); 33 | assert(data->private->buffer != NULL); 34 | snprintf(data->private->buffer,15,"%f", *(data->private->value.d)); 35 | return data->private->buffer; 36 | } 37 | int d2i(unknown_t data) { return (int)(*(data->private->value.d) + 0.5); } 38 | double d2d(unknown_t data) { return *(data->private->value.d); } 39 | 40 | // string -> 41 | char *s2s(unknown_t data) { return *(data->private->value.s); } 42 | int s2i(unknown_t data) { return atoi(*(data->private->value.s)); } 43 | double s2d(unknown_t data) { return atof(*(data->private->value.s)); } 44 | 45 | unknown_t new_int(int* value) { 46 | unknown_t v = malloc(sizeof(Unknown)); assert(v != NULL); 47 | v->as_string = i2s; 48 | v->as_int = i2i; 49 | v->as_double = i2d; 50 | v->private = malloc(sizeof(internals)); 51 | v->private->type = INTEGER; 52 | v->private->value.i = value; 53 | v->private->buffer = NULL; 54 | return v; 55 | } 56 | 57 | unknown_t new_double(double* value) { 58 | unknown_t v = malloc(sizeof(Unknown)); assert(v != NULL); 59 | v->as_string = d2s; 60 | v->as_int = d2i; 61 | v->as_double = d2d; 62 | v->private = malloc(sizeof(internals)); 63 | v->private->type = DOUBLE; 64 | v->private->value.d = value; 65 | v->private->buffer = NULL; 66 | return v; 67 | } 68 | 69 | unknown_t new_string(char** value) { 70 | unknown_t v = malloc(sizeof(Unknown)); assert(v != NULL); 71 | v->as_string = s2s; 72 | v->as_int = s2i; 73 | v->as_double = s2d; 74 | v->private = malloc(sizeof(internals)); 75 | v->private->type = STRING; 76 | v->private->value.s = value; 77 | v->private->buffer = NULL; 78 | return v; 79 | } 80 | 81 | void let(unknown_t rval, unknown_t lval) { 82 | switch(rval->private->type) { 83 | case INTEGER: *(rval->private->value.i) = as(int, lval); break; 84 | case DOUBLE: *(rval->private->value.d) = as(double, lval); break; 85 | case STRING: *(rval->private->value.s) = as(string, lval); break; 86 | } 87 | } 88 | 89 | void __let_int(unknown_t rval, int lval) { 90 | switch(rval->private->type) { 91 | case INTEGER: *(rval->private->value.i) = lval; break; 92 | case DOUBLE: *(rval->private->value.d) = (double)lval; break; 93 | case STRING: 94 | sprintf(*(rval->private->value.s), "%d", lval); 95 | break; 96 | } 97 | } 98 | 99 | void __let_double(unknown_t rval, double lval) { 100 | switch(rval->private->type) { 101 | case INTEGER: *(rval->private->value.i) = (int)lval; break; 102 | case DOUBLE: *(rval->private->value.d) = lval; break; 103 | case STRING: sprintf(*(rval->private->value.s), "%f", lval); break; 104 | } 105 | } 106 | 107 | void __let_string(unknown_t rval, char *lval) { 108 | switch(rval->private->type) { 109 | case INTEGER: *(rval->private->value.i) = atoi(lval); break; 110 | case DOUBLE: *(rval->private->value.d) = atof(lval); break; 111 | case STRING: *(rval->private->value.s) = lval; break; 112 | } 113 | } 114 | 115 | int *__val_int(unknown_t val) { 116 | assert(val->private->type == INTEGER); 117 | return val->private->value.i; 118 | } 119 | 120 | double *__val_double(unknown_t val) { 121 | assert(val->private->type == DOUBLE); 122 | return val->private->value.d; 123 | } 124 | 125 | char **__val_string(unknown_t val) { 126 | assert(val->private->type == STRING); 127 | return val->private->value.s; 128 | } 129 | 130 | bool is_int(unknown_t val) { 131 | return val->private->type == INTEGER; 132 | } 133 | 134 | bool is_double(unknown_t val) { 135 | return val->private->type == DOUBLE; 136 | } 137 | 138 | bool is_string(unknown_t val) { 139 | return val->private->type == STRING; 140 | } 141 | -------------------------------------------------------------------------------- /src/unknown/unknown.h: -------------------------------------------------------------------------------- 1 | #ifndef __UNKNOWN_H 2 | #define __UNKNOWN_H 3 | 4 | #include 5 | 6 | typedef struct Unknown *unknown_t; 7 | typedef struct internals *internals_t; 8 | 9 | // expose public part with accessors to retrieve value 10 | struct Unknown { 11 | char* (*as_string) (unknown_t); 12 | int (*as_int) (unknown_t); 13 | double (*as_double) (unknown_t); 14 | internals_t private; 15 | } Unknown; 16 | 17 | // constructors 18 | unknown_t new_int (int* value); 19 | unknown_t new_double (double* value); 20 | unknown_t new_string (char** value); 21 | 22 | // syntactic sugar 23 | #define new(t,v) new_##t(&v) 24 | #define as(t,v) v->as_##t(v) 25 | 26 | void __let(unknown_t, unknown_t); 27 | void __let_int (unknown_t, int); 28 | void __let_double(unknown_t, double); 29 | void __let_string(unknown_t, char*); 30 | 31 | #define __l2(x, y) __let(x, y) 32 | #define __l3(x, t, v) __let_##t(x, v) 33 | #define __lx(_1,_2,_3,NAME,...) NAME 34 | #define let(...) __lx(__VA_ARGS__, __l3, __l2, __l1)(__VA_ARGS__) 35 | 36 | int *__val_int(unknown_t); 37 | double *__val_double(unknown_t); 38 | char **__val_string(unknown_t); 39 | 40 | #define val(t,x) *(__val_##t(x)) 41 | 42 | bool is_int(unknown_t); 43 | bool is_double(unknown_t); 44 | bool is_string(unknown_t); 45 | 46 | #define is(t,x) is_##t(x) 47 | 48 | #endif 49 | --------------------------------------------------------------------------------