blob: 08acc1f6bf3a5db842602d1661e268b133c62981 [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 Osman7c979f52019-02-12 13:27:51 -050012#include "SkRefCnt.h"
13#include "SkString.h"
14#include "SkTArray.h"
15
Brian Osmane5d532e2019-02-26 14:58:40 -050016#include <string.h>
17
Brian Osmanbdcdf1a2019-03-04 10:55:22 -050018struct SkCurve;
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.
Brian Osmanb77d5022019-03-06 11:08:48 -050030 * - SkReflected::VisitTypes can be used to enumerate all Types.
Brian Osman23b96c02019-02-19 13:04:40 -050031 *
32 * Together, this simplifies the implementation of serialization and other dynamic type factories.
33 *
34 * Finally, all SkReflected-derived types must implement visitFields, which provides field-level
35 * reflection, in conjunction with SkFieldVisitor. See SkFieldVisitor, below.
36 *
37 * To create a new reflected class:
38 * - Derive the class (directly or indirectly) from SkReflected.
39 * - Ensure that the class can be default constructed.
40 * - In the public area of the class declaration, add REFLECTED(<ClassName>, <BaseClassName>).
41 * If the class is abstract, use REFLECTED_ABSTRACT(<ClassName>, <BaseClassName>) instead.
42 * - Add a one-time call to REGISTER_REFLECTED(<ClassName>) at initialization time.
43 * - Implement visitFields(), as described below.
44 */
Brian Osman7c979f52019-02-12 13:27:51 -050045class SkReflected : public SkRefCnt {
46public:
47 typedef sk_sp<SkReflected>(*Factory)();
48 struct Type {
49 const char* fName;
50 const Type* fBase;
51 Factory fFactory;
52
53 bool isDerivedFrom(const Type* t) const {
54 const Type* base = fBase;
55 while (base) {
56 if (base == t) {
57 return true;
58 }
59 base = base->fBase;
60 }
61 return false;
62 }
63 };
64
65 virtual const Type* getType() const = 0;
66 static const Type* GetType() {
67 static Type gType{ "SkReflected", nullptr, nullptr };
68 return &gType;
69 }
70
71 bool isOfType(const Type* t) const {
72 const Type* thisType = this->getType();
73 return thisType == t || thisType->isDerivedFrom(t);
74 }
75
76 static void Register(const Type* type) {
77 gTypes.push_back(type);
78 }
79
80 static sk_sp<SkReflected> CreateInstance(const char* name) {
81 for (const Type* type : gTypes) {
82 if (0 == strcmp(name, type->fName)) {
83 return type->fFactory();
84 }
85 }
86 return nullptr;
87 }
88
89 virtual void visitFields(SkFieldVisitor*) = 0;
90
Brian Osmanb77d5022019-03-06 11:08:48 -050091 static void VisitTypes(std::function<void(const Type*)> visitor);
92
Brian Osman7c979f52019-02-12 13:27:51 -050093private:
94 static SkSTArray<16, const Type*, true> gTypes;
95};
96
97#define REFLECTED(TYPE, BASE) \
98 static sk_sp<SkReflected> CreateProc() { \
99 return sk_sp<SkReflected>(new TYPE()); \
100 } \
101 static const Type* GetType() { \
102 static Type gType{ #TYPE, BASE::GetType(), CreateProc }; \
103 return &gType; \
104 } \
105 const Type* getType() const override { return GetType(); }
106
107#define REFLECTED_ABSTRACT(TYPE, BASE) \
108 static const Type* GetType() { \
109 static Type gType{ #TYPE, BASE::GetType(), nullptr }; \
110 return &gType; \
111 } \
112 const Type* getType() const override { return GetType(); }
113
114#define REGISTER_REFLECTED(TYPE) SkReflected::Register(TYPE::GetType())
115
116///////////////////////////////////////////////////////////////////////////////
117
Brian Osman7c979f52019-02-12 13:27:51 -0500118struct SkPoint;
119
Brian Osman23b96c02019-02-19 13:04:40 -0500120/**
121 * SkFieldVisitor is an interface that can be implemented by any class to visit all fields of
122 * SkReflected types, and of types that implement the visitFields() function.
123 *
124 * Classes implementing the interface must supply implementations of virtual functions that visit
125 * basic types (float, int, bool, SkString, etc...), as well as helper methods for entering the
126 * scope of an object or array.
127 *
128 * All visit functions supply a field name, and a non-constant reference to an actual field.
129 * This allows visitors to serialize or deserialize collections of objects, or perform edits on
130 * existing objects.
131 *
132 * Classes that implement visitFields (typically derived from SkReflected) should simply call
133 * visit() for each of their fields, passing a (unique) field name, and the actual field. If your
134 * class has derived fields, it's best to only visit() the fields that you would serialize, then
135 * enforce any constraints afterwards.
136 *
137 * See SkParticleSerialization.h for example visitors that perform serialization to and from JSON.
138 */
Brian Osman7c979f52019-02-12 13:27:51 -0500139class SkFieldVisitor {
140public:
141 virtual ~SkFieldVisitor() {}
142
Brian Osman23b96c02019-02-19 13:04:40 -0500143 // Visit functions for primitive types, to be implemented by derived visitors.
Brian Osman2991cbe2019-02-19 10:45:56 -0500144 virtual void visit(const char*, float&) = 0;
145 virtual void visit(const char*, int&) = 0;
146 virtual void visit(const char*, bool&) = 0;
147 virtual void visit(const char*, SkString&) = 0;
Brian Osman7c979f52019-02-12 13:27:51 -0500148
Brian Osman2991cbe2019-02-19 10:45:56 -0500149 virtual void visit(const char*, SkPoint&) = 0;
150 virtual void visit(const char*, SkColor4f&) = 0;
Brian Osman7c979f52019-02-12 13:27:51 -0500151
Brian Osmane5d532e2019-02-26 14:58:40 -0500152 // Accommodation for enums, where caller can supply a value <-> string map
153 struct EnumStringMapping {
154 int fValue;
155 const char* fName;
156 };
157 virtual void visit(const char*, int&, const EnumStringMapping*, int count) = 0;
158
Brian Osman23b96c02019-02-19 13:04:40 -0500159 // Specific virtual signature for SkCurve, to allow for heavily customized UI in SkGuiVisitor.
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500160 virtual void visit(const char* name, SkCurve& c);
Brian Osman112aa2d2019-02-15 10:45:56 -0500161
Brian Osman23b96c02019-02-19 13:04:40 -0500162 // Default visit function for structs with no special behavior. It is assumed that any such
163 // struct implements visitFields(SkFieldVisitor*) to recursively visit each of its fields.
Brian Osman7c979f52019-02-12 13:27:51 -0500164 template <typename T>
165 void visit(const char* name, T& value) {
166 this->enterObject(name);
167 value.visitFields(this);
168 this->exitObject();
169 }
170
Brian Osman23b96c02019-02-19 13:04:40 -0500171 // Specialization for SkTArrays. In conjunction with the enterArray/exitArray virtuals, this
172 // allows visitors to resize an array (for deserialization), and apply a single edit operation
173 // (remove or move a single element). Each element of the array is visited as normal.
Brian Osman5de7ea42019-02-14 13:23:51 -0500174 template <typename T, bool MEM_MOVE>
175 void visit(const char* name, SkTArray<T, MEM_MOVE>& arr) {
176 arr.resize_back(this->enterArray(name, arr.count()));
177 for (int i = 0; i < arr.count(); ++i) {
178 this->visit(nullptr, arr[i]);
Brian Osman7c979f52019-02-12 13:27:51 -0500179 }
Brian Osman5de7ea42019-02-14 13:23:51 -0500180 this->exitArray().apply(arr);
Brian Osman7c979f52019-02-12 13:27:51 -0500181 }
182
Brian Osman23b96c02019-02-19 13:04:40 -0500183 // Specialization for sk_sp pointers to types derived from SkReflected. Those types are known
184 // to implement visitFields. This allows the visitor to modify the contents of the object, or
185 // even replace it with an entirely new object. The virtual function uses SkReflected as a
186 // common type, but uses SkReflected::Type to communicate the required base-class. In this way,
187 // the new object can be verified to match the type of the original (templated) pointer.
Brian Osman7c979f52019-02-12 13:27:51 -0500188 template <typename T>
189 void visit(const char* name, sk_sp<T>& obj) {
190 this->enterObject(name);
191
192 sk_sp<SkReflected> newObj = obj;
193 this->visit(newObj, T::GetType());
194 if (newObj != obj) {
195 if (!newObj || newObj->isOfType(T::GetType())) {
196 obj.reset(static_cast<T*>(newObj.release()));
197 } else {
198 obj.reset();
199 }
200 }
201
202 if (obj) {
203 obj->visitFields(this);
204 }
205 this->exitObject();
206 }
207
208protected:
Brian Osman23b96c02019-02-19 13:04:40 -0500209 // Helper struct to allow exitArray to specify a single operation performed on the array.
Brian Osman5de7ea42019-02-14 13:23:51 -0500210 struct ArrayEdit {
211 enum class Verb {
212 kNone,
213 kRemove,
214 kMoveForward,
215 };
216
217 Verb fVerb = Verb::kNone;
218 int fIndex = 0;
219
220 template <typename T, bool MEM_MOVE>
221 void apply(SkTArray<T, MEM_MOVE>& arr) const {
222 switch (fVerb) {
223 case Verb::kNone:
224 break;
225 case Verb::kRemove:
226 for (int i = fIndex; i < arr.count() - 1; ++i) {
227 arr[i] = arr[i + 1];
228 }
229 arr.pop_back();
230 break;
231 case Verb::kMoveForward:
232 if (fIndex > 0 && fIndex < arr.count()) {
233 std::swap(arr[fIndex - 1], arr[fIndex]);
234 }
235 break;
236 }
237 }
238 };
239
Brian Osmane5d532e2019-02-26 14:58:40 -0500240 static const char* EnumToString(int value, const EnumStringMapping* map, int count) {
241 for (int i = 0; i < count; ++i) {
242 if (map[i].fValue == value) {
243 return map[i].fName;
244 }
245 }
246 return nullptr;
247 }
248 static int StringToEnum(const char* str, const EnumStringMapping* map, int count) {
249 for (int i = 0; i < count; ++i) {
250 if (0 == strcmp(str, map[i].fName)) {
251 return map[i].fValue;
252 }
253 }
254 return -1;
255 }
256
Brian Osman7c979f52019-02-12 13:27:51 -0500257 virtual void enterObject(const char* name) = 0;
258 virtual void exitObject() = 0;
Brian Osman5de7ea42019-02-14 13:23:51 -0500259
260 virtual int enterArray(const char* name, int oldCount) = 0;
261 virtual ArrayEdit exitArray() = 0;
262
Brian Osman7c979f52019-02-12 13:27:51 -0500263 virtual void visit(sk_sp<SkReflected>&, const SkReflected::Type* baseType) = 0;
Brian Osman7c979f52019-02-12 13:27:51 -0500264};
265
266#endif // SkReflected_DEFINED