Skip to content

Commit 07cf188

Browse files
authored
Merge pull request #8 from JSchaenzle/feature/linked-subscribers
Added linked subscribers
2 parents 6ffd40f + d9141ed commit 07cf188

File tree

4 files changed

+139
-41
lines changed

4 files changed

+139
-41
lines changed

README.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@ Cedux is a tool which enables developer to write clean C applications that don't
88

99
It works like this:
1010
- Application state is stored in a global tree which is created by the `CEDUX_DEFINE_STORE()` macro.
11-
- Producers of data dispatch actions to the store via calls to `cedux_dispatch_x()`.
12-
- _Reducers_, which are registered during application initialization, receive dispatched actions and the current state tree and are responsible for updating the state as needed.
13-
- Optional _Subscribers_, which are also registered during application initialization, receive the new state tree after reducers process any actions.
11+
- Producers of data (ISR, etc.) dispatch actions to the store via calls to `cedux_dispatch_x()`.
12+
- _Reducers_, which are registered during application initialization, receive dispatched actions, along with the current state tree, and are responsible for updating the state as needed.
13+
- _Subscribers_, which are also registered during application initialization, receive the new state tree after reducers process any actions.
14+
- _Linked Subscribers_ run any time the Reducer they are linked to does work.
1415
- In the `main` loop, calls to `cedux_run_x()` check for dispatched actions and forward them to all registered reducers.
1516

1617
## Recommendations
1718
- Try to only modify the state tree from within reducers.
1819
- Use a tagged-union for the action type (see `example.c`)
20+
- Use subscribers to perform side effects and dispact more actions if needed.
1921

2022
## Example
2123
Take a look at `example.c` for a simple example usage.
24+
You can compile and run it like this: `gcc example.c -o cedux.out && ./cedux.out`
2225

2326
## The Nitty-Gritty Details
2427
### Cedux Usage
@@ -33,22 +36,26 @@ In this macro, `TREE_TYPE` is the type (structure definition) that describes you
3336

3437
For example, `CEDUX_DEFINE_STORE(struct my_app_state, struct action, my_store)` would create a store which contains a state tree of type `struct my_app_state`. Actions of type `struct action` could be dispatched to the store. After the macro, a variable `my_store` exists which is the store. The state tree is accessible via `my_store.tree`;
3538

36-
3739
### Initialize the Store
3840
To initialize the store call `cedux_init_x()`. This sets up the internal list and queue and returns the store.
3941

40-
#### Register Reducers
42+
### Register Reducers
4143
`cedux_register_x_reducer(store, reducer)` where `store` is a pointer to the store created by `CEDUX_DEFINE_STORE` and `reducer` is a function pointer to a reducer function. The reducer function must have a signature of `void reducer(<tree type pointer>, action)`
4244

43-
#### Register Subscribers (Optional)
44-
`cedux_register_x_subscriber(store, subscriber, data)` where `store` is a pointer to the store created by `CEDUX_DEFINE_STORE`, `subscriber` is the subscriber function, and `data` is optional extra data to be passed with each call to the subscriber. The subscriber function must have a signature of `void subscriber(<tree type pointer>, void *data)`. Cedux does not look at or modify `data` at all, so you can use it for whatever extra information you need, or just set it to `NULL` and ignore it.
45+
### Register Generic Subscribers
46+
`cedux_register_x_subscriber(store, subscriber, data)` where `store` is a pointer to the store created by `CEDUX_DEFINE_STORE`, `subscriber` is the subscriber function, and `data` is optional extra data to be passed with each call to the subscriber. The subscriber function must have a signature of `void subscriber(<store handle>, <tree type pointer>, void *data)`. Cedux does not look at or modify `data` at all, so you can use it for whatever extra information you need, or just set it to `NULL` and ignore it.
47+
A generic subscriber will get called any time any reducer does work.
48+
49+
### Register Linked Subscribers
50+
`cedux_register_x_linked_subscriber(store, subscriber, data, reducer)` where `store` is a pointer to the store created by `CEDUX_DEFINE_STORE`, `subscriber` is the subscriber function, and `data` is optional extra data to be passed with each call to the subscriber, and reducer is the reducer to link it to. The subscriber function must have a signature of `void subscriber(<store handle>, <tree type pointer>, void *data)`. Cedux does not look at or modify `data` at all, so you can use it for whatever extra information you need, or just set it to `NULL` and ignore it.
51+
A linked subscriber will get called only when the reducer that it is linked to does work.
4552

