blob: 0904a31df6fe2cd9361a88f6f9483a209818eb5e [file] [log] [blame]
Brian Osman7c979f52019-02-12 13:27:51 -05001/*
2* Copyright 2019 Google LLC
3*
4* Use of this source code is governed by a BSD-style license that can be
5* found in the LICENSE file.
6*/
7
8#ifndef SkReflected_DEFINED
9#define SkReflected_DEFINED
10
11#include "SkColor.h"
Brian Osman112aa2d2019-02-15 10:45:56 -050012#include "SkCurve.h"
Brian Osman7c979f52019-02-12 13:27:51 -050013#include "SkRefCnt.h"
14#include "SkString.h"
15#include "SkTArray.h"
16
Brian Osmane5d532e2019-02-26 14:58:40 -050017#include <string.h>
18
Brian Osman7c979f52019-02-12 13:27:51 -050019class SkFieldVisitor;
20class SkRandom;
21
Brian Osman23b96c02019-02-19 13:04:40 -050022/**
23 * Classes and macros for a lightweight reflection system.
24 *
25 * Classes that derive from SkReflected have several features:
26 * - Access to an SkReflected::Type instance, via static GetType() or virtual getType()
27 * The Type instance can be used to create additional instances (fFactory), get the name
28 * of the type, and answer queries of the form "is X derived from Y".
29 * - Given a string containing a type name, SkReflected can create an instance of that type.
30 * - SkReflected::VisitTypes can be used to enumerate all Types, or all Types derived from a
31 * particular base class.
32 *
33 * Together, this simplifies the implementation of serialization and other dynamic type factories.
34 *
35 * Finally, all SkReflected-derived types must implement visitFields, which provides field-level
36 * reflection, in conjunction with SkFieldVisitor. See SkFieldVisitor, below.
37 *
38 * To create a new reflected class:
39 * - Derive the class (directly or indirectly) from SkReflected.
40 * - Ensure that the class can be default constructed.
41 * - In the public area of the class declaration, add REFLECTED(<ClassName>, <BaseClassName>).
42 * If the class is abstract, use REFLECTED_ABSTRACT(<ClassName>, <BaseClassName>) instead.
43 * - Add a one-time call to REGISTER_REFLECTED(<ClassName>) at initialization time.
44 * - Implement visitFields(), as described below.
45 */
Brian Osman7c979f52019-02-12 13:27:51 -050046class SkReflected : public SkRefCnt {
47public:
48 typedef sk_sp<SkReflected>(*Factory)();
49 struct Type {
50 const char* fName;
51 const Type* fBase;
52 Factory fFactory;
53
54 bool isDerivedFrom(const Type* t) const {
55 const Type* base = fBase;
56 while (base) {
57 if (base == t) {
58 return true;
59 }
60 base = base->fBase;
61 }
62 return false;
63 }
64 };
65
66 virtual const Type* getType() const = 0;
67 static const Type* GetType() {
68 static Type gType{ "SkReflected", nullptr, nullptr };
69 return &gType;
70 }
71
72 bool isOfType(const Type* t) const {
73 const Type* thisType = this->getType();
74 return thisType == t || thisType->isDerivedFrom(t);
75 }
76
77 static void Register(const Type* type) {
78 gTypes.push_back(type);
79 }
80
81 static sk_sp<SkReflected> CreateInstance(const char* name) {
82 for (const Type* type : gTypes) {
83 if (0 == strcmp(name, type->fName)) {
84 return type->fFactory();
85 }
86 }
87 return nullptr;
88 }
89
90 virtual void visitFields(SkFieldVisitor*) = 0;
91
92 static void VisitTypes(std::function<void(const Type*)> visitor,
93 const Type* baseType = nullptr);
94private:
95 static SkSTArray<16, const Type*, true> gTypes;
96};
97
98#define REFLECTED(TYPE, BASE) \
99 static sk_sp<SkReflected> CreateProc() { \
100 return sk_sp<SkReflected>(new TYPE()); \
101 } \
102 static const Type* GetType() { \
103 static Type gType{ #TYPE, BASE::GetType(), CreateProc }; \
104 return &gType; \
105 } \
106 const Type* getType() const override { return GetType(); }
107
108#define REFLECTED_ABSTRACT(TYPE, BASE) \
109 static const Type* GetType() { \
110 static Type gType{ #TYPE, BASE::GetType(), nullptr }; \
111 return &gType; \
112 } \
113 const Type* getType() const override { return GetType(); }
114
115#define REGISTER_REFLECTED(TYPE) SkReflected::Register(TYPE::GetType())
116
117///////////////////////////////////////////////////////////////////////////////
118
Brian Osman7c979f52019-02-12 13:27:51 -0500119struct SkPoint;
120
Brian Osman23b96c02019-02-19 13:04:40 -0500121/**
122 * SkFieldVisitor is an interface that can be implemented by any class to visit all fields of
123 * SkReflected types, and of types that implement the visitFields() function.
124 *
125 * Classes implementing the interface must supply implementations of virtual functions that visit
126 * basic types (float, int, bool, SkString, etc...), as well as helper methods for entering the
127 * scope of an object or array.
128 *
129 * All visit functions supply a field name, and a non-constant reference to an actual field.
130 * This allows visitors to serialize or deserialize collections of objects, or perform edits on
131 * existing objects.
132 *
133 * Classes that implement visitFields (typically derived from SkReflected) should simply call
134 * visit() for each of their fields, passing a (unique) field name, and the actual field. If your
135 * class has derived fields, it's best to only visit() the fields that you would serialize, then
136 * enforce any constraints afterwards.
137 *
138 * See SkParticleSerialization.h for example visitors that perform serialization to and from JSON.
139 */
Brian Osman7c979f52019-02-12 13:27:51 -0500140class SkFieldVisitor {
141public:
142 virtual ~SkFieldVisitor() {}
143
Brian Osman23b96c02019-02-19 13:04:40 -0500144 // Visit functions for primitive types, to be implemented by derived visitors.
Brian Osman2991cbe2019-02-19 10:45:56 -0500145 virtual void visit(const char*, float&) = 0;
146 virtual void visit(const char*, int&) = 0;
147 virtual void visit(const char*, bool&) = 0;
148 virtual void visit(const char*, SkString&) = 0;
Brian Osman7c979f52019-02-12 13:27:51 -0500149
Brian Osman2991cbe2019-02-19 10:45:56 -0500150 virtual void visit(const char*, SkPoint&) = 0;
151 virtual void visit(const char*, SkColor4f&) = 0;
Brian Osman7c979f52019-02-12 13:27:51 -0500152
Brian Osmane5d532e2019-02-26 14:58:40 -0500153 // Accommodation for enums, where caller can supply a value <-> string map
154 struct EnumStringMapping {
155 int fValue;
156 const char* fName;
157 };
158 virtual void visit(const char*, int&, const EnumStringMapping*, int count) = 0;
159
Brian Osman23b96c02019-02-19 13:04:40 -0500160 // Specific virtual signature for SkCurve, to allow for heavily customized UI in SkGuiVisitor.
Brian Osman2991cbe2019-02-19 10:45:56 -0500161 virtual void visit(const char* name, SkCurve& c) {
Brian Osman112aa2d2019-02-15 10:45:56 -0500162 this->enterObject(name);
163 c.visitFields(this);
164 this->exitObject();
165 }
166
Brian Osman23b96c02019-02-19 13:04:40 -0500167 // Default visit function for structs with no special behavior. It is assumed that any such
168 // struct implements visitFields(SkFieldVisitor*) to recursively visit each of its fields.
Brian Osman7c979f52019-02-12 13:27:51 -0500169 template <typename T>
170 void visit(const char* name, T& value) {
171 this->enterObject(name);
172 value.visitFields(this);
173 this->exitObject();
174 }
175
Brian Osman23b96c02019-02-19 13:04:40 -0500176 // Specialization for SkTArrays. In conjunction with the enterArray/exitArray virtuals, this
177 // allows visitors to resize an array (for deserialization), and apply a single edit operation
178 // (remove or move a single element). Each element of the array is visited as normal.
Brian Osman5de7ea42019-02-14 13:23:51 -0500179 template <typename T, bool MEM_MOVE>
180 void visit(const char* name, SkTArray<T, MEM_MOVE>& arr) {
181 arr.resize_back(this->enterArray(name, arr.count()));
182 for (int i = 0; i < arr.count(); ++i) {
183 this->visit(nullptr, arr[i]);
Brian Osman7c979f52019-02-12 13:27:51 -0500184 }
Brian Osman5de7ea42019-02-14 13:23:51 -0500185 this->exitArray().apply(arr);
Brian Osman7c979f52019-02-12 13:27:51 -0500186 }
187
Brian Osman23b96c02019-02-19 13:04:40 -0500188 // Specialization for sk_sp pointers to types derived from SkReflected. Those types are known
189 // to implement visitFields. This allows the visitor to modify the contents of the object, or
190 // even replace it with an entirely new object. The virtual function uses SkReflected as a
191 // common type, but uses SkReflected::Type to communicate the required base-class. In this way,
192 // the new object can be verified to match the type of the original (templated) pointer.
Brian Osman7c979f52019-02-12 13:27:51 -0500193 template <typename T>
194 void visit(const char* name, sk_sp<T>& obj) {
195 this->enterObject(name);
196
197 sk_sp<SkReflected> newObj = obj;
198 this->visit(newObj, T::GetType());
199 if (newObj != obj) {
200 if (!newObj || newObj->isOfType(T::GetType())) {
201 obj.reset(static_cast<T*>(newObj.release()));
202 } else {
203 obj.reset();
204 }
205 }
206
207 if (obj) {
208 obj->visitFields(this);
209 }
210 this->exitObject();
211 }
212
213protected:
Brian Osman23b96c02019-02-19 13:04:40 -0500214 // Helper struct to allow exitArray to specify a single operation performed on the array.
Brian Osman5de7ea42019-02-14 13:23:51 -0500215 struct ArrayEdit {
216 enum class Verb {
217 kNone,
218 kRemove,
219 kMoveForward,
220 };
221
222 Verb fVerb = Verb::kNone;
223 int fIndex = 0;
224
225 template <typename T, bool MEM_MOVE>
226 void apply(SkTArray<T, MEM_MOVE>& arr) const {
227 switch (fVerb) {
228 case Verb::kNone:
229 break;
230 case Verb::kRemove:
231 for (int i = fIndex; i < arr.count() - 1; ++i) {
232 arr[i] = arr[i + 1];
233 }
234 arr.pop_back();
235 break;
236 case Verb::kMoveForward:
237 if (fIndex > 0 && fIndex < arr.count()) {
238 std::swap(arr[fIndex - 1], arr[fIndex]);
239 }
240 break;
241 }
242 }
243 };
244
Brian Osmane5d532e2019-02-26 14:58:40 -0500245 static const char* EnumToString(int value, const EnumStringMapping* map, int count) {
246 for (int i = 0; i < count; ++i) {
247 if (map[i].fValue == value) {
248 return map[i].fName;
249 }
250 }
251 return nullptr;
252 }
253 static int StringToEnum(const char* str, const EnumStringMapping* map, int count) {
254 for (int i = 0; i < count; ++i) {
255 if (0 == strcmp(str, map[i].fName)) {
256 return map[i].fValue;
257 }
258 }
259 return -1;
260 }
261
Brian Osman7c979f52019-02-12 13:27:51 -0500262 virtual void enterObject(const char* name) = 0;
263 virtual void exitObject() = 0;
Brian Osman5de7ea42019-02-14 13:23:51 -0500264
265 virtual int enterArray(const char* name, int oldCount) = 0;
266 virtual ArrayEdit exitArray() = 0;
267
Brian Osman7c979f52019-02-12 13:27:51 -0500268 virtual void visit(sk_sp<SkReflected>&, const SkReflected::Type* baseType) = 0;
Brian Osman7c979f52019-02-12 13:27:51 -0500269};
270
271#endif // SkReflected_DEFINED