1/* Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com>
2 http://woboq.com/blog/property-bindings-in-cpp.html
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
5associated documentation files (the "Software"), to deal in the Software without restriction,
6including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
7and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
8subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in all copies or substantial
11portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
14NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
16OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
17CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18*/
19
20#pragma once
21#include <functional>
22#include <unordered_set>
23#include <type_traits>
24
25class property_base
26{
27 /* Set of properties which are subscribed to this one.
28 When this property is changed, subscriptions are refreshed */
29 std::unordered_set<property_base *> subscribers;
30
31 /* Set of properties this property is depending on. */
32 std::unordered_set<property_base *> dependencies;
33
34public:
35 virtual ~property_base()
36 { clearSubscribers(); clearDependencies(); }
37
38 // re-evaluate this property
39 virtual void evaluate() = 0;
40
41 property_base() = default;
42 property_base(const property_base &other) : dependencies(other.dependencies) {
43 for (property_base *p : dependencies)
44 p->subscribers.insert(this);
45 }
46
47protected:
48 /* This function is called by the derived class when the property has changed
49 The default implementation re-evaluates all the property subscribed to this one. */
50 virtual void notify() {
51 auto copy = subscribers;
52 for (property_base *p : copy) {
53 p->evaluate();
54 }
55 }
56
57 /* Derived class call this function whenever this property is accessed.
58 It register the dependencies. */
59 void accessed() {
60 if (current && current != this) {
61 subscribers.insert(current);
62 current->dependencies.insert(this);
63 }
64 }
65
66 void clearSubscribers() {
67 for (property_base *p : subscribers)
68 p->dependencies.erase(this);
69 subscribers.clear();
70 }
71 void clearDependencies() {
72 for (property_base *p : dependencies)
73 p->subscribers.erase(this);
74 dependencies.clear();
75 }
76
77 /* Helper class that is used on the stack to set the current property being evaluated */
78 struct evaluation_scope {
79 evaluation_scope(property_base *prop) : previous(current) {
80 current = prop;
81 }
82 ~evaluation_scope() { current = previous; }
83 property_base *previous;
84 };
85private:
86 friend struct evaluation_scope;
87 /* thread_local */ static property_base *current;
88};
89
90//FIXME move to .cpp file
91property_base *property_base::current = 0;
92
93/** The property class represents a property of type T that can be assigned a value, or a bindings.
94 When assigned a bindings, the binding is re-evaluated whenever one of the property used in it
95 is changed */
96template <typename T>
97struct property : property_base {
98 typedef std::function<T()> binding_t;
99
100 property() = default;
101 property(const T &t) : value(t) {}
102 property(const binding_t &b) : binding(b) { evaluate(); }
103
104 void operator=(const T &t) {
105 value = t;
106 clearDependencies();
107 notify();
108 }
109 void operator=(const binding_t &b) {
110 binding = b;
111 evaluate();
112 }
113
114 //make it possible to initialize directly with lamda without any casts
115 template<typename B> property(const B &b, typename std::enable_if<std::is_constructible<T, B>::value, int*>::type = nullptr) : property(T(b)) {}
116 template<typename B> typename std::enable_if<std::is_constructible<T, B>::value>::type operator= (const B &b) { *this=T(b); }
117 template<typename B> property(const B &b, typename std::enable_if<std::is_constructible<binding_t, B>::value && !std::is_constructible<T, B>::value, int*>::type = nullptr) : property(binding_t(b)) {}
118 template<typename B> typename std::enable_if<std::is_constructible<binding_t, B>::value && !std::is_constructible<T, B>::value>::type operator= (const B &b) { *this=binding_t(b); }
119
120 const T &get() const {
121 const_cast<property*>(this)->accessed();
122 return value;
123 }
124
125 //automatic conversions
126 const T &operator()() const { return get(); }
127 operator const T&() const { return get(); }
128
129 void evaluate() override {
130 if (binding) {
131 clearDependencies();
132 evaluation_scope scope(this);
133 value = binding();
134 }
135 notify();
136 }
137
138protected:
139 T value;
140 binding_t binding;
141};
142
143template<typename T>
144struct property_hook : property<T> {
145 typedef std::function<void()> hook_t;
146 typedef typename property<T>::binding_t binding_t;
147 void notify() override {
148 property<T>::notify();
149 hook();
150 }
151 property_hook(hook_t h) : hook(h) { }
152 property_hook(hook_t h, const T &t) : property<T>(t), hook(h) { }
153 property_hook(hook_t h, binding_t b) : property<T>(b), hook(h) { }
154 using property<T>::operator=;
155private:
156 hook_t hook;
157};
158
159/** property_wrapper do not own the property, but use a getter and a setter */
160template <typename T>
161struct property_wrapper : property_base {
162 typedef std::function<T()> binding_t;
163 typedef std::function<void(const T&)> write_hook_t;
164 typedef std::function<T()> read_hook_t;
165 explicit property_wrapper(write_hook_t w, read_hook_t r) : write_hook(std::move(w)), read_hook(std::move(r)) { }
166
167 T get() const {
168 const_cast<property_wrapper*>(this)->accessed();
169 return read_hook();
170 }
171 void operator=(const T &t) {
172 write_hook(t);
173 notify();
174 }
175
176 void operator=(const binding_t &b) {
177 binding = b;
178 evaluate();
179 }
180
181 T operator()() const { return get(); }
182 operator T() const { return get(); }
183
184 using property_base::notify;
185protected:
186 void evaluate() override {
187 if (binding) {
188 clearDependencies();
189 evaluation_scope scope(this);
190 write_hook(binding());
191 }
192 notify();
193 }
194private:
195 const write_hook_t write_hook;
196 const read_hook_t read_hook;
197 binding_t binding;
198};
199