4653
### Dispatch Actions
4754
Call the dispatch function to send an action to the store. This method pushes the action into the stores action queue to be handled later by the run function.
4855
`cedux_dispatch_x(store, action)` where `store` is a pointer to the store and `action` is a variable for `ACTION_TYPE`.
4956

5057
### Run
51-
Somewhere in the main loop of your application you need to call the Cedux run function. This function checks if any actions have been dispatched and if so, pops them out of the action queue and sends them to all registered reducers.
58+
Somewhere in the main loop of your application you need to call the Cedux run function. This function checks if any actions have been dispatched and if so, sends them to all registered reducers.
5259
`cedux_run_x(TStore * p_store)`
5360

5461
### Generated Code
@@ -67,7 +74,7 @@ To use Cedux you'll need to copy the following files into your application.
6774
- queue.h _(Used for the action queue)_
6875
- list.h _(Used to hold the registered reducers)_
6976

70-
For more information on the queue implementation see: https://spin.atomicobject.com/2017/03/08/message-queue-for-c/
77+
See example.c for a demonstration.
7178

7279
### Thread Safety
7380

cedux.h

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
#define CEDUX_DECLARE_STORE(TREE_TYPE, ACTION_TYPE, STORE_NAME) \
99
\
1010
struct STORE_NAME##_handle; \
11-
typedef void(*STORE_NAME##_REDUCER)(TREE_TYPE * p_tree, ACTION_TYPE action); \
12-
typedef void(*STORE_NAME##_SUBSCRIBER)(const TREE_TYPE * p_tree, void *data); \
11+
typedef bool(*STORE_NAME##_REDUCER)(TREE_TYPE * p_tree, ACTION_TYPE action); \
12+
typedef void(*STORE_NAME##_SUBSCRIBER)(struct STORE_NAME##_handle * p_store, \
13+
TREE_TYPE const * const p_tree, void *data); \
1314
struct STORE_NAME##_subscriber_container \
1415
{ \
1516
STORE_NAME##_SUBSCRIBER subscriber; \
1617
void *data; \
18+
STORE_NAME##_REDUCER linked_reducer; \
1719
}; \
18-
QUEUE_DECLARATION(STORE_NAME##_action_queue, ACTION_TYPE, 16) \
20+
QUEUE_TYPE_DECLARATION(STORE_NAME##_action_queue, ACTION_TYPE, 16) \
21+
QUEUE_DECLARATION(STORE_NAME##_action_queue, ACTION_TYPE) \
1922
LIST_DECLARATION(STORE_NAME##_reducer_list, STORE_NAME##_REDUCER, 32) \
2023
LIST_DECLARATION(STORE_NAME##_subscriber_list, struct STORE_NAME##_subscriber_container, 32) \
2124
\
@@ -24,6 +27,10 @@ void cedux_register_##STORE_NAME##_reducer(struct STORE_NAME##_handle * p_store,
2427
void cedux_register_##STORE_NAME##_subscriber(struct STORE_NAME##_handle * p_store, \
2528
STORE_NAME##_SUBSCRIBER subscriber, \
2629
void *data); \
30+
void cedux_register_##STORE_NAME##_linked_subscriber(struct STORE_NAME##_handle * p_store, \
31+
STORE_NAME##_SUBSCRIBER subscriber, \
32+
void *data, \
33+
STORE_NAME##_REDUCER linked_reducer); \
2734
struct STORE_NAME##_handle cedux_init_##STORE_NAME(void); \
2835
void cedux_dispatch_##STORE_NAME(struct STORE_NAME##_handle * p_store, ACTION_TYPE action); \
2936
bool cedux_run_##STORE_NAME(struct STORE_NAME##_handle * p_store); \
@@ -54,10 +61,18 @@ void cedux_register_##STORE_NAME##_reducer(struct STORE_NAME##_handle * p_store,
5461
\
5562
void cedux_register_##STORE_NAME##_subscriber(struct STORE_NAME##_handle * p_store, \
5663
STORE_NAME##_SUBSCRIBER subscriber, \
57-
void *data) { \
64+
void * p_data) { \
65+
cedux_register_##STORE_NAME##_linked_subscriber(p_store, subscriber, p_data, NULL); \
66+
} \
67+
\
68+
void cedux_register_##STORE_NAME##_linked_subscriber(struct STORE_NAME##_handle * p_store, \
69+
STORE_NAME##_SUBSCRIBER subscriber, \
70+
void * p_data, \
71+
STORE_NAME##_REDUCER linked_reducer) { \
5872
struct STORE_NAME##_subscriber_container container; \
5973
container.subscriber = subscriber; \
60-
container.data = data; \
74+
container.data = p_data; \
75+
container.linked_reducer = linked_reducer; \
6176
STORE_NAME##_subscriber_list_push(&p_store->subscriber_list, &container); \
6277
} \
6378
\
@@ -88,16 +103,28 @@ bool cedux_run_##STORE_NAME(struct STORE_NAME##_handle * p_store) {
88103
{ \
89104
LIST_FOR_EACH(p_store->reducer_list, reducer) \
90105
{ \
91-
reducer(&p_store->tree, action); \
106+
bool reducer_did_work = reducer(&p_store->tree, action); \
107+
if (reducer_did_work) \
108+
{ \
109+
did_work = true; \
110+
LIST_FOR_EACH(p_store->subscriber_list, subscriber_container) \
111+
{ \
112+
if (subscriber_container.linked_reducer == reducer) \
113+
{ \
114+
subscriber_container.subscriber(p_store, &p_store->tree, subscriber_container.data);\
115+
} \
116+
} \
117+
} \
92118
} \
93-
did_work = true; \
94119
} \
95120
if (p_store->lock_release) p_store->lock_release(p_store->lock); \
96121
if (did_work) \
97122
{ \
98123
LIST_FOR_EACH(p_store->subscriber_list, subscriber_container) \
99124
{ \
100-
subscriber_container.subscriber(&p_store->tree, subscriber_container.data); \
125+
if (subscriber_container.linked_reducer == NULL) { \
126+
subscriber_container.subscriber(p_store, &p_store->tree, subscriber_container.data); \
127+
} \
101128
} \
102129
} \
103130
return did_work; \
@@ -111,6 +138,8 @@ void cedux_set_threadsafe_##STORE_NAME(struct STORE_NAME##_handle * p_store, voi
111138
p_store->lock_release = lock_release; \
112139
}
113140

141+
#define STORE_TREE(STORE) STORE.tree
142+
114143
typedef void(*cedux_lock_func_t)(void *lock);
115144

116145
#endif

example.c

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
void timer_handler (int signum);
99
void setup_timer(void);
1010

11+
// HEADER DETAILS *************************************************************************************
12+
// Normally this code would be in a header file. For the sake of having a concise demo, I'm putting it
13+
// all in one file.
14+
//
1115
struct branch_a { int leaves; };
1216
struct tree { struct branch_a a; };
1317

1418
enum action_type {
1519
ADD_LEAVES,
1620
REMOVE_LEAVES,
21+
BURN_THE_TREE,
1722
};
1823

1924
struct add_leaves_data { int count; };
@@ -28,61 +33,115 @@ struct my_action_def {
2833
};
2934
};
3035

31-
// Normally this would be in a header file. This line defines a struct
32-
// for the store handles called 'struct my_store_handle'
36+
// This macro declares the store data structure as well as all of the functions which are needed
37+
// to register reducers, and subscribers, and dispatch actions. It also declares the cedux_run_my_store
38+
// function.
3339
CEDUX_DECLARE_STORE(struct tree, struct my_action_def, my_store);
3440

41+
//
42+
// END OF HEADER CONTENT
43+
// ****************************************************************************************************
44+
45+
46+
// This macro defines the implementation of the functions which are used to register reducers,
47+
// subscribers, and dispatch actions. It also defines the cedux_run_my_store function.
3548
CEDUX_DEFINE_STORE(struct tree, struct my_action_def, my_store);
3649

3750
struct my_store_handle my_store;
3851

39-
void reducer_1(struct tree * p_tree, struct my_action_def action)
52+
bool primary_reducer(struct tree * p_tree, struct my_action_def action)
4053
{
54+
// Reducers are the ONLY place where the state tree should be modified.
55+
// Return true if the state is updated. Otherwise return false.
4156
switch (action.type)
4257
{
4358
case ADD_LEAVES:
4459
p_tree->a.leaves += action.add_leaves_data.count;
45-
break;
60+
return true;
4661
case REMOVE_LEAVES:
47-
p_tree->a.leaves -= action.add_leaves_data.count;
48-
break;
62+
p_tree->a.leaves -= action.subtract_leaves_data.count;
63+
if (p_tree->a.leaves < 0) { p_tree->a.leaves = 0; }
64+
return true;
4965
default:
50-
break;
66+
return false;
5167
}
5268
}
5369

54-
void subscriber_func(const struct tree * p_tree, void *data)
70+
bool burn_tree_reducer(struct tree * p_tree, struct my_action_def action)
71+
{
72+
// Reducers are the ONLY place where the state tree should be modified.
73+
// Return true if the state is updated. Otherwise return false.
74+
if (action.type == BURN_THE_TREE) {
75+
p_tree->a.leaves = 0;
76+
return true;
77+
} else {
78+
return false;
79+
}
80+
}
81+
82+
void generic_subscriber(struct my_store_handle * p_store, struct tree const * const p_tree, void *data)
83+
{
84+
// Subscribers are for performing side-effects. Ideally they should be pure functions which perform some
85+
// action as a result of a state change.
86+
// The store handle is passed in so that you can dispatch new actions within here if you want to.
87+
// The state tree is passed in so that you can reference the state. It's const so you shouldn't modify it.
88+
int number = (int)data;
89+
printf("Generic Subscriber. Num leaves %d! Data: %d:\n", p_tree->a.leaves, number);
90+
}
91+
92+
void linked_subscriber(struct my_store_handle * p_store, struct tree const * const p_tree, void *data)
5593
{
5694
int number = (int)data;
57-
printf("Subscriber %d: Num leaves %d!\n", number, p_tree->a.leaves);
95+
printf("The tree burned down!! Num leaves %d. Data: %d:\n", p_tree->a.leaves, number);
5896
}
5997

6098
int main(void)
6199
{
62100
my_store = cedux_init_my_store(); // Initialize the internals of the store (list, queue)
63101

64-
cedux_register_my_store_reducer(&my_store, reducer_1);
65-
cedux_register_my_store_subscriber(&my_store, subscriber_func, (void *)1);
66-
cedux_register_my_store_subscriber(&my_store, subscriber_func, (void *)2);
102+
// Setup initial state.
103+
my_store.tree.a.leaves = 1000;
67104

68-
setup_timer();
105+
// Register reducers
106+
cedux_register_my_store_reducer(&my_store, primary_reducer);
107+
cedux_register_my_store_reducer(&my_store, burn_tree_reducer);
108+
109+
// Register a generic subscriber. Generic subscribers will always run any time any reducer does work.
110+
cedux_register_my_store_subscriber(&my_store, generic_subscriber, (void *)1);
111+
// You can register additional subscribers which get a unique "context". That context will always be
112+
// baseed to the subscriber.
113+
cedux_register_my_store_subscriber(&my_store, generic_subscriber, (void *)2);
114+
// You can also register a linked subscriber that will only get called if the
115+
// reducer that it is linked to does work.
116+
cedux_register_my_store_linked_subscriber(&my_store, linked_subscriber, (void *)3, burn_tree_reducer);
117+
118+
setup_timer(); // Setup for demo
69119

70120
while(1) {
71-
bool did_work = cedux_run_my_store(&my_store);
121+
bool did_work = cedux_run_my_store(&my_store); // REQUIRED. You must call this every run of your main loop. This is where all the magic happens.
72122
if (did_work) {
73-
printf("Did work.\n");
123+
printf("A reducer did work.\n");
74124
}
75125
}
76126
}
77127

78128
void timer_handler (int signum)
79129
{
80-
cedux_dispatch_my_store(&my_store, (struct my_action_def){
81-
.type = rand() % 2 != 1 ? ADD_LEAVES : REMOVE_LEAVES,
82-
.add_leaves_data = {
83-
.count = rand() % 100
84-
}
85-
});
130+
// Create an action and fill in the data based on some randomness...
131+
int random_val = rand();
132+
printf("Random num: %d\n", random_val);
133+
struct my_action_def action;
134+
action.type = rand() % 2 != 1 ? ADD_LEAVES : REMOVE_LEAVES;
135+
// Every so often burn down the tree!
136+
action.type = (random_val % 4 == 0) ? BURN_THE_TREE : action.type;
137+
if (action.type == ADD_LEAVES) {
138+
action.add_leaves_data.count = random_val % 100;
139+
} else if (action.type == REMOVE_LEAVES) {
140+
action.subtract_leaves_data.count = random_val % 100;
141+
}
142+
143+
// Dispatch the action
144+
cedux_dispatch_my_store(&my_store, action);
86145
}
87146

88147
void setup_timer(void)

0 commit comments

Comments
 (0)