1 | /* Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com> |
2 | http://woboq.com/blog/property-bindings-in-cpp.html |
3 | |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
5 | associated documentation files (the "Software"), to deal in the Software without restriction, |
6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, |
7 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, |
8 | subject to the following conditions: |
9 | |
10 | The above copyright notice and this permission notice shall be included in all copies or substantial |
11 | portions of the Software. |
12 | |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
14 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES |
16 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
17 | CONNECTION 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 | |
25 | class 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 | |
34 | public: |
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 | |
47 | protected: |
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 | }; |
85 | private: |
86 | friend struct evaluation_scope; |
87 | /* thread_local */ static property_base *current; |
88 | }; |
89 | |
90 | //FIXME move to .cpp file |
91 | property_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 */ |
96 | template <typename T> |
97 | struct 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 | |
138 | protected: |
139 | T value; |
140 | binding_t binding; |
141 | }; |
142 | |
143 | template<typename T> |
144 | struct 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=; |
155 | private: |
156 | hook_t hook; |
157 | }; |
158 | |
159 | /** property_wrapper do not own the property, but use a getter and a setter */ |
160 | template <typename T> |
161 | struct 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; |
185 | protected: |
186 | void evaluate() override { |
187 | if (binding) { |
188 | clearDependencies(); |
189 | evaluation_scope scope(this); |
190 | write_hook(binding()); |
191 | } |
192 | notify(); |
193 | } |
194 | private: |
195 | const write_hook_t write_hook; |
196 | const read_hook_t read_hook; |
197 | binding_t binding; |
198 | }; |
199 | |