Skip to content

Commit 804b366

Browse files
authored
add documentation (#22)
* add documentation
1 parent 983b4e8 commit 804b366

File tree

6 files changed

+223
-62
lines changed

6 files changed

+223
-62
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ packageProject(
6161
INCLUDE_DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION}
6262
DEPENDENCIES ""
6363
)
64+

README.md

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,82 @@
99

1010
A thread-safe event-listener template and observable value implementation for C++17.
1111

12-
## Examples
12+
## API
1313

14-
Full examples can be found in the [examples directory](https://github.com/TheLartians/Observe/tree/master/examples).
15-
16-
### Using observe::Event
14+
The core API is best illustrated by an example.
1715

1816
```cpp
19-
observe::Event<float,float> onClick;
20-
auto observer = onClick.createObserver([](auto x, auto y){ handleClick(x,y); });
21-
onClick.emit(0,0); // emits event to all observers
22-
observer.reset(); // removes observer from event
17+
#include <string>
18+
#include <iostream>
19+
20+
#include <observe/event.h>
21+
22+
void example() {
23+
// events can be valueless
24+
observe::Event<> eventA;
25+
26+
// or have arguments
27+
observe::Event<std::string, float> eventB;
28+
29+
// connect will always trigger when an event is triggered
30+
eventA.connect([](){
31+
std::cout << "A triggered" << std::endl;
32+
});
33+
34+
// observers will remove themselves from the event on destroy or reset
35+
observe::Observer observer = eventB.createObserver([](const std::string &str, float v){
36+
std::cout << "B triggered with " << str << " and " << v << std::endl;
37+
});
38+
39+
// call emit to trigger all observers
40+
eventA.emit();
41+
eventB.emit("meaning of life", 42);
42+
43+
// `observe::Observer` can store any type of observer
44+
observer.observe(eventA, [](){ std::cout << "I am now observing A" << std::endl; });
45+
46+
// to remove an observer without destroying the object, call reset
47+
observer.reset();
48+
}
2349
```
2450

51+
Note that events and observers are thread and exception safe.
52+
2553
### Using observe::Value
2654

55+
The project also includes a header `observe/value.h` with an experimental observable value implementation.
56+
The API is still subject to change, so use with caution.
57+
2758
```cpp
2859
observe::Value a = 1;
2960
observe::Value b = 2;
61+
62+
// contains the sum of `a` and `b`
3063
observe::DependentObservableValue sum([](auto a, auto b){ return a+b; },a,b);
31-
sum.onChange.connect([](auto &v){ std::cout << "The result changed to " << r << std::endl; });
64+
65+
// all observable values contain an `Event` `onChange`
66+
sum.onChange.connect([](auto &v){
67+
std::cout << "The result changed to " << r << std::endl;
68+
});
69+
70+
// access the value by dereferencing
3271
std::cout << "The result is " << *sum << std::endl; // -> the result is 3
72+
73+
// changes will automatically propagate through dependent values
3374
a.set(3); // -> The result changed to 5
3475
```
3576
3677
## Installation and usage
3778
38-
With [CPM](https://github.com/TheLartians/CPM), observe::Event can be used in a CMake project simply by adding the following to the project's `CMakeLists.txt`.
79+
With [CPM.cmake](https://github.com/TheLartians/CPM) you can easily add the headers to your project.
3980
4081
```cmake
4182
CPMAddPackage(
42-
NAME LarsObserve
43-
VERSION 2.1
44-
GIT_REPOSITORY https://github.com/TheLartians/Observe.git
83+
NAME Observe
84+
VERSION 3.0
85+
GITHUB_REPOSITORY TheLartians/Observe
4586
)
4687
47-
target_link_libraries(myProject LarsObserve)
88+
target_link_libraries(myProject Observe)
4889
```
4990

50-
Alternatively, the repository can be cloned locally and included it via `add_subdirectory`. Installing observe::Event will make it findable in CMake's `find_package`.

include/observe/event.h

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma once
22

3+
#include <observe/observer.h>
4+
35
#include <algorithm>
46
#include <functional>
57
#include <memory>
@@ -9,42 +11,21 @@
911

1012
namespace observe {
1113

12-
template <typename... Args> class Event;
14+
template <typename... Args> class SharedEvent;
1315

14-
class Observer {
16+
template <typename... Args> class Event {
1517
public:
16-
struct Base {
17-
virtual ~Base() {}
18-
};
19-
20-
Observer() {}
21-
Observer(Observer &&other) = default;
22-
template <typename L> Observer(L &&l) : data(new L(std::move(l))) {}
23-
24-
Observer &operator=(const Observer &other) = delete;
25-
Observer &operator=(Observer &&other) = default;
26-
27-
template <typename L> Observer &operator=(L &&l) {
28-
data.reset(new L(std::move(l)));
29-
return *this;
30-
}
31-
32-
template <typename H, typename... Args> void observe(Event<Args...> &event, const H &handler) {
33-
data.reset(new typename Event<Args...>::Observer(event.createObserver(handler)));
34-
}
35-
36-
void reset() { data.reset(); }
37-
operator bool() const { return bool(data); }
38-
39-
private:
40-
std::unique_ptr<Base> data;
41-
};
18+
/**
19+
* The handler type for this event
20+
*/
21+
using Handler = std::function<void(const Args &...)>;
4222

43-
template <typename... Args> class Event {
4423
private:
45-
using Handler = std::function<void(const Args &...)>;
4624
using HandlerID = size_t;
4725

26+
/**
27+
* Stores an event handler
28+
*/
4829
struct StoredHandler {
4930
HandlerID id;
5031
std::shared_ptr<Handler> callback;
@@ -58,6 +39,10 @@ namespace observe {
5839
std::mutex observerMutex;
5940
};
6041

42+
/**
43+
* Contains the event's data and handlers
44+
* Observers should store a `weak_ptr` to the data to observe event lifetime
45+
*/
6146
std::shared_ptr<Data> data;
6247

6348
HandlerID addHandler(Handler h) const {
@@ -67,14 +52,23 @@ namespace observe {
6752
}
6853

6954
protected:
55+
/**
56+
* Copy and assignment is `protected` to prevent accidental duplication of the event and its
57+
* handlers. If you need this, use `SharedEvent` instead.
58+
*/
7059
Event(const Event &) = default;
7160
Event &operator=(const Event &) = default;
7261

7362
public:
74-
struct Observer : public observe::Observer::Base {
63+
/**
64+
* The specific Observer implementation for this event
65+
*/
66+
class Observer : public observe::Observer::Base {
67+
private:
7568
std::weak_ptr<Data> data;
7669
HandlerID id;
7770

71+
public:
7872
Observer() {}
7973
Observer(const std::weak_ptr<Data> &_data, HandlerID _id) : data(_data), id(_id) {}
8074

@@ -84,11 +78,17 @@ namespace observe {
8478
Observer &operator=(const Observer &other) = delete;
8579
Observer &operator=(Observer &&other) = default;
8680

81+
/**
82+
* Observe another event of the same type
83+
*/
8784
void observe(const Event &event, const Handler &handler) {
8885
reset();
8986
*this = event.createObserver(handler);
9087
}
9188

89+
/**
90+
* Removes the handler from the event
91+
*/
9292
void reset() {
9393
if (auto d = data.lock()) {
9494
std::lock_guard<std::mutex> lock(d->observerMutex);
@@ -107,49 +107,72 @@ namespace observe {
107107
Event() : data(std::make_shared<Data>()) {}
108108

109109
Event(Event &&other) : Event() { *this = std::move(other); }
110+
110111
Event &operator=(Event &&other) {
111112
std::swap(data, other.data);
112113
return *this;
113114
}
114115

116+
/**
117+
* Call all handlers currently connected to the event in the order they were added (thread
118+
* safe). If a handler is removed before its turn (by another thread or previous handler) it
119+
* will not be called.
120+
*/
115121
void emit(Args... args) const {
116122
std::vector<std::weak_ptr<Handler>> handlers;
117123
handlers.resize(data->observers.size());
118-
data->observerMutex.lock();
119-
std::transform(data->observers.begin(), data->observers.end(), handlers.begin(),
120-
[](auto &h) { return h.callback; });
121-
data->observerMutex.unlock();
124+
{
125+
std::lock_guard<std::mutex> lock(data->observerMutex);
126+
std::transform(data->observers.begin(), data->observers.end(), handlers.begin(),
127+
[](auto &h) { return h.callback; });
128+
}
122129
for (auto &weakCallback : handlers) {
123130
if (auto callback = weakCallback.lock()) {
124131
(*callback)(args...);
125132
}
126133
}
127134
}
128135

136+
/**
137+
* Add a temporary handler to the event.
138+
* The handlers lifetime will be managed by the returned observer object.
139+
*/
129140
Observer createObserver(const Handler &h) const { return Observer(data, addHandler(h)); }
130141

142+
/**
143+
* Add a permanent handler to the event.
144+
*/
131145
void connect(const Handler &h) const { addHandler(h); }
132146

133-
void clearObservers() {
147+
/**
148+
* Remove all handlers (temporary and permanent) connected to the event.
149+
*/
150+
void reset() const {
134151
std::lock_guard<std::mutex> lock(data->observerMutex);
135152
data->observers.clear();
136153
}
137154

155+
/**
156+
* The number of observers connected to the event.
157+
*/
138158
size_t observerCount() const {
139159
std::lock_guard<std::mutex> lock(data->observerMutex);
140160
return data->observers.size();
141161
}
142162
};
143163

144-
template <typename... Args> class EventReference : public Event<Args...> {
145-
protected:
146-
using Base = Event<Args...>;
147-
164+
/**
165+
* An event class that can be copied and assigned.
166+
* Behaves just like a more efficient `std::shared_ptr<Event<Args...>>` without derefencing.
167+
*/
168+
template <typename... Args> class SharedEvent : public Event<Args...> {
148169
public:
149-
EventReference(const Base &other) : Base(other) {}
170+
using Event<Args...>::Event;
171+
172+
SharedEvent(const SharedEvent<Args...> &other) : Event<Args...>(other) {}
150173

151-
EventReference &operator=(const Base &other) {
152-
Base::operator=(other);
174+
SharedEvent &operator=(const SharedEvent<Args...> &other) {
175+
Event<Args...>::operator=(other);
153176
return *this;
154177
}
155178
};

include/observe/observer.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#pragma once
2+
3+
#include <memory>
4+
5+
namespace observe {
6+
7+
template <typename... Args> class Event;
8+
9+
/**
10+
* A generic event observer class
11+
*/
12+
class Observer {
13+
public:
14+
/**
15+
* The base class for observer implementations
16+
*/
17+
struct Base {
18+
/**
19+
* Observers should remove themselves from their events once destroyed
20+
*/
21+
virtual ~Base() {}
22+
};
23+
24+
Observer() {}
25+
Observer(Observer &&other) = default;
26+
template <typename L> Observer(L &&l) : data(new L(std::move(l))) {}
27+
28+
Observer &operator=(const Observer &other) = delete;
29+
Observer &operator=(Observer &&other) = default;
30+
31+
template <typename L> Observer &operator=(L &&l) {
32+
data.reset(new L(std::move(l)));
33+
return *this;
34+
}
35+
36+
/**
37+
* Observe an event with the callback
38+
*/
39+
template <typename H, typename... Args> void observe(Event<Args...> &event, const H &handler) {
40+
data.reset(new typename Event<Args...>::Observer(event.createObserver(handler)));
41+
}
42+
43+
/**
44+
* remove the callback from the event
45+
*/
46+
void reset() { data.reset(); }
47+
48+
/**
49+
* returns `true` if the event
50+
*/
51+
operator bool() const { return bool(data); }
52+
53+
private:
54+
std::unique_ptr<Base> data;
55+
};
56+
57+
} // namespace observe

test/source/event.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ TEST_CASE("Event") {
5656

5757
SUBCASE("clear observers") {
5858
observe::Observer observer = event.createObserver([&]() { observeCount++; });
59-
event.clearObservers();
59+
event.reset();
6060
REQUIRE(event.observerCount() == 0);
6161
event.emit();
6262
REQUIRE(connectCount == 0);
@@ -127,9 +127,9 @@ TEST_CASE("Event") {
127127
}
128128
}
129129

130-
TEST_CASE("EventReference") {
131-
observe::Event<> onA, onB;
132-
observe::EventReference<> onR(onA);
130+
TEST_CASE("SharedEvent") {
131+
observe::SharedEvent<> onA, onB;
132+
observe::SharedEvent<> onR(onA);
133133
unsigned aCount = 0, bCount = 0;
134134
onR.connect([&]() { aCount++; });
135135
onA.emit();

0 commit comments

Comments
 (0)