Skip to content

Commit

Permalink
add documentation (#22)
Browse files Browse the repository at this point in the history
* add documentation
  • Loading branch information
TheLartians authored Apr 23, 2020
1 parent 983b4e8 commit 804b366
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 62 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ packageProject(
INCLUDE_DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION}
DEPENDENCIES ""
)

70 changes: 55 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,82 @@

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

## Examples
## API

Full examples can be found in the [examples directory](https://github.com/TheLartians/Observe/tree/master/examples).

### Using observe::Event
The core API is best illustrated by an example.

```cpp
observe::Event<float,float> onClick;
auto observer = onClick.createObserver([](auto x, auto y){ handleClick(x,y); });
onClick.emit(0,0); // emits event to all observers
observer.reset(); // removes observer from event
#include <string>
#include <iostream>

#include <observe/event.h>

void example() {
// events can be valueless
observe::Event<> eventA;

// or have arguments
observe::Event<std::string, float> eventB;

// connect will always trigger when an event is triggered
eventA.connect([](){
std::cout << "A triggered" << std::endl;
});

// observers will remove themselves from the event on destroy or reset
observe::Observer observer = eventB.createObserver([](const std::string &str, float v){
std::cout << "B triggered with " << str << " and " << v << std::endl;
});

// call emit to trigger all observers
eventA.emit();
eventB.emit("meaning of life", 42);

// `observe::Observer` can store any type of observer
observer.observe(eventA, [](){ std::cout << "I am now observing A" << std::endl; });

// to remove an observer without destroying the object, call reset
observer.reset();
}
```

Note that events and observers are thread and exception safe.

### Using observe::Value

The project also includes a header `observe/value.h` with an experimental observable value implementation.
The API is still subject to change, so use with caution.

```cpp
observe::Value a = 1;
observe::Value b = 2;

// contains the sum of `a` and `b`
observe::DependentObservableValue sum([](auto a, auto b){ return a+b; },a,b);
sum.onChange.connect([](auto &v){ std::cout << "The result changed to " << r << std::endl; });

// all observable values contain an `Event` `onChange`
sum.onChange.connect([](auto &v){
std::cout << "The result changed to " << r << std::endl;
});

// access the value by dereferencing
std::cout << "The result is " << *sum << std::endl; // -> the result is 3

// changes will automatically propagate through dependent values
a.set(3); // -> The result changed to 5
```
## Installation and usage
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`.
With [CPM.cmake](https://github.com/TheLartians/CPM) you can easily add the headers to your project.
```cmake
CPMAddPackage(
NAME LarsObserve
VERSION 2.1
GIT_REPOSITORY https://github.com/TheLartians/Observe.git
NAME Observe
VERSION 3.0
GITHUB_REPOSITORY TheLartians/Observe
)
target_link_libraries(myProject LarsObserve)
target_link_libraries(myProject Observe)
```

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`.
109 changes: 66 additions & 43 deletions include/observe/event.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <observe/observer.h>

#include <algorithm>
#include <functional>
#include <memory>
Expand All @@ -9,42 +11,21 @@

namespace observe {

template <typename... Args> class Event;
template <typename... Args> class SharedEvent;

class Observer {
template <typename... Args> class Event {
public:
struct Base {
virtual ~Base() {}
};

Observer() {}
Observer(Observer &&other) = default;
template <typename L> Observer(L &&l) : data(new L(std::move(l))) {}

Observer &operator=(const Observer &other) = delete;
Observer &operator=(Observer &&other) = default;

template <typename L> Observer &operator=(L &&l) {
data.reset(new L(std::move(l)));
return *this;
}

template <typename H, typename... Args> void observe(Event<Args...> &event, const H &handler) {
data.reset(new typename Event<Args...>::Observer(event.createObserver(handler)));
}

void reset() { data.reset(); }
operator bool() const { return bool(data); }

private:
std::unique_ptr<Base> data;
};
/**
* The handler type for this event
*/
using Handler = std::function<void(const Args &...)>;

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

/**
* Stores an event handler
*/
struct StoredHandler {
HandlerID id;
std::shared_ptr<Handler> callback;
Expand All @@ -58,6 +39,10 @@ namespace observe {
std::mutex observerMutex;
};

/**
* Contains the event's data and handlers
* Observers should store a `weak_ptr` to the data to observe event lifetime
*/
std::shared_ptr<Data> data;

HandlerID addHandler(Handler h) const {
Expand All @@ -67,14 +52,23 @@ namespace observe {
}

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

public:
struct Observer : public observe::Observer::Base {
/**
* The specific Observer implementation for this event
*/
class Observer : public observe::Observer::Base {
private:
std::weak_ptr<Data> data;
HandlerID id;

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

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

/**
* Observe another event of the same type
*/
void observe(const Event &event, const Handler &handler) {
reset();
*this = event.createObserver(handler);
}

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

Event(Event &&other) : Event() { *this = std::move(other); }

Event &operator=(Event &&other) {
std::swap(data, other.data);
return *this;
}

/**
* Call all handlers currently connected to the event in the order they were added (thread
* safe). If a handler is removed before its turn (by another thread or previous handler) it
* will not be called.
*/
void emit(Args... args) const {
std::vector<std::weak_ptr<Handler>> handlers;
handlers.resize(data->observers.size());
data->observerMutex.lock();
std::transform(data->observers.begin(), data->observers.end(), handlers.begin(),
[](auto &h) { return h.callback; });
data->observerMutex.unlock();
{
std::lock_guard<std::mutex> lock(data->observerMutex);
std::transform(data->observers.begin(), data->observers.end(), handlers.begin(),
[](auto &h) { return h.callback; });
}
for (auto &weakCallback : handlers) {
if (auto callback = weakCallback.lock()) {
(*callback)(args...);
}
}
}

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

/**
* Add a permanent handler to the event.
*/
void connect(const Handler &h) const { addHandler(h); }

void clearObservers() {
/**
* Remove all handlers (temporary and permanent) connected to the event.
*/
void reset() const {
std::lock_guard<std::mutex> lock(data->observerMutex);
data->observers.clear();
}

/**
* The number of observers connected to the event.
*/
size_t observerCount() const {
std::lock_guard<std::mutex> lock(data->observerMutex);
return data->observers.size();
}
};

template <typename... Args> class EventReference : public Event<Args...> {
protected:
using Base = Event<Args...>;

/**
* An event class that can be copied and assigned.
* Behaves just like a more efficient `std::shared_ptr<Event<Args...>>` without derefencing.
*/
template <typename... Args> class SharedEvent : public Event<Args...> {
public:
EventReference(const Base &other) : Base(other) {}
using Event<Args...>::Event;

SharedEvent(const SharedEvent<Args...> &other) : Event<Args...>(other) {}

EventReference &operator=(const Base &other) {
Base::operator=(other);
SharedEvent &operator=(const SharedEvent<Args...> &other) {
Event<Args...>::operator=(other);
return *this;
}
};
Expand Down
57 changes: 57 additions & 0 deletions include/observe/observer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

#include <memory>

namespace observe {

template <typename... Args> class Event;

/**
* A generic event observer class
*/
class Observer {
public:
/**
* The base class for observer implementations
*/
struct Base {
/**
* Observers should remove themselves from their events once destroyed
*/
virtual ~Base() {}
};

Observer() {}
Observer(Observer &&other) = default;
template <typename L> Observer(L &&l) : data(new L(std::move(l))) {}

Observer &operator=(const Observer &other) = delete;
Observer &operator=(Observer &&other) = default;

template <typename L> Observer &operator=(L &&l) {
data.reset(new L(std::move(l)));
return *this;
}

/**
* Observe an event with the callback
*/
template <typename H, typename... Args> void observe(Event<Args...> &event, const H &handler) {
data.reset(new typename Event<Args...>::Observer(event.createObserver(handler)));
}

/**
* remove the callback from the event
*/
void reset() { data.reset(); }

/**
* returns `true` if the event
*/
operator bool() const { return bool(data); }

private:
std::unique_ptr<Base> data;
};

} // namespace observe
8 changes: 4 additions & 4 deletions test/source/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ TEST_CASE("Event") {

SUBCASE("clear observers") {
observe::Observer observer = event.createObserver([&]() { observeCount++; });
event.clearObservers();
event.reset();
REQUIRE(event.observerCount() == 0);
event.emit();
REQUIRE(connectCount == 0);
Expand Down Expand Up @@ -127,9 +127,9 @@ TEST_CASE("Event") {
}
}

TEST_CASE("EventReference") {
observe::Event<> onA, onB;
observe::EventReference<> onR(onA);
TEST_CASE("SharedEvent") {
observe::SharedEvent<> onA, onB;
observe::SharedEvent<> onR(onA);
unsigned aCount = 0, bCount = 0;
onR.connect([&]() { aCount++; });
onA.emit();
Expand Down
Loading

0 comments on commit 804b366

Please sign in to comment.