blob: cd4609ef5f3f44c36948c7604e83d52d0bf8e359 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "impeller/geometry/color.h"
#include "impeller/geometry/matrix.h"
#include "impeller/geometry/point.h"
#include "impeller/geometry/rect.h"
#include "impeller/geometry/scalar.h"
#include "impeller/geometry/vector.h"
namespace impeller {
struct CaptureProcTable;
#define _FOR_EACH_CAPTURE_PROPERTY(PROPERTY_V) \
PROPERTY_V(bool, Boolean, boolean) \
PROPERTY_V(int, Integer, integer) \
PROPERTY_V(Scalar, Scalar, scalar) \
PROPERTY_V(Point, Point, point) \
PROPERTY_V(Vector3, Vector3, vector3) \
PROPERTY_V(Rect, Rect, rect) \
PROPERTY_V(Color, Color, color) \
PROPERTY_V(Matrix, Matrix, matrix) \
PROPERTY_V(std::string, String, string)
template <typename Type>
struct CaptureCursorListElement {
std::string label;
explicit CaptureCursorListElement(const std::string& label) : label(label){};
virtual ~CaptureCursorListElement() = default;
//----------------------------------------------------------------------------
/// @brief Determines if previously captured data matches closely enough with
/// newly recorded data to safely emitted in its place. If this
/// returns `false`, then the remaining elements in the capture list
/// are discarded and re-recorded.
///
/// This mechanism ensures that the UI of an interactive inspector can
/// never deviate from reality, even if the schema of the captured
/// data were to significantly deviate.
///
virtual bool MatchesCloselyEnough(const Type& other) const = 0;
};
#define _CAPTURE_TYPE(type_name, pascal_name, lower_name) k##pascal_name,
#define _CAPTURE_PROPERTY_CAST_DECLARATION(type_name, pascal_name, lower_name) \
std::optional<type_name> As##pascal_name() const;
/// A capturable property type
struct CaptureProperty : public CaptureCursorListElement<CaptureProperty> {
enum class Type { _FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_TYPE) };
struct Options {
struct Range {
Scalar min;
Scalar max;
};
/// Readonly properties are always re-recorded during capture. Any edits
/// made to readonly values in-between captures are overwritten during the
/// next capture.
bool readonly = false;
/// An inspector hint that can be used for displaying sliders. Only used for
/// numeric types. Rounded down for integer types.
std::optional<Range> range;
};
Options options;
CaptureProperty(const std::string& label, Options options);
virtual ~CaptureProperty();
virtual Type GetType() const = 0;
virtual void Invoke(const CaptureProcTable& proc_table) = 0;
bool MatchesCloselyEnough(const CaptureProperty& other) const override;
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_CAST_DECLARATION)
};
#define _CAPTURE_PROPERTY_DECLARATION(type_name, pascal_name, lower_name) \
struct Capture##pascal_name##Property final : public CaptureProperty { \
type_name value; \
\
static std::shared_ptr<Capture##pascal_name##Property> \
Make(const std::string& label, type_name value, Options options); \
\
/* |CaptureProperty| */ \
Type GetType() const override; \
\
/* |CaptureProperty| */ \
void Invoke(const CaptureProcTable& proc_table) override; \
\
private: \
Capture##pascal_name##Property(const std::string& label, \
type_name value, \
Options options); \
\
FML_DISALLOW_COPY_AND_ASSIGN(Capture##pascal_name##Property); \
};
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_DECLARATION);
#define _CAPTURE_PROC(type_name, pascal_name, lower_name) \
std::function<void(Capture##pascal_name##Property&)> lower_name = \
[](Capture##pascal_name##Property& value) {};
struct CaptureProcTable {
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROC)
};
template <typename Type>
class CapturePlaybackList {
public:
CapturePlaybackList() = default;
~CapturePlaybackList() {
// Force the list element type to inherit the CRTP type. We can't enforce
// this as a template requirement directly because `CaptureElement` has a
// recursive `CaptureCursorList<CaptureElement>` property, and so the
// compiler fails the check due to the type being incomplete.
static_assert(std::is_base_of_v<CaptureCursorListElement<Type>, Type>);
}
void Rewind() { cursor_ = 0; }
size_t Count() { return values_.size(); }
std::shared_ptr<Type> GetNext(std::shared_ptr<Type> captured,
bool force_overwrite) {
if (cursor_ < values_.size()) {
std::shared_ptr<Type>& result = values_[cursor_];
if (result->MatchesCloselyEnough(*captured)) {
if (force_overwrite) {
values_[cursor_] = captured;
}
// Safe playback is possible.
++cursor_;
return result;
}
// The data has changed too much from the last capture to safely continue
// playback. Discard this and all subsequent elements to re-record.
values_.resize(cursor_);
}
++cursor_;
values_.push_back(captured);
return captured;
}
std::shared_ptr<Type> FindFirstByLabel(const std::string& label) {
for (std::shared_ptr<Type>& value : values_) {
if (value->label == label) {
return value;
}
}
return nullptr;
}
void Iterate(std::function<void(Type&)> iterator) const {
for (auto& value : values_) {
iterator(*value);
}
}
private:
size_t cursor_ = 0;
std::vector<std::shared_ptr<Type>> values_;
FML_DISALLOW_COPY_AND_ASSIGN(CapturePlaybackList);
};
/// A document of capture data, containing a list of properties and a list
/// of subdocuments.
struct CaptureElement final : public CaptureCursorListElement<CaptureElement> {
CapturePlaybackList<CaptureProperty> properties;
CapturePlaybackList<CaptureElement> children;
static std::shared_ptr<CaptureElement> Make(const std::string& label);
void Rewind();
bool MatchesCloselyEnough(const CaptureElement& other) const override;
private:
explicit CaptureElement(const std::string& label);
FML_DISALLOW_COPY_AND_ASSIGN(CaptureElement);
};
#ifdef IMPELLER_ENABLE_CAPTURE
#define _CAPTURE_PROPERTY_RECORDER_DECLARATION(type_name, pascal_name, \
lower_name) \
type_name Add##pascal_name(const std::string& label, type_name value, \
CaptureProperty::Options options = {});
#else
#define _CAPTURE_PROPERTY_RECORDER_DECLARATION(type_name, pascal_name, \
lower_name) \
inline type_name Add##pascal_name(const std::string& label, type_name value, \
CaptureProperty::Options options = {}) { \
return value; \
}
#endif
class Capture {
public:
explicit Capture(const std::string& label);
Capture();
static Capture MakeInactive();
inline Capture CreateChild(const std::string& label) {
#ifdef IMPELLER_ENABLE_CAPTURE
if (!active_) {
return Capture();
}
auto new_capture = Capture(label);
new_capture.element_ =
element_->children.GetNext(new_capture.element_, false);
new_capture.element_->Rewind();
return new_capture;
#else
return Capture();
#endif
}
std::shared_ptr<CaptureElement> GetElement() const;
void Rewind();
_FOR_EACH_CAPTURE_PROPERTY(_CAPTURE_PROPERTY_RECORDER_DECLARATION)
private:
#ifdef IMPELLER_ENABLE_CAPTURE
std::shared_ptr<CaptureElement> element_;
bool active_ = false;
#endif
};
class CaptureContext {
public:
CaptureContext();
static CaptureContext MakeInactive();
static CaptureContext MakeAllowlist(
std::initializer_list<std::string> allowlist);
bool IsActive() const;
void Rewind();
Capture GetDocument(const std::string& label);
bool DoesDocumentExist(const std::string& label) const;
private:
struct InactiveFlag {};
explicit CaptureContext(InactiveFlag);
CaptureContext(std::initializer_list<std::string> allowlist);
#ifdef IMPELLER_ENABLE_CAPTURE
bool active_ = false;
std::optional<std::unordered_set<std::string>> allowlist_;
std::unordered_map<std::string, Capture> documents_;
#endif
};
} // namespace impeller