skia2/modules/particles/include/SkReflected.h
Brian Osman 9dac0d8216 Particles: Better integration for ResourceProvider
This untangles some of the dirty state tracking and dynamic rebuilding
support (that's only needed for the GUI editor), so the core code is
more streamlined. It also paves the way for feeding the RP to bindings.

Change-Id: I208ec59622154fdb2845c3ae8f7efb070d1abfc7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/257476
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2019-12-03 14:12:50 +00:00

229 lines
8.3 KiB
C++

/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkReflected_DEFINED
#define SkReflected_DEFINED
#include "include/core/SkColor.h"
#include "include/core/SkRefCnt.h"
#include "include/private/SkTArray.h"
#include <functional> // std::function
#include <string.h>
class SkFieldVisitor;
struct SkPoint;
class SkString;
/**
* Classes and macros for a lightweight reflection system.
*
* Classes that derive from SkReflected have several features:
* - Access to an SkReflected::Type instance, via static GetType() or virtual getType()
* The Type instance can be used to create additional instances (fFactory), get the name
* of the type, and answer queries of the form "is X derived from Y".
* - Given a string containing a type name, SkReflected can create an instance of that type.
* - SkReflected::VisitTypes can be used to enumerate all Types.
*
* Together, this simplifies the implementation of serialization and other dynamic type factories.
*
* Finally, all SkReflected-derived types must implement visitFields, which provides field-level
* reflection, in conjunction with SkFieldVisitor. See SkFieldVisitor, below.
*
* To create a new reflected class:
* - Derive the class (directly or indirectly) from SkReflected.
* - Ensure that the class can be default constructed.
* - In the public area of the class declaration, add REFLECTED(<ClassName>, <BaseClassName>).
* If the class is abstract, use REFLECTED_ABSTRACT(<ClassName>, <BaseClassName>) instead.
* - Add a one-time call to REGISTER_REFLECTED(<ClassName>) at initialization time.
* - Implement visitFields(), as described below.
*/
class SkReflected : public SkRefCnt {
public:
typedef sk_sp<SkReflected>(*Factory)();
struct Type {
const char* fName;
const Type* fBase;
Factory fFactory;
bool fRegistered = false;
bool isDerivedFrom(const Type* t) const {
const Type* base = fBase;
while (base) {
if (base == t) {
return true;
}
base = base->fBase;
}
return false;
}
};
virtual const Type* getType() const = 0;
static const Type* GetType() {
static Type gType{ "SkReflected", nullptr, nullptr };
RegisterOnce(&gType);
return &gType;
}
bool isOfType(const Type* t) const {
const Type* thisType = this->getType();
return thisType == t || thisType->isDerivedFrom(t);
}
static sk_sp<SkReflected> CreateInstance(const char* name) {
for (const Type* type : gTypes) {
if (0 == strcmp(name, type->fName)) {
return type->fFactory();
}
}
return nullptr;
}
virtual void visitFields(SkFieldVisitor*) = 0;
static void VisitTypes(std::function<void(const Type*)> visitor);
protected:
static void RegisterOnce(Type* type) {
if (!type->fRegistered) {
gTypes.push_back(type);
type->fRegistered = true;
}
}
private:
static SkSTArray<16, const Type*, true> gTypes;
};
#define REFLECTED(TYPE, BASE) \
static sk_sp<SkReflected> CreateProc() { \
return sk_sp<SkReflected>(new TYPE()); \
} \
static const Type* GetType() { \
static Type gType{ #TYPE, BASE::GetType(), CreateProc }; \
RegisterOnce(&gType); \
return &gType; \
} \
const Type* getType() const override { return GetType(); }
#define REFLECTED_ABSTRACT(TYPE, BASE) \
static const Type* GetType() { \
static Type gType{ #TYPE, BASE::GetType(), nullptr }; \
RegisterOnce(&gType); \
return &gType; \
} \
const Type* getType() const override { return GetType(); }
#define REGISTER_REFLECTED(TYPE) TYPE::GetType()
///////////////////////////////////////////////////////////////////////////////
/**
* SkFieldVisitor is an interface that can be implemented by any class to visit all fields of
* SkReflected types, and of types that implement the visitFields() function.
*
* Classes implementing the interface must supply implementations of virtual functions that visit
* basic types (float, int, bool, SkString), as well as helper methods for entering the scope of
* an object or array.
*
* All visit functions supply a field name, and a non-constant reference to an actual field.
* This allows visitors to serialize or deserialize collections of objects, or perform edits on
* existing objects.
*
* Classes that implement visitFields (typically derived from SkReflected) should simply call
* visit() for each of their fields, passing a (unique) field name, and the actual field. If your
* class has derived fields, it's best to only visit() the fields that you would serialize, then
* enforce any constraints afterwards.
*
* See SkParticleSerialization.h for example visitors that perform serialization to and from JSON.
*/
class SkFieldVisitor {
public:
virtual ~SkFieldVisitor() {}
// Visit functions for primitive types, to be implemented by derived visitors.
virtual void visit(const char*, float&) = 0;
virtual void visit(const char*, int&) = 0;
virtual void visit(const char*, bool&) = 0;
virtual void visit(const char*, SkString&) = 0;
// Specialization for SkTArrays. In conjunction with the enterArray/exitArray virtuals, this
// allows visitors to resize an array (for deserialization), and apply a single edit operation
// (remove a single element). Each element of the array is visited as normal.
template <typename T, bool MEM_MOVE>
void visit(const char* name, SkTArray<T, MEM_MOVE>& arr) {
arr.resize_back(this->enterArray(name, arr.count()));
for (int i = 0; i < arr.count(); ++i) {
this->visit(nullptr, arr[i]);
}
this->exitArray().apply(arr);
}
// Specialization for sk_sp pointers to types derived from SkReflected. Those types are known
// to implement visitFields. This allows the visitor to modify the contents of the object, or
// even replace it with an entirely new object. The virtual function uses SkReflected as a
// common type, but uses SkReflected::Type to communicate the required base-class. In this way,
// the new object can be verified to match the type of the original (templated) pointer.
template <typename T>
void visit(const char* name, sk_sp<T>& obj) {
this->enterObject(name);
sk_sp<SkReflected> newObj = obj;
this->visit(newObj, T::GetType());
if (newObj != obj) {
if (!newObj || newObj->isOfType(T::GetType())) {
obj.reset(static_cast<T*>(newObj.release()));
} else {
obj.reset();
}
}
if (obj) {
obj->visitFields(this);
}
this->exitObject();
}
protected:
// Helper struct to allow exitArray to specify a single operation performed on the array.
struct ArrayEdit {
enum class Verb {
kNone,
kRemove,
};
Verb fVerb = Verb::kNone;
int fIndex = 0;
template <typename T, bool MEM_MOVE>
void apply(SkTArray<T, MEM_MOVE>& arr) const {
switch (fVerb) {
case Verb::kNone:
break;
case Verb::kRemove:
for (int i = fIndex; i < arr.count() - 1; ++i) {
arr[i] = arr[i + 1];
}
arr.pop_back();
break;
}
}
};
private:
virtual void enterObject(const char* name) = 0;
virtual void exitObject() = 0;
virtual int enterArray(const char* name, int oldCount) = 0;
virtual ArrayEdit exitArray() = 0;
virtual void visit(sk_sp<SkReflected>&, const SkReflected::Type* baseType) = 0;
};
#endif // SkReflected_DEFINED