├── .gitignore ├── src └── reactive │ ├── Event.h │ ├── non_thread_safe │ ├── ReactiveProperty.h │ ├── Event.h │ └── ObservableProperty.h │ ├── details │ ├── threading │ │ ├── SpinLock.h │ │ ├── dummy_mutex.h │ │ ├── details │ │ │ └── SpinLockSpinner.h │ │ └── upgrade_mutex.h │ ├── Delegate.h │ ├── utils │ │ ├── DeferredForwardKeyContainer.h │ │ ├── DeferredForwardContainer.h │ │ └── optional.hpp │ ├── Event.h │ └── ObservableProperty.h │ ├── bind.h │ ├── blocking.h │ ├── ObservableProperty.h │ ├── ReactiveProperty.h │ └── observer.h ├── test ├── CMakeLists.txt ├── main.cpp ├── TestEvent.h ├── TestReactiveProperty.h ├── TestBindableProperty.h ├── TestMultiObserver.h ├── BenchmarkDeferredContainer.h ├── BenchmarkReactivity.h ├── BenchmarkOwnedProperty.h └── TestObservableProperty.h ├── LICENSE └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Clion 3 | ################# 4 | .idea 5 | cmake-build-debug 6 | cmake-build-release 7 | 8 | ################# 9 | ## Visual Studio 10 | ################# 11 | .vs 12 | CMakeSettings.json -------------------------------------------------------------------------------- /src/reactive/Event.h: -------------------------------------------------------------------------------- 1 | #ifndef REACTIVE_EVENT_H 2 | #define REACTIVE_EVENT_H 3 | 4 | #include "details/Event.h" 5 | 6 | namespace reactive{ 7 | 8 | template 9 | using Event = details::Event; 10 | 11 | } 12 | 13 | #endif //REACTIVE_EVENT_H 14 | -------------------------------------------------------------------------------- /src/reactive/non_thread_safe/ReactiveProperty.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../ReactiveProperty.h" 4 | 5 | namespace reactive { 6 | namespace non_thread_safe { 7 | 8 | template 9 | using ReactiveProperty = reactive::ReactiveProperty; 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | project(test) 3 | 4 | #set(CMAKE_CXX_STANDARD 14) 5 | 6 | set(CMAKE_CXX_FLAGS "-std=c++1z") 7 | 8 | include_directories(../src) 9 | 10 | set(SOURCE_FILES main.cpp TestReactiveProperty.h) 11 | add_executable(lib_test ${SOURCE_FILES}) -------------------------------------------------------------------------------- /src/reactive/non_thread_safe/Event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../details/Event.h" 4 | 5 | namespace reactive { 6 | namespace non_thread_safe { 7 | 8 | template 9 | using Event = details::ConfigurableEvent; 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /src/reactive/non_thread_safe/ObservableProperty.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../ObservableProperty.h" 4 | 5 | namespace reactive { 6 | namespace non_thread_safe { 7 | 8 | template 9 | using ObservableProperty = reactive::ObservableProperty; 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | // flag for VS2017 c++ compiler 2 | #define _ENABLE_ATOMIC_ALIGNMENT_FIX 3 | 4 | #include 5 | 6 | #include "TestEvent.h" 7 | #include "TestObservableProperty.h" 8 | #include "TestBindableProperty.h" 9 | #include "TestReactiveProperty.h" 10 | #include "TestMultiObserver.h" 11 | 12 | 13 | #include "BenchmarkOwnedProperty.h" 14 | #include "BenchmarkReactivity.h" 15 | #include "BenchmarkDeferredContainer.h" 16 | 17 | 18 | int main() { 19 | 20 | //TestEvent().test_all(); 21 | //TestObservableProperty().test_all(); 22 | //TestMultiObserver().test_all(); 23 | //TestReactiveProperty().test_all(); 24 | /* 25 | TestBindableProperty().test_all(); 26 | 27 | 28 | 29 | BenchmarkOwnedProperty().benchmark_all(); 30 | */ 31 | //BenchmarkDeferredContainer().benchmark_all(); 32 | 33 | BenchmarkReactivity().benchmark_all(); 34 | 35 | return 0; 36 | } -------------------------------------------------------------------------------- /src/reactive/details/threading/SpinLock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "details/SpinLockSpinner.h" 7 | 8 | namespace threading { 9 | 10 | template 11 | class SpinLock { 12 | std::atomic_flag spinLock = ATOMIC_FLAG_INIT; 13 | 14 | public: 15 | SpinLock(){} 16 | SpinLock(const SpinLock&) = delete; 17 | SpinLock(SpinLock&&) = delete; 18 | 19 | bool try_lock(){ 20 | return !spinLock.test_and_set(std::memory_order_acquire); 21 | } 22 | 23 | void lock() { 24 | details::SpinLockSpinner::spinWhile([&](){ 25 | return spinLock.test_and_set(std::memory_order_acquire); 26 | }); 27 | } 28 | 29 | void unlock() { 30 | spinLock.clear(std::memory_order_release); // release lock 31 | } 32 | }; 33 | } -------------------------------------------------------------------------------- /src/reactive/details/threading/dummy_mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace threading { 4 | struct dummy_mutex { 5 | void lock() {} 6 | bool try_lock() { return true; }; 7 | void unlock() {} 8 | 9 | void lock_shared() {} 10 | bool try_lock_shared() { return true; }; 11 | void unlock_shared() {} 12 | 13 | /*void shared_lock() {} 14 | void shared_unlock() {}*/ 15 | 16 | void unlock_and_lock_shared() {}; 17 | void unlock_upgrade_and_lock() {}; 18 | 19 | 20 | void lock_upgrade() {}; 21 | bool try_lock_upgrade() { return true; }; 22 | void unlock_upgrade() {}; 23 | 24 | bool try_unlock_shared_and_lock_upgrade() { return true; }; 25 | 26 | template 27 | bool try_unlock_shared_and_lock_upgrade_for(Args&&...) { return true; }; 28 | template 29 | bool try_unlock_shared_and_lock_upgrade_until(Args&&...) { return true; }; 30 | 31 | void unlock_and_lock_upgrade() {}; 32 | void unlock_upgrade_and_lock_shared() {}; 33 | }; 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 tower120 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/reactive/details/threading/details/SpinLockSpinner.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace threading { 6 | enum class SpinLockMode { Nonstop, Yield, Sleep, Adaptive }; 7 | 8 | namespace details{ 9 | namespace SpinLockSpinner{ 10 | 11 | template 12 | static void spinWhile(Closure&& closure){ 13 | if (mode == SpinLockMode::Adaptive) { 14 | // fast return 15 | if (!closure()) { 16 | return; 17 | } 18 | 19 | while (true) { 20 | // 1. first as spinner 21 | for (int i = 0; i < 100; i++) { 22 | if (!closure()) { 23 | return; 24 | } 25 | } 26 | 27 | // 2. then add some yield 28 | for (int i = 0; i < 1000; i++) { 29 | if (!closure()) { 30 | return; 31 | } 32 | std::this_thread::yield(); 33 | } 34 | 35 | // 3. then go to sleep 36 | std::this_thread::sleep_for(std::chrono::nanoseconds(1)); 37 | } 38 | 39 | return; 40 | } 41 | 42 | 43 | while (true) { 44 | if (!closure()) { 45 | return; 46 | } 47 | if (mode == SpinLockMode::Sleep) { 48 | std::this_thread::sleep_for(std::chrono::nanoseconds(1)); 49 | } 50 | else if (mode == SpinLockMode::Yield) { 51 | std::this_thread::yield(); 52 | } 53 | } 54 | } 55 | 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/reactive/bind.h: -------------------------------------------------------------------------------- 1 | #ifndef REACTIVE_BIND_H 2 | #define REACTIVE_BIND_H 3 | 4 | #include 5 | #include "observer.h" 6 | 7 | namespace reactive { 8 | 9 | namespace details { 10 | template 11 | auto bind(const std::shared_ptr& obj, Closure&& closure, const Observables&... observables) { 12 | auto observer = /*details::*/MultiObserver::observe_w_unsubscribe_impl( 13 | [ 14 | closure = std::forward(closure) 15 | , obj_weak = std::weak_ptr(obj) 16 | ](auto&& unsubscribe_self, auto&&...args){ 17 | auto obj_ptr = obj_weak.lock(); 18 | if (!obj_ptr) { 19 | unsubscribe_self(); 20 | return; 21 | } 22 | 23 | closure(std::forward(unsubscribe_self), std::move(obj_ptr), std::forward(args)...); 24 | } 25 | , observables.shared_ptr()...); 26 | 27 | observer->execute(); 28 | 29 | return [observer]() { observer->unsubscribe(); }; 30 | } 31 | } 32 | 33 | 34 | template 35 | auto bind(const std::shared_ptr& obj, Closure&& closure, const Observables&... observables) { 36 | return details::bind(obj, 37 | [closure = std::forward(closure)](auto&& unsubscribe_self, auto&&...args) { 38 | closure(std::forward(args)...); 39 | } 40 | , observables... 41 | ); 42 | } 43 | 44 | template 45 | auto bind_w_unsubscribe(const std::shared_ptr& obj, Closure&& closure, const Observables&... observables) { 46 | return details::bind(obj, std::forward(closure), observables...); 47 | } 48 | 49 | } 50 | 51 | #endif //REACTIVE_BIND_H 52 | -------------------------------------------------------------------------------- /test/TestEvent.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_TESTEVENT_H 2 | #define TEST_TESTEVENT_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class TestEvent{ 9 | public: 10 | void test_simple(){ 11 | reactive::Event onMove; 12 | 13 | onMove += [](int x, int y){ 14 | std::cout << "position = " << x << "," << y << std::endl; 15 | }; 16 | 17 | onMove(1,2); 18 | onMove(10,20); 19 | } 20 | 21 | void test_unsubscribe_from_observer(){ 22 | reactive::Event onMove; 23 | 24 | onMove += [](int x, int y){ 25 | std::cout << "position = " << x << "," << y << std::endl; 26 | }; 27 | 28 | reactive::Delegate another_observer = [&](int x, int y){ 29 | if (x == 10){ 30 | onMove -= another_observer; 31 | } 32 | std::cout << "another observer " << x << "," << y << std::endl; 33 | }; 34 | 35 | onMove += another_observer; 36 | 37 | 38 | onMove(1,2); 39 | onMove(10,20); 40 | onMove(30,50); 41 | onMove(100,50); 42 | 43 | } 44 | 45 | 46 | void test_action() { 47 | reactive::Event onMove; 48 | 49 | reactive::Delegate action = [](int) { 50 | std::cout << "act " << std::endl; 51 | }; 52 | 53 | onMove += action; 54 | 55 | onMove(1); 56 | onMove(1); 57 | onMove -= action; 58 | 59 | onMove(1); 60 | onMove(1); 61 | } 62 | 63 | 64 | 65 | void test_all(){ 66 | std::cout << "Simple test." << std::endl; 67 | test_simple(); 68 | std::cout << std::endl; 69 | 70 | std::cout << "Unsubscribe right from observer." << std::endl; 71 | test_unsubscribe_from_observer(); 72 | std::cout << std::endl; 73 | 74 | /*std::cout << "Action test." << std::endl; 75 | test_action(); 76 | std::cout << std::endl;*/ 77 | } 78 | }; 79 | 80 | #endif //TEST_TESTEVENT_H -------------------------------------------------------------------------------- /src/reactive/details/Delegate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace reactive { 9 | 10 | namespace details { 11 | namespace Delegate { 12 | // 0 - reserved for empty tag? 13 | static std::atomic delegate_uuid{ 1 }; // should be enough approx. for 1000 years at 3Ghz continuous incrementation 14 | } 15 | } 16 | 17 | // pass by copy 18 | class DelegateTag { 19 | unsigned long long tag; 20 | 21 | protected: 22 | DelegateTag(std::nullptr_t) 23 | :tag(0) 24 | {} 25 | public: 26 | DelegateTag() 27 | : tag(details::Delegate::delegate_uuid.fetch_add(1)) 28 | {} 29 | 30 | bool operator==(const DelegateTag& other) const { 31 | return tag == other.tag; 32 | } 33 | bool operator!=(const DelegateTag& other) const { 34 | return tag != other.tag; 35 | } 36 | }; 37 | 38 | namespace details { 39 | class DelegateTagEmpty : public DelegateTag { 40 | using DelegateTag::DelegateTag; 41 | public: 42 | DelegateTagEmpty() : DelegateTag(nullptr) {}; 43 | using DelegateTag::operator==; 44 | using DelegateTag::operator!=; 45 | }; 46 | 47 | class DelegateBase {}; 48 | } 49 | 50 | 51 | template 52 | class Delegate : details::DelegateBase { 53 | using Self = Delegate; 54 | DelegateTag m_tag; 55 | 56 | using Fn = std::function; 57 | Fn m_fn; 58 | 59 | public: 60 | template, Self >::value> 62 | > 63 | Delegate(FnT&& fn) 64 | :m_fn(std::forward(fn)) 65 | {} 66 | 67 | template 68 | Delegate(DelegateTag tag, FnT&& fn) 69 | : m_tag(tag) 70 | , m_fn(std::forward(fn)) 71 | {} 72 | 73 | 74 | void operator=(const Fn& fn) { 75 | fn = fn; 76 | } 77 | void operator=(Fn&& fn) { 78 | fn = std::move(fn); 79 | } 80 | 81 | const DelegateTag& tag() const { 82 | return m_tag; 83 | } 84 | const Fn& function() const { 85 | return m_fn; 86 | } 87 | 88 | }; 89 | 90 | } 91 | -------------------------------------------------------------------------------- /test/TestReactiveProperty.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_TESTREACTIVEPROPERTY_H 2 | #define TEST_TESTREACTIVEPROPERTY_H 3 | 4 | #include 5 | 6 | 7 | class TestReactiveProperty{ 8 | public: 9 | void test_simple(){ 10 | reactive::ObservableProperty x = 1; 11 | reactive::ObservableProperty y = 2; 12 | reactive::ReactiveProperty summ {-1}; 13 | 14 | 15 | reactive::ReactiveProperty summ_copy = 24; 16 | 17 | /*summ_copy = summ; 18 | std::cout << summ.getCopy() << std::endl;*/ 19 | 20 | summ.set([](int x, int y){ 21 | return x+y; 22 | }, x, y); 23 | std::cout << summ.getCopy() << std::endl; 24 | 25 | summ.set([](int x, int y){ 26 | return x-y; 27 | }, x, y); 28 | x = 4; 29 | std::cout << summ.getCopy() << std::endl; 30 | 31 | summ = 10; 32 | x = -1; 33 | std::cout << summ.getCopy() << std::endl; 34 | 35 | 36 | { 37 | auto summ_ptr = summ.write_lock(); 38 | *summ_ptr = 5; 39 | } 40 | std::cout << summ.getCopy() << std::endl; 41 | std::cout << summ_copy.getCopy() << std::endl; 42 | } 43 | 44 | void test_update(){ 45 | reactive::ObservableProperty x = 1; 46 | reactive::ObservableProperty y = 2; 47 | reactive::ReactiveProperty> vec2 = {10, 20}; 48 | 49 | vec2 += [](const std::pair& pair){ 50 | std::cout << pair.first << ", " << pair.second << std::endl; 51 | }; 52 | 53 | vec2.update([](std::pair& pair, int x){ 54 | pair.first = x; 55 | }, x); 56 | 57 | x = 100; 58 | 59 | //x.pulse(); 60 | } 61 | 62 | void test_nonthreadsafe() { 63 | reactive::ReactiveProperty, reactive::blocking, false> vec2 = { 10, 20 }; 64 | vec2->first; 65 | } 66 | 67 | void test_all(){ 68 | //test_simple(); 69 | //test_update(); 70 | 71 | test_nonthreadsafe(); 72 | } 73 | }; 74 | 75 | #endif //TEST_TESTREACTIVEPROPERTY_H 76 | -------------------------------------------------------------------------------- /test/TestBindableProperty.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_TESTBINDABLEPROPERTY_H 2 | #define TEST_TESTBINDABLEPROPERTY_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | class TestBindableProperty { 11 | public: 12 | void test_simple(){ 13 | reactive::ObservableProperty len = 2; 14 | reactive::ObservableProperty len2; 15 | reactive::ObservableProperty y{100}; 16 | 17 | class Box{ 18 | int m_len = 0; 19 | public: 20 | 21 | auto len(int x) { 22 | m_len = x; 23 | } 24 | 25 | void show(){ 26 | std::cout << m_len << std::endl; 27 | } 28 | }; 29 | 30 | std::shared_ptr box = std::make_shared(); 31 | 32 | 33 | len = 40; 34 | 35 | 36 | reactive::bind(len2.shared_ptr(), [](reactive::ObservableProperty len2, int len, int y) { 37 | len2 = 2+y+len; 38 | }, len, y); 39 | 40 | auto unbind = reactive::bind(box, [](auto box, int len){ 41 | box->len(len); 42 | }, len); 43 | 44 | len = 20; 45 | 46 | unbind(); 47 | 48 | len = 30; 49 | box->show(); 50 | 51 | 52 | std::cout << len2.getCopy() << std::endl; 53 | } 54 | 55 | 56 | void test_w_unsubscribe() { 57 | reactive::ObservableProperty len = 2; 58 | reactive::ObservableProperty y{ 100 }; 59 | 60 | class Box { 61 | int m_len = 0; 62 | public: 63 | 64 | auto len(int x) { 65 | m_len = x; 66 | } 67 | 68 | void show() { 69 | std::cout << m_len << std::endl; 70 | } 71 | }; 72 | 73 | std::shared_ptr box = std::make_shared(); 74 | 75 | 76 | len = 40; 77 | 78 | 79 | reactive::bind_w_unsubscribe(box, [](auto unsubscibe, auto box, int len) { 80 | if (len > 100) unsubscibe(); 81 | box->len(len); 82 | box->show(); 83 | }, len); 84 | 85 | len = 50; 86 | len = 101; 87 | len = 60; 88 | 89 | } 90 | 91 | void test_all(){ 92 | //test_simple(); 93 | test_w_unsubscribe(); 94 | } 95 | }; 96 | 97 | #endif //TEST_TESTBINDABLEPROPERTY_H 98 | -------------------------------------------------------------------------------- /src/reactive/details/utils/DeferredForwardKeyContainer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "optional.hpp" 4 | 5 | #include "DeferredForwardContainer.h" 6 | 7 | #include "../threading/SpinLock.h" 8 | 9 | namespace utils { 10 | 11 | namespace details { 12 | template 13 | struct DeferredForwardKeyContainerElement { 14 | Key key; 15 | T value; 16 | 17 | DeferredForwardKeyContainerElement(Key&& key) 18 | : key(std::move(key)) 19 | {} 20 | DeferredForwardKeyContainerElement(const Key& key) 21 | : key(key) 22 | {} 23 | 24 | template 25 | DeferredForwardKeyContainerElement(KeyT&& key, Arg&& arg, Args&&...args) 26 | : key(std::forward(key)) 27 | , value(std::forward(arg), std::forward(args)...) 28 | {} 29 | 30 | DeferredForwardKeyContainerElement(DeferredForwardKeyContainerElement>&& other) 31 | : key(std::move(other.key)) 32 | , value(std::move(*other.value)) 33 | {} 34 | 35 | template 36 | bool operator==(const Other& other) const{ 37 | return key == other.key; 38 | } 39 | }; 40 | } 41 | 42 | template< 43 | class Key, 44 | class T, 45 | class ActionListLock = threading::SpinLock /*std::mutex*/, 46 | class ListMutationLock = std::shared_mutex, 47 | bool unordered = true 48 | > 49 | class DefferedForwardKeyContainer : protected DeferredForwardContainer< 50 | details::DeferredForwardKeyContainerElement 51 | , details::DeferredForwardKeyContainerElement > 52 | , ActionListLock 53 | , ListMutationLock 54 | , unordered 55 | > 56 | { 57 | 58 | using Base = DeferredForwardContainer< 59 | details::DeferredForwardKeyContainerElement 60 | , details::DeferredForwardKeyContainerElement > 61 | , ActionListLock 62 | , ListMutationLock 63 | , unordered 64 | >; 65 | 66 | //using DefferedElement = details::DeferredForwardKeyContainerElement >; 67 | 68 | public: 69 | DefferedForwardKeyContainer() {} 70 | 71 | template 72 | void emplace(KeyT&& key, Args&&...args) { 73 | Base::emplace(std::forward(key), std::forward(args)... ); 74 | } 75 | 76 | template 77 | void remove(KeyT&& key) { 78 | Base::remove(std::forward(key)); 79 | } 80 | 81 | using Base::foreach; 82 | 83 | template 84 | void foreach_value(Closure&& closure) { 85 | Base::foreach([&](auto&& key_value) { 86 | closure(key_value.value); 87 | }); 88 | } 89 | }; 90 | } -------------------------------------------------------------------------------- /src/reactive/blocking.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include 5 | 6 | namespace reactive { 7 | struct blocking {}; 8 | struct nonblocking {}; 9 | struct nonblocking_atomic {}; 10 | struct default_blocking{}; 11 | 12 | namespace details{ 13 | namespace default_blocking{ 14 | template 15 | constexpr bool static and_all(Bool0 bool0){ 16 | return bool0; 17 | } 18 | template 19 | constexpr bool static and_all(Bool0 bool0, Bools... bools){ 20 | return bool0 && and_all(bools...); 21 | } 22 | 23 | 24 | template 25 | constexpr auto static and_plus(Int0 int0){ 26 | return int0; 27 | } 28 | template 29 | constexpr auto static and_plus(Int0 int0, Ints... ints){ 30 | return int0 + and_plus(ints...); 31 | } 32 | 33 | // need this just to fix VS2017 v_141 compiler bug 34 | // #https://developercommunity.visualstudio.com/content/problem/69543/vs2017-v-141-parameter-pack-expansion-compilation.html 35 | template 36 | struct is_efficiently_copyable { 37 | static constexpr const bool value = 38 | and_all(std::is_copy_constructible::value...) && and_plus(sizeof(Ts)...) <= 128; 39 | }; 40 | template 41 | struct is_atomic { 42 | #if defined(_MSC_VER) || (defined(__GNUC__) && (__GNUC__ < 7)) 43 | // non standart compatible behavior. Non default constructible object can not be loaded from std::atomic 44 | // #https://developercommunity.visualstudio.com/content/problem/69560/stdatomic-load-does-not-work-with-non-default-cons.html 45 | static constexpr const bool value = 46 | and_all(std::is_trivially_copyable::value...) && and_all(std::is_nothrow_default_constructible::value...); 47 | #else 48 | static constexpr const bool value = and_all(std::is_trivially_copyable::value...); 49 | #endif 50 | }; 51 | } 52 | } 53 | 54 | 55 | /** 56 | * Non-blocking by default if 57 | * All copy constructable & summary size <= 128 58 | */ 59 | template 60 | using get_default_blocking = std::conditional_t< 61 | details::default_blocking::is_efficiently_copyable::value 62 | , std::conditional_t< 63 | details::default_blocking::is_atomic::value 64 | , nonblocking_atomic 65 | , nonblocking 66 | > 67 | , blocking 68 | >; 69 | 70 | template 71 | using get_blocking_mode = std::conditional_t< 72 | std::is_same::value 73 | , get_default_blocking 74 | , mode 75 | >; 76 | } 77 | -------------------------------------------------------------------------------- /test/TestMultiObserver.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_TESTMULTIOBSERVER_H 2 | #define TEST_TESTMULTIOBSERVER_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | /* 9 | template 10 | class DumReactiveProperty 11 | : public reactive::details::ObservableProperty 12 | , public std::enable_shared_from_this>{ 13 | 14 | std::atomic> unsubscriber; 15 | public: 16 | template 17 | void set(Closure&& closure, Observables&...observables){ 18 | // atomic? 19 | std::unique_lock l(m_lock()); 20 | 21 | unsubscriber(); 22 | 23 | // auto (ObserverNonBlocking) 24 | auto observer = reactive::details::subscribe_impl([closure, property_weak = property.weak_ptr()](auto&&...args){ 25 | reactive::ObservableProperty property{property_weak}; 26 | if(!property) return; 27 | property = closure(args...); 28 | }, observables...); 29 | 30 | // update property 31 | //observer->execute(); 32 | } 33 | 34 | ~DumReactiveProperty(){ 35 | std::unique_lock l(m_lock()); 36 | if (unsubscriber) { 37 | unsubscriber(); 38 | } 39 | } 40 | };*/ 41 | 42 | 43 | class TestMultiObserver{ 44 | public: 45 | 46 | 47 | void test_simple(){ 48 | reactive::ObservableProperty x = 1; 49 | reactive::ObservableProperty y = 2; 50 | 51 | 52 | reactive::ObservableProperty summ; 53 | 54 | // variant 1 55 | std::function* unsub_ptr; 56 | std::function unsub = reactive::observe([&](int x, int y){ 57 | std::cout << x << " : " << y << std::endl; 58 | (*unsub_ptr)(); 59 | }, x, y); 60 | unsub_ptr = &unsub; 61 | 62 | x = 10; 63 | x = 10; 64 | x = 9; 65 | x.pulse(); 66 | unsub(); 67 | x = 11; 68 | } 69 | 70 | void test_self_unsubscribe() { 71 | reactive::ObservableProperty x = 1; 72 | reactive::ObservableProperty y = 2; 73 | 74 | reactive::observe_w_unsubscribe([&](auto unsubscribe, int x, int y) { 75 | if (x == 10) unsubscribe(); 76 | std::cout << x << " : " << y << std::endl; 77 | }, x, y); 78 | 79 | x = 20; 80 | y = 40; 81 | 82 | x = 10; 83 | 84 | y = 100; 85 | 86 | } 87 | 88 | void test_all(){ 89 | //test_simple(); 90 | test_self_unsubscribe(); 91 | } 92 | }; 93 | 94 | #endif //TEST_TESTMULTIOBSERVER_H 95 | -------------------------------------------------------------------------------- /test/BenchmarkDeferredContainer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class BenchmarkDeferredContainer { 8 | public: 9 | const int count = 100'000; 10 | 11 | // Somehow... There is no difference at all 12 | using T = long long int; 13 | using SingleThreadedList = utils::DeferredForwardContainer; 14 | using MultiThreadedList = utils::DeferredForwardContainer; 15 | SingleThreadedList list; 16 | std::vector vec; 17 | 18 | void benchmark_fill() { 19 | using namespace std::chrono; 20 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 21 | 22 | for (int i = 0; i < count; i++) { 23 | list.emplace(i); 24 | } 25 | 26 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 27 | auto duration = duration_cast(t2 - t1).count(); 28 | std::cout << "filled in : " << duration << std::endl; 29 | } 30 | 31 | void benchmark_foreach() { 32 | using namespace std::chrono; 33 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 34 | 35 | 36 | long long int sum = 0; 37 | for (int i = 0; i < count; i++) { 38 | list.foreach([&](int i) { 39 | sum += i; 40 | }); 41 | } 42 | 43 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 44 | auto duration = duration_cast(t2 - t1).count(); 45 | std::cout << sum << std::endl; 46 | std::cout << "foreached in : " << duration << std::endl; 47 | } 48 | 49 | 50 | 51 | void benchmark_vec_fill() { 52 | using namespace std::chrono; 53 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 54 | 55 | for (int i = 0; i < count; i++) { 56 | vec.emplace_back(i); 57 | } 58 | 59 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 60 | auto duration = duration_cast(t2 - t1).count(); 61 | std::cout << "vec filled in : " << duration << std::endl; 62 | } 63 | 64 | void benchmark_vec_foreach() { 65 | using namespace std::chrono; 66 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 67 | 68 | 69 | long long int sum = 0; 70 | for (int i = 0; i < count; i++) { 71 | for(auto&& i : vec){ 72 | sum += i; 73 | } 74 | } 75 | 76 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 77 | auto duration = duration_cast(t2 - t1).count(); 78 | std::cout << sum << std::endl; 79 | std::cout << "vec foreached in : " << duration << std::endl; 80 | } 81 | 82 | void benchmark_all() { 83 | benchmark_fill(); 84 | benchmark_foreach(); 85 | benchmark_vec_fill(); 86 | benchmark_vec_foreach(); 87 | } 88 | }; -------------------------------------------------------------------------------- /src/reactive/details/Event.h: -------------------------------------------------------------------------------- 1 | #ifndef REACTIVE_DETAILS_EVENT_H 2 | #define REACTIVE_DETAILS_EVENT_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | 11 | #include "Delegate.h" 12 | 13 | #include "threading/SpinLock.h" 14 | #include "utils/DeferredForwardKeyContainer.h" 15 | 16 | namespace reactive { 17 | namespace details { 18 | 19 | // thread-safe, non-blocking 20 | // subscribe/unsubscribe may be deffered to next () call 21 | template< 22 | class ActionListLock /*= threading::SpinLock*/, 23 | class ListMutationLock /*= std::shared_mutex*/, 24 | class ...Args> 25 | class ConfigurableEventBase { 26 | using Delegate = typename reactive::Delegate; 27 | protected: 28 | utils::DefferedForwardKeyContainer< 29 | DelegateTag, std::function 30 | , ActionListLock, ListMutationLock 31 | > list; 32 | 33 | public: 34 | template 35 | void subscribe(const DelegateTag& tag, Fn&& fn) { 36 | list.emplace(tag, std::forward(fn)); 37 | } 38 | 39 | template 40 | void subscribe(const typename reactive::Delegate& delegate) { 41 | list.emplace(delegate.tag(), delegate.function()); 42 | } 43 | 44 | template>::value > > 45 | void operator+=(Closure&& closure) { 46 | subscribe(DelegateTagEmpty{}, std::forward(closure)); 47 | } 48 | template 49 | void operator+=(const typename reactive::Delegate& delegate) { 50 | subscribe(delegate); 51 | } 52 | 53 | 54 | void operator-=(const DelegateTag& tag) { 55 | list.remove(tag); 56 | } 57 | void operator-=(const Delegate& delegate) { 58 | list.remove(delegate.tag()); 59 | } 60 | 61 | 62 | template 63 | void operator()(Ts&&...ts) { 64 | list.foreach_value([&](auto& delegate) { 65 | //delegate(std::forward(ts)...); // can't move here 66 | delegate(ts...); // can't move here 67 | }); 68 | } 69 | }; 70 | 71 | 72 | template< 73 | class ActionListLock /*= threading::SpinLock*/, 74 | class ListMutationLock /*= std::shared_mutex*/, 75 | class ...Args> 76 | using ConfigurableEvent = ConfigurableEventBase; 77 | 78 | 79 | template 80 | using Event = ConfigurableEvent< 81 | threading::SpinLock, 82 | std::shared_mutex, 83 | Args... 84 | >; 85 | } 86 | } 87 | 88 | #endif //REACTIVE_DETAILS_EVENT_H -------------------------------------------------------------------------------- /test/BenchmarkReactivity.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class BenchmarkReactivity { 12 | public: 13 | const int count = 100'000; 14 | 15 | template 16 | struct Data { 17 | T x1, x2, x3, x4; 18 | R sum; 19 | 20 | Data() { 21 | sum.set([](auto&& x1, auto&& x2, auto&& x3, auto&& x4) { 22 | return x1 + x2 + x3 + x4; 23 | }, x1, x2, x3, x4); 24 | } 25 | 26 | template 27 | void update(I1&& x1, I2&& x2, I3&& x3, I4&& x4) { 28 | this->x1 = x1; 29 | this->x2 = x2; 30 | this->x3 = x3; 31 | this->x4 = x4; 32 | } 33 | }; 34 | 35 | template 36 | void benchmark_fill(Container& container) { 37 | using namespace std::chrono; 38 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 39 | 40 | for (int i = 0; i < count; i++) { 41 | container.emplace_back(); 42 | } 43 | 44 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 45 | auto duration = duration_cast(t2 - t1).count(); 46 | std::cout << "filled in : " << duration << std::endl; 47 | } 48 | 49 | template 50 | void benchmark_update(Container& container) { 51 | using namespace std::chrono; 52 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 53 | 54 | long long sum = 0; 55 | for (int i = 0; i < count; i++) { 56 | container[i].update(i, i+1, i+2, i+3); 57 | sum += container[i].sum.getCopy(); 58 | } 59 | 60 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 61 | auto duration = duration_cast(t2 - t1).count(); 62 | std::cout << "updated in : " << duration 63 | << " (" << sum << ")" 64 | << std::endl; 65 | } 66 | 67 | void benchmark_all() { 68 | // There will be 400'000 update calls in total. 69 | using T = long long; 70 | { 71 | std::cout << "Test threaded." << std::endl; 72 | using Element = Data, reactive::ReactiveProperty >; 73 | std::vector list; 74 | benchmark_fill(list); 75 | benchmark_update(list); 76 | std::cout << "---" << std::endl; 77 | } 78 | { 79 | std::cout << "Test non-threaded." << std::endl; 80 | using Element = Data, reactive::non_thread_safe::ReactiveProperty >; 81 | std::vector list; 82 | benchmark_fill(list); 83 | benchmark_update(list); 84 | std::cout << "---" << std::endl; 85 | } 86 | { 87 | std::cout << "Test mix." << std::endl; 88 | using Element = Data, reactive::ReactiveProperty >; 89 | std::vector list; 90 | benchmark_fill(list); 91 | benchmark_update(list); 92 | std::cout << "---" << std::endl; 93 | } 94 | 95 | char ch; 96 | std::cin >> ch; 97 | } 98 | }; -------------------------------------------------------------------------------- /src/reactive/details/utils/DeferredForwardContainer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../threading/SpinLock.h" 9 | #include "../threading/dummy_mutex.h" 10 | 11 | namespace utils { 12 | /* 13 | Implementation details: 14 | Possible to implement with one deque, but: 15 | It is much faster (2-10 times) to use 2 vectors, then one deque 16 | */ 17 | 18 | namespace details{ 19 | // zero-size (static) locks for dummy_mutex 20 | template 21 | class DeferredForwardContainerBase { 22 | protected: 23 | ListMutationLock list_mutation_lock; 24 | ActionListLock action_list_lock; 25 | public: 26 | DeferredForwardContainerBase() {}; 27 | }; 28 | 29 | template<> 30 | class DeferredForwardContainerBase { 31 | protected: 32 | inline static threading::dummy_mutex action_list_lock; 33 | inline static threading::dummy_mutex list_mutation_lock; 34 | public: 35 | DeferredForwardContainerBase() {}; 36 | }; 37 | } 38 | 39 | // thread safe (use threading::dummy_mutex to disable) 40 | // safe to add/remove while iterate 41 | // non-blocking 42 | // unordered (by default, but may be ordered) - does not require copy on remove 43 | template< 44 | class T, 45 | class DeferredActionValue = T, 46 | class ActionListLock = threading::SpinLock /*std::mutex*/, 47 | class ListMutationLock = std::shared_mutex, 48 | bool unordered = true 49 | > 50 | class DeferredForwardContainer : public details::DeferredForwardContainerBase { 51 | using Base = details::DeferredForwardContainerBase; 52 | using Base::action_list_lock; 53 | using Base::list_mutation_lock; 54 | 55 | enum class Action { remove, add }; 56 | struct DeferredActionElement { 57 | Action action; 58 | DeferredActionValue value; 59 | 60 | template 61 | DeferredActionElement(ActionT&& action, Arg&& arg, Args&&... args) 62 | : action(std::forward(action)) 63 | , value(std::forward(arg), std::forward(args)...) 64 | {} 65 | }; 66 | std::vector deferredActionList; 67 | 68 | using ListElement = T; 69 | using List = std::vector; 70 | List list; 71 | 72 | void apply_actions() { 73 | std::unique_lock l(action_list_lock); 74 | if (deferredActionList.empty()) return; 75 | 76 | std::unique_lock l2(list_mutation_lock); 77 | for (DeferredActionElement& action : deferredActionList) { 78 | if (action.action == Action::add) { 79 | list.emplace_back(std::move(action.value)); 80 | } else { 81 | // remove one 82 | auto it = std::find_if(list.begin(), list.end(), [&](ListElement& element) { 83 | return (element == action.value); 84 | }); 85 | 86 | if (unordered) { 87 | if (it != list.end()) { 88 | std::iter_swap(it, list.end() - 1); 89 | list.pop_back(); 90 | } 91 | } else { 92 | if (it != list.end()) { 93 | list.erase(it); 94 | } 95 | } 96 | } 97 | } 98 | 99 | deferredActionList.clear(); 100 | } 101 | 102 | public: 103 | DeferredForwardContainer() {} 104 | 105 | template 106 | void emplace(Args&&...args) { 107 | std::unique_lock l(action_list_lock); 108 | deferredActionList.emplace_back(Action::add, std::forward(args)...); 109 | } 110 | 111 | template 112 | void remove(Args&&...args) { 113 | std::unique_lock l(action_list_lock); 114 | deferredActionList.emplace_back(Action::remove, std::forward(args)...); 115 | } 116 | 117 | template 118 | void foreach(Closure&& closure) { 119 | apply_actions(); 120 | 121 | std::shared_lock l(list_mutation_lock); 122 | for (ListElement& element : list) { 123 | closure(element); 124 | } 125 | } 126 | }; 127 | 128 | } -------------------------------------------------------------------------------- /src/reactive/ObservableProperty.h: -------------------------------------------------------------------------------- 1 | #ifndef REACTIVE_OBSERVABLEPROPERTY_H 2 | #define REACTIVE_OBSERVABLEPROPERTY_H 3 | 4 | #include "details/ObservableProperty.h" 5 | 6 | namespace reactive{ 7 | 8 | class ObservablePropertyBase {}; 9 | 10 | // just wraps details::ObservableProperty with shared_ptr 11 | template 12 | class ObservableProperty : ObservablePropertyBase { 13 | public: 14 | static constexpr const bool threadsafe = t_threadsafe; 15 | private: 16 | using Self = ObservableProperty; 17 | 18 | using Property = std::conditional_t 20 | , details::ObservablePropertyConfigurable 21 | >; 22 | std::shared_ptr ptr; 23 | public: 24 | using Value = T; 25 | using WeakPtr = std::weak_ptr; 26 | using SharedPtr = std::shared_ptr; 27 | using ReadLock = typename Property::ReadLock; 28 | using WriteLock = typename Property::WriteLock; 29 | 30 | //using blocking_mode = typename Property::blocking_mode; 31 | 32 | ObservableProperty() 33 | : ptr(std::make_shared()){}; 34 | 35 | ObservableProperty(const WeakPtr& weak) 36 | : ptr(weak.lock()){}; 37 | 38 | ObservableProperty(const SharedPtr& shared) 39 | : ptr(shared) {}; 40 | ObservableProperty(SharedPtr&& shared) 41 | : ptr(std::move(shared)) {}; 42 | 43 | 44 | template< 45 | class Arg, class ...Args 46 | , class = typename std::enable_if_t< 47 | !(std::is_same< std::decay_t, Self >::value 48 | || std::is_same< std::decay_t, WeakPtr >::value 49 | || std::is_same< std::decay_t, SharedPtr >::value 50 | ) 51 | > 52 | > 53 | ObservableProperty(Arg&& arg, Args&&...args) 54 | : ptr(std::make_shared(std::forward(arg), std::forward(args)...) ) {}; 55 | 56 | 57 | ObservableProperty(ObservableProperty&&) = default; 58 | ObservableProperty& operator=(ObservableProperty&&) = default; 59 | 60 | ObservableProperty(const ObservableProperty& other) 61 | :ptr(std::make_shared(other.getCopy())) {} 62 | ObservableProperty& operator=(const ObservableProperty& other) { 63 | *ptr = *(other.ptr); 64 | return *this; 65 | } 66 | 67 | 68 | WeakPtr weak_ptr() const{ 69 | return {ptr}; 70 | } 71 | 72 | const SharedPtr& shared_ptr() const{ 73 | return ptr; 74 | } 75 | 76 | operator bool() const{ 77 | return ptr.operator bool(); 78 | } 79 | 80 | // forward 81 | 82 | template 83 | void operator+=(DelegateT&& closure) const { 84 | ptr->operator+=(std::forward(closure)); 85 | } 86 | template 87 | void subscribe(const DelegateTag& tag, Fn&& fn) const { 88 | ptr->subscribe(tag, std::forward(fn)); 89 | } 90 | template 91 | void operator-=(Delegate&& closure) const { 92 | ptr->operator-=(std::forward(closure)); 93 | } 94 | 95 | 96 | 97 | void operator=(const T& value) { 98 | ptr->operator=(value); 99 | } 100 | void operator=(T&& value) { 101 | ptr->operator=(std::move(value)); 102 | } 103 | 104 | template > 105 | const T* operator->() const { 106 | return &(ptr->value); 107 | } 108 | template > 109 | const T& operator*() const { 110 | return ptr->value; 111 | } 112 | 113 | ReadLock lock() const { 114 | return ptr->lock(); 115 | } 116 | WriteLock write_lock() { 117 | return ptr->write_lock(); 118 | } 119 | T getCopy() const{ 120 | return ptr->getCopy(); 121 | } 122 | 123 | void pulse() const{ 124 | ptr->pulse(); 125 | } 126 | }; 127 | 128 | } 129 | 130 | 131 | #endif //REACTIVE_OBSERVABLEPROPERTY_H 132 | -------------------------------------------------------------------------------- /test/BenchmarkOwnedProperty.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | class BenchmarkOwnedProperty { 10 | public: 11 | 12 | struct Data_plain { 13 | static const int size = 1; 14 | 15 | std::atomic x1; 16 | char c1[size]; 17 | std::atomic x2; 18 | char c2[size]; 19 | std::atomic x3; 20 | char c3[size]; 21 | std::atomic x4; 22 | char c4[size]; 23 | 24 | Data_plain(const Data_plain& other) 25 | : x1{ other.x1.load() } 26 | , x2(other.x2.load()) 27 | , x3(other.x3.load()) 28 | , x4(other.x4.load()) 29 | {} 30 | 31 | Data_plain(int i1, int i2, int i3, int i4) 32 | :/* Base() 33 | , */ 34 | x1(i1) 35 | , x2(i2) 36 | , x3(i3) 37 | , x4(i4) 38 | {} 39 | 40 | int sum() { 41 | return x1 + x2 + x3 + x4; 42 | } 43 | }; 44 | 45 | struct Data { 46 | reactive::details::ObservableProperty x1, x2, x3, x4; 47 | 48 | Data(int i1, int i2, int i3, int i4) 49 | : x1(i1) 50 | , x2(i2) 51 | , x3(i3) 52 | , x4(i4) 53 | {} 54 | 55 | int sum() { 56 | return x1.getCopy() + x2.getCopy() + x3.getCopy() + x4.getCopy(); 57 | } 58 | }; 59 | 60 | 61 | struct Data2 { 62 | reactive::ObservableProperty x1, x2, x3, x4; 63 | 64 | Data2(int i1, int i2, int i3, int i4) 65 | : x1(i1) 66 | , x2(i2) 67 | , x3(i3) 68 | , x4(i4) 69 | {} 70 | 71 | int sum() { 72 | return x1.getCopy() + x2.getCopy() + x3.getCopy() + x4.getCopy(); 73 | } 74 | }; 75 | 76 | struct Data3 { 77 | reactive::ObservableProperty x1, x2, x3, x4; 78 | 79 | Data3(int i1, int i2, int i3, int i4) 80 | : x1(i1) 81 | , x2(i2) 82 | , x3(i3) 83 | , x4(i4) 84 | {} 85 | 86 | int sum() { 87 | return x1.getCopy() + x2.getCopy() + x3.getCopy() + x4.getCopy(); 88 | } 89 | }; 90 | 91 | 92 | const int count = 100'000; 93 | 94 | void benchmark_plain() { 95 | using namespace std::chrono; 96 | 97 | std::vector< Data_plain > list; 98 | list.reserve(count); 99 | { 100 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 101 | 102 | for (int i = 0; i < count; ++i) { 103 | list.emplace_back(i, i + 1, i + 2, i + 3); 104 | } 105 | 106 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 107 | auto duration = duration_cast(t2 - t1).count(); 108 | std::cout << "Added in : " << duration << std::endl; 109 | } 110 | 111 | { 112 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 113 | 114 | int sum = 0; 115 | for (int i = 0; i < count; ++i) { 116 | sum += list[i].sum(); 117 | } 118 | 119 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 120 | auto duration = duration_cast(t2 - t1).count(); 121 | std::cout << "Traverse in : " << duration << std::endl; 122 | std::cout << sum << std::endl; 123 | } 124 | } 125 | 126 | void benchmark_owned() { 127 | using namespace std::chrono; 128 | 129 | std::vector< std::shared_ptr > list; 130 | list.reserve(count); 131 | { 132 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 133 | 134 | for (int i = 0; i < count; ++i) { 135 | list.emplace_back(std::make_shared(i, i + 1, i + 2, i + 3)); 136 | } 137 | 138 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 139 | auto duration = duration_cast(t2 - t1).count(); 140 | std::cout << "Added in : " << duration << std::endl; 141 | } 142 | 143 | { 144 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 145 | 146 | int sum = 0; 147 | for (int i = 0; i < count; ++i) { 148 | sum += list[i]->sum(); 149 | } 150 | 151 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 152 | auto duration = duration_cast(t2 - t1).count(); 153 | std::cout << "Traverse in : " << duration << std::endl; 154 | std::cout << sum << std::endl; 155 | } 156 | } 157 | 158 | 159 | template 160 | void benchmark_properties() { 161 | using namespace std::chrono; 162 | 163 | std::vector< D > list; 164 | list.reserve(count); 165 | { 166 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 167 | 168 | for (int i = 0; i < count; ++i) { 169 | list.emplace_back(i, i + 1, i + 2, i + 3); 170 | } 171 | 172 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 173 | auto duration = duration_cast(t2 - t1).count(); 174 | std::cout << "Added in : " << duration << std::endl; 175 | } 176 | { 177 | high_resolution_clock::time_point t1 = high_resolution_clock::now(); 178 | 179 | int sum = 0; 180 | for (int i = 0; i < count; ++i) { 181 | sum += list[i].sum(); 182 | } 183 | 184 | high_resolution_clock::time_point t2 = high_resolution_clock::now(); 185 | auto duration = duration_cast(t2 - t1).count(); 186 | std::cout << "Traverse in : " << duration << std::endl; 187 | std::cout << sum << std::endl; 188 | } 189 | } 190 | 191 | 192 | void benchmark_all() { 193 | std::cout << "Plain" << std::endl; 194 | benchmark_plain(); 195 | std::cout << std::endl; 196 | 197 | std::cout << "Owned" << std::endl; 198 | benchmark_owned(); 199 | std::cout << std::endl; 200 | 201 | std::cout << "Properties" << std::endl; 202 | benchmark_properties(); 203 | std::cout << std::endl; 204 | 205 | std::cout << "no threaded Properties" << std::endl; 206 | benchmark_properties(); 207 | std::cout << std::endl; 208 | } 209 | }; -------------------------------------------------------------------------------- /test/TestObservableProperty.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_TESTOBSERVABLEPROPERTY_H 2 | #define TEST_TESTOBSERVABLEPROPERTY_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | class TestObservableProperty{ 10 | public: 11 | void test_simple(){ 12 | reactive::ObservableProperty len{1}; 13 | 14 | len += [](int len){ 15 | std::cout << "len = " << len << std::endl; 16 | }; 17 | 18 | len = 10; 19 | len = 20; 20 | 21 | std::cout << len.getCopy() << std::endl; 22 | } 23 | 24 | void test_unsubscribe() { 25 | reactive::ObservableProperty len{ 1 }; 26 | 27 | reactive::Delegate delegate2 = [](int len) { 28 | std::cout << std::showpoint << "len = " << len << std::endl; 29 | }; 30 | len += delegate2; 31 | 32 | len = 10; 33 | len = 20; 34 | /* 35 | std::cout << len.getCopy() << std::endl;*/ 36 | } 37 | 38 | void test_in_thread(){ 39 | { 40 | reactive::ObservableProperty len{1}; 41 | 42 | std::thread t{[len_ptr = len.weak_ptr()]() { 43 | while (true) { 44 | reactive::ObservableProperty len{len_ptr}; 45 | if (!len) { 46 | std::cout << "len died." << std::endl; 47 | return; 48 | } 49 | 50 | std::cout << "len =" << len.getCopy() << std::endl; 51 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 52 | } 53 | }}; 54 | t.detach(); 55 | 56 | std::this_thread::sleep_for(std::chrono::seconds(1)); 57 | } 58 | std::this_thread::sleep_for(std::chrono::seconds(1)); 59 | } 60 | 61 | 62 | void test_read_lock(){ 63 | struct Pos{ 64 | int x,y; 65 | Pos(const Pos&) = delete; 66 | Pos(Pos&&) = default; 67 | Pos(int x, int y) : x(x), y(y){} 68 | }; 69 | 70 | reactive::ObservableProperty len{Pos(1,2)}; 71 | 72 | { 73 | auto len_ptr = len.lock(); 74 | std::cout 75 | << len_ptr->x 76 | << len_ptr->y; 77 | } 78 | } 79 | 80 | void test_write_lock(){ 81 | struct Pos{ 82 | int x,y; 83 | Pos() {}; 84 | //Pos() noexcept {}; 85 | //Pos(const Pos&) = delete; 86 | Pos(int x, int y) : x(x), y(y){} 87 | }; 88 | 89 | reactive::ObservableProperty len{1,2}; 90 | 91 | len += [](const Pos& len){ 92 | std::cout << "len = " << len.x << "," << len.y << std::endl; 93 | }; 94 | 95 | { 96 | auto len_ptr = len.write_lock(); 97 | 98 | len_ptr->x = 100; 99 | len_ptr->y = 200; 100 | } 101 | } 102 | 103 | 104 | void test_copy(){ 105 | struct Pos{ 106 | int x,y; 107 | Pos() noexcept {} 108 | //Pos(const Pos&) = delete; 109 | Pos(int x, int y) : x(x), y(y){} 110 | }; 111 | 112 | reactive::ObservableProperty pos1{1,2}; 113 | 114 | pos1 += [](const Pos& len){ 115 | std::cout << "pos1 = " << len.x << "," << len.y << std::endl; 116 | }; 117 | 118 | auto pos2 = pos1; 119 | { 120 | auto pos2_ptr = pos2.lock(); 121 | std::cout << "Now pos2 = " 122 | << pos2_ptr->x << "," 123 | << pos2_ptr->y 124 | << std::endl; 125 | } 126 | 127 | pos1 = Pos{3,4}; 128 | 129 | pos2 += [](const Pos& len){ 130 | std::cout << "pos2 = " << len.x << "," << len.y << std::endl; 131 | }; 132 | pos2 = pos1; 133 | } 134 | 135 | void test_silent(){ 136 | reactive::ObservableProperty i1{1}; 137 | i1 += [](int i){ 138 | std::cout << i << std::endl; 139 | }; 140 | 141 | { 142 | auto i_ptr = i1.write_lock(); 143 | i_ptr.silent(); 144 | 145 | *i_ptr = 15; 146 | } 147 | } 148 | 149 | void test_size(){ 150 | struct T { char v[32]; }; 151 | //using T = long; 152 | reactive::details::ObservableProperty ia; 153 | reactive::details::ObservableProperty in; 154 | reactive::details::ObservableProperty ib; 155 | 156 | std::cout << sizeof(T) << std::endl; 157 | std::cout << sizeof(ia) << std::endl; 158 | std::cout << sizeof(in) << std::endl; 159 | std::cout << sizeof(ib) << std::endl; 160 | std::cout << std::endl; 161 | std::cout << sizeof(reactive::Event) << std::endl; 162 | 163 | 164 | struct Test 165 | { 166 | std::atomic value; 167 | reactive::Event e; 168 | }; 169 | 170 | std::cout << sizeof(Test) << std::endl; 171 | } 172 | 173 | void test_nontreadsafe() { 174 | struct Vec2 { 175 | int x, y; 176 | }; 177 | reactive::ObservableProperty vec2; 178 | { 179 | auto vec2_ptr = vec2.write_lock(); 180 | vec2_ptr->x = 1; 181 | vec2_ptr->y = 2; 182 | } 183 | 184 | 185 | std::cout << vec2->x << std::endl; 186 | } 187 | 188 | void test_all(){ 189 | //test_simple(); 190 | //test_unsubscribe(); 191 | //test_in_thread(); 192 | //test_read_lock(); 193 | //test_write_lock(); 194 | //test_copy(); 195 | //test_silent(); 196 | 197 | test_nontreadsafe(); 198 | 199 | //test_size(); 200 | } 201 | }; 202 | 203 | #endif //TEST_TESTOBSERVABLEPROPERTY_H 204 | -------------------------------------------------------------------------------- /src/reactive/ReactiveProperty.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_REACTIVEPROPERTY2_H 2 | #define TEST_REACTIVEPROPERTY2_H 3 | 4 | #include 5 | #include "observer.h" 6 | 7 | namespace reactive{ 8 | 9 | class ReactivePropertyBase {}; 10 | 11 | template 12 | class ReactiveProperty : ReactivePropertyBase { 13 | public: 14 | static constexpr const bool threadsafe = t_threadsafe; 15 | private: 16 | using Self = ReactiveProperty; 17 | 18 | using DataBase = 19 | std::conditional_t 21 | , details::ObservablePropertyConfigurable 22 | >; 23 | 24 | struct DataNoLock { 25 | using Lock = typename DataBase::Lock; 26 | static constexpr const bool base_have_lock = true; 27 | }; 28 | struct DataHaveLock { 29 | using Lock = threading::SpinLock; 30 | Lock reactiveproperty_lock; 31 | static constexpr const bool base_have_lock = false; 32 | }; 33 | 34 | 35 | 36 | using DataLock = std::conditional_t< 37 | !threadsafe 38 | ,DataNoLock /* use threading::dummy_mutex from ObservablePropertyConfigurable */ 39 | ,std::conditional_t< 40 | std::is_same::value 41 | , DataHaveLock 42 | , DataNoLock 43 | > 44 | >; 45 | 46 | 47 | struct Data 48 | : public DataBase 49 | , public DataLock 50 | { 51 | using Base = DataBase; 52 | using Base::operator=; 53 | using Base::Base; 54 | using typename DataLock::Lock; 55 | using Base::set_value; 56 | using Base::threadsafe; 57 | 58 | auto& get_mutex(std::true_type base_have_lock) { 59 | return Base::m_lock; 60 | } 61 | auto& get_mutex(std::false_type base_have_lock) { 62 | return DataLock::reactiveproperty_lock; 63 | } 64 | auto& get_mutex() { 65 | return get_mutex(std::integral_constant{}); 66 | } 67 | 68 | std::function unsubscriber; 69 | }; 70 | 71 | std::shared_ptr ptr; 72 | public: 73 | using Value = T; 74 | using WeakPtr = std::weak_ptr; 75 | using SharedPtr = std::shared_ptr; 76 | using ReadLock = typename Data::ReadLock; 77 | using WriteLock = typename Data::WriteLock; 78 | //using blocking_mode = typename DataBase::blocking_mode; 79 | 80 | 81 | ReactiveProperty() 82 | : ptr(std::make_shared()){}; 83 | 84 | ReactiveProperty(const WeakPtr& weak) 85 | : ptr(weak.lock()){}; 86 | 87 | ReactiveProperty(const SharedPtr& shared) 88 | : ptr(shared) {}; 89 | ReactiveProperty(SharedPtr&& shared) 90 | : ptr(std::move(shared)) {}; 91 | 92 | 93 | template 94 | ReactiveProperty(const ObservableProperty& other) 95 | : ReactiveProperty(other.getCopy()) 96 | { 97 | set_impl([](const T& value) -> const T& { 98 | return value; 99 | }, other); 100 | } 101 | 102 | 103 | 104 | // in place ctr 105 | template< 106 | class Arg, class ...Args 107 | , class = typename std::enable_if_t< 108 | !( 109 | std::is_same< std::decay_t, Self >::value 110 | || std::is_same< std::decay_t, WeakPtr >::value 111 | || std::is_same< std::decay_t, SharedPtr >::value 112 | 113 | || std::is_base_of>::value 114 | || std::is_base_of>::value 115 | 116 | /*|| std::is_same< std::decay_t, ObservableProperty >::value 117 | || std::is_same< std::decay_t, ObservableProperty >::value 118 | || std::is_same< std::decay_t, ObservableProperty >::value 119 | || std::is_same< std::decay_t, ObservableProperty >::value*/ 120 | ) 121 | > 122 | > 123 | ReactiveProperty(Arg&& arg, Args&&...args) 124 | : ptr(std::make_shared(std::forward(arg), std::forward(args)...) ) {}; 125 | 126 | 127 | // copy ctr, as listener 128 | ReactiveProperty(const ReactiveProperty& other) 129 | : ReactiveProperty(other.getCopy()) 130 | { 131 | set_impl([](const T& value) -> const T& { 132 | return value; 133 | }, other); 134 | } 135 | 136 | private: 137 | template 138 | void set_assigment(const Any& any){ 139 | set([](const T& value) -> const T& { 140 | return value; 141 | }, any); 142 | } 143 | 144 | public: 145 | ReactiveProperty& operator=(const ReactiveProperty& other){ 146 | set_assigment(other); 147 | return *this; 148 | } 149 | template 150 | ReactiveProperty& operator=(const ReactiveProperty& other){ 151 | set_assigment(other); 152 | return *this; 153 | } 154 | template 155 | ReactiveProperty& operator=(const ObservableProperty& other){ 156 | set_assigment(other); 157 | return *this; 158 | } 159 | 160 | 161 | ReactiveProperty(ReactiveProperty&& other) noexcept 162 | :ptr(std::move(other.ptr)) {} 163 | ReactiveProperty& operator=(ReactiveProperty&& other) noexcept { 164 | ptr = std::move(other.ptr); 165 | return *this; 166 | } 167 | 168 | private: 169 | template 170 | void set_impl(Closure&& closure, const Observables&... observables){ 171 | std::unique_lock l(ptr->get_mutex()); 172 | 173 | if (ptr->unsubscriber){ 174 | ptr->unsubscriber(); 175 | } 176 | 177 | auto observer = reactive::details::MultiObserver::observe_impl( 178 | [closure = std::forward(closure), ptr_weak = std::weak_ptr(ptr)](auto&&...args){ 179 | std::shared_ptr ptr = ptr_weak.lock(); 180 | if(!ptr) return; 181 | 182 | (*ptr) = closure(std::forward(args)...); 183 | }, observables.shared_ptr()...); 184 | 185 | ptr->unsubscriber = [observer](){ observer->unsubscribe(); }; 186 | 187 | if (update_value) { 188 | observer->execute([&](auto &&...args) { 189 | ptr->set_value(closure(std::forward(args)...), std::move(l)); 190 | }); 191 | } 192 | } 193 | public: 194 | template 195 | void set(Closure&& closure, const Observables&... observables){ 196 | set_impl(std::forward(closure), observables...); 197 | } 198 | 199 | 200 | template 201 | void update(Closure&& closure, const Observables&... observables){ 202 | std::unique_lock l(ptr->get_mutex()); 203 | 204 | if (ptr->unsubscriber){ 205 | ptr->unsubscriber(); 206 | } 207 | 208 | auto observer = reactive::details::MultiObserver::observe_impl( 209 | [closure = std::forward(closure), ptr_weak = std::weak_ptr(ptr)](auto&&...args){ 210 | std::shared_ptr ptr = ptr_weak.lock(); 211 | if(!ptr) return; 212 | 213 | auto write_ptr = ptr->write_lock(); 214 | closure(write_ptr.get(), std::forward(args)...); 215 | }, observables.shared_ptr()...); 216 | 217 | ptr->unsubscriber = [observer](){ observer->unsubscribe(); }; 218 | 219 | observer->execute([&](auto &&...args) { 220 | auto write_ptr = ptr->write_lock(std::move(l)); 221 | closure(write_ptr.get(), std::forward(args)...); 222 | }); 223 | } 224 | 225 | 226 | WeakPtr weak_ptr() const{ 227 | return {ptr}; 228 | } 229 | 230 | const SharedPtr& shared_ptr() const{ 231 | return ptr; 232 | } 233 | 234 | operator bool() const{ 235 | return ptr.operator bool(); 236 | } 237 | 238 | 239 | template 240 | void operator+=(Delegate&& closure) const { 241 | ptr->operator+=(std::forward(closure)); 242 | } 243 | template 244 | void subscribe(const DelegateTag& tag, Fn&& fn) const { 245 | ptr->subscribe(tag, std::forward(fn)); 246 | } 247 | template 248 | void operator-=(Delegate&& closure) const { 249 | ptr->operator-=(std::forward(closure)); 250 | } 251 | 252 | 253 | 254 | void operator=(const T& value) { 255 | std::unique_lock l(ptr->get_mutex()); 256 | if (ptr->unsubscriber){ 257 | ptr->unsubscriber(); 258 | } 259 | ptr->set_value(value, std::move(l)); 260 | } 261 | void operator=(T&& value) { 262 | std::unique_lock l(ptr->get_mutex()); 263 | if (ptr->unsubscriber){ 264 | ptr->unsubscriber(); 265 | } 266 | ptr->set_value(std::move(value), std::move(l)); 267 | } 268 | 269 | 270 | 271 | ReadLock lock() const { 272 | return ptr->lock(); 273 | } 274 | WriteLock write_lock() { 275 | std::unique_lock l(ptr->get_mutex()); 276 | if (ptr->unsubscriber){ 277 | ptr->unsubscriber(); 278 | } 279 | return ptr->write_lock(std::move(l)); 280 | } 281 | T getCopy() const{ 282 | return ptr->getCopy(); 283 | } 284 | 285 | template > 286 | const T* operator->() const { 287 | return &(ptr->value); 288 | } 289 | template > 290 | const T& operator*() const { 291 | return ptr->value; 292 | } 293 | 294 | 295 | void pulse() const{ 296 | ptr->pulse(); 297 | } 298 | 299 | ~ReactiveProperty(){ 300 | if (!ptr) return; // moved? 301 | 302 | std::unique_lock l(ptr->get_mutex()); 303 | if (ptr->unsubscriber){ 304 | ptr->unsubscriber(); 305 | } 306 | } 307 | 308 | }; 309 | } 310 | 311 | #endif //TEST_REACTIVEPROPERTY2_H -------------------------------------------------------------------------------- /src/reactive/observer.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTIOBSERVER_H 2 | #define MULTIOBSERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "details/threading/SpinLock.h" 12 | #include "details/Delegate.h" 13 | 14 | #include "blocking.h" 15 | 16 | namespace reactive{ 17 | 18 | namespace details{ 19 | namespace MultiObserver { 20 | // helper functions 21 | template 22 | static void foreach(Closure &&closure) {} 23 | template 24 | static void foreach(Closure &&closure, Arg &&arg, Args &&... args) { 25 | closure( 26 | std::integral_constant{}, 27 | std::forward(arg) 28 | ); 29 | foreach(std::forward(closure), std::forward(args)...); 30 | } 31 | template 32 | static void foreach(Closure &&closure, Args &&... args) { 33 | foreach<0>(std::forward(closure), std::forward(args)...); 34 | } 35 | 36 | 37 | template 38 | static constexpr decltype(auto) apply_impl(F &&f, Tuple &&t, std::index_sequence) { 39 | return f(std::get(std::forward(t))...); 40 | } 41 | template 42 | static constexpr decltype(auto) apply(F &&f, Tuple &&t) { 43 | return apply_impl(std::forward(f), std::forward(t), 44 | std::make_index_sequence>::value>{}); 45 | } 46 | 47 | template 48 | static void foreach_tuple(Closure &&closure, Tuple &&tuple) { 49 | reactive::details::MultiObserver::apply([&](auto &&... args) { 50 | foreach(closure, std::forward(args)...); 51 | }, tuple); 52 | } 53 | 54 | template 55 | static auto lockWeakPtrs(const std::tuple...> &tuple, std::index_sequence) { 56 | return std::make_tuple(std::get(tuple).lock()...); 57 | } 58 | 59 | template 60 | static auto lockWeakPtrs(const std::tuple...> &tuple) { 61 | return lockWeakPtrs(tuple, std::make_index_sequence{}); 62 | } 63 | 64 | 65 | template 66 | static constexpr bool and_all(const V &... v) { 67 | bool result = true; 68 | (void) std::initializer_list{(result = result && v, 0)...}; 69 | return result; 70 | } 71 | template 72 | static constexpr bool or_all(const V &... v) { 73 | bool result = false; 74 | (void)std::initializer_list{(result = result || v, 0)...}; 75 | return result; 76 | } 77 | 78 | 79 | template 80 | class ObserverBase { 81 | protected: 82 | bool unsubscribed = false; 83 | 84 | using Lock = threading::SpinLock; 85 | Lock lock; 86 | public: 87 | using ObservablesTuple = std::tuple...>; 88 | ObservablesTuple observable_weak_ptrs; 89 | 90 | DelegateTag tag; 91 | 92 | ObserverBase(const std::shared_ptr&... observables) 93 | : observable_weak_ptrs(observables...) {} 94 | 95 | void unsubscribe() { 96 | std::unique_lock l(lock); 97 | if (unsubscribed) return; 98 | 99 | foreach_tuple([&](auto i, auto &observable) { 100 | auto ptr = observable.lock(); 101 | if (!ptr) return; 102 | 103 | *ptr -= tag; 104 | }, observable_weak_ptrs); 105 | 106 | unsubscribed = true; 107 | } 108 | }; 109 | 110 | template 111 | class ObserverBlocking : public ObserverBase { 112 | using Base = ObserverBase; 113 | public: 114 | using Base::observable_weak_ptrs; 115 | using Base::unsubscribe; 116 | 117 | std::decay_t closure; 118 | 119 | template 120 | ObserverBlocking(ClosureT &&closure, const std::shared_ptr &... observables) 121 | : Base(observables...) 122 | , closure(std::forward(closure)) {} 123 | 124 | template 125 | void run(IntegralConstant, Arg &&) { 126 | execute(); 127 | } 128 | 129 | void execute(){ 130 | execute(closure); 131 | } 132 | 133 | private: 134 | template 135 | void apply_closure(ClosureT&& closure, ObservableLocks& observable_locks, std::false_type){ 136 | reactive::details::MultiObserver::apply([&](auto &... locks) { 137 | closure(locks.get()...); 138 | }, observable_locks); 139 | } 140 | template 141 | void apply_closure(ClosureT&& closure, ObservableLocks& observable_locks, std::true_type){ 142 | reactive::details::MultiObserver::apply([&](auto &... locks) { 143 | closure([&](){ unsubscribe(); }, locks.get()...); 144 | }, observable_locks); 145 | } 146 | public: 147 | template 148 | void execute(ClosureT&& closure){ 149 | auto shared_ptrs = lockWeakPtrs(observable_weak_ptrs); 150 | const bool all_locked = reactive::details::MultiObserver::apply([](auto &... ptrs) { return and_all(ptrs...); }, shared_ptrs); 151 | 152 | if (!all_locked) { 153 | // can't be reactive further, unsubscribe 154 | unsubscribe(); 155 | return; 156 | } 157 | 158 | auto observable_locks = reactive::details::MultiObserver::apply([](auto &... observables) { 159 | return std::make_tuple(observables->lock()...); 160 | }, shared_ptrs); 161 | 162 | apply_closure(std::forward(closure), observable_locks, std::integral_constant{}); 163 | } 164 | }; 165 | 166 | template 167 | class ObserverNonBlocking : public ObserverBase { 168 | using Base = ObserverBase; 169 | 170 | using Base::lock; 171 | using Lock = typename Base::Lock; 172 | public: 173 | using Base::unsubscribe; 174 | std::decay_t closure; 175 | 176 | std::tuple observable_values; 177 | 178 | template 179 | ObserverNonBlocking(ClosureT &&closure, const std::shared_ptr&... observables) 180 | : Base(observables...) 181 | , closure(std::forward(closure)) 182 | , observable_values(observables->getCopy()...) {} 183 | 184 | private: 185 | template 186 | void apply_closure(ClosureT&& closure, const TmpValues& tmp_observable_values, std::false_type){ 187 | reactive::details::MultiObserver::apply(std::forward(closure), tmp_observable_values); 188 | } 189 | template 190 | void apply_closure(ClosureT&& closure, const TmpValues& tmp_observable_values, std::true_type){ 191 | reactive::details::MultiObserver::apply( 192 | [&](auto&&...args){ 193 | closure( [&](){ this->unsubscribe(); }, std::forward(args)... ); 194 | } 195 | , tmp_observable_values 196 | ); 197 | } 198 | public: 199 | 200 | // non-blocking 201 | template 202 | void run(IntegralConstant, Arg &&arg) { 203 | // update values cache 204 | lock.lock(); 205 | auto &value = std::get(observable_values); 206 | value = std::forward(arg); 207 | const auto tmp_observable_values = observable_values; 208 | lock.unlock(); 209 | 210 | apply_closure(closure, tmp_observable_values, std::integral_constant{}); 211 | } 212 | 213 | void execute() { 214 | execute(closure); 215 | } 216 | 217 | template 218 | void execute(ClosureT&& closure){ 219 | lock.lock(); 220 | const auto tmp_observable_values = observable_values; 221 | lock.unlock(); 222 | 223 | apply_closure(std::forward(closure), tmp_observable_values, std::integral_constant{}); 224 | } 225 | 226 | }; 227 | 228 | 229 | template 230 | auto observe_impl(Closure &&closure, const std::shared_ptr&... observables) { 231 | assert(and_all(observables...) && "all observables must exists on observe()!"); 232 | 233 | // mix_threadsafe only safe to work in nonblocking mode 234 | constexpr const bool all_non_threadsafe = and_all(!Observables::threadsafe...); 235 | constexpr const bool some_non_threadsafe = or_all (!Observables::threadsafe...); 236 | constexpr const bool mix_threadsafe = some_non_threadsafe && !all_non_threadsafe; 237 | 238 | constexpr const bool efficiently_copyable = reactive::details::default_blocking::is_efficiently_copyable::value; 239 | 240 | static_assert(!(mix_threadsafe && std::is_same::value) 241 | , "You trying to mix thread-safe with non-thread-safe properties in observer in blocking mode. Use default_blocking or nonblocking observer mode."); 242 | 243 | 244 | // If someone in blocking mode - play safe, work in blocking mode too 245 | constexpr const bool have_property_in_blocking_mode = or_all( std::is_same::value... ); 246 | 247 | 248 | constexpr const bool is_default_blocking = std::is_same::value; 249 | 250 | constexpr const bool do_blocking = 251 | ( is_default_blocking 252 | ? (mix_threadsafe ? false /* required */ : 253 | (all_non_threadsafe && efficiently_copyable /* nonthread properties always in blocking mode */ ? false : have_property_in_blocking_mode) 254 | ) 255 | : std::is_same::value 256 | ); 257 | 258 | using Observer = std::conditional_t< 259 | do_blocking 260 | , ObserverBlocking 261 | , ObserverNonBlocking 262 | >; 263 | 264 | std::shared_ptr observer = std::make_shared( 265 | std::forward(closure), observables... 266 | ); 267 | 268 | foreach([&](auto i, auto &observable) { 269 | using I = decltype(i); 270 | observable->subscribe(observer->tag, [observer](auto &&arg) { 271 | observer->run(I{}, std::forward(arg)); 272 | }); 273 | }, observables...); 274 | 275 | return observer; 276 | } 277 | 278 | 279 | template 280 | auto observe_w_unsubscribe_impl(Closure &&closure, const std::shared_ptr&... observables) { 281 | return observe_impl(std::forward(closure), observables...); 282 | }; 283 | 284 | 285 | } // namespace multiobserver 286 | } // namespace details 287 | 288 | 289 | template 290 | auto observe(Closure&& closure, Observables&... observables){ 291 | return [ 292 | observer = details::MultiObserver::observe_impl( std::forward(closure), observables.shared_ptr()... ) 293 | ](){ 294 | observer->unsubscribe(); 295 | }; 296 | } 297 | 298 | 299 | template 300 | auto observe_w_unsubscribe(Closure&& closure, Observables&... observables){ 301 | return 302 | [ 303 | observer = details::MultiObserver::observe_impl( std::forward(closure), observables.shared_ptr()... ) 304 | ](){ 305 | observer->unsubscribe(); 306 | }; 307 | } 308 | 309 | 310 | } 311 | 312 | #endif //MULTIOBSERVER_H 313 | -------------------------------------------------------------------------------- /src/reactive/details/ObservableProperty.h: -------------------------------------------------------------------------------- 1 | #ifndef REACTIVE_DETAILS_OBSERVABLEPROPERTY_H 2 | #define REACTIVE_DETAILS_OBSERVABLEPROPERTY_H 3 | 4 | #include "threading/upgrade_mutex.h" 5 | #include "Event.h" 6 | 7 | #include "../blocking.h" 8 | 9 | namespace reactive { 10 | namespace details { 11 | 12 | // equality from #https://stackoverflow.com/a/36360646 13 | namespace details 14 | { 15 | template 16 | struct equality : std::false_type {}; 17 | 18 | template 19 | struct equality()==std::declval())> 20 | : std::true_type {}; 21 | } 22 | template 23 | struct has_equal_op : details::equality {}; 24 | 25 | namespace details { 26 | // ObservableProperty Does not inherit Settings, just because of non working VS 2017 "Empty base optimization" 27 | // #https://stackoverflow.com/questions/12701469/why-empty-base-class-optimization-is-not-working 28 | // #https://developercommunity.visualstudio.com/content/problem/69605/vs2017-c-empty-base-optimization-does-not-work-wit.html 29 | template 30 | struct Settings { 31 | using blocking_mode = get_blocking_mode; 32 | 33 | static const constexpr bool do_blocking = std::is_same::value; 34 | static const constexpr bool atomic_value = std::is_same::value; 35 | 36 | using Lock = std::conditional_t> 39 | >; 40 | }; 41 | 42 | // zero size dummy_mutex optimisation 43 | template 44 | class ObservablePropertyLock; 45 | template<> 46 | class ObservablePropertyLock { 47 | protected: 48 | inline static threading::dummy_mutex m_lock; 49 | }; 50 | 51 | template 52 | class ObservablePropertyLock { 53 | protected: 54 | mutable Lock m_lock; 55 | }; 56 | } 57 | 58 | 59 | // use with shared_ptr 60 | // thread-safe 61 | // reactive::nonblocking = value will be copied twice on set 62 | // safe to change value right from the observer (may cause infinite loop) 63 | template::Lock 65 | , class EvenActionListLock = threading::SpinLock 66 | , class EventMutationListLock = std::shared_mutex 67 | > 68 | class ObservablePropertyConfigurable 69 | : public details::ObservablePropertyLock< ObservablePropertyLock_mutex > 70 | { 71 | friend friend_class; 72 | using Self = ObservablePropertyConfigurable; 73 | 74 | 75 | using Settings = details::Settings; 76 | using BaseLock = details::ObservablePropertyLock< ObservablePropertyLock_mutex >; 77 | 78 | public: 79 | using blocking_mode = typename Settings::blocking_mode; 80 | static constexpr const bool threadsafe = 81 | !( 82 | std::is_same::value 83 | && std::is_same::value 84 | && std::is_same::value 85 | ); 86 | protected: 87 | static const constexpr bool do_blocking = Settings::do_blocking; 88 | static const constexpr bool atomic_value = Settings::atomic_value; 89 | using Lock = ObservablePropertyLock_mutex; 90 | 91 | using BaseLock::m_lock; 92 | 93 | public: 94 | using Value = T; 95 | 96 | protected: 97 | // variables order matters (for smaller object size) 98 | std::conditional_t, T> value; 99 | mutable ConfigurableEvent event; 100 | 101 | public: 102 | ObservablePropertyConfigurable() {} 103 | 104 | // in place construction 105 | template< 106 | class Arg, class ...Args 107 | , class = typename std::enable_if_t< !atomic_value && !std::is_same< std::decay_t, Self >::value > 108 | > 109 | ObservablePropertyConfigurable(Arg&& arg, Args&&...args) 110 | : value( std::forward(arg), std::forward(args)... ) {}; 111 | 112 | // for atomic 113 | template< 114 | class Arg, class ...Args 115 | , class = typename std::enable_if_t< atomic_value && !std::is_same< std::decay_t, Self >::value > 116 | , typename = void 117 | > 118 | ObservablePropertyConfigurable(Arg&& arg, Args&&...args) 119 | : value( T(std::forward(arg), std::forward(args)...) ) {}; 120 | 121 | 122 | // do not copy event list 123 | ObservablePropertyConfigurable(const ObservablePropertyConfigurable& other) 124 | :value(other.getCopy()) {} 125 | ObservablePropertyConfigurable& operator=(const ObservablePropertyConfigurable& other) { 126 | set_value(other.getCopy()); 127 | return *this; 128 | } 129 | 130 | // move all (event list too) 131 | // non thread safe 132 | ObservablePropertyConfigurable(ObservablePropertyConfigurable&&) = default; 133 | ObservablePropertyConfigurable& operator=(ObservablePropertyConfigurable&&) = default; 134 | 135 | // event control (non-blocking) 136 | template 137 | void operator+=(DelegateT&& closure) const { 138 | event += std::forward(closure); 139 | } 140 | template 141 | void subscribe(Delegate&& closure) const{ 142 | event.subscribe(std::forward(closure)); 143 | } 144 | template 145 | void subscribe(const DelegateTag& tag, Fn&& fn) const{ 146 | event.subscribe(tag, std::forward(fn)); 147 | } 148 | template 149 | void operator-=(Delegate&& closure) const { 150 | event -= std::forward(closure); 151 | } 152 | 153 | private: 154 | static bool need_trigger_event(const T& old_value, const T& new_value, std::true_type) { 155 | return !(new_value == old_value); 156 | } 157 | static bool need_trigger_event(const T& old_value, const T& new_value, std::false_type) { 158 | return true; 159 | } 160 | protected: 161 | static bool need_trigger_event(const T& old_value, const T& new_value) { 162 | return need_trigger_event(old_value, new_value, std::integral_constant::value >{}); 163 | } 164 | 165 | template 166 | void set_value(Any&& any) { 167 | std::unique_lock l(m_lock); 168 | set_value(any, std::move(l)); 169 | } 170 | 171 | private: 172 | template 173 | void set_value_impl(Any&& any, std::unique_lock&& lock, std::true_type do_block) { 174 | const bool need_event = need_trigger_event(any, this->value); 175 | 176 | //std::unique_lock l(lock); 177 | this->value = std::forward(any); 178 | 179 | if (!need_event) return; 180 | 181 | std::shared_lock sl(acme::upgrade_lock(std::move(lock))); 182 | event(this->value); 183 | } 184 | template 185 | void set_value_impl(Any&& any, std::unique_lock&& lock, std::false_type do_block) { 186 | const bool need_event = need_trigger_event(any, this->value); 187 | 188 | //lock.lock(); 189 | const T temp_value = any; 190 | this->value = std::forward(any); // potential atomic store 191 | lock.unlock(); 192 | 193 | if (!need_event) return; 194 | event(temp_value); 195 | } 196 | protected: 197 | template 198 | void set_value(Any&& any, std::unique_lock&& lock) { 199 | set_value_impl(std::forward(any), std::move(lock), std::integral_constant{}); 200 | } 201 | 202 | public: 203 | // value setters (may block) 204 | void operator=(const T& value) { 205 | set_value(value); 206 | } 207 | void operator=(T&& value) { 208 | set_value(std::move(value)); 209 | } 210 | 211 | T getCopy() const { 212 | const auto shared = lock(); 213 | return shared.get(); 214 | } 215 | 216 | class ReadLockNonCopy { 217 | friend Self; 218 | protected: 219 | const Self& self; 220 | std::shared_lock lock; // Lock = upgarde_mutex 221 | 222 | ReadLockNonCopy(const Self& self) 223 | : self(self) 224 | , lock(self.m_lock) 225 | {} 226 | public: 227 | ReadLockNonCopy(ReadLockNonCopy&&) = default; 228 | ReadLockNonCopy(const ReadLockNonCopy&) = delete; 229 | 230 | const T& get() const { 231 | return self.value; 232 | } 233 | operator const T&() const { 234 | return get(); 235 | } 236 | /*const T& operator()() const { 237 | return get(); 238 | }*/ 239 | const T* operator->() const { 240 | return &get(); 241 | } 242 | const T& operator*() const{ 243 | return get(); 244 | } 245 | 246 | void unlock() { 247 | lock.unlock(); 248 | } 249 | }; 250 | 251 | 252 | class ReadLockAtomic { 253 | friend Self; 254 | protected: 255 | const T value; 256 | 257 | const auto& getValue(const Self& self, std::true_type is_atomic) const 258 | { 259 | return self.value; 260 | } 261 | const T& getValue(const Self& self, std::false_type is_atomic) const 262 | { 263 | std::unique_lock tmp_lock(self.m_lock); // SpinLock 264 | return self.value; 265 | } 266 | 267 | ReadLockAtomic(const Self& self) 268 | :value(getValue(self, std::integral_constant{})) 269 | {} 270 | public: 271 | ReadLockAtomic(ReadLockAtomic&&) = default; 272 | ReadLockAtomic(const ReadLockAtomic&) = delete; 273 | 274 | const T& get() const { 275 | return value; 276 | } 277 | operator const T&() const { 278 | return get(); 279 | } 280 | /*const T& operator()() const { 281 | return get(); 282 | }*/ 283 | const T* operator->() const { 284 | return &get(); 285 | } 286 | const T& operator*() const{ 287 | return get(); 288 | } 289 | 290 | void unlock(){} 291 | }; 292 | 293 | using ReadLock = std::conditional_t; 294 | 295 | 296 | class WriteLockBase{ 297 | protected: 298 | bool m_silent = false; 299 | 300 | public: 301 | void silent(bool be_silent = true){ 302 | m_silent = be_silent; 303 | } 304 | }; 305 | 306 | class WriteLockNonCopy : public WriteLockBase{ 307 | friend Self; 308 | protected: 309 | Self* self; 310 | using Lock = typename Self::Lock; // Lock = upgarde_mutex 311 | std::unique_lock lock; 312 | 313 | WriteLockNonCopy(Self& self) 314 | : self(&self) 315 | , lock(self.m_lock) 316 | {} 317 | WriteLockNonCopy(Self& self, std::unique_lock&& lock) 318 | : self(&self) 319 | , lock(std::move(lock)) 320 | {} 321 | public: 322 | WriteLockNonCopy(WriteLockNonCopy&& other) 323 | : self(other.self) 324 | , lock(std::move(other.lock)) 325 | { 326 | other.self = nullptr; 327 | } 328 | 329 | WriteLockNonCopy(const WriteLockNonCopy&) = delete; 330 | 331 | 332 | T& get() { 333 | return self->value; 334 | } 335 | operator T&() { 336 | return get(); 337 | } 338 | /*const T& operator()() const { 339 | return get(); 340 | }*/ 341 | T* operator->() { 342 | return &get(); 343 | } 344 | T& operator*(){ 345 | return get(); 346 | } 347 | 348 | void unlock() { 349 | if (!self) return; 350 | finish(); 351 | self = nullptr; 352 | } 353 | 354 | private: 355 | void finish(std::true_type do_block) { 356 | std::shared_lock sl(acme::upgrade_lock(std::move(lock))); 357 | self->event(self->value); 358 | } 359 | void finish(std::false_type do_block) { 360 | const T temp_value = self->value; 361 | lock.unlock(); 362 | 363 | self->event(temp_value); 364 | } 365 | void finish() { 366 | if (this->m_silent) return; 367 | finish(std::integral_constant{}); 368 | } 369 | public: 370 | ~WriteLockNonCopy(){ 371 | if (!self) return; // moved? 372 | finish(); 373 | } 374 | }; 375 | 376 | 377 | // lockless 378 | class WriteLockAtomic : public WriteLockBase{ 379 | friend Self; 380 | protected: 381 | Self* self; 382 | T value; 383 | 384 | WriteLockAtomic(Self& self) 385 | : self(&self) 386 | , value(self.value) 387 | {} 388 | 389 | template 390 | WriteLockAtomic(Self& self, std::unique_lock&& lock) 391 | : self(&self) 392 | , value(self.value) 393 | {} 394 | public: 395 | WriteLockAtomic(WriteLockAtomic&& other) 396 | : self(other.self) 397 | , value(std::move(other.value)) 398 | { 399 | other.self = nullptr; 400 | } 401 | 402 | WriteLockAtomic(const WriteLockAtomic&) = delete; 403 | 404 | 405 | T& get() { 406 | return value; 407 | } 408 | operator T&() { 409 | return get(); 410 | } 411 | /*const T& operator()() const { 412 | return get(); 413 | }*/ 414 | T* operator->() { 415 | return &get(); 416 | } 417 | T& operator*() { 418 | return get(); 419 | } 420 | 421 | void unlock() { 422 | if (!self) return; 423 | finish(); 424 | self = nullptr; 425 | } 426 | 427 | private: 428 | void finish() { 429 | self->value = value; 430 | 431 | if (this->m_silent) return; 432 | 433 | self->event(value); 434 | } 435 | public: 436 | ~WriteLockAtomic() { 437 | if (!self) return; // moved? 438 | finish(); 439 | } 440 | }; 441 | 442 | using WriteLock = std::conditional_t; 443 | 444 | protected: 445 | template 446 | WriteLock write_lock(std::unique_lock&& lock) { 447 | return {*this, std::move(lock)}; 448 | } 449 | 450 | public: 451 | // TODO: rename to read / write 452 | ReadLock lock() const { 453 | return {*this}; 454 | } 455 | WriteLock write_lock() { 456 | return {*this}; 457 | } 458 | 459 | private: 460 | void do_pulse(std::true_type do_blocking) const { 461 | std::shared_lock sl(m_lock); 462 | event(this->value); 463 | } 464 | void do_pulse(std::false_type do_blocking) const { 465 | event(getCopy()); 466 | } 467 | public: 468 | void pulse() const{ 469 | do_pulse(std::integral_constant{}); 470 | } 471 | 472 | }; 473 | 474 | template 475 | using ObservableProperty = ObservablePropertyConfigurable; 476 | } 477 | } 478 | 479 | #endif //REACTIVE_DETAILS_OBSERVABLEPROPERTY_H 480 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Header only reactive C++ library. Thread-safe, memory-safe. 2 | Concsists from: 3 | * [Event](#event) (C# like `Event`, but thread-safe, does not block while event occurs, subscribtion/unsbuscription possible right from the observer ) 4 | * [ObservableProperty](#observableproperty) 5 | * [ReactiveProperty](#reactiveproperty) 6 | 7 | Helpers [observe](#observe) and [bind](#bind). 8 | 9 | There is also [non thread safe version](#non_thread_safe). And you can mix them safely. 10 | 11 | Througthput. 400'000 reactive updates took 60ms on my Intel i7. Should be more than enough to build your UI model/view interaction. See `test/BenchmarkReactivity.h` to know your mileage. 12 | 13 | # Usage 14 | Add `src` folder to your compiler's INCLUDE path. 15 | 16 | ```C++ 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace reactive; 22 | 23 | ObservableProperty x = 1; 24 | ObservableProperty y = 2; 25 | 26 | ReactiveProperty sum; 27 | sum.set([](int x, int y){ return x+y; }, x, y); 28 | 29 | sum += [](int sum){ 30 | cout << "new sum is " << sum << endl; 31 | }; 32 | 33 | struct MyWidget{ 34 | void setText(const std::string& msg){ 35 | cout << msg << endl; 36 | } 37 | }; 38 | auto widget = std::make_shared(); // need shared_ptr to keep track about widget aliveness in multithreaded environment 39 | 40 | // non-intrusive bind 41 | bind(widget, [](auto& widget, int sum, int x, int y){ 42 | widget->setText(std::to_string(sum) + " = " + std::to_string(x) + " + " + std::to_string(y)); 43 | }, sum, x, y); 44 | 45 | x = 10; 46 | widget.reset(); // safe to kill, bind will take care about auto unsubscribing 47 | y = 20; 48 | 49 | ``` 50 | 51 | # Event 52 | 53 | Event is basis of reactivity. It let us know that something changed. 54 | 55 | ```C++ 56 | #include 57 | using namespace reactive; 58 | 59 | Event mouseMove; 60 | 61 | mouseMove += [](int x, int y){ 62 | std::cout << "mouse position " << x << ":" << y << std::endl; 63 | }; 64 | 65 | Delegate delegate; 66 | delegate = [&](int x, int y){ 67 | if (y == 100){ 68 | mouseMove -= delegate; // it is possible to unsubscribe/subscrive right from the event; 69 | } 70 | std::cout << "delegate can be unsubscribed. Position " << x << ":" << y << std::endl; 71 | }; 72 | mouseMove += delegate; // delegate's function copied to event queue 73 | mouseMove -= delegate; 74 | 75 | 76 | // Delegate is shortcut for this: 77 | DelegateTag tag; 78 | mouseMove.subscribe(tag, [&, tag](int x, int y){ 79 | if (x == 100){ 80 | mouseMove -= tag; // You can store tag ( delegate.tag() ) for latter unsubscription 81 | } 82 | std::cout << "Event can be unsubscribed by Delegate tag too. Position " << x << ":" << y << std::endl; 83 | }); 84 | mouseMove -= tag; 85 | 86 | // call event 87 | mouseMove(10,6); 88 | ``` 89 | 90 | #### `reactive/Event.h` Synopsis: 91 | `void operator()()` 92 | `void operator+=(Closure&&)` 93 | `void operator+=(const Delegate&)` 94 | `void operator-=(const Delegate&)` 95 | `void operator-=(const DelegateTag&)` 96 | `void subscribe(const DelegateTag&, Closure&&)` 97 | 98 | #### Implementation details: 99 | Event use "deferred" container (see `details/utils/DeferredForwardContainer.h`), erase/emplace queued in separate std::vector, and applied before foreach(). Thus, foreach() have minimal interference with container modification. 100 | 101 | * subscription/unsubscription occurs before next event() call. 102 | * Subscription/unsubscription never blocked by event call(); 103 | * event call() does not block another event call(), if there is no subscription's/unsubscription's from previous call. Otherwise block till changes to event queue applied. 104 | * Event queue is unordered. 105 | 106 | ### Delegate 107 | ```C++ 108 | struct Delegate{ 109 | std::function 110 | DelegateTag m_tag 111 | 112 | auto& tag() { return m_tag; } 113 | } 114 | ``` 115 | 116 | ### DelegateTag 117 | ```C++ 118 | struct DelegateTag{ 119 | unsigned log long int uuid; 120 | } 121 | ``` 122 | 123 | 124 | # ObservableProperty 125 | 126 | ObservableProperty = Value + Event 127 | 128 | ```C++ 129 | using namespace reactive; 130 | 131 | struct Vec2{ 132 | int x,y; 133 | Vec2() nothrow {} 134 | Vec2(int x, int y) 135 | :x(x) 136 | ,y(y) 137 | {} 138 | } 139 | 140 | ObservableProperty vec {10, 20}; 141 | 142 | vec += [](const Vec2& vec){ 143 | std::cout << "new vec " << vec.x << ", " << vec.y << std::endl; 144 | }; 145 | 146 | vec = {3,4}; 147 | // Output: new vec 3,4 148 | 149 | { 150 | auto vec_ptr = vec.write_lock(); 151 | vec_ptr->x = 2; 152 | vec_ptr->y = 5; 153 | } 154 | // Output: new vec 2,5 155 | 156 | { 157 | auto vec_ptr = vec.lock(); 158 | std::cout << "current vec " << vec_ptr->x << ", " << vec_ptr->y << std::endl; 159 | } 160 | // Output: current vec 2,5 161 | 162 | std::thread t([](vec_weak = vec.weak_ptr()){ 163 | ObservableProperty property(vec_weak); 164 | if (!property) return; 165 | 166 | Vec2 vec = property.getCopy(); 167 | std::cout << "current vec " << vec.x << ", " << vec.y << std::endl; 168 | }); 169 | 170 | ``` 171 | 172 | 173 | If possible, on set, new value compares with previous, and event triggers only if values are not the same. 174 | 175 | 176 | #### `reactive/ObservableProperty.h` Synopsis 177 | **constructors** 178 | `ObservableProperty(Args&&...)` in place object construct 179 | `ObservableProperty(const ObservableProperty&)` will copy only value 180 | `ObservableProperty(ObservableProperty&&)` 181 | `ObservableProperty(const WeakPtr&)` may be invalid after construction. Check with bool() 182 | `ObservableProperty(const SharedPtr&)` 183 | **pointer** 184 | `WeakPtr weak_ptr() const` 185 | `const SharedPtr& shared_ptr() const` 186 | `operator bool() const` 187 | **event manipulation** 188 | `void operator+=(Closure&&) const` 189 | `void operator+=(const Delegate&) const` 190 | `void operator-=(const Delegate&) const` 191 | `void operator-=(const DelegateTag&) const` 192 | `void subscribe(const DelegateTag&, Closure&&) const` 193 | `void pulse() const` 194 | **accessors** 195 | `ReadLock lock() const` 196 | `T getCopy() const` 197 | **mutators** 198 | `WriteLock write_lock()` 199 | `void operator=(const T& value)` 200 | `void operator=(T&& value)` 201 | `ObservableProperty& operator=(const ObservableProperty&)` will copy only value 202 | 203 | --- 204 | > `lock()`/`write_lock()` lock object(with mutex, if applicable, see below), and provides pointer like object access. `WriteLock` will trigger event on destruction (aka update). 205 | --- 206 | 207 | `ReadLock`/`WriteLock` Synopsis (`ReadLock` with `const` modifier): 208 | `T& get()` 209 | `operator T&()` 210 | `T* operator->()` 211 | `T& operator*()` 212 | `void unlock()` unlocks underlying mutex(if applicable) and call event loop with new value(for `WriteLock`) 213 | `void silent(bool be_silent = true)` does not call event on WriteLock destruction 214 | 215 | `ObservableProperty` may be configured with additional parameter `ObservableProperty`. 216 | 217 | Where `blocking_mode` can be: 218 | * `default_blocking` (by default). 219 | ``` 220 | if (T is trivially copyable && size <= 128) nonblocking_atomic 221 | if (T is copyable && size <= 128) nonblocking 222 | else blocking 223 | ``` 224 | * `blocking` use `upgrade_mutex`. ReadLock use shared_lock. WriteLock use unique_lock. On setting new value, mutex locks with shared_lock, event called with value reference. 225 | * `nonblocking` use `SpinLock`. ReadLock copy value, does not use lock. WriteLock use unique_lock. On setting new value, event called with value copy (no locks). 226 | * `nonblocking_atomic` use `std::atomic`. ReadLock copy value, does not use lock. WriteLock work with value copy, then atomically update property's value with it. On setting new value, event called with value copy (no locks). 227 | 228 | All in all, `blocking` never copy value, but lock internal mutex each time when you work with it. For small objects it is faster to copy, than lock, that's why `blocking` not used as default. 229 | 230 | Thoeretically, hardware supported std::atomic with nonblocking_atomic should be the fastest. Keep in mind, that mostly, atomics are lockless for sizeof(T) <= 8. 231 | 232 | Most of the time you will be happy with default. But, for containers, blocking mode preferable: 233 | ```C++ 234 | ObservableProperty< std::vector, blocking > 235 | ``` 236 | Because all other modes, will make temporary copy of the vector. 237 | 238 | 239 | #### Implementation details: 240 | ObservableProperty internally holds shared_ptr. This needed to track alivness(and postpone destruction) in multithreaded environment. 241 | 242 | Observable property consists from value and event. Event internally holds queue of observers, in heap allocated memory (std::vector) anyway. So shared_ptr construction overhead is not that big. 243 | 244 | ```C++ 245 | template struct ObservableProperty{ 246 | struct Data{ 247 | T value; 248 | Event event; 249 | }; 250 | std::shared_ptr data; 251 | } 252 | ``` 253 | 254 | 255 | # ReactiveProperty 256 | 257 | Same as ObservableProperty + Can listen multiple ObservableProperties/ReactiveProperties and update value reactively. 258 | 259 | ```C++ 260 | using namespace reactive; 261 | 262 | ObservableProperty x = 1; 263 | ObservableProperty y = 3; 264 | 265 | struct Vec2{ 266 | int x,y; 267 | Vec2(int x, int y) :x(x) ,y(y){} 268 | } 269 | ReactiveProperty vec2 {0, 0}; 270 | 271 | vec2.set([](int x, int y){ return Vec2{x*x, y*y}; }, x, y); 272 | 273 | vec2 += [](const Vec2& vec2){ 274 | cout << "vec2 is " << vec2.x << ", " << vec2.y << endl; 275 | }; 276 | 277 | x = 10; 278 | // Output: vec2 is 100, 9 279 | 280 | y = 2; 281 | // Output: vec2 is 100, 4 282 | 283 | vec2.update([](Vec2& vec2, int x, int y){ vec2.y = x+y; }, x, y); // unsubscribe, and modify value 284 | // Output: vec2 is 100, 12 285 | 286 | x = 12; 287 | // Output: vec2 is 100, 14 288 | 289 | vec2 = {3,4}; // unsubscribe, set value 290 | // Output: vec2 is 3, 4 291 | 292 | x = -2; // will not triger any changes in vec2 293 | ``` 294 | 295 | #### Synopsis 296 | same as ObservableProperty, except all mutators, first unsubscrbe previous listeners. 297 | 298 | `set(Closure&& closure, ObservableProperty/ReactiveProperty&...)` 299 | observe properties, and call `closure` with values of properties (const Ts&...), result of `closure` set as current ReactiveProperty value. See [observe](#observe). 300 | ```C++ 301 | template 302 | struct ReactiveProperty{ 303 | T value; 304 | void set(Closure&& closure, Properties&&... properties){ 305 | unsubscribe_previous(); 306 | 307 | observe([closure, &value]( auto& ... values){ 308 | value = closure(values...); 309 | }, properties); 310 | } 311 | } 312 | ``` 313 | 314 | 315 | `update(Closure&&, ObservableProperty/ReactiveProperty&...)` 316 | observe properties, and call `closure` with first parameter as current value reference, and other as values of properties (const Ts&...). See [observe](#observe). 317 | ```C++ 318 | template 319 | struct ReactiveProperty{ 320 | T value; 321 | void update(Closure&& closure, Properties&&... properties){ 322 | unsubscribe_previous(); 323 | 324 | observe([closure, &value]( auto& ... values){ 325 | closure(value, values...); 326 | }, properties); 327 | } 328 | } 329 | ``` 330 | 331 | `void operator=(const ObservableProperty/ReactiveProperty& property)` listen for property changes, and update self value with new one. 332 | 333 | 334 | # Observe 335 | Allow observe multiple properties. 336 | ```C++ 337 | using namespace reactive; 338 | 339 | ObservableProperty x = 1; 340 | ObservableProperty y = 2; 341 | 342 | auto unsubscribe = observe([](int x, int y){ 343 | std::cout << x << ", " << y << std::endl; 344 | }, x, y); 345 | 346 | y = 4; 347 | // Output: 1, 4 348 | 349 | x = 5; 350 | // Output: 5, 4 351 | 352 | unsubscribe(); 353 | x = 8; 354 | // trigger nothing 355 | 356 | 357 | observe_w_unsubscribe([](auto unsubscribe, int x, int y){ 358 | if (x == 100){ 359 | unsubscribe(); 360 | return; 361 | } 362 | 363 | std::cout << x << ", " << y << std::endl; 364 | }, x, y); 365 | 366 | x = 6; 367 | // Output: 6, 4 368 | 369 | x = 100; 370 | y = 200; 371 | // unsubscribed, trigger nothing 372 | ``` 373 | 374 | `observe`/`observe_w_unsubscribe` have optional `blocking_mode` template parameter: 375 | ```C++ 376 | template 377 | auto observe(Closure&&, Observables&...) 378 | ``` 379 | 380 | * If blocking_mode == blocking, closure called with observables.lock()... If someone of observables dies, `observe` auto-unsubscribes. 381 | * Otherwise, values stored in local tuple, and each time observables changes, tuple updates. Closure called with copy of that tuple. Thus, ommiting potential mutex lock on observables.lock()... If someone of observables dies, closure will be called with last known value of dead observable. Thus, it stop listen only when all observables dies. 382 | 383 | `default_blocking` will try to use non-blocking mode when possible. 384 | 385 | 386 | # Bind 387 | 388 | `bind` designed for non-intrusive binding ObservableProperties/ReactivePropeties to non-aware class. 389 | Class must be in `std::shared_ptr`. `bind` take care of observables and object lifetimes. 390 | 391 | ```C++ 392 | using namespace reactive; 393 | 394 | ObservableProperty len = 2; 395 | ObservableProperty y{ 100 }; 396 | 397 | class Box { 398 | int m_len = 0; 399 | public: 400 | 401 | auto len(int x) { 402 | m_len = x; 403 | } 404 | 405 | void show() { 406 | std::cout << m_len << std::endl; 407 | } 408 | }; 409 | 410 | std::shared_ptr box = std::make_shared(); 411 | 412 | len = 40; 413 | 414 | bind(box, [](auto box, int len) { 415 | box->len(len); 416 | box->show(); 417 | }, len); 418 | 419 | bind_w_unsubscribe(box, [](auto unsubscibe, auto box, int len) { 420 | if (len > 100) unsubscibe(); 421 | box->len(len); 422 | box->show(); 423 | }, len); 424 | 425 | len = 50; 426 | len = 101; 427 | len = 60; 428 | ``` 429 | 430 | `bind` Stores object's weak_ptr, listen for observables. If object dies, unsubscribe self. 431 | `bind_w_unsubscribe` do the same - but you can manually unsubscribe. 432 | 433 | #### Synopsis 434 | ```C++ 435 | template 436 | auto bind(const std::shared_ptr& obj, Closure&& closure, const Observables&... observables) 437 | // return unsubscriber 438 | ``` 439 | 440 | ```C++ 441 | template 442 | auto bind_w_unsubscribe(const std::shared_ptr& obj, Closure&& closure, const Observables&... observables) 443 | // return unsubscriber 444 | ``` 445 | 446 | # non_thread_safe 447 | 448 | Non thread safe version lies in reactive/non_thread_safe namespace and folder. 449 | The only difference, apart being not thread safe, is existance of `operator->()` and `operator*()`, which allow value access without `lock()`/`getCopy()` 450 | 451 | You can mix thread-safe with non-thread-safe version: 452 | 453 | ```C++ 454 | #include 455 | #include 456 | #include 457 | 458 | using namespace reactive; 459 | 460 | ObservableProperty i1{1}; 461 | 462 | 463 | struct MyWidget{ 464 | non_thread_safe::ObservableProperty i2{2}; 465 | 466 | vodi show(){ 467 | std::cout << *i2 << std::endl; // operator*() exists in non_thread_safe version 468 | } 469 | }; 470 | MyWidget widget; 471 | 472 | ReactiveProperty sum; 473 | 474 | // nonblocking stores copy of values when their event triggers, 475 | // thus it is safe to mix threaded and non-threaded properties in this mode (value must be copyable) 476 | // and only in nonblocking mode 477 | sum.set([&](int i1, int i2){ // you may ommit , it will be set by default for this case 478 | return i1 + i2; 479 | }, i1, i2); 480 | ``` 481 | 482 | See `test/BenchmarkReactivity.h` for performance comparsion. Huge (10-20 times) difference in gcc 6.3 compiled version, and almost the same speed in all versions under VS2017. 483 | 484 | 485 | ---- 486 | # Compiler support 487 | 488 | Library should compiles with any standard compatible c++14/17 compiler. 489 | Tested with clang, gcc 6.3, vs 2017 c++ 490 | 491 | ## VS2015-2017 , GCC < 7 492 | Objects in ObservableProperty, ReactiveProperty must be, also, no-throw default constructable in order to work in nonblocking_atomic mode 493 | https://developercommunity.visualstudio.com/content/problem/69560/stdatomic-load-does-not-work-with-non-default-cons.html 494 | 495 | in default_blocking mode, trivially constructable objects, but without no-throw default constructor, will work in nonblocking mode. 496 | -------------------------------------------------------------------------------- /src/reactive/details/threading/upgrade_mutex.h: -------------------------------------------------------------------------------- 1 | // From https://github.com/HowardHinnant/upgrade_mutex 2 | // Modified: .cpp moved to .h 3 | 4 | //---------------------------- upgrade_mutex.h --------------------------------- 5 | // 6 | // This software is in the public domain. The only restriction on its use is 7 | // that no one can remove it from the public domain by claiming ownership of it, 8 | // including the original authors. 9 | // 10 | // There is no warranty of correctness on the software contained herein. Use 11 | // at your own risk. 12 | // 13 | //------------------------------------------------------------------------------ 14 | 15 | #ifndef UPGRADE_MUTEX 16 | #define UPGRADE_MUTEX 17 | 18 | /* 19 | synopsis 20 | 21 | namespace acme 22 | { 23 | 24 | class upgrade_mutex 25 | { 26 | public: 27 | upgrade_mutex(); 28 | ~upgrade_mutex(); 29 | 30 | upgrade_mutex(const upgrade_mutex&) = delete; 31 | upgrade_mutex& operator=(const upgrade_mutex&) = delete; 32 | 33 | // Exclusive ownership 34 | 35 | void lock(); 36 | bool try_lock(); 37 | template 38 | bool try_lock_for(const std::chrono::duration& rel_time); 39 | template 40 | bool 41 | try_lock_until( 42 | const std::chrono::time_point& abs_time); 43 | void unlock(); 44 | 45 | // Shared ownership 46 | 47 | void lock_shared(); 48 | bool try_lock_shared(); 49 | template 50 | bool 51 | try_lock_shared_for(const std::chrono::duration& rel_time); 52 | template 53 | bool 54 | try_lock_shared_until( 55 | const std::chrono::time_point& abs_time); 56 | void unlock_shared(); 57 | 58 | // Upgrade ownership 59 | 60 | void lock_upgrade(); 61 | bool try_lock_upgrade(); 62 | template 63 | bool 64 | try_lock_upgrade_for( 65 | const std::chrono::duration& rel_time); 66 | template 67 | bool 68 | try_lock_upgrade_until( 69 | const std::chrono::time_point& abs_time); 70 | void unlock_upgrade(); 71 | 72 | // Shared <-> Exclusive -- unused by Locks without std::lib cooperation 73 | 74 | bool try_unlock_shared_and_lock(); 75 | template 76 | bool 77 | try_unlock_shared_and_lock_for( 78 | const std::chrono::duration& rel_time); 79 | template 80 | bool 81 | try_unlock_shared_and_lock_until( 82 | const std::chrono::time_point& abs_time); 83 | void unlock_and_lock_shared(); 84 | 85 | // Shared <-> Upgrade 86 | 87 | bool try_unlock_shared_and_lock_upgrade(); 88 | template 89 | bool 90 | try_unlock_shared_and_lock_upgrade_for( 91 | const std::chrono::duration& rel_time); 92 | template 93 | bool 94 | try_unlock_shared_and_lock_upgrade_until( 95 | const std::chrono::time_point& abs_time); 96 | void unlock_upgrade_and_lock_shared(); 97 | 98 | // Upgrade <-> Exclusive 99 | 100 | void unlock_upgrade_and_lock(); 101 | void unlock_and_lock_upgrade(); 102 | 103 | // unused by Locks without std::lib cooperation 104 | 105 | bool try_unlock_upgrade_and_lock(); 106 | template 107 | bool 108 | try_unlock_upgrade_and_lock_for( 109 | const std::chrono::duration& rel_time); 110 | template 111 | bool 112 | try_unlock_upgrade_and_lock_until( 113 | const std::chrono::time_point& abs_time); 114 | }; 115 | 116 | template 117 | class upgrade_lock 118 | { 119 | public: 120 | typedef Mutex mutex_type; 121 | 122 | ~upgrade_lock(); 123 | upgrade_lock() noexcept; 124 | upgrade_lock(upgrade_lock const&) = delete; 125 | upgrade_lock& operator=(upgrade_lock const&) = delete; 126 | upgrade_lock(upgrade_lock&& ul) noexcept; 127 | upgrade_lock& operator=(upgrade_lock&& ul); 128 | 129 | explicit upgrade_lock(mutex_type& m); 130 | upgrade_lock(mutex_type& m, std::defer_lock_t) noexcept; 131 | upgrade_lock(mutex_type& m, std::try_to_lock_t); 132 | upgrade_lock(mutex_type& m, std::adopt_lock_t) noexcept; 133 | template 134 | upgrade_lock(mutex_type& m, 135 | const std::chrono::time_point& abs_time); 136 | template 137 | upgrade_lock(mutex_type& m, 138 | const std::chrono::duration& rel_time); 139 | 140 | // Shared <-> Upgrade 141 | 142 | upgrade_lock(std::shared_lock&& sl, std::try_to_lock_t); 143 | 144 | template 145 | upgrade_lock(std::shared_lock&& sl, 146 | const std::chrono::time_point& abs_time); 147 | template 148 | upgrade_lock(std::shared_lock&& sl, 149 | const std::chrono::duration& rel_time); 150 | explicit operator std::shared_lock () &&; 151 | 152 | // Exclusive <-> Upgrade 153 | 154 | explicit upgrade_lock(std::unique_lock&& ul); 155 | explicit operator std::unique_lock () &&; 156 | 157 | // Upgrade 158 | 159 | void lock(); 160 | bool try_lock(); 161 | template 162 | bool try_lock_for(const std::chrono::duration& rel_time); 163 | template 164 | bool 165 | try_lock_until( 166 | const std::chrono::time_point& abs_time); 167 | void unlock(); 168 | 169 | void swap(upgrade_lock&& u); 170 | mutex_type* release(); 171 | 172 | bool owns_lock() const; 173 | explicit operator bool () const; 174 | mutex_type* mutex() const; 175 | }; 176 | 177 | template 178 | void 179 | swap(upgrade_lock& x, upgrade_lock& y); 180 | 181 | } // acme 182 | */ 183 | 184 | #include 185 | #include 186 | #include 187 | #include 188 | #include 189 | 190 | namespace acme 191 | { 192 | 193 | // upgrade_mutex 194 | 195 | class upgrade_mutex 196 | { 197 | std::mutex mut_; 198 | std::condition_variable gate1_; 199 | std::condition_variable gate2_; 200 | unsigned state_; 201 | 202 | static const unsigned write_entered_ = 1U << (sizeof(unsigned)*CHAR_BIT - 1); 203 | static const unsigned upgradable_entered_ = write_entered_ >> 1; 204 | static const unsigned n_readers_ = ~(write_entered_ | upgradable_entered_); 205 | 206 | public: 207 | upgrade_mutex() 208 | : state_(0) 209 | {} 210 | 211 | ~upgrade_mutex() = default; 212 | 213 | upgrade_mutex(const upgrade_mutex&) = delete; 214 | upgrade_mutex& operator=(const upgrade_mutex&) = delete; 215 | 216 | // Exclusive ownership 217 | 218 | void lock() 219 | { 220 | std::unique_lock lk(mut_); 221 | while (state_ & (write_entered_ | upgradable_entered_)) 222 | gate1_.wait(lk); 223 | state_ |= write_entered_; 224 | while (state_ & n_readers_) 225 | gate2_.wait(lk); 226 | } 227 | 228 | bool try_lock() 229 | { 230 | std::unique_lock lk(mut_); 231 | if (state_ == 0) 232 | { 233 | state_ = write_entered_; 234 | return true; 235 | } 236 | return false; 237 | } 238 | template 239 | bool try_lock_for(const std::chrono::duration& rel_time) 240 | { 241 | return try_lock_until(std::chrono::steady_clock::now() + rel_time); 242 | } 243 | template 244 | bool 245 | try_lock_until( 246 | const std::chrono::time_point& abs_time); 247 | void unlock() 248 | { 249 | std::lock_guard _(mut_); 250 | state_ = 0; 251 | gate1_.notify_all(); 252 | } 253 | 254 | // Shared ownership 255 | 256 | void lock_shared() 257 | { 258 | std::unique_lock lk(mut_); 259 | while ((state_ & write_entered_) || (state_ & n_readers_) == n_readers_) 260 | gate1_.wait(lk); 261 | unsigned num_readers = (state_ & n_readers_) + 1; 262 | state_ &= ~n_readers_; 263 | state_ |= num_readers; 264 | } 265 | 266 | bool try_lock_shared() 267 | { 268 | std::unique_lock lk(mut_); 269 | unsigned num_readers = state_ & n_readers_; 270 | if (!(state_ & write_entered_) && num_readers != n_readers_) 271 | { 272 | ++num_readers; 273 | state_ &= ~n_readers_; 274 | state_ |= num_readers; 275 | return true; 276 | } 277 | return false; 278 | } 279 | 280 | template 281 | bool 282 | try_lock_shared_for(const std::chrono::duration& rel_time) 283 | { 284 | return try_lock_shared_until(std::chrono::steady_clock::now() + 285 | rel_time); 286 | } 287 | template 288 | bool 289 | try_lock_shared_until( 290 | const std::chrono::time_point& abs_time); 291 | void unlock_shared() 292 | { 293 | std::lock_guard _(mut_); 294 | unsigned num_readers = (state_ & n_readers_) - 1; 295 | state_ &= ~n_readers_; 296 | state_ |= num_readers; 297 | if (state_ & write_entered_) 298 | { 299 | if (num_readers == 0) 300 | gate2_.notify_one(); 301 | } 302 | else 303 | { 304 | if (num_readers == n_readers_ - 1) 305 | gate1_.notify_one(); 306 | } 307 | } 308 | 309 | // Upgrade ownership 310 | 311 | void lock_upgrade() 312 | { 313 | std::unique_lock lk(mut_); 314 | while ((state_ & (write_entered_ | upgradable_entered_)) || 315 | (state_ & n_readers_) == n_readers_) 316 | gate1_.wait(lk); 317 | unsigned num_readers = (state_ & n_readers_) + 1; 318 | state_ &= ~n_readers_; 319 | state_ |= upgradable_entered_ | num_readers; 320 | } 321 | 322 | bool try_lock_upgrade() 323 | { 324 | std::unique_lock lk(mut_); 325 | unsigned num_readers = state_ & n_readers_; 326 | if (!(state_ & (write_entered_ | upgradable_entered_)) 327 | && num_readers != n_readers_) 328 | { 329 | ++num_readers; 330 | state_ &= ~n_readers_; 331 | state_ |= upgradable_entered_ | num_readers; 332 | return true; 333 | } 334 | return false; 335 | } 336 | 337 | template 338 | bool 339 | try_lock_upgrade_for( 340 | const std::chrono::duration& rel_time) 341 | { 342 | return try_lock_upgrade_until(std::chrono::steady_clock::now() + 343 | rel_time); 344 | } 345 | template 346 | bool 347 | try_lock_upgrade_until( 348 | const std::chrono::time_point& abs_time); 349 | void unlock_upgrade() 350 | { 351 | { 352 | std::lock_guard _(mut_); 353 | unsigned num_readers = (state_ & n_readers_) - 1; 354 | state_ &= ~(upgradable_entered_ | n_readers_); 355 | state_ |= num_readers; 356 | } 357 | gate1_.notify_all(); 358 | } 359 | 360 | 361 | // Shared <-> Exclusive 362 | 363 | bool try_unlock_shared_and_lock() 364 | { 365 | std::unique_lock lk(mut_); 366 | if (state_ == 1) 367 | { 368 | state_ = write_entered_; 369 | return true; 370 | } 371 | return false; 372 | } 373 | 374 | template 375 | bool 376 | try_unlock_shared_and_lock_for( 377 | const std::chrono::duration& rel_time) 378 | { 379 | return try_unlock_shared_and_lock_until( 380 | std::chrono::steady_clock::now() + rel_time); 381 | } 382 | template 383 | bool 384 | try_unlock_shared_and_lock_until( 385 | const std::chrono::time_point& abs_time); 386 | void unlock_and_lock_shared() 387 | { 388 | { 389 | std::lock_guard _(mut_); 390 | state_ = 1; 391 | } 392 | gate1_.notify_all(); 393 | } 394 | 395 | 396 | // Shared <-> Upgrade 397 | 398 | bool try_unlock_shared_and_lock_upgrade() 399 | { 400 | std::unique_lock lk(mut_); 401 | if (!(state_ & (write_entered_ | upgradable_entered_))) 402 | { 403 | state_ |= upgradable_entered_; 404 | return true; 405 | } 406 | return false; 407 | } 408 | template 409 | bool 410 | try_unlock_shared_and_lock_upgrade_for( 411 | const std::chrono::duration& rel_time) 412 | { 413 | return try_unlock_shared_and_lock_upgrade_until( 414 | std::chrono::steady_clock::now() + rel_time); 415 | } 416 | template 417 | bool 418 | try_unlock_shared_and_lock_upgrade_until( 419 | const std::chrono::time_point& abs_time); 420 | void unlock_upgrade_and_lock_shared() 421 | { 422 | { 423 | std::lock_guard _(mut_); 424 | state_ &= ~upgradable_entered_; 425 | } 426 | gate1_.notify_all(); 427 | } 428 | 429 | // Upgrade <-> Exclusive 430 | 431 | void unlock_upgrade_and_lock() 432 | { 433 | std::unique_lock lk(mut_); 434 | unsigned num_readers = (state_ & n_readers_) - 1; 435 | state_ &= ~(upgradable_entered_ | n_readers_); 436 | state_ |= write_entered_ | num_readers; 437 | while (state_ & n_readers_) 438 | gate2_.wait(lk); 439 | } 440 | bool try_unlock_upgrade_and_lock() 441 | { 442 | std::unique_lock lk(mut_); 443 | if (state_ == (upgradable_entered_ | 1)) 444 | { 445 | state_ = write_entered_; 446 | return true; 447 | } 448 | return false; 449 | } 450 | template 451 | bool 452 | try_unlock_upgrade_and_lock_for( 453 | const std::chrono::duration& rel_time) 454 | { 455 | return try_unlock_upgrade_and_lock_until( 456 | std::chrono::steady_clock::now() + rel_time); 457 | } 458 | template 459 | bool 460 | try_unlock_upgrade_and_lock_until( 461 | const std::chrono::time_point& abs_time); 462 | void unlock_and_lock_upgrade() 463 | { 464 | { 465 | std::lock_guard _(mut_); 466 | state_ = upgradable_entered_ | 1; 467 | } 468 | gate1_.notify_all(); 469 | } 470 | }; 471 | 472 | template 473 | bool 474 | upgrade_mutex::try_lock_until( 475 | const std::chrono::time_point& abs_time) 476 | { 477 | std::unique_lock lk(mut_); 478 | if (state_ & (write_entered_ | upgradable_entered_)) 479 | { 480 | while (true) 481 | { 482 | std::cv_status status = gate1_.wait_until(lk, abs_time); 483 | if ((state_ & (write_entered_ | upgradable_entered_)) == 0) 484 | break; 485 | if (status == std::cv_status::timeout) 486 | return false; 487 | } 488 | } 489 | state_ |= write_entered_; 490 | if (state_ & n_readers_) 491 | { 492 | while (true) 493 | { 494 | std::cv_status status = gate2_.wait_until(lk, abs_time); 495 | if ((state_ & n_readers_) == 0) 496 | break; 497 | if (status == std::cv_status::timeout) 498 | { 499 | state_ &= ~write_entered_; 500 | return false; 501 | } 502 | } 503 | } 504 | return true; 505 | } 506 | 507 | template 508 | bool 509 | upgrade_mutex::try_lock_shared_until( 510 | const std::chrono::time_point& abs_time) 511 | { 512 | std::unique_lock lk(mut_); 513 | if ((state_ & write_entered_) || (state_ & n_readers_) == n_readers_) 514 | { 515 | while (true) 516 | { 517 | std::cv_status status = gate1_.wait_until(lk, abs_time); 518 | if ((state_ & write_entered_) == 0 && 519 | (state_ & n_readers_) < n_readers_) 520 | break; 521 | if (status == std::cv_status::timeout) 522 | return false; 523 | } 524 | } 525 | unsigned num_readers = (state_ & n_readers_) + 1; 526 | state_ &= ~n_readers_; 527 | state_ |= num_readers; 528 | return true; 529 | } 530 | 531 | template 532 | bool 533 | upgrade_mutex::try_lock_upgrade_until( 534 | const std::chrono::time_point& abs_time) 535 | { 536 | std::unique_lock lk(mut_); 537 | if ((state_ & (write_entered_ | upgradable_entered_)) || 538 | (state_ & n_readers_) == n_readers_) 539 | { 540 | while (true) 541 | { 542 | std::cv_status status = gate1_.wait_until(lk, abs_time); 543 | if ((state_ & (write_entered_ | upgradable_entered_)) == 0 && 544 | (state_ & n_readers_) < n_readers_) 545 | break; 546 | if (status == std::cv_status::timeout) 547 | return false; 548 | } 549 | } 550 | unsigned num_readers = (state_ & n_readers_) + 1; 551 | state_ &= ~n_readers_; 552 | state_ |= upgradable_entered_ | num_readers; 553 | return true; 554 | } 555 | 556 | template 557 | bool 558 | upgrade_mutex::try_unlock_shared_and_lock_until( 559 | const std::chrono::time_point& abs_time) 560 | { 561 | std::unique_lock lk(mut_); 562 | if (state_ != 1) 563 | { 564 | while (true) 565 | { 566 | std::cv_status status = gate2_.wait_until(lk, abs_time); 567 | if (state_ == 1) 568 | break; 569 | if (status == std::cv_status::timeout) 570 | return false; 571 | } 572 | } 573 | state_ = write_entered_; 574 | return true; 575 | } 576 | 577 | template 578 | bool 579 | upgrade_mutex::try_unlock_shared_and_lock_upgrade_until( 580 | const std::chrono::time_point& abs_time) 581 | { 582 | std::unique_lock lk(mut_); 583 | if ((state_ & (write_entered_ | upgradable_entered_)) != 0) 584 | { 585 | while (true) 586 | { 587 | std::cv_status status = gate2_.wait_until(lk, abs_time); 588 | if ((state_ & (write_entered_ | upgradable_entered_)) == 0) 589 | break; 590 | if (status == std::cv_status::timeout) 591 | return false; 592 | } 593 | } 594 | state_ |= upgradable_entered_; 595 | return true; 596 | } 597 | 598 | template 599 | bool 600 | upgrade_mutex::try_unlock_upgrade_and_lock_until( 601 | const std::chrono::time_point& abs_time) 602 | { 603 | std::unique_lock lk(mut_); 604 | if ((state_ & n_readers_) != 1) 605 | { 606 | while (true) 607 | { 608 | std::cv_status status = gate2_.wait_until(lk, abs_time); 609 | if ((state_ & n_readers_) == 1) 610 | break; 611 | if (status == std::cv_status::timeout) 612 | return false; 613 | } 614 | } 615 | state_ = write_entered_; 616 | return true; 617 | } 618 | 619 | // upgrade_lock 620 | 621 | template 622 | class upgrade_lock 623 | { 624 | public: 625 | typedef Mutex mutex_type; 626 | 627 | private: 628 | mutex_type* m_; 629 | bool owns_; 630 | 631 | struct __nat {int _;}; 632 | 633 | public: 634 | ~upgrade_lock() 635 | { 636 | if (owns_) 637 | m_->unlock_upgrade(); 638 | } 639 | 640 | upgrade_lock() noexcept 641 | : m_(nullptr), owns_(false) {} 642 | 643 | upgrade_lock(upgrade_lock const&) = delete; 644 | upgrade_lock& operator=(upgrade_lock const&) = delete; 645 | 646 | upgrade_lock(upgrade_lock&& ul) noexcept 647 | : m_(ul.m_), owns_(ul.owns_) 648 | { 649 | ul.m_ = nullptr; 650 | ul.owns_ = false; 651 | } 652 | 653 | upgrade_lock& operator=(upgrade_lock&& ul) 654 | { 655 | if (owns_) 656 | m_->unlock_upgrade(); 657 | m_ = ul.m_; 658 | owns_ = ul.owns_; 659 | ul.m_ = nullptr; 660 | ul.owns_ = false; 661 | return *this; 662 | } 663 | 664 | explicit upgrade_lock(mutex_type& m) 665 | : m_(&m), owns_(true) 666 | {m_->lock_upgrade();} 667 | 668 | upgrade_lock(mutex_type& m, std::defer_lock_t) noexcept 669 | : m_(&m), owns_(false) {} 670 | 671 | upgrade_lock(mutex_type& m, std::try_to_lock_t) 672 | : m_(&m), owns_(m.try_lock_upgrade()) {} 673 | 674 | upgrade_lock(mutex_type& m, std::adopt_lock_t) noexcept 675 | : m_(&m), owns_(true) {} 676 | 677 | template 678 | upgrade_lock(mutex_type& m, 679 | const std::chrono::time_point& abs_time) 680 | : m_(&m), owns_(m.try_lock_upgrade_until(abs_time)) {} 681 | template 682 | upgrade_lock(mutex_type& m, 683 | const std::chrono::duration& rel_time) 684 | : m_(&m), owns_(m.try_lock_upgrade_for(rel_time)) {} 685 | 686 | // Shared <-> Upgrade 687 | 688 | upgrade_lock(std::shared_lock&& sl, std::try_to_lock_t) 689 | : m_(nullptr), owns_(false) 690 | { 691 | if (sl.owns_lock()) 692 | { 693 | if (sl.mutex()->try_unlock_shared_and_lock_upgrade()) 694 | { 695 | m_ = sl.release(); 696 | owns_ = true; 697 | } 698 | } 699 | else 700 | m_ = sl.release(); 701 | } 702 | 703 | template 704 | upgrade_lock(std::shared_lock&& sl, 705 | const std::chrono::time_point& abs_time) 706 | : m_(nullptr), owns_(false) 707 | { 708 | if (sl.owns_lock()) 709 | { 710 | if (sl.mutex()->try_unlock_shared_and_lock_upgrade_until(abs_time)) 711 | { 712 | m_ = sl.release(); 713 | owns_ = true; 714 | } 715 | } 716 | else 717 | m_ = sl.release(); 718 | } 719 | 720 | template 721 | upgrade_lock(std::shared_lock&& sl, 722 | const std::chrono::duration& rel_time) 723 | : m_(nullptr), owns_(false) 724 | { 725 | if (sl.owns_lock()) 726 | { 727 | if (sl.mutex()->try_unlock_shared_and_lock_upgrade_for(rel_time)) 728 | { 729 | m_ = sl.release(); 730 | owns_ = true; 731 | } 732 | } 733 | else 734 | m_ = sl.release(); 735 | } 736 | 737 | explicit operator std::shared_lock () && 738 | { 739 | if (owns_) 740 | { 741 | m_->unlock_upgrade_and_lock_shared(); 742 | return std::shared_lock(*release(), std::adopt_lock); 743 | } 744 | if (m_ != nullptr) 745 | return std::shared_lock(*release(), std::defer_lock); 746 | return std::shared_lock{}; 747 | } 748 | 749 | // Exclusive <-> Upgrade 750 | 751 | explicit upgrade_lock(std::unique_lock&& ul) 752 | : m_(ul.mutex()), owns_(ul.owns_lock()) 753 | { 754 | if (owns_) 755 | m_->unlock_and_lock_upgrade(); 756 | ul.release(); 757 | } 758 | 759 | explicit operator std::unique_lock () && 760 | { 761 | if (owns_) 762 | { 763 | m_->unlock_upgrade_and_lock(); 764 | return std::unique_lock(*release(), std::adopt_lock); 765 | } 766 | if (m_ != nullptr) 767 | return std::unique_lock(*release(), std::defer_lock); 768 | return std::unique_lock{}; 769 | } 770 | 771 | // Upgrade 772 | 773 | void lock(); 774 | bool try_lock(); 775 | template 776 | bool try_lock_for(const std::chrono::duration& rel_time) 777 | { 778 | return try_lock_until(std::chrono::steady_clock::now() + rel_time); 779 | } 780 | template 781 | bool 782 | try_lock_until( 783 | const std::chrono::time_point& abs_time); 784 | void unlock(); 785 | 786 | void swap(upgrade_lock&& u) 787 | { 788 | std::swap(m_, u.m_); 789 | std::swap(owns_, u.owns_); 790 | } 791 | 792 | mutex_type* release() 793 | { 794 | mutex_type* r = m_; 795 | m_ = 0; 796 | owns_ = false; 797 | return r; 798 | } 799 | 800 | bool owns_lock() const {return owns_;} 801 | explicit operator bool () const {return owns_;} 802 | mutex_type* mutex() const {return m_;} 803 | }; 804 | 805 | template 806 | void 807 | upgrade_lock::lock() 808 | { 809 | if (m_ == nullptr) 810 | throw std::system_error(std::error_code(EPERM, std::system_category()), 811 | "upgrade_lock::lock: references null mutex"); 812 | if (owns_) 813 | throw std::system_error(std::error_code(EDEADLK, std::system_category()), 814 | "upgrade_lock::lock: already locked"); 815 | m_->lock_upgrade(); 816 | owns_ = true; 817 | } 818 | 819 | template 820 | bool 821 | upgrade_lock::try_lock() 822 | { 823 | if (m_ == nullptr) 824 | throw std::system_error(std::error_code(EPERM, std::system_category()), 825 | "upgrade_lock::try_lock: references null mutex"); 826 | if (owns_) 827 | throw std::system_error(std::error_code(EDEADLK, std::system_category()), 828 | "upgrade_lock::try_lock: already locked"); 829 | owns_ = m_->try_lock_upgrade(); 830 | return owns_; 831 | } 832 | 833 | template 834 | template 835 | bool 836 | upgrade_lock::try_lock_until( 837 | const std::chrono::time_point& abs_time) 838 | { 839 | if (m_ == nullptr) 840 | throw std::system_error(std::error_code(EPERM, std::system_category()), 841 | "upgrade_lock::try_lock_until: references null mutex"); 842 | if (owns_) 843 | throw std::system_error(std::error_code(EDEADLK, std::system_category()), 844 | "upgrade_lock::try_lock_until: already locked"); 845 | owns_ = m_->try_lock_upgrade_until(abs_time); 846 | return owns_; 847 | } 848 | 849 | template 850 | void 851 | upgrade_lock::unlock() 852 | { 853 | if (!owns_) 854 | throw std::system_error(std::error_code(EPERM, std::system_category()), 855 | "upgrade_lock::unlock: not locked"); 856 | m_->unlock_upgrade(); 857 | owns_ = false; 858 | } 859 | 860 | template 861 | inline 862 | void 863 | swap(upgrade_lock& x, upgrade_lock& y) 864 | { 865 | x.swap(y); 866 | } 867 | 868 | } // acme 869 | 870 | #endif // UPGRADE_MUTEX 871 | -------------------------------------------------------------------------------- /src/reactive/details/utils/optional.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016 Martin Moene 3 | // 4 | // https://github.com/martinmoene/optional-lite 5 | // 6 | // This code is licensed under the MIT License (MIT). 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 9 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 10 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 11 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 12 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 13 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 14 | // THE SOFTWARE. 15 | 16 | #pragma once 17 | 18 | #ifndef NONSTD_OPTIONAL_LITE_HPP 19 | #define NONSTD_OPTIONAL_LITE_HPP 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #define optional_lite_VERSION "2.0.0" 26 | 27 | // variant-lite alignment configuration: 28 | 29 | #ifndef optional_CONFIG_MAX_ALIGN_HACK 30 | # define optional_CONFIG_MAX_ALIGN_HACK 0 31 | #endif 32 | 33 | #ifndef optional_CONFIG_ALIGN_AS 34 | // no default, used in #if defined() 35 | #endif 36 | 37 | #ifndef optional_CONFIG_ALIGN_AS_FALLBACK 38 | # define optional_CONFIG_ALIGN_AS_FALLBACK double 39 | #endif 40 | 41 | // Compiler detection (C++17 is speculative): 42 | 43 | #define optional_CPP11_OR_GREATER ( __cplusplus >= 201103L ) 44 | #define optional_CPP14_OR_GREATER ( __cplusplus >= 201402L ) 45 | #define optional_CPP17_OR_GREATER ( __cplusplus >= 201700L ) 46 | 47 | // half-open range [lo..hi): 48 | #define optional_BETWEEN( v, lo, hi ) ( lo <= v && v < hi ) 49 | 50 | #if defined(_MSC_VER) && !defined(__clang__) 51 | # define optional_COMPILER_MSVC_VERSION (_MSC_VER / 100 - 5 - (_MSC_VER < 1900)) 52 | #else 53 | # define optional_COMPILER_MSVC_VERSION 0 54 | #endif 55 | 56 | #if defined __GNUC__ 57 | # define optional_COMPILER_GNUC_VERSION __GNUC__ 58 | #else 59 | # define optional_COMPILER_GNUC_VERSION 0 60 | #endif 61 | 62 | #if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 7, 14 ) 63 | # pragma warning( push ) 64 | # pragma warning( disable: 4345 ) // initialization behavior changed 65 | #endif 66 | 67 | #if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 7, 15 ) 68 | # pragma warning( push ) 69 | # pragma warning( disable: 4814 ) // in C++14 'constexpr' will not imply 'const' 70 | #endif 71 | 72 | // Presence of C++11 language features: 73 | 74 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 10 75 | # define optional_HAVE_AUTO 1 76 | # define optional_HAVE_NULLPTR 1 77 | # define optional_HAVE_STATIC_ASSERT 1 78 | #endif 79 | 80 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 12 81 | # define optional_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG 1 82 | # define optional_HAVE_INITIALIZER_LIST 1 83 | #endif 84 | 85 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 14 86 | # define optional_HAVE_ALIAS_TEMPLATE 1 87 | # define optional_HAVE_CONSTEXPR_11 1 88 | # define optional_HAVE_ENUM_CLASS 1 89 | # define optional_HAVE_EXPLICIT_CONVERSION 1 90 | # define optional_HAVE_IS_DEFAULT 1 91 | # define optional_HAVE_IS_DELETE 1 92 | # define optional_HAVE_NOEXCEPT 1 93 | # define optional_HAVE_REF_QUALIFIER 1 94 | #endif 95 | 96 | // Presence of C++14 language features: 97 | 98 | #if optional_CPP14_OR_GREATER 99 | # define optional_HAVE_CONSTEXPR_14 1 100 | #endif 101 | 102 | // Presence of C++17 language features: 103 | 104 | #if optional_CPP17_OR_GREATER 105 | # define optional_HAVE_ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE 1 106 | #endif 107 | 108 | // Presence of C++ library features: 109 | 110 | #if optional_COMPILER_GNUC_VERSION 111 | # define optional_HAVE_TR1_TYPE_TRAITS 1 112 | # define optional_HAVE_TR1_ADD_POINTER 1 113 | #endif 114 | 115 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 9 116 | # define optional_HAVE_TYPE_TRAITS 1 117 | # define optional_HAVE_STD_ADD_POINTER 1 118 | #endif 119 | 120 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 11 121 | # define optional_HAVE_ARRAY 1 122 | #endif 123 | 124 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 12 125 | # define optional_HAVE_CONDITIONAL 1 126 | #endif 127 | 128 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 14 || (optional_COMPILER_MSVC_VERSION >= 9 && _HAS_CPP0X) 129 | # define optional_HAVE_CONTAINER_DATA_METHOD 1 130 | #endif 131 | 132 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 12 133 | # define optional_HAVE_REMOVE_CV 1 134 | #endif 135 | 136 | #if optional_CPP11_OR_GREATER || optional_COMPILER_MSVC_VERSION >= 14 137 | # define optional_HAVE_SIZED_TYPES 1 138 | #endif 139 | 140 | // For the rest, consider VC14 as C++11 for variant-lite: 141 | 142 | #if optional_COMPILER_MSVC_VERSION >= 14 143 | # undef optional_CPP11_OR_GREATER 144 | # define optional_CPP11_OR_GREATER 1 145 | #endif 146 | 147 | // C++ feature usage: 148 | 149 | #if optional_HAVE_CONSTEXPR_11 150 | # define optional_constexpr constexpr 151 | #else 152 | # define optional_constexpr /*constexpr*/ 153 | #endif 154 | 155 | #if optional_HAVE_CONSTEXPR_14 156 | # define optional_constexpr14 constexpr 157 | #else 158 | # define optional_constexpr14 /*constexpr*/ 159 | #endif 160 | 161 | #if optional_HAVE_NOEXCEPT 162 | # define optional_noexcept noexcept 163 | #else 164 | # define optional_noexcept /*noexcept*/ 165 | #endif 166 | 167 | #if optional_HAVE_NULLPTR 168 | # define optional_nullptr nullptr 169 | #else 170 | # define optional_nullptr NULL 171 | #endif 172 | 173 | #if optional_HAVE_REF_QUALIFIER 174 | # define optional_ref_qual & 175 | # define optional_refref_qual && 176 | #else 177 | # define optional_ref_qual /*&*/ 178 | # define optional_refref_qual /*&&*/ 179 | #endif 180 | 181 | // additional includes: 182 | 183 | #if optional_HAVE_INITIALIZER_LIST 184 | # include 185 | #endif 186 | 187 | #if optional_HAVE_TYPE_TRAITS 188 | # include 189 | #elif optional_HAVE_TR1_TYPE_TRAITS 190 | # include 191 | #endif 192 | 193 | // 194 | // in_place: code duplicated in any-lite, optional-lite, variant-lite: 195 | // 196 | 197 | #if ! nonstd_lite_HAVE_IN_PLACE_TYPES 198 | 199 | namespace nonstd { 200 | 201 | namespace detail { 202 | 203 | template< class T > 204 | struct in_place_type_tag {}; 205 | 206 | template< std::size_t I > 207 | struct in_place_index_tag {}; 208 | 209 | } // namespace detail 210 | 211 | struct in_place_t {}; 212 | 213 | template< class T > 214 | inline in_place_t in_place( detail::in_place_type_tag = detail::in_place_type_tag() ) 215 | { 216 | return in_place_t(); 217 | } 218 | 219 | template< std::size_t I > 220 | inline in_place_t in_place( detail::in_place_index_tag = detail::in_place_index_tag() ) 221 | { 222 | return in_place_t(); 223 | } 224 | 225 | // mimic templated typedef: 226 | 227 | #define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) 228 | #define nonstd_lite_in_place_index_t(T) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) 229 | 230 | #define nonstd_lite_HAVE_IN_PLACE_TYPES 1 231 | 232 | } // namespace nonstd 233 | 234 | #endif // nonstd_lite_HAVE_IN_PLACE_TYPES 235 | 236 | // 237 | // optional: 238 | // 239 | 240 | namespace nonstd { namespace optional_lite { 241 | 242 | /// class optional 243 | 244 | template< typename T > 245 | class optional; 246 | 247 | namespace detail { 248 | 249 | // C++11 emulation: 250 | 251 | #if variant_HAVE_CONDITIONAL 252 | 253 | using std::conditional; 254 | 255 | #else 256 | 257 | template< bool Cond, class Then, class Else > 258 | struct conditional; 259 | 260 | template< class Then, class Else > 261 | struct conditional< true , Then, Else > { typedef Then type; }; 262 | 263 | template< class Then, class Else > 264 | struct conditional< false, Then, Else > { typedef Else type; }; 265 | 266 | #endif // variant_HAVE_CONDITIONAL 267 | 268 | struct nulltype{}; 269 | 270 | template< typename Head, typename Tail > 271 | struct typelist 272 | { 273 | typedef Head head; 274 | typedef Tail tail; 275 | }; 276 | 277 | #if optional_CONFIG_MAX_ALIGN_HACK 278 | 279 | // Max align, use most restricted type for alignment: 280 | 281 | #define optional_UNIQUE( name ) optional_UNIQUE2( name, __LINE__ ) 282 | #define optional_UNIQUE2( name, line ) optional_UNIQUE3( name, line ) 283 | #define optional_UNIQUE3( name, line ) name ## line 284 | 285 | #define optional_ALIGN_TYPE( type ) \ 286 | type optional_UNIQUE( _t ); struct_t< type > optional_UNIQUE( _st ) 287 | 288 | template< typename T > 289 | struct struct_t { T _; }; 290 | 291 | union max_align_t 292 | { 293 | optional_ALIGN_TYPE( char ); 294 | optional_ALIGN_TYPE( short int ); 295 | optional_ALIGN_TYPE( int ); 296 | optional_ALIGN_TYPE( long int ); 297 | optional_ALIGN_TYPE( float ); 298 | optional_ALIGN_TYPE( double ); 299 | optional_ALIGN_TYPE( long double ); 300 | optional_ALIGN_TYPE( char * ); 301 | optional_ALIGN_TYPE( short int * ); 302 | optional_ALIGN_TYPE( int * ); 303 | optional_ALIGN_TYPE( long int * ); 304 | optional_ALIGN_TYPE( float * ); 305 | optional_ALIGN_TYPE( double * ); 306 | optional_ALIGN_TYPE( long double * ); 307 | optional_ALIGN_TYPE( void * ); 308 | 309 | #ifdef HAVE_LONG_LONG 310 | optional_ALIGN_TYPE( long long ); 311 | #endif 312 | 313 | struct Unknown; 314 | 315 | Unknown ( * optional_UNIQUE(_) )( Unknown ); 316 | Unknown * Unknown::* optional_UNIQUE(_); 317 | Unknown ( Unknown::* optional_UNIQUE(_) )( Unknown ); 318 | 319 | struct_t< Unknown ( * )( Unknown) > optional_UNIQUE(_); 320 | struct_t< Unknown * Unknown::* > optional_UNIQUE(_); 321 | struct_t< Unknown ( Unknown::* )(Unknown) > optional_UNIQUE(_); 322 | }; 323 | 324 | #undef optional_UNIQUE 325 | #undef optional_UNIQUE2 326 | #undef optional_UNIQUE3 327 | 328 | #undef optional_ALIGN_TYPE 329 | 330 | #elif defined( optional_CONFIG_ALIGN_AS ) // optional_CONFIG_MAX_ALIGN_HACK 331 | 332 | // Use user-specified type for alignment: 333 | 334 | #define optional_ALIGN_AS( unused ) \ 335 | optional_CONFIG_ALIGN_AS 336 | 337 | #else // optional_CONFIG_MAX_ALIGN_HACK 338 | 339 | // Determine POD type to use for alignment: 340 | 341 | #define optional_ALIGN_AS( to_align ) \ 342 | typename type_of_size< alignment_types, alignment_of< to_align >::value >::type 343 | 344 | template 345 | struct alignment_of; 346 | 347 | template 348 | struct alignment_of_hack 349 | { 350 | char c; 351 | T t; 352 | alignment_of_hack(); 353 | }; 354 | 355 | template 356 | struct alignment_logic 357 | { 358 | enum { value = A < S ? A : S }; 359 | }; 360 | 361 | template< typename T > 362 | struct alignment_of 363 | { 364 | enum { value = alignment_logic< 365 | sizeof( alignment_of_hack ) - sizeof(T), sizeof(T) >::value, }; 366 | }; 367 | 368 | template< typename List, size_t N > 369 | struct type_of_size 370 | { 371 | typedef typename conditional< 372 | N == sizeof( typename List::head ), 373 | typename List::head, 374 | typename type_of_size::type >::type type; 375 | }; 376 | 377 | template< size_t N > 378 | struct type_of_size< nulltype, N > 379 | { 380 | typedef optional_CONFIG_ALIGN_AS_FALLBACK type; 381 | }; 382 | 383 | template< typename T> 384 | struct struct_t { T _; }; 385 | 386 | #define optional_ALIGN_TYPE( type ) \ 387 | typelist< type , typelist< struct_t< type > 388 | 389 | struct Unknown; 390 | 391 | typedef 392 | optional_ALIGN_TYPE( char ), 393 | optional_ALIGN_TYPE( short ), 394 | optional_ALIGN_TYPE( int ), 395 | optional_ALIGN_TYPE( long ), 396 | optional_ALIGN_TYPE( float ), 397 | optional_ALIGN_TYPE( double ), 398 | optional_ALIGN_TYPE( long double ), 399 | 400 | optional_ALIGN_TYPE( char *), 401 | optional_ALIGN_TYPE( short * ), 402 | optional_ALIGN_TYPE( int * ), 403 | optional_ALIGN_TYPE( long * ), 404 | optional_ALIGN_TYPE( float * ), 405 | optional_ALIGN_TYPE( double * ), 406 | optional_ALIGN_TYPE( long double * ), 407 | 408 | optional_ALIGN_TYPE( Unknown ( * )( Unknown ) ), 409 | optional_ALIGN_TYPE( Unknown * Unknown::* ), 410 | optional_ALIGN_TYPE( Unknown ( Unknown::* )( Unknown ) ), 411 | 412 | nulltype 413 | > > > > > > > > > > > > > > 414 | > > > > > > > > > > > > > > 415 | > > > > > > 416 | alignment_types; 417 | 418 | #undef optional_ALIGN_TYPE 419 | 420 | #endif // optional_CONFIG_MAX_ALIGN_HACK 421 | 422 | /// C++03 constructed union to hold value. 423 | 424 | template< typename T > 425 | union storage_t 426 | { 427 | private: 428 | friend class optional; 429 | 430 | typedef T value_type; 431 | 432 | storage_t() {} 433 | 434 | storage_t( value_type const & v ) 435 | { 436 | construct_value( v ); 437 | } 438 | 439 | void construct_value( value_type const & v ) 440 | { 441 | ::new( value_ptr() ) value_type( v ); 442 | } 443 | 444 | #if optional_CPP11_OR_GREATER 445 | 446 | storage_t( value_type && v ) 447 | { 448 | construct_value( std::move( v ) ); 449 | } 450 | 451 | void construct_value( value_type && v ) 452 | { 453 | ::new( value_ptr() ) value_type( std::move( v ) ); 454 | } 455 | 456 | #endif 457 | 458 | void destruct_value() 459 | { 460 | value_ptr()->~T(); 461 | } 462 | 463 | value_type const * value_ptr() const 464 | { 465 | return as(); 466 | } 467 | 468 | value_type * value_ptr() 469 | { 470 | return as(); 471 | } 472 | 473 | value_type const & value() const optional_ref_qual 474 | { 475 | return * value_ptr(); 476 | } 477 | 478 | value_type & value() optional_ref_qual 479 | { 480 | return * value_ptr(); 481 | } 482 | 483 | #if optional_CPP11_OR_GREATER 484 | 485 | value_type const && value() const optional_refref_qual 486 | { 487 | return * value_ptr(); 488 | } 489 | 490 | value_type && value() optional_refref_qual 491 | { 492 | return * value_ptr(); 493 | } 494 | 495 | #endif 496 | 497 | #if optional_CPP11_OR_GREATER 498 | 499 | using aligned_storage_t = typename std::aligned_storage< sizeof(value_type), alignof(value_type) >::type; 500 | aligned_storage_t data; 501 | 502 | #elif optional_CONFIG_MAX_ALIGN_HACK 503 | 504 | typedef struct { unsigned char data[ sizeof(value_type) ]; } aligned_storage_t; 505 | 506 | max_align_t hack; 507 | aligned_storage_t data; 508 | 509 | #else 510 | typedef optional_ALIGN_AS(value_type) align_as_type; 511 | 512 | typedef struct { align_as_type data[ 1 + ( sizeof(value_type) - 1 ) / sizeof(align_as_type) ]; } aligned_storage_t; 513 | aligned_storage_t data; 514 | 515 | # undef optional_ALIGN_AS 516 | 517 | #endif // optional_CONFIG_MAX_ALIGN_HACK 518 | 519 | void * ptr() optional_noexcept 520 | { 521 | return &data; 522 | } 523 | 524 | void const * ptr() const optional_noexcept 525 | { 526 | return &data; 527 | } 528 | 529 | template 530 | U * as() 531 | { 532 | return reinterpret_cast( ptr() ); 533 | } 534 | 535 | template 536 | U const * as() const 537 | { 538 | return reinterpret_cast( ptr() ); 539 | } 540 | }; 541 | 542 | } // namespace detail 543 | 544 | /// disengaged state tag 545 | 546 | struct nullopt_t 547 | { 548 | struct init{}; 549 | optional_constexpr nullopt_t( init ) {} 550 | }; 551 | 552 | #if optional_HAVE_CONSTEXPR_11 553 | constexpr nullopt_t nullopt{ nullopt_t::init{} }; 554 | #else 555 | // extra parenthesis to prevent the most vexing parse: 556 | const nullopt_t nullopt(( nullopt_t::init() )); 557 | #endif 558 | 559 | /// optional access error 560 | 561 | class bad_optional_access : public std::logic_error 562 | { 563 | public: 564 | explicit bad_optional_access() 565 | : logic_error( "bad optional access" ) {} 566 | }; 567 | 568 | /// optional 569 | 570 | template< typename T> 571 | class optional 572 | { 573 | private: 574 | typedef void (optional::*safe_bool)() const; 575 | 576 | public: 577 | typedef T value_type; 578 | 579 | optional_constexpr optional() optional_noexcept 580 | : has_value_( false ) 581 | , contained() 582 | {} 583 | 584 | optional_constexpr optional( nullopt_t ) optional_noexcept 585 | : has_value_( false ) 586 | , contained() 587 | {} 588 | 589 | optional( optional const & rhs ) 590 | : has_value_( rhs.has_value() ) 591 | { 592 | if ( rhs.has_value() ) 593 | contained.construct_value( rhs.contained.value() ); 594 | } 595 | 596 | #if optional_CPP11_OR_GREATER 597 | optional_constexpr14 optional( optional && rhs ) noexcept( std::is_nothrow_move_constructible::value ) 598 | : has_value_( rhs.has_value() ) 599 | { 600 | if ( rhs.has_value() ) 601 | contained.construct_value( std::move( rhs.contained.value() ) ); 602 | } 603 | #endif 604 | 605 | optional_constexpr optional( value_type const & value ) 606 | : has_value_( true ) 607 | , contained( value ) 608 | {} 609 | 610 | #if optional_CPP11_OR_GREATER 611 | 612 | optional_constexpr optional( value_type && value ) 613 | : has_value_( true ) 614 | , contained( std::move( value ) ) 615 | {} 616 | 617 | template< class... Args > 618 | optional_constexpr explicit optional( nonstd_lite_in_place_type_t(T), Args&&... args ) 619 | : has_value_( true ) 620 | , contained( T( std::forward(args)...) ) 621 | {} 622 | 623 | template< class U, class... Args > 624 | optional_constexpr explicit optional( nonstd_lite_in_place_type_t(T), std::initializer_list il, Args&&... args ) 625 | : has_value_( true ) 626 | , contained( T( il, std::forward(args)...) ) 627 | {} 628 | 629 | #endif // optional_CPP11_OR_GREATER 630 | 631 | ~optional() 632 | { 633 | if ( has_value() ) 634 | contained.destruct_value(); 635 | } 636 | 637 | // assignment 638 | 639 | optional & operator=( nullopt_t ) optional_noexcept 640 | { 641 | reset(); 642 | return *this; 643 | } 644 | 645 | optional & operator=( optional const & rhs ) 646 | #if optional_CPP11_OR_GREATER 647 | noexcept( std::is_nothrow_move_assignable::value && std::is_nothrow_move_constructible::value ) 648 | #endif 649 | { 650 | if ( has_value() == true && rhs.has_value() == false ) reset(); 651 | else if ( has_value() == false && rhs.has_value() == true ) initialize( *rhs ); 652 | else if ( has_value() == true && rhs.has_value() == true ) contained.value() = *rhs; 653 | return *this; 654 | } 655 | 656 | #if optional_CPP11_OR_GREATER 657 | 658 | optional & operator=( optional && rhs ) noexcept 659 | { 660 | if ( has_value() == true && rhs.has_value() == false ) reset(); 661 | else if ( has_value() == false && rhs.has_value() == true ) initialize( std::move( *rhs ) ); 662 | else if ( has_value() == true && rhs.has_value() == true ) contained.value() = std::move( *rhs ); 663 | return *this; 664 | } 665 | 666 | template< class U, 667 | typename = typename std::enable_if< std::is_same< typename std::decay::type, T>::value >::type > 668 | optional & operator=( U && v ) 669 | { 670 | if ( has_value() ) contained.value() = std::forward( v ); 671 | else initialize( T( std::forward( v ) ) ); 672 | return *this; 673 | } 674 | 675 | template< class... Args > 676 | void emplace( Args&&... args ) 677 | { 678 | *this = nullopt; 679 | initialize( T( std::forward(args)...) ); 680 | } 681 | 682 | template< class U, class... Args > 683 | void emplace( std::initializer_list il, Args&&... args ) 684 | { 685 | *this = nullopt; 686 | initialize( T( il, std::forward(args)...) ); 687 | } 688 | 689 | #endif // optional_CPP11_OR_GREATER 690 | 691 | // swap 692 | 693 | void swap( optional & rhs ) 694 | #if optional_CPP11_OR_GREATER 695 | noexcept( std::is_nothrow_move_constructible::value && noexcept( std::swap( std::declval(), std::declval() ) ) ) 696 | #endif 697 | { 698 | using std::swap; 699 | if ( has_value() == true && rhs.has_value() == true ) { swap( **this, *rhs ); } 700 | else if ( has_value() == false && rhs.has_value() == true ) { initialize( *rhs ); rhs.reset(); } 701 | else if ( has_value() == true && rhs.has_value() == false ) { rhs.initialize( **this ); reset(); } 702 | } 703 | 704 | // observers 705 | 706 | optional_constexpr value_type const * operator ->() const 707 | { 708 | return assert( has_value() ), 709 | contained.value_ptr(); 710 | } 711 | 712 | optional_constexpr14 value_type * operator ->() 713 | { 714 | return assert( has_value() ), 715 | contained.value_ptr(); 716 | } 717 | 718 | optional_constexpr value_type const & operator *() const optional_ref_qual 719 | { 720 | return assert( has_value() ), 721 | contained.value(); 722 | } 723 | 724 | optional_constexpr14 value_type & operator *() optional_ref_qual 725 | { 726 | return assert( has_value() ), 727 | contained.value(); 728 | } 729 | 730 | #if optional_CPP11_OR_GREATER 731 | 732 | optional_constexpr value_type const && operator *() const optional_refref_qual 733 | { 734 | assert( has_value() ); 735 | return std::move( contained.value() ); 736 | } 737 | 738 | optional_constexpr14 value_type && operator *() optional_refref_qual 739 | { 740 | assert( has_value() ); 741 | return std::move( contained.value() ); 742 | } 743 | 744 | #endif 745 | 746 | #if optional_CPP11_OR_GREATER 747 | optional_constexpr explicit operator bool() const optional_noexcept 748 | { 749 | return has_value(); 750 | } 751 | #else 752 | optional_constexpr operator safe_bool() const optional_noexcept 753 | { 754 | return has_value() ? &optional::this_type_does_not_support_comparisons : 0; 755 | } 756 | #endif 757 | 758 | optional_constexpr bool has_value() const optional_noexcept 759 | { 760 | return has_value_; 761 | } 762 | 763 | optional_constexpr14 value_type const & value() const optional_ref_qual 764 | { 765 | if ( ! has_value() ) 766 | throw bad_optional_access(); 767 | 768 | return contained.value(); 769 | } 770 | 771 | optional_constexpr14 value_type & value() optional_ref_qual 772 | { 773 | if ( ! has_value() ) 774 | throw bad_optional_access(); 775 | 776 | return contained.value(); 777 | } 778 | 779 | #if optional_HAVE_REF_QUALIFIER 780 | 781 | optional_constexpr14 value_type const && value() const optional_refref_qual 782 | { 783 | if ( ! has_value() ) 784 | throw bad_optional_access(); 785 | 786 | return std::move( contained.value() ); 787 | } 788 | 789 | optional_constexpr14 value_type && value() optional_refref_qual 790 | { 791 | if ( ! has_value() ) 792 | throw bad_optional_access(); 793 | 794 | return std::move( contained.value() ); 795 | } 796 | 797 | #endif 798 | 799 | #if optional_CPP11_OR_GREATER 800 | 801 | template< class U > 802 | optional_constexpr value_type value_or( U && v ) const optional_ref_qual 803 | { 804 | return has_value() ? contained.value() : static_cast(std::forward( v ) ); 805 | } 806 | 807 | template< class U > 808 | optional_constexpr value_type value_or( U && v ) const optional_refref_qual 809 | { 810 | return has_value() ? std::move( contained.value() ) : static_cast(std::forward( v ) ); 811 | } 812 | 813 | #else 814 | 815 | template< class U > 816 | optional_constexpr value_type value_or( U const & v ) const 817 | { 818 | return has_value() ? contained.value() : static_cast( v ); 819 | } 820 | 821 | #endif // optional_CPP11_OR_GREATER 822 | 823 | // modifiers 824 | 825 | void reset() optional_noexcept 826 | { 827 | if ( has_value() ) 828 | contained.destruct_value(); 829 | 830 | has_value_ = false; 831 | } 832 | 833 | private: 834 | void this_type_does_not_support_comparisons() const {} 835 | 836 | template< typename V > 837 | void initialize( V const & value ) 838 | { 839 | assert( ! has_value() ); 840 | contained.construct_value( value ); 841 | has_value_ = true; 842 | } 843 | 844 | #if optional_CPP11_OR_GREATER 845 | template< typename V > 846 | void initialize( V && value ) 847 | { 848 | assert( ! has_value() ); 849 | contained.construct_value( std::move( value ) ); 850 | has_value_ = true; 851 | } 852 | #endif 853 | 854 | private: 855 | bool has_value_; 856 | detail::storage_t< value_type > contained; 857 | 858 | }; 859 | 860 | // Relational operators 861 | 862 | template< typename T > bool operator==( optional const & x, optional const & y ) 863 | { 864 | return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y; 865 | } 866 | 867 | template< typename T > bool operator!=( optional const & x, optional const & y ) 868 | { 869 | return !(x == y); 870 | } 871 | 872 | template< typename T > bool operator<( optional const & x, optional const & y ) 873 | { 874 | return (!y) ? false : (!x) ? true : *x < *y; 875 | } 876 | 877 | template< typename T > bool operator>( optional const & x, optional const & y ) 878 | { 879 | return (y < x); 880 | } 881 | 882 | template< typename T > bool operator<=( optional const & x, optional const & y ) 883 | { 884 | return !(y < x); 885 | } 886 | 887 | template< typename T > bool operator>=( optional const & x, optional const & y ) 888 | { 889 | return !(x < y); 890 | } 891 | 892 | // Comparison with nullopt 893 | 894 | template< typename T > bool operator==( optional const & x, nullopt_t ) optional_noexcept 895 | { 896 | return (!x); 897 | } 898 | 899 | template< typename T > bool operator==( nullopt_t, optional const & x ) optional_noexcept 900 | { 901 | return (!x); 902 | } 903 | 904 | template< typename T > bool operator!=( optional const & x, nullopt_t ) optional_noexcept 905 | { 906 | return bool(x); 907 | } 908 | 909 | template< typename T > bool operator!=( nullopt_t, optional const & x ) optional_noexcept 910 | { 911 | return bool(x); 912 | } 913 | 914 | template< typename T > bool operator<( optional const &, nullopt_t ) optional_noexcept 915 | { 916 | return false; 917 | } 918 | 919 | template< typename T > bool operator<( nullopt_t, optional const & x ) optional_noexcept 920 | { 921 | return bool(x); 922 | } 923 | 924 | template< typename T > bool operator<=( optional const & x, nullopt_t ) optional_noexcept 925 | { 926 | return (!x); 927 | } 928 | 929 | template< typename T > bool operator<=( nullopt_t, optional const & ) optional_noexcept 930 | { 931 | return true; 932 | } 933 | 934 | template< typename T > bool operator>( optional const & x, nullopt_t ) optional_noexcept 935 | { 936 | return bool(x); 937 | } 938 | 939 | template< typename T > bool operator>( nullopt_t, optional const & ) optional_noexcept 940 | { 941 | return false; 942 | } 943 | 944 | template< typename T > bool operator>=( optional const &, nullopt_t ) 945 | { 946 | return true; 947 | } 948 | 949 | template< typename T > bool operator>=( nullopt_t, optional const & x ) 950 | { 951 | return (!x); 952 | } 953 | 954 | // Comparison with T 955 | 956 | template< typename T > bool operator==( optional const & x, const T& v ) 957 | { 958 | return bool(x) ? *x == v : false; 959 | } 960 | 961 | template< typename T > bool operator==( T const & v, optional const & x ) 962 | { 963 | return bool(x) ? v == *x : false; 964 | } 965 | 966 | template< typename T > bool operator!=( optional const & x, const T& v ) 967 | { 968 | return bool(x) ? *x != v : true; 969 | } 970 | 971 | template< typename T > bool operator!=( T const & v, optional const & x ) 972 | { 973 | return bool(x) ? v != *x : true; 974 | } 975 | 976 | template< typename T > bool operator<( optional const & x, const T& v ) 977 | { 978 | return bool(x) ? *x < v : true; 979 | } 980 | 981 | template< typename T > bool operator<( T const & v, optional const & x ) 982 | { 983 | return bool(x) ? v < *x : false; 984 | } 985 | 986 | template< typename T > bool operator<=( optional const & x, const T& v ) 987 | { 988 | return bool(x) ? *x <= v : true; 989 | } 990 | 991 | template< typename T > bool operator<=( T const & v, optional const & x ) 992 | { 993 | return bool(x) ? v <= *x : false; 994 | } 995 | 996 | template< typename T > bool operator>( optional const & x, const T& v ) 997 | { 998 | return bool(x) ? *x > v : false; 999 | } 1000 | 1001 | template< typename T > bool operator>( T const & v, optional const & x ) 1002 | { 1003 | return bool(x) ? v > *x : true; 1004 | } 1005 | 1006 | template< typename T > bool operator>=( optional const & x, const T& v ) 1007 | { 1008 | return bool(x) ? *x >= v : false; 1009 | } 1010 | 1011 | template< typename T > bool operator>=( T const & v, optional const & x ) 1012 | { 1013 | return bool(x) ? v >= *x : true; 1014 | } 1015 | 1016 | // Specialized algorithms 1017 | 1018 | template< typename T > 1019 | void swap( optional & x, optional & y ) 1020 | #if optional_CPP11_OR_GREATER 1021 | noexcept( noexcept( x.swap(y) ) ) 1022 | #endif 1023 | { 1024 | x.swap( y ); 1025 | } 1026 | 1027 | #if optional_CPP11_OR_GREATER 1028 | 1029 | template< class T > 1030 | optional_constexpr optional< typename std::decay::type > make_optional( T && v ) 1031 | { 1032 | return optional< typename std::decay::type >( std::forward( v ) ); 1033 | } 1034 | 1035 | template< class T, class...Args > 1036 | optional_constexpr optional make_optional( Args&&... args ) 1037 | { 1038 | return optional( in_place, std::forward(args)...); 1039 | } 1040 | 1041 | template< class T, class U, class... Args > 1042 | optional_constexpr optional make_optional( std::initializer_list il, Args&&... args ) 1043 | { 1044 | return optional( in_place, il, std::forward(args)...); 1045 | } 1046 | 1047 | #else 1048 | 1049 | template< typename T > 1050 | optional make_optional( T const & v ) 1051 | { 1052 | return optional( v ); 1053 | } 1054 | 1055 | #endif // optional_CPP11_OR_GREATER 1056 | 1057 | } // namespace optional 1058 | 1059 | using namespace optional_lite; 1060 | 1061 | } // namespace nonstd 1062 | 1063 | #if optional_CPP11_OR_GREATER 1064 | 1065 | // specialize the std::hash algorithm: 1066 | 1067 | namespace std { 1068 | 1069 | template< class T > 1070 | class hash< nonstd::optional > 1071 | { 1072 | public: 1073 | std::size_t operator()( nonstd::optional const & v ) const optional_noexcept 1074 | { 1075 | return bool( v ) ? hash()( *v ) : 0; 1076 | } 1077 | }; 1078 | 1079 | } //namespace std 1080 | 1081 | #endif // optional_CPP11_OR_GREATER 1082 | 1083 | #endif // NONSTD_OPTIONAL_LITE_HPP 1084 | --------------------------------------------------------------------------------