/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef C2UTILS_INTERFACE_HELPER_H_ #define C2UTILS_INTERFACE_HELPER_H_ #include #include #include #include #include #include /** * Interface Helper */ using C2R = C2SettingResultsBuilder; template::value> struct _c2_reduce_enum_to_underlying_type { typedef T type; }; template struct _c2_reduce_enum_to_underlying_type { typedef typename std::underlying_type::type type; }; /** * Helper class to implement parameter reflectors. This class is dynamic and is designed to be * shared by multiple interfaces. This allows interfaces to add structure descriptors as needed. */ class C2ReflectorHelper : public C2ParamReflector { public: C2ReflectorHelper() = default; virtual ~C2ReflectorHelper() = default; virtual std::unique_ptr describe( C2Param::CoreIndex paramIndex) const override; /** * Adds support for describing the given parameters. * * \param Params types of codec 2.0 structs (or parameters) to describe */ template C2_INLINE void addStructDescriptors() { std::vector structs; addStructDescriptors(structs, (_Tuple *)nullptr); } /** * Adds support for describing a specific struct. * * \param strukt descriptor for the struct that will be moved out. */ void addStructDescriptor(C2StructDescriptor &&strukt); private: template class C2_HIDE _Tuple { }; /** * Adds support for describing the given descriptors. * * \param structs List of structure descriptors to add support for */ void addStructDescriptors( std::vector &structs, _Tuple<> *); /** * Utility method that adds support for describing the given descriptors in a recursive manner * one structure at a time using a list of structure descriptors temporary. * * \param T the type of codec 2.0 struct to describe * \param Params rest of the structs * \param structs Temporary list of structure descriptors used to optimize the operation. */ template C2_INLINE void addStructDescriptors( std::vector &structs, _Tuple *) { structs.emplace_back((T*)nullptr); addStructDescriptors(structs, (_Tuple *)nullptr); } mutable std::mutex _mMutex; std::map _mStructs; ///< descriptors }; /** * Utility class that implements the codec 2.0 interface API-s for some parameters. * * This class must be subclassed. */ class C2InterfaceHelper { public: /** * Returns the base offset of a field at |offset| that could be part of an array or part of a * sub-structure. * * This method does not do field size verification, e.g. if offset if obtained from a structure, * it will not stop at the structure boundary - this is okay, as we just want the base offset * here, which is the same. */ static size_t GetBaseOffset(const std::shared_ptr &reflector, C2Param::CoreIndex index, size_t offset); /** * The interface helper class uses references to std::shared_ptr config parameters. * Internally, these need to be generalized to std::shared_ptr refs, but the cast is * not allowed (as these are references). As such, this class never returns pointer to the * shared_ptrs. */ struct ParamRef { template::value>::type> inline C2_HIDE ParamRef(std::shared_ptr ¶m) : _mRef(reinterpret_cast*>(¶m)) { } // used by comparison operator for containers operator std::shared_ptr *() const { return _mRef; } /** * Returns a shared pointer to the parameter. */ std::shared_ptr get() const { return *_mRef; } private: std::shared_ptr *_mRef; }; /** * Field helper. * * Contains additional information for the field: possible values, and currently supported * values. */ class FieldHelper { public: /** * Creates helper for a field with given possible values. * * \param param parameter reference. The parameter does not have to be initialized at this * point. * \param field field identifier * \param values possible values for the field */ FieldHelper(const ParamRef ¶m, const _C2FieldId &field, std::unique_ptr &&values); /** * Creates a param-field identifier for this field. This method is called after the * underlying parameter has been initialized. * * \aram index * * @return C2ParamField */ C2ParamField makeParamField(C2Param::Index index) const; /** * Sets the currently supported values for this field. * * \param values currently supported values that will be moved out */ void setSupportedValues(std::unique_ptr &&values); /** * Gets the currently supported values for this field. This defaults to the possible values * if currently supported values were never set. */ const C2FieldSupportedValues *getSupportedValues() const; /** * Gets the possible values for this field. */ const C2FieldSupportedValues *getPossibleValues() const; protected: // TODO: move to impl for safety ParamRef mParam; _C2FieldId mFieldId; std::unique_ptr mPossible; std::unique_ptr mSupported; ///< if different from possible }; template struct C2_HIDE Param; class ParamHelper; /** * Factory is an interface to get the parameter helpers from a std::shared_ptr &. */ class Factory { // \todo this may be already in ParamHelper virtual std::shared_ptr getReflector() const = 0; virtual std::shared_ptr getParamHelper(const ParamRef ¶m) const = 0; public: virtual ~Factory() = default; template Param get(std::shared_ptr ¶m, std::shared_ptr altValue = nullptr) const { return Param(getParamHelper(ParamRef(param)), altValue == nullptr ? param : altValue, getReflector()); } }; /** * Typed field helper. */ template struct Field { /** * Constructor. * * \param helper helper for this field * \param index parameter index (this is needed as it is not available during parameter * construction) \todo remove */ Field(std::shared_ptr helper, C2Param::Index index); bool supportsAtAll(T value) const { return C2FieldSupportedValuesHelper(*_mHelper->getPossibleValues()).supports(value); } bool supportsNow(T value) const { return C2FieldSupportedValuesHelper(*_mHelper->getSupportedValues()).supports(value); } /** * Creates a conflict resolution suggestion builder for this field. */ C2ParamFieldValuesBuilder shouldBe() const; /** * Creates a currently supported values builder for this field. This is only supported * for non-const fields to disallow setting supported values for dependencies. */ C2ParamFieldValuesBuilder mustBe(); operator C2ParamField() const { return _mField; } // TODO C2R validatePossible(const T &value __unused) const { /// TODO return C2R::Ok(); } private: std::shared_ptr _mHelper; C2ParamField _mField; }; class ParamHelper { public: ParamHelper(ParamRef param, C2StringLiteral name, C2StructDescriptor &&); ParamHelper(ParamHelper &&); ~ParamHelper(); /** * Finds a field descriptor. */ std::shared_ptr findField(size_t baseOffs, size_t baseSize) const; /// returns the parameter ref for this parameter const ParamRef ref() const; /// returns the current value of this parameter as modifiable. The constness of this /// object determines the constness of the returned value. std::shared_ptr value(); /// returns the current value of this parameter as const std::shared_ptr value() const; /** * Performs a configuration change request for this parameter. * * \param value the value that is being assigned to this parameter. * This could be pointing to the current value of the * parameter. This must not change. * \param mayBlock whether blocking is allowed * \param endValue the resulting value * \param factory parameter factory (to access dependencies) * \param failures vector of failures to append any failures from this * operation * * \retval C2_OK configuration was successful * \retval C2_BAD_VALUE value is incorrect (TBD) * \retval C2_NO_MEMORY not enough memory to perform the assignment * \retval C2_TIMED_OUT configuration timed out * \retval C2_BLOCKING configuration requires blocking to be allowed * \retval C2_CORRUPTED interface is corrupted */ c2_status_t trySet( const C2Param *value, bool mayBlock, bool *changed, Factory &factory, std::vector>* const failures); /// returns parameter indices that depend on this parameter const std::vector getDownDependencies() const; /// adds a dependent parameter void addDownDependency(C2Param::Index index); /// returns that parameter refs for parameters that depend on this const std::vector getDependenciesAsRefs() const; /// returns and moves out stored struct descriptor C2StructDescriptor retrieveStructDescriptor(); /// returns the name of this parameter C2String name() const; /// returns the index of this parameter C2Param::Index index() const; /// returns the parameter descriptor std::shared_ptr getDescriptor() const; /** * Validates param helper. * * For now, this fills field info for const params. * * \retval C2_CORRUPTED the parameter cannot be added as such */ c2_status_t validate(const std::shared_ptr &reflector); protected: typedef C2ParamDescriptor::attrib_t attrib_t; attrib_t& attrib(); /// sets the default value of this parameter void setDefaultValue(std::shared_ptr default_); /// sets the setter method void setSetter(std::function setter); /// sets the getter method void setGetter(std::function(bool)> getter); /// sets the dependencies void setDependencies(std::vector indices, std::vector refs); /// sets the fields and their supported values void setFields(std::vector &&fields); /// build this into a final ParamHelper object std::shared_ptr build(); class Impl; std::unique_ptr mImpl; }; /** * Typed parameter helper. This provides access to members as well as field helpers. */ template struct C2_HIDE Param { Param( std::shared_ptr helper, std::shared_ptr ¶m, std::shared_ptr reflector) : v(*param.get()), _mTypedParam(param), _mHelper(helper), _mReflector(reflector) { } template using FieldType = Field< typename _c2_reduce_enum_to_underlying_type< typename std::remove_const< typename std::remove_extent::type>::type>::type>; template FieldType F(S &field) { size_t offs = (uintptr_t)&field - (uintptr_t)&get(); // this must fall either within sizeof(T) + FLEX_SIZE or param->size() // size_t size = sizeof(field); // mParam may be null size_t baseSize = sizeof(typename std::remove_extent::type); size_t baseOffs = GetBaseOffset( _mReflector, T::CORE_INDEX, offs - sizeof(C2Param)); if (~baseOffs == 0) { // C2_LOG(FATAL) << "unknown field at offset " << offs << " size " << sizeof(S) // << " base-size " << baseSize; // __builtin_trap(); } else { baseOffs += sizeof(C2Param); } std::shared_ptr helper = _mHelper->findField(baseOffs, baseSize); return FieldType(helper, _mTypedParam->index()); } // const Param have const Fields; however, remove const from S template const FieldType F(S &field) const { return const_cast>(const_cast(this)->F(field)); } /// Returns a const ref value of this const param. const T &get() const { return *_mTypedParam.get(); } /// Returns a modifiable ref value of this non-const param. T &set() { return *_mTypedParam.get(); } /// Const-reference to the value.s T const &v; private: std::shared_ptr _mTypedParam; std::shared_ptr _mHelper; std::shared_ptr _mReflector; }; template using C2P = Param; /** * Templated move builder class for a parameter helper. */ template class C2_HIDE ParamBuilder : private ParamHelper { public: /** Construct the parameter builder from minimal info required. */ ParamBuilder(std::shared_ptr ¶m, C2StringLiteral name) : ParamHelper(param, name, C2StructDescriptor((T*)nullptr)), mTypedParam(¶m) { attrib() = attrib_t::IS_PERSISTENT; } /** Makes this parameter required. */ inline ParamBuilder &required() { attrib() |= attrib_t::IS_REQUIRED; return *this; } /** Makes this parameter transient (not persistent). */ inline ParamBuilder &transient() { attrib() &= ~attrib_t::IS_PERSISTENT; return *this; } /** Makes this parameter hidden (not exposed in JAVA API). */ inline ParamBuilder &hidden() { attrib() |= attrib_t::IS_HIDDEN; return *this; } /** Makes this parameter internal (not exposed to query/settings). */ inline ParamBuilder &internal() { attrib() |= attrib_t::IS_INTERNAL; return *this; } /** Adds default value. Must be added exactly once. */ inline ParamBuilder &withDefault(std::shared_ptr default_) { // CHECK(!mDefaultValue); // WARN_IF(!default_); // could be nullptr if OOM // technically, this could be in the parent *mTypedParam = std::shared_ptr(T::From(C2Param::Copy(*default_).release())); setDefaultValue(default_); std::shared_ptr *typedParam = mTypedParam; setGetter([typedParam](bool) -> std::shared_ptr { return std::static_pointer_cast(*typedParam); }); return *this; } /** Adds default value. Must be added exactly once. */ inline ParamBuilder &withDefault(T *default_) { return withDefault(std::shared_ptr(default_)); } /** Adds all fields to this parameter with their possible values. */ inline ParamBuilder &withFields(std::vector &&fields_) { setFields(std::move(fields_)); return *this; } /** * Adds a constant value (also as default). Must be added exactly once. * * Const parameters by definition have no dependencies. */ inline ParamBuilder &withConstValue(std::shared_ptr default_) { attrib() |= attrib_t::IS_CONST; setSetter([default_]( const C2Param *value, bool mayBlock __unused, bool *changed, Factory &) -> C2R { *changed = false; const T *typedValue = T::From(value); if (typedValue == nullptr) { return C2R::Corrupted(); // TODO BadValue/Invalid. This should not happen here. } if (*typedValue != *default_) { return C2R::Corrupted(); // TODO ReadOnly(*default_); } *changed = false; return C2R::Ok(); }); return withDefault(default_); } /** Adds constant value (also as default). Must be added exactly once. */ inline ParamBuilder &withConstValue(T *default_) { return withConstValue(std::shared_ptr(default_)); } /** * Use a strict setter. * * \param fn strict setter * \param deps dependencies (references) */ template inline ParamBuilder &withSetter( C2R (*fn)(bool, const C2P &, C2P &, const C2P &...), std::shared_ptr& ... deps) { attrib() |= attrib_t::IS_STRICT; std::shared_ptr *typedParam = mTypedParam; setSetter([typedParam, fn, &deps...]( const C2Param *value, bool mayBlock, bool *changed, Factory &factory) -> C2R { *changed = false; const T *typedValue = T::From(value); if (typedValue == nullptr) { return C2R::Corrupted(); // TODO BadValue/Invalid. This should not happen here. } // Do copy-on-change for parameters in this helper so change can be detected by // a change of the pointer. Do this by working on a proposed value. std::shared_ptr proposedValue = std::shared_ptr(T::From(C2Param::Copy(*value).release())); if (proposedValue == nullptr) { return C2R::NoMemory(value->index()); } C2P oldValue = factory.get(*typedParam); // Get a parameter helper with value pointing to proposedValue C2P helper = factory.get(*typedParam, proposedValue); C2R result = fn(mayBlock, oldValue, helper, factory.get(deps)...); // If value changed, copy result to current value if (helper.get() != *typedParam->get()) { *typedParam = proposedValue; *changed = true; } return result; }); setDependencies(std::vector{ deps->index()... }, std::vector{ ParamRef(deps)... }); return *this; } /** * Use a non-strict setter. * * \param fn non-strict setter * \param deps dependencies (references) */ template inline ParamBuilder &withSetter( C2R (*fn)(bool, C2P &, const C2P &...), std::shared_ptr& ... deps) { std::shared_ptr *typedParam = mTypedParam; setSetter([typedParam, fn, &deps...]( const C2Param *value, bool mayBlock, bool *changed, Factory &factory) -> C2R { *changed = false; const T *typedValue = T::From(value); if (typedValue == nullptr) { return C2R::Corrupted(); // TODO BadValue/Invalid. This should not happen here. } // Do copy-on-change for parameters in this helper so change can be detected by // a change of the pointer. Do this by working on a proposed value. std::shared_ptr proposedValue = std::shared_ptr(T::From(C2Param::Copy(*value).release())); if (proposedValue == nullptr) { return C2R::NoMemory(value->index()); } // Get a parameter helper with value pointing to proposedValue C2P helper = factory.get(*typedParam, proposedValue); C2R result = fn(mayBlock, helper, factory.get(deps)...); // If value changed, copy result to current value if (helper.get() != *typedParam->get()) { *typedParam = proposedValue; *changed = true; } return result; }); setDependencies(std::vector{ deps->index()... }, std::vector{ ParamRef(deps)... }); return *this; } /** * Marks this a calculated (read-only) field. * * \param fn non-strict setter (calculator) * \param deps dependencies (references) */ template inline ParamBuilder &calculatedAs( C2R (*fn)(bool, C2P &, const C2P &...), std::shared_ptr& ... deps) { attrib() |= attrib_t::IS_READ_ONLY; return withSetter(fn, std::forward(deps)...); } inline std::shared_ptr build() { return ParamHelper::build(); } protected: std::shared_ptr *mTypedParam; }; template static ParamBuilder DefineParam(std::shared_ptr ¶m, C2StringLiteral name) { return ParamBuilder(param, name); } public: c2_status_t query( const std::vector &stackParams, const std::vector &heapParamIndices, c2_blocking_t mayBlock, std::vector>* const heapParams) const; /** * Helper implementing config calls as well as other configuration updates. * * This method is virtual, so implementations may provide wrappers around it (and perform * actions just before and after a configuration). * * \param params * \param mayBlock * \param failures * \param updateParams if true, the updated parameter values are copied back into the arguments * passed in |params| * \param changes pointed to a vector to receive settings with their values changed. If not * null, settings with their values changed are added to this. * \return result from config */ virtual c2_status_t config( const std::vector ¶ms, c2_blocking_t mayBlock, std::vector>* const failures, bool updateParams = true, std::vector> *changes = nullptr); c2_status_t querySupportedParams( std::vector> *const params) const; c2_status_t querySupportedValues( std::vector &fields, c2_blocking_t mayBlock) const; std::shared_ptr getReflector() { return mReflector; } typedef std::unique_lock Lock; /** * Locks the interface and returns a lock. This lock must be unlocked or released without * calling any other blocking call. */ Lock lock() const; private: void setInterfaceAddressBounds(uintptr_t start, uintptr_t end) { // TODO: exclude this helper (void)start; (void)end; } protected: mutable std::mutex mMutex; std::shared_ptr mReflector; struct FactoryImpl; std::shared_ptr _mFactory; C2InterfaceHelper(std::shared_ptr reflector); /** * Adds a parameter to this interface. * \note This method CHECKs. * * \param param parameter to add. */ void addParameter(std::shared_ptr param); /** * Returns the dependency index for a parameter. * * \param ix the index of the parameter */ size_t getDependencyIndex_l(C2Param::Index ix) const; virtual ~C2InterfaceHelper() = default; /** * Sets subclass instance's address and size. * * \todo allow subclass to specify parameter address range directly (e.g. do not assume that * they are local to the subclass instance) * * \param T type of the derived instance * \param instance pointer to the derived instance */ template inline C2_HIDE void setDerivedInstance(T *instance) { setInterfaceAddressBounds((uintptr_t)instance, (uintptr_t)(instance + 1)); } C2_DO_NOT_COPY(C2InterfaceHelper); }; /** * Creates a C2ParamFieldValuesBuilder class for a field of a parameter * * \param spParam a configuration parameter in an interface class subclassed from C2InterfaceHelper. * \param field a field of such parameter */ #define C2F(spParam, field) \ C2ParamFieldValuesBuilder< \ typename _c2_reduce_enum_to_underlying_type< \ typename std::remove_reference< \ typename std::remove_extent< \ decltype(spParam->field)>::type>::type>::type>( \ C2ParamField(spParam.get(), &spParam->field)) #endif // C2UTILS_INTERFACE_HELPER_H_