diff --git a/.travis.yml b/.travis.yml index 1abf898..e1388f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,9 +34,11 @@ before_install: # Update compilers - eval "${MATRIX_EVAL}" - echo "CC=$CC CXX=$CXX" - # Install a supported cmake version (>= 3.5) - - wget -O cmake.sh https://cmake.org/files/v3.10/cmake-3.10.0-rc1-Linux-x86_64.sh + # Install a supported cmake version (>= 3.14) + - wget -O cmake.sh https://cmake.org/files/v3.14/cmake-3.14.0-Linux-x86_64.sh - sudo sh cmake.sh --skip-license --exclude-subdir --prefix=/usr/local + - export PATH=/usr/local/bin:$PATH + - cmake --version install: - cmake -H. -Bbuild diff --git a/CMakeLists.txt b/CMakeLists.txt index 448a598..77e09df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,14 +3,14 @@ cmake_minimum_required(VERSION 3.5 FATAL_ERROR) # ---- Project ---- project(LarsEvent - VERSION 1.0 + VERSION 2.0 LANGUAGES CXX ) # ---- Configuration variables ---- -option(ENABLE_LARS_EVENT_TESTS "Enable tests" OFF) -option(BUILD_LARS_EVENT_EXAMPLES "Enable examples" OFF) +option(LARS_EVENT_ENABLE_TESTS "Enable tests" OFF) +option(LARS_EVENT_BUILD_EXAMPLES "Enable examples" OFF) # ---- Include guards ---- @@ -86,13 +86,13 @@ install( # ---- Examples ---- -IF(${BUILD_LARS_EVENT_EXAMPLES}) +IF(${LARS_EVENT_BUILD_EXAMPLES}) add_subdirectory(examples) ENDIF() # ---- Test ---- -if(${ENABLE_LARS_EVENT_TESTS}) +if(${LARS_EVENT_ENABLE_TESTS}) ENABLE_TESTING() add_subdirectory(tests) endif() diff --git a/README.md b/README.md index e68c393..09188d5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,44 @@ [![Build Status](https://travis-ci.com/TheLartians/Event.svg?branch=master)](https://travis-ci.com/TheLartians/Event) -# lars::event +# lars::Event -A c++11 event-listener system template. See [Examples](https://github.com/TheLartians/Event/tree/master/examples) for usage. +A thread-safe event-listener template and observable value implementation for C++17. + +# Examples + +Full examples can be found in the [examples directory](https://github.com/TheLartians/Event/tree/master/examples). + +## lars::Event + +```c++ +lars::Event 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 +``` + +## lars::ObservableValue + +```c++ +lars::ObservableValue a = 1; +lars::ObservableValue b = 2; +lars::DependentObservableValue sum([](auto a, auto b){ return a+b; },a,b); +sum.onChange.connect([](auto &v){ std::cout << "The result is " << r << std::endl; }); +a.set(3); // -> "The result is 5" +``` + +# Installation and usage + +With [CPM](https://github.com/TheLartians/CPM), lars::Event can be added to your project by adding the following to your projects' `CMakeLists.txt`. + +```cmake +CPMAddPackage( + NAME LarsEvent + VERSION 2.0 + GIT_REPOSITORY https://github.com/TheLartians/Event.git +) + +target_link_libraries(myProject LarsEvent) +``` + +Alternatively, download the repository include it via `add_subdirectory`. Installing lars::Event will make it findable in CMake's `find_package`. diff --git a/examples/mouse_events.cpp b/examples/mouse_events.cpp deleted file mode 100644 index 9553c48..0000000 --- a/examples/mouse_events.cpp +++ /dev/null @@ -1,49 +0,0 @@ - -#include -#include - -using namespace lars; -using namespace std; - -using ClickEvent = Event; - -struct GuiElement{ - ClickEvent clicked; - void mouse_down(float x,float y){ clicked.notify(x, y); } -}; - -int main(int argc, char **argv) { - GuiElement A,B; - ClickEvent::Observer observer_1; - // pseudo-base class can hold any type of observer - Observer observer_2; - - observer_1.observe(A.clicked,[](float x,float y){ - cout << "observer 1 : A clicked at " << x << ", " << y << endl; - }); - - observer_2.observe(B.clicked,[](float x,float y){ - cout << "observer 2 : B clicked at " << x << ", " << y << endl; - }); - - // anonymous observer - B.clicked.connect([](float x,float y){ - cout << " : B clicked at " << x << ", " << y << endl; - }); - - { - auto temporary_observer = A.clicked.create_observer([](float x,float y){ - cout << "tmp observer: A clicked at " << x << ", " << y << endl; - }); - cout << "A has two observers, B has two observers" << endl; - A.mouse_down(1, 0); - B.mouse_down(0, 1); - } - - observer_2 = move(observer_1); - - cout << "A has one observer, B has one observer" << endl; - A.mouse_down(2, 0); - B.mouse_down(0, 2); -} - diff --git a/include/lars/event.h b/include/lars/event.h index c014512..0c647f7 100644 --- a/include/lars/event.h +++ b/include/lars/event.h @@ -2,217 +2,150 @@ #include #include -#include #include #include +#include namespace lars{ template class Event; class Observer{ - public: + public: struct Base{ virtual ~Base(){} }; Observer(){} - Observer(Observer &&other){ std::swap(data,other.data); } + Observer(Observer &&other) = default; template Observer(L && l):data(new L(std::move(l))){ } Observer & operator=(const Observer &other) = delete; - Observer & operator=(Observer &&other){ - data.reset(); - std::swap(data,other.data); - return *this; - } + Observer & operator=(Observer &&other) = default; - template Observer & operator=(L && l){ data.reset(new L(std::move(l))); return *this; } + template Observer & operator=(L && l){ + data.reset(new L(std::move(l))); + return *this; + } - template void observe(Event & event,H handler){ - data.reset(new typename Event::Observer(event,handler)); + template void observe(Event & event,const H &handler){ + data.reset(new typename Event::Observer(event.createObserver(handler))); } void reset(){ data.reset(); } - operator bool(){ return bool(data); } + operator bool() const { return bool(data); } - private: + private: std::unique_ptr data; }; - class MultiOberserver{ - protected: - std::vector observers; - public: - void add_observer(Observer && l){ observers.emplace_back(std::move(l)); } - - template void observe(Event & event,H handler){ - observers.emplace_back(); - observers.back().observe(event,handler); - } - - void clear_observer(){ observers.clear(); } - void pop_observer(){ observers.pop_back(); } - - }; - - template class Event:private std::shared_ptr*>{ - + template class Event{ + private: + using Handler = std::function; - using ObserverList = std::list; - using iterator = typename ObserverList::iterator; - - mutable ObserverList observers; + using HandlerID = size_t; - iterator insert_handler(Handler h)const{ - return observers.insert(observers.end(),h); + struct StoredHandler { + HandlerID id; + Handler callback; + }; + + using HandlerList = std::vector; + using EventPointer = std::shared_ptr; + using WeakEventPointer = std::weak_ptr; + + mutable HandlerID IDCounter = 0; + mutable HandlerList observers; + mutable std::mutex observerMutex; + EventPointer self; + + HandlerID addHandler(Handler h)const{ + std::lock_guard lock(observerMutex); + observers.emplace_back(StoredHandler{IDCounter,h}); + return IDCounter++; } - void erase_handler(const iterator &it)const{ - observers.erase(it); + void eraseHandler(const HandlerID &id)const{ + std::lock_guard lock(observerMutex); + auto it = std::find_if(observers.begin(), observers.end(), [&](auto &o){ return o.id == id; }); + if (it != observers.end()) { observers.erase(it); } } - public: + public: struct Observer:public lars::Observer::Base{ - std::weak_ptr the_event; - typename ObserverList::iterator it; - - Observer(){ } + WeakEventPointer parent; + HandlerID id; - Observer(const Event & s,Handler f){ - observe(s,f); - } - - Observer(Observer &&other){ - the_event = other.the_event; - it = other.it; - other.the_event.reset(); + Observer(){} + Observer(const EventPointer & _parent, HandlerID _id):parent(_parent), id(_id){ } + Observer(Observer &&other) = default; Observer(const Observer &other) = delete; Observer & operator=(const Observer &other) = delete; + Observer & operator=(Observer &&other)=default; - Observer & operator=(Observer &&other){ - reset(); - the_event = other.the_event; - it = other.it; - other.the_event.reset(); - return *this; - } - - void observe(const Event & s,Handler h){ - reset(); - the_event = s; - it = s.insert_handler(h); + void observe(const Event &event, const Handler &handler){ + *this = event.createObserver(handler); } - + void reset(){ - if(!the_event.expired()) (*the_event.lock())->erase_handler(it); - the_event.reset(); + if(!parent.expired()){ + (*parent.lock())->eraseHandler(id); + } + parent.reset(); } ~Observer(){ reset(); } }; - Event():std::shared_ptr(std::make_shared(this)){ } + Event():self(std::make_shared(this)){ + + } Event(const Event &) = delete; - Event(Event &&) = default; + + Event(Event &&other){ + *this = std::move(other); + } Event & operator=(const Event &) = delete; - Event & operator=(Event &&) = default; - - void notify(Args ... args)const{ - for(auto it = observers.begin();it != observers.end();){ - auto &f = *it; - auto next = it; - ++next; - f(args...); - it = next; - } + + Event & operator=(Event &&other){ + self = std::move(other.self); + *self = this; + observers = std::move(other.observers); + IDCounter = other.IDCounter; + return *this; } - Observer create_observer(const Handler &h)const{ - return Observer(*this,h); + void emit(Args ... args) const { + observerMutex.lock(); + auto tmpObservers = observers; + observerMutex.unlock(); + for(auto &observer: tmpObservers){ + observer.callback(args...); + } } - void connect(const Handler &h)const{ - insert_handler(h); + Observer createObserver(const Handler &h)const{ + return Observer(self, addHandler(h)); } - void transfer_observers_to(Event &other){ - for(auto &observer:observers) observer->the_event = &other; - other.observers.splice(other.observers.end(),observers.begin(),observers.end()); + void connect(const Handler &h)const{ + addHandler(h); } - void clear_observers(){ + void clearObservers(){ + std::lock_guard lock(observerMutex); observers.clear(); } - size_t observers_count() const { + size_t observerCount() const { + std::lock_guard lock(observerMutex); return observers.size(); } }; - struct ObservableValueBase{ - Event<> on_set; - }; - - struct ObservableValueWithChangeEventBase:public ObservableValueBase{ - Event<> on_change; - }; - - template class ObservableValue:public Base{ - T value; - public: - std::function converter; - - template ObservableValue(Args ... args):value(std::forward(args)...){} - - const T & get()const{ return value; } - - operator const T &()const{ return get(); } - - void set(const T &other){ value = other; if(converter) converter(value); Base::on_set.notify(); } - void set(T &&other){ value = std::forward(other); if(converter) converter(value); Base::on_set.notify(); } - ObservableValue & operator=(const T &other){ set(other); return *this; } - ObservableValue & operator=(T &&other){ set(std::forward(other)); return *this; } - - void set_silently(const T &other){ value = other; if(converter) converter(value); } - void set_silently(T &&other){ value = std::forward(other); if(converter) converter(value); } - }; - - template class ObservableValueWithChangeEvent:public ObservableValue{ - private: - T previous_value; - - void init(){ - previous_value = *this; - this->on_set.connect([this](){ - if(previous_value != this->get()){ Base::on_change.notify(); previous_value = this->get(); } - }); - } - - public: - template ObservableValueWithChangeEvent(Args ... args):ObservableValue(std::forward(args)...){ init(); } - }; - - template class SharedObservableValue:public std::shared_ptr>{ - public: - using std::shared_ptr>::shared_ptr; - SharedObservableValue(const std::shared_ptr> &other):std::shared_ptr>(other){} - }; - - template SharedObservableValue make_shared_observable_value(Args ... args){ - return std::make_shared>(args...); - } - - } - - - - - - diff --git a/include/lars/observable_value.h b/include/lars/observable_value.h new file mode 100644 index 0000000..a9dffa5 --- /dev/null +++ b/include/lars/observable_value.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +namespace lars { + + template class ObservableValue { + protected: + T value; + + public: + using OnChange = Event; + OnChange onChange; + + template ObservableValue(Args ... args):value(std::forward(args)...){ + } + + ObservableValue(ObservableValue &&) = delete; + ObservableValue &operator=(ObservableValue &&) = delete; + + template void set(Args ... args){ + value = T(std::forward(args)...); + onChange.emit(value); + } + + template void setSilently(Args ... args){ + value = T(std::forward(args)...); + } + + const T & get()const{ + return value; + } + + const T & operator*()const{ + return value; + } + + const T * operator->()const{ + return &value; + } + + }; + + template ObservableValue(T) -> ObservableValue; + + template class DependentObservableValue: public ObservableValue { + private: + std::tuple::OnChange::Observer ...> observers; + + public: + template DependentObservableValue( + const H &handler, + const ObservableValue & ... deps + ): + ObservableValue(handler(deps.get()...)), + observers(std::make_tuple(deps.onChange.createObserver([&,this](const auto &){ + this->set(handler(deps.get()...)); + })...)) + { + + } + + }; + + template DependentObservableValue( + F, + const ObservableValue &... + ) -> DependentObservableValue::type, D...>; + +} \ No newline at end of file diff --git a/include/lars/thread_safe_event.h b/include/lars/thread_safe_event.h deleted file mode 100644 index 37a8758..0000000 --- a/include/lars/thread_safe_event.h +++ /dev/null @@ -1,108 +0,0 @@ -#include - -#include - -namespace lars{ - - template class ThreadSafeEvent:private std::shared_ptr*>{ - - using Handler = std::function; - using ObserverList = std::list; - using iterator = typename ObserverList::iterator; - mutable std::mutex mutex; - - mutable ObserverList observers; - - iterator insert_handler(Handler h)const{ - std::lock_guard guard(mutex); - return observers.insert(observers.end(),h); - } - - void erase_handler(const iterator &it)const{ - std::lock_guard guard(mutex); - observers.erase(it); - } - - public: - - struct Observer:public lars::Observer::Base{ - std::weak_ptr the_event; - typename ObserverList::iterator it; - - Observer(){ } - - Observer(const ThreadSafeEvent & s,Handler f){ - observe(s,f); - } - - Observer(Observer &&other){ - the_event = other.the_event; - it = other.it; - other.the_event.reset(); - } - - Observer(const Observer &other) = delete; - - Observer & operator=(const Observer &other) = delete; - - Observer & operator=(Observer &&other){ - reset(); - the_event = other.the_event; - it = other.it; - other.the_event.reset(); - return *this; - } - - void observe(const ThreadSafeEvent & s,Handler h){ - reset(); - the_event = s; - it = s.insert_handler(h); - } - - void reset(){ - if(!the_event.expired()) (*the_event.lock())->erase_handler(it); - the_event.reset(); - } - - ~Observer(){ reset(); } - }; - - ThreadSafeEvent():std::shared_ptr(std::make_shared(this)){ } - - ThreadSafeEvent(const ThreadSafeEvent &) = delete; - ThreadSafeEvent(ThreadSafeEvent &&) = default; - - ThreadSafeEvent & operator=(const ThreadSafeEvent &) = delete; - ThreadSafeEvent & operator=(ThreadSafeEvent &&) = default; - - void notify(Args... args)const{ - decltype(observers) safe_observers; - { - std::lock_guard guard(mutex); - safe_observers = observers; - } - for(auto &f:safe_observers) f(args...); - } - - Observer create_observer(Handler h)const{ - return Observer(*this,h); - } - - void connect(Handler h)const{ - insert_handler(h); - } - - void transfer_observers_to(ThreadSafeEvent &other){ - for(auto &observer:observers) observer->the_event = &other; - other.observers.splice(other.observers.end(),observers.begin(),observers.end()); - } - - void clear_observers(){ - observers.clear(); - } - - }; - - -} - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 41b7130..4400458 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,35 +1,63 @@ -cmake_minimum_required(VERSION 3.5 FATAL_ERROR) +cmake_minimum_required(VERSION 3.14 FATAL_ERROR) -# ---- Project ---- +# ---- Options ---- -project(Tests CXX) +option(ENABLE_TEST_COVERAGE "Enable test coverage" OFF) -# ---- CXX Flags ---- - -if(NOT CMAKE_CXX_STANDARD GREATER 17) - set(CMAKE_CXX_STANDARD 17) -endif() - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") - -# ---- Requires ---- +# ---- Dependencies ---- if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) find_package(LarsEvent REQUIRED) endif() -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/get_catch2.cmake) +include(FetchContent) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v2.5.0 +) + +FetchContent_MakeAvailable(Catch2) # ---- Create binary ---- file(GLOB tests_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) -add_executable(tests ${tests_sources}) -add_dependencies(tests catch2-project) -target_link_libraries(tests catch2) -target_link_libraries(tests LarsEvent) +add_executable(lars-event-tests ${tests_sources}) +target_link_libraries(lars-event-tests Catch2 LarsEvent) +set_target_properties(lars-event-tests PROPERTIES CXX_STANDARD 17 COMPILE_FLAGS "-Wall -pedantic -Wextra") + +if (NOT ${ENABLE_TEST_COVERAGE}) # clang warns about unused coverage flag + set_target_properties(lars-event-tests PROPERTIES COMPILE_FLAGS "-Werror") +endif() + # ---- Add tests ---- ENABLE_TESTING() -ADD_TEST(test tests) - +ADD_TEST(lars-event-tests lars-event-tests) +# ---- code coverage ---- + +if (${ENABLE_TEST_COVERAGE}) + + FetchContent_Declare( + extra-cmake-modules + GIT_REPOSITORY https://github.com/bilke/cmake-modules + GIT_TAG 5893e3eb3aaec104f86ba81ee90b7e9279b74c3f + ) + + if(NOT extra-cmake-modules_POPULATED) + FetchContent_Populate(extra-cmake-modules) + include(${extra-cmake-modules_SOURCE_DIR}/CodeCoverage.cmake) + endif() + + APPEND_COVERAGE_COMPILER_FLAGS() + + set(COVERAGE_GCOVR_EXCLUDES ${Catch2_SOURCE_DIR} ${CMAKE_CURRENT_LIST_DIR}) + + SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML( + NAME lars-event-tests-coverage + EXECUTABLE lars-event-tests + DEPENDENCIES lars-event-tests + ) +endif() diff --git a/tests/cmake/get_catch2.cmake b/tests/cmake/get_catch2.cmake deleted file mode 100644 index 3019aee..0000000 --- a/tests/cmake/get_catch2.cmake +++ /dev/null @@ -1,21 +0,0 @@ -include(ExternalProject) -find_package(Wget REQUIRED) - -ExternalProject_Add( - catch2-project - PREFIX ${CMAKE_BINARY_DIR}/catch2 - DOWNLOAD_DIR catch2 - DOWNLOAD_COMMAND ${WGET_EXECUTABLE} https://github.com/catchorg/Catch2/releases/download/v2.7.0/catch.hpp - TIMEOUT 10 - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD ON -) - -ExternalProject_Get_Property(catch2-project download_dir) - -add_library(catch2 INTERFACE) -target_include_directories(catch2 INTERFACE ${download_dir}/..) -add_library(catch2::catch ALIAS catch2) diff --git a/tests/event.cpp b/tests/event.cpp index 4205b09..f38179d 100644 --- a/tests/event.cpp +++ b/tests/event.cpp @@ -2,58 +2,108 @@ #include -TEST_CASE("ClickEvent") { - using namespace lars; - - using ClickEvent = Event; - - struct GuiElement{ - ClickEvent clicked; - void mouse_down(float x,float y){ clicked.notify(x, y); } - }; - - GuiElement A,B; - ClickEvent::Observer observer_1; - // pseudo-base class can hold any type of observer - Observer observer_2; - - float ox = 0, oy = 0; - size_t o1Count = 0, o2Count = 0, o3Count = 0, o4Count = 0; - - observer_1.observe(A.clicked,[&](float x,float y){ REQUIRE(ox == x); REQUIRE(oy == y); o1Count++; }); - observer_2.observe(B.clicked,[&](float x,float y){ REQUIRE(ox == x); REQUIRE(oy == y); o2Count++; }); - REQUIRE(A.clicked.observers_count() == 1); - REQUIRE(B.clicked.observers_count() == 1); - - do { - auto temporary_observer = A.clicked.create_observer([&](float x,float y){ REQUIRE(ox == x); REQUIRE(oy == y); o4Count++; }); - REQUIRE(A.clicked.observers_count() == 2); - - ox += 1; - A.mouse_down(ox, oy); +using namespace lars; + +// instantiate template for coverage +template class lars::Event<>; + +TEST_CASE("Event"){ + + SECTION("connect and observe"){ + lars::Event<> event; + REQUIRE(event.observerCount() == 0); + unsigned connectCount = 0, observeCount = 0; + event.connect([&](){ connectCount++; }); - // anonymous observer - B.clicked.connect([&](float x,float y){ REQUIRE(ox == x); REQUIRE(oy == y); o3Count++; }); - REQUIRE(A.clicked.observers_count() == 2); - REQUIRE(B.clicked.observers_count() == 2); - - oy += 1; - B.mouse_down(ox, oy); - } while (0); - - observer_2 = std::move(observer_1); - - REQUIRE(A.clicked.observers_count() == 1); - REQUIRE(B.clicked.observers_count() == 1); - - ox += 1; - A.mouse_down(ox, oy); - oy += 1; - B.mouse_down(ox, oy); - - REQUIRE(o1Count == 2); - REQUIRE(o2Count == 1); - REQUIRE(o3Count == 2); - REQUIRE(o4Count == 1); + SECTION("reset observer"){ + lars::Event<>::Observer observer; + observer = event.createObserver([&](){ observeCount++; }); + for (int i=0; i<10; ++i) { event.emit(); } + REQUIRE(event.observerCount() == 2); + observer.reset(); + REQUIRE(event.observerCount() == 1); + for (int i=0; i<10; ++i) { event.emit(); } + REQUIRE(observeCount == 10); + REQUIRE(connectCount == 20); + } + + SECTION("scoped observer"){ + SECTION("lars::Observer"){ + lars::Observer observer; + observer.observe(event, [&](){ observeCount++; }); + REQUIRE(event.observerCount() == 2); + for (int i=0; i<10; ++i) { event.emit(); } + } + SECTION("lars::Event<>::Observer"){ + lars::Event<>::Observer observer; + observer.observe(event, [&](){ observeCount++; }); + REQUIRE(event.observerCount() == 2); + for (int i=0; i<10; ++i) { event.emit(); } + } + REQUIRE(event.observerCount() == 1); + for (int i=0; i<10; ++i) { event.emit(); } + REQUIRE(observeCount == 10); + REQUIRE(connectCount == 20); + } + + SECTION("clear observers"){ + lars::Observer observer = event.createObserver([&](){ observeCount++; }); + event.clearObservers(); + REQUIRE(event.observerCount() == 0); + event.emit(); + REQUIRE(connectCount == 0); + } + } + + SECTION("removing observer during emit"){ + lars::Event<> event; + lars::Event<>::Observer observer; + unsigned count = 0; + observer = event.createObserver([&](){ observer.reset(); count++; }); + event.emit(); + REQUIRE(count == 1); + event.emit(); + REQUIRE(count == 1); + } + + SECTION("adding observers during emit"){ + lars::Event<> event; + std::function callback; + callback = [&](){ event.connect(callback); }; + event.connect(callback); + REQUIRE(event.observerCount() == 1); + event.emit(); + REQUIRE(event.observerCount() == 2); + event.emit(); + REQUIRE(event.observerCount() == 4); + } + + SECTION("emit data"){ + lars::Event event; + int sum = 0; + event.connect([&](auto a, auto b){ sum = a + b; }); + event.emit(2,3); + REQUIRE(sum == 5); + } + + SECTION("move"){ + lars::Observer observer; + int result = 0; + lars::Event event; + { + lars::Event tmpEvent; + observer = tmpEvent.createObserver([&](auto i){ result = i; }); + tmpEvent.emit(5); + REQUIRE(result == 5); + event = Event(std::move(tmpEvent)); + REQUIRE(tmpEvent.observerCount() == 0); + } + REQUIRE(event.observerCount() == 1); + event.emit(3); + REQUIRE(result == 3); + observer.reset(); + REQUIRE(event.observerCount() == 0); + } } + diff --git a/tests/observable_value.cpp b/tests/observable_value.cpp new file mode 100644 index 0000000..914a7db --- /dev/null +++ b/tests/observable_value.cpp @@ -0,0 +1,57 @@ +#include + +#include + +// instantiate templates for coverage +template class lars::ObservableValue; +template class lars::DependentObservableValue; + +TEST_CASE("Observable Value") { + using namespace lars; + + ObservableValue value(0); + + unsigned total = 0; + value.onChange.connect([&](auto &v){ total+=v; }); + REQUIRE(*value == 0); + value.set(1); + value.set(2); + REQUIRE(total == 3); + REQUIRE(*value == 2); +} + +TEST_CASE("Dependent Observable Value") { + using namespace lars; + + ObservableValue a(1); + ObservableValue b(1); + DependentObservableValue sum([](auto a, auto b){ return a+b; },a,b); + + REQUIRE(*sum == 2); + a.set(2); + REQUIRE(*sum == 3); + b.set(3); + REQUIRE(*sum == 5); + + ObservableValue c(3); + DependentObservableValue prod([](auto a, auto b){ return a*b; },sum,c); + + REQUIRE(*prod == 15); + a.set(1); + REQUIRE(*prod == 12); + b.set(4); + REQUIRE(*prod == 15); + c.set(2); + REQUIRE(*prod == 10); + + c.setSilently(3); + REQUIRE(*prod == 10); +} + +TEST_CASE("Operators") { + using namespace lars; + + struct A { int a = 0; }; + ObservableValue value; + REQUIRE(value->a == 0); +} \ No newline at end of file