Darwin Neuroevolution Framework
properties.h
1 // Copyright 2018 The Darwin Neuroevolution Framework Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #pragma once
16 
17 #include "utils.h"
18 #include "exception.h"
19 #include "stringify.h"
20 
21 #include <third_party/json/json.h>
22 using nlohmann::json;
23 
24 #include <assert.h>
25 #include <iostream>
26 #include <memory>
27 #include <string>
28 #include <type_traits>
29 #include <vector>
30 using namespace std;
31 
32 namespace core {
33 
34 class PropertySet;
35 
36 template <class T>
37 class TypedProperty;
38 
41 class Property {
42  public:
43  Property(PropertySet* parent, const char* name, const char* description)
44  : parent_(parent), name_(name), description_(description) {
45  CHECK(parent != nullptr);
46  CHECK(!name_.empty());
47  CHECK(!description_.empty());
48  }
49 
50  virtual ~Property() = default;
51 
53  const string& name() const { return name_; }
54 
56  const string& description() const { return description_; }
57 
59  PropertySet* parent() const { return parent_; }
60 
62  virtual string value() const = 0;
63 
65  virtual string defaultValue() const = 0;
66 
68  virtual PropertySet* childPropertySet() const { return nullptr; }
69 
71  virtual void setValue(const string& str) = 0;
72 
74  virtual vector<string> knownValues() const = 0;
75 
78  virtual void copyFrom(const Property& src) = 0;
79 
81  virtual json toJson() const = 0;
82 
84  virtual void fromJson(const json& json_obj) = 0;
85 
88  template <class T>
89  const T& nativeValue() const {
90  auto typed_property = dynamic_cast<const TypedProperty<T>*>(this);
91  if (typed_property == nullptr)
92  throw core::Exception("Invalid property type");
93  return typed_property->nativeValue();
94  }
95 
97  template <class T>
98  bool isA() const {
99  return dynamic_cast<const TypedProperty<T>*>(this) != nullptr;
100  }
101 
102  private:
103  PropertySet* parent_ = nullptr;
104  const string name_;
105  const string description_;
106 };
107 
108 template <class T>
109 class TypedProperty : public Property {
110  public:
111  TypedProperty(PropertySet* parent,
112  const char* name,
113  T default_value,
114  T* value,
115  const char* description)
116  : Property(parent, name, description),
117  default_value_(default_value),
118  value_(value) {}
119 
120  string value() const override { return core::toString(*value_); }
121 
122  string defaultValue() const override { return core::toString(default_value_); }
123 
124  void setValue(const string& str) override;
125 
126  vector<string> knownValues() const override { return core::knownValues<T>(); }
127 
128  void copyFrom(const Property& src) override;
129 
130  json toJson() const override { return value(); }
131 
132  void fromJson(const json& json_obj) override { setValue(json_obj); }
133 
134  const T& nativeValue() const { return *value_; }
135 
136  private:
137  T default_value_;
138  T* value_ = nullptr;
139 };
140 
193 template <class TAG>
195  public:
196  using TagType = TAG;
197 
198  public:
199  virtual ~PropertySetVariant() = default;
200 
202  TAG tag() const { return tag_; }
203 
205  void selectCase(TAG tag) {
206  if (cases_.find(tag_) == cases_.end())
207  throw core::Exception("Invalid variant case");
208  tag_ = tag;
209  }
210 
212  const PropertySet* activeCase() const {
213  auto it = cases_.find(tag_);
214  CHECK(it != cases_.end());
215  return it->second;
216  }
217 
220  auto it = cases_.find(tag_);
221  CHECK(it != cases_.end());
222  return it->second;
223  }
224 
229  json toJson() const {
230  json json_obj = json::object();
231  json_obj["tag"] = core::toString(tag_);
232  for (const auto& [case_tag, case_value] : cases_) {
233  json_obj[core::toString(case_tag)] = case_value->toJson();
234  }
235  return json_obj;
236  }
237 
244  void fromJson(const json& json_obj) {
245  CHECK(json_obj.is_object());
246 
247  bool at_least_one_case = false;
248  tag_ = core::fromString<TAG>(json_obj.at("tag"));
249  for (auto& [case_tag, case_value] : cases_) {
250  auto json_it = json_obj.find(core::toString(case_tag));
251  if (json_it != json_obj.end()) {
252  case_value->fromJson(json_it.value());
253  at_least_one_case = true;
254  } else {
255  case_value->resetToDefaultValues();
256  }
257  }
258 
259  // sanity check
260  CHECK(cases_.empty() || at_least_one_case);
261  }
262 
264  void copyFrom(const PropertySetVariant& src) {
265  CHECK(typeid(*this) == typeid(src));
266  CHECK(cases_.size() == src.cases_.size());
267  tag_ = src.tag_;
268  for (const auto& [case_tag, case_value] : src.cases_) {
269  cases_.at(case_tag)->copyFrom(*case_value);
270  }
271  }
272 
273  protected:
274  bool registerCase(TAG tag, PropertySet* property_set) {
275  CHECK(cases_.insert({ tag, property_set }).second, "Duplicate cases");
276  tag_ = tag;
277  return true;
278  }
279 
280  private:
281  TAG tag_ = TAG(-1);
282  map<TAG, PropertySet*> cases_;
283 };
284 
285 template <class T>
286 class VariantProperty : public Property {
287  using TagType = typename T::TagType;
288 
289  public:
290  VariantProperty(PropertySet* parent,
291  const char* name,
292  TagType default_case,
293  T* variant,
294  const char* description)
295  : Property(parent, name, description),
296  default_case_(default_case),
297  variant_(variant) {}
298 
299  string value() const override { return core::toString(variant_->tag()); }
300 
301  string defaultValue() const override { return core::toString(default_case_); }
302 
303  PropertySet* childPropertySet() const override { return variant_->activeCase(); }
304 
305  void setValue(const string& str) override;
306 
307  vector<string> knownValues() const override { return core::knownValues<TagType>(); }
308 
309  void copyFrom(const Property& src) override;
310 
311  json toJson() const override { return variant_->toJson(); }
312 
313  void fromJson(const json& json_obj) override { variant_->fromJson(json_obj); }
314 
315  private:
316  TagType default_case_;
317  T* variant_ = nullptr;
318 };
319 
389  public:
390  PropertySet() = default;
391  virtual ~PropertySet() = default;
392 
394  vector<Property*> properties() {
395  vector<Property*> properties;
396  for (const auto& property : properties_)
397  properties.push_back(property.get());
398  return properties;
399  }
400 
402  vector<const Property*> properties() const {
403  vector<const Property*> properties;
404  for (const auto& property : properties_)
405  properties.push_back(property.get());
406  return properties;
407  }
408 
411  for (const auto& property : properties_)
412  property->setValue(property->defaultValue());
413  }
414 
416  void copyFrom(const PropertySet& src) {
417  CHECK(typeid(*this) == typeid(src), "Incompatible property sets");
418  CHECK(properties_.size() == src.properties_.size());
419 
420  if (sealed_) {
421  throw core::Exception("Attempting to use 'copyFrom' on a sealed property set");
422  }
423 
424  for (size_t i = 0; i < properties_.size(); ++i)
425  properties_[i]->copyFrom(*src.properties_[i]);
426  }
427 
430  void seal(bool sealed = true) {
431  sealed_ = sealed;
432  }
433 
435  bool sealed() const { return sealed_; }
436 
438  void onPropertyChange(const Property* property) {
439  if (sealed_) {
440  throw core::Exception("Attempting to set property '%s' on a sealed property set",
441  property->name());
442  }
443  }
444 
450  json toJson() const {
451  json json_obj = json::object();
452  for (const auto& property : properties_) {
453  json_obj[property->name()] = property->toJson();
454  }
455  return json_obj;
456  }
457 
464  void fromJson(const json& json_obj) {
465  CHECK(json_obj.is_object());
466 
467  if (sealed_) {
468  throw core::Exception("Attempting to use 'fromJson' on a sealed property set");
469  }
470 
471  bool at_least_one_value = false;
472  for (const auto& property : properties_) {
473  auto json_it = json_obj.find(property->name());
474  if (json_it != json_obj.end()) {
475  property->fromJson(json_it.value());
476  at_least_one_value = true;
477  } else
478  property->setValue(property->defaultValue());
479  }
480 
481  // sanity check, if none of the fields match we have something
482  // completely different than we expected
483  CHECK(json_obj.empty() || properties_.empty() || at_least_one_value);
484  }
485 
486  protected:
487  template <class T>
488  T registerProperty(const char* name,
489  T default_value,
490  T* value,
491  const char* description) {
492  assert(name != nullptr);
493  assert(value != nullptr);
494  properties_.push_back(
495  make_unique<TypedProperty<T>>(this, name, default_value, value, description));
496  return default_value;
497  }
498 
499  template <class V, class TAG>
500  bool registerVariant(const char* name,
501  TAG default_case,
502  V* variant,
503  const char* description) {
504  assert(name != nullptr);
505  assert(variant != nullptr);
506  variant->selectCase(default_case);
507  properties_.push_back(
508  make_unique<VariantProperty<V>>(this, name, default_case, variant, description));
509  return true;
510  }
511 
512  private:
513  vector<unique_ptr<Property>> properties_;
514  bool sealed_ = false;
515 };
516 
517 template <class T>
518 void TypedProperty<T>::setValue(const string& str) {
519  parent()->onPropertyChange(this);
520  *value_ = core::fromString<T>(str);
521 }
522 
523 template <class T>
524 void TypedProperty<T>::copyFrom(const Property& src) {
525  parent()->onPropertyChange(this);
526  *value_ = *dynamic_cast<const TypedProperty&>(src).value_;
527 }
528 
529 template <class T>
530 void VariantProperty<T>::setValue(const string& str) {
531  parent()->onPropertyChange(this);
532  variant_->selectCase(core::fromString<TagType>(str));
533 }
534 
535 template <class T>
536 void VariantProperty<T>::copyFrom(const Property& src) {
537  parent()->onPropertyChange(this);
538  variant_->copyFrom(*dynamic_cast<const VariantProperty&>(src).variant_);
539 }
540 
541 // convenience macro for defining properties
542 #define PROPERTY(name, type, default_value, description) \
543  type name { registerProperty<type>(#name, type(default_value), &name, (description)) }
544 
545 // convenience macro for defining variants
546 #define VARIANT(name, type, default_case, description) \
547  type name; \
548  bool name##_INIT { registerVariant<type>(#name, (default_case), &name, (description)) }
549 
550 // convenience macro for defining variant cases
551 #define CASE(tag, name, type) \
552  type name; \
553  bool name##_INIT { registerCase((tag), &name) }
554 
555 } // namespace core
PropertySet * parent() const
The PropertySet which contains this property.
Definition: properties.h:59
The base for exception types in the Darwin framework.
Definition: exception.h:27
void onPropertyChange(const Property *property)
Called before updating a property value.
Definition: properties.h:438
Generic utilities.
Definition: exception.h:24
void fromJson(const json &json_obj)
Deserialize a set of properties from a json object.
Definition: properties.h:464
void fromJson(const json &json_obj)
Deserialize from a json object.
Definition: properties.h:244
bool isA() const
Returns true if the property wraps a T value.
Definition: properties.h:98
vector< const Property * > properties() const
Accessor to the list of properties.
Definition: properties.h:402
STL namespace.
const T & nativeValue() const
Type-safe accessor to the native property value.
Definition: properties.h:89
void selectCase(TAG tag)
Selects the active PropertySet case.
Definition: properties.h:205
void copyFrom(const PropertySetVariant &src)
Transfer values between two property set variants.
Definition: properties.h:264
virtual PropertySet * childPropertySet() const
Return the optional child PropertySet, or nullptr
Definition: properties.h:68
TAG tag() const
The tag value indicating the active PropertySet case.
Definition: properties.h:202
json toJson() const
Serialize all the properties to a json object.
Definition: properties.h:450
bool sealed() const
Returns true if the property set is sealed.
Definition: properties.h:435
Reflection interface to a property in a PropertySet.
Definition: properties.h:41
const string & description() const
Property description.
Definition: properties.h:56
vector< Property * > properties()
Accessor to the list of properties.
Definition: properties.h:394
json toJson() const
Serialize to a json object.
Definition: properties.h:229
void copyFrom(const PropertySet &src)
Transfer values between two property sets.
Definition: properties.h:416
The foundation for data structures supporting runtime reflection.
Definition: properties.h:388
PropertySet * activeCase()
Returns the active PropertySet.
Definition: properties.h:219
Classes derived from this are not copyable or movable.
Definition: utils.h:69
A variant type (tagged-union) with PropertySet fields.
Definition: properties.h:194
void resetToDefaultValues()
Resets all the properties to the default values.
Definition: properties.h:410
string toString(const T &value)
Convenience helper to stringify a value.
Definition: stringify.h:172
vector< string > knownValues()
Convenience helper to return the set of known values.
Definition: stringify.h:184
const string & name() const
Property name.
Definition: properties.h:53
void seal(bool sealed=true)
After sealing a PropertySet, all attempts to modify it through the Property interface will throw an e...
Definition: properties.h:430
const PropertySet * activeCase() const
Returns the active PropertySet.
Definition: properties.h:212