Doc: Improve PropertyContainer documentation

This commit is contained in:
Pieter Hijma
2025-04-29 20:39:56 +02:00
committed by Chris Hennes
parent 16a4e11eb2
commit ecb61310c1
2 changed files with 232 additions and 29 deletions

View File

@@ -73,13 +73,30 @@ enum PropertyType
struct AppExport PropertyData
{
/// @brief Struct to hold the property specification.
struct PropertySpec
{
/// The name of the property.
const char * Name;
/// The group name of the property.
const char * Group;
/// The documentation string of the property.
const char * Docu;
short Offset, Type;
/// The offset of the property in the container.
short Offset;
/// The type of the property.
short Type;
/**
* @brief Construct a PropertySpec.
*
* @param[in] name The name of the property.
* @param[in] group The group name of the property.
* @param[in] doc The documentation string of the property.
* @param[in] offset The offset of the property in the container.
* @param[in] type The type of the property.
*/
inline PropertySpec(const char *name, const char *group, const char *doc, short offset, short type)
:Name(name),Group(group),Docu(doc),Offset(offset),Type(type)
{}
@@ -89,15 +106,37 @@ struct AppExport PropertyData
//be able to return the offset to a property from the accepted containers. This allows you to use
//one function implementation for multiple container types without losing all type safety by
//accepting void*
/**
* @brief Struct that represents the base for offset calculation.
*
* This struct is used to calculate the offset of a property in a container.
* It can be constructed from either a PropertyContainer or an Extension.
*/
struct OffsetBase
{
/**
* @brief Construct an OffsetBase from a PropertyContainer.
*
* @param[in] container The PropertyContainer to construct from.
*/
// Lint wants these marked explicit, but they are currently used implicitly in enough
// places that I don't wnt to fix it. Instead we disable the Lint message.
// NOLINTNEXTLINE(runtime/explicit)
OffsetBase(const App::PropertyContainer* container) : m_container(container) {}
/**
* @brief Construct an OffsetBase from an Extension.
*
* @param[in] container The Extension to construct from.
*/
// NOLINTNEXTLINE(runtime/explicit)
OffsetBase(const App::Extension* container) : m_container(container) {}
/**
* @brief Get the offset to a property.
*
* @param[in] prop The property to get the offset to.
* @return The offset to the property relative to the base offset.
*/
short int getOffsetTo(const App::Property* prop) const {
auto *pt = (const char*)prop;
auto *base = (const char *)m_container;
@@ -105,6 +144,11 @@ struct AppExport PropertyData
return -1;
return (short) (pt-base);
}
/**
* @brief Get the base offset in bytes.
*
* @return The base offset in bytes.
*/
char* getOffset() const {return (char*) m_container;}
private:
@@ -112,11 +156,15 @@ struct AppExport PropertyData
};
// clang-format off
// A multi index container for holding the property spec, with the following
// index,
// * a sequence, to preserve creation order
// * hash index on property name
// * hash index on property pointer offset
/**
* @brief A multi index container for holding the property spec.
*
* The multi index has the following index:
* - a sequence, to preserve creation order
* - hash index on property name
* - hash index on property pointer offset
*/
mutable bmi::multi_index_container<
PropertySpec,
bmi::indexed_by<
@@ -133,15 +181,52 @@ struct AppExport PropertyData
> propertyData;
// clang-format on
/// Whether the property data is merged with the parent.
mutable bool parentMerged = false;
/// The parent property data.
const PropertyData* parentPropertyData;
void addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup= nullptr, PropertyType = Prop_None, const char* PropertyDocu= nullptr );
/**
* @brief Add a property to the property data.
*
* @param[in] offsetBase The base offset to offset the property.
* @param[in] PropName The name of the property.
* @param[in] Prop The property to add.
* @param[in] PropertyGroup The group name of the property.
* @param[in] Type The type of the property.
* @param[in] PropertyDocu The documentation string of the property.
*/
void addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup= nullptr, PropertyType Type=Prop_None, const char* PropertyDocu=nullptr );
/**
* @brief Find a property by its name.
*
* @param[in] offsetBase The base offset for the property.
* @param[in] PropName The name of the property to find.
* @return The property specification if found; `nullptr` otherwise.
*/
const PropertySpec *findProperty(OffsetBase offsetBase,const char* PropName) const;
/**
* @brief Find a property by its pointer.
*
* @param[in] offsetBase The base offset for the property.
* @param[in] prop The property to find.
* @return The property specification if found; `nullptr` otherwise.
*/
const PropertySpec *findProperty(OffsetBase offsetBase,const Property* prop) const;
/**
* @name PropertyData accessors
*
* @details These methods are used to access the property data based on:
* - the offset base,
* - either:
* - a property pointer, or
* - a property name.
* @{
*/
const char* getName (OffsetBase offsetBase,const Property* prop) const;
short getType (OffsetBase offsetBase,const Property* prop) const;
short getType (OffsetBase offsetBase,const char* name) const;
@@ -149,15 +234,70 @@ struct AppExport PropertyData
const char* getGroup (OffsetBase offsetBase,const Property* prop) const;
const char* getDocumentation(OffsetBase offsetBase,const char* name) const;
const char* getDocumentation(OffsetBase offsetBase,const Property* prop) const;
/// @}
/**
* @brief Get a property by its name.
*
* @param[in] offsetBase The base offset for the property.
* @param[in] name The name of the property to find.
* @return The property if found; `nullptr` otherwise.
*/
Property *getPropertyByName(OffsetBase offsetBase,const char* name) const;
/**
* @brief Get a map of properties.
*
* @param[in] offsetBase The base offset for the property.
* @param[out] Map A map of property names to properties.
*/
void getPropertyMap(OffsetBase offsetBase,std::map<std::string,Property*> &Map) const;
/**
* @brief Get a list of properties.
*
* @param[in] offsetBase The base offset for the property.
* @param[out] List A vector of properties.
*/
void getPropertyList(OffsetBase offsetBase,std::vector<Property*> &List) const;
/**
* @brief Get a list of properties with their names.
*
* @param[in] offsetBase The base offset for the property.
* @param[out] List A vector of pairs, where each pair contains the name and
* the property.
*/
void getPropertyNamedList(OffsetBase offsetBase, std::vector<std::pair<const char*,Property*> > &List) const;
/// See PropertyContainer::visitProperties for semantics
/**
* @brief Visit each property in the PropertyData struct.
*
* @param[in] offsetBase The base offset for the property.
* @param[in] visitor The function to apply to each property.
*
* @see PropertyContainer::visitProperties()
*/
void visitProperties(OffsetBase offsetBase, const std::function<void(Property*)>& visitor) const;
/**
* @brief Merge the PropertyData structs.
*
* Merge two PropertyData structs. If `other` is `nullptr`, merge with the
* parent PropertyData.
*
* @param[in] other (Optional) The other PropertyData to merge with;
*/
void merge(PropertyData *other=nullptr) const;
/**
* @brief Split the PropertyData structs.
*
* This method splits the PropertyData structs. It is used to
* restore the parent PropertyData after a merge.
*
* @param[in] other The other PropertyData to split with; this can be the parent PropertyData.
*/
void split(PropertyData *other);
};
@@ -251,7 +391,7 @@ public:
*
* The list may contain duplicates and aliases.
*
* @param[out] List A vector of pairs, where each pair contains the name and
* @param[out] list A vector of pairs, where each pair contains the name and
* the property.
*/
virtual void getPropertyNamedList(std::vector<std::pair<const char*,Property*> > &list) const;

View File

@@ -62,17 +62,28 @@
*
* @section propframe_intro Introduction
*
* The property framework introduces the ability to access attributes (member
* variables) of a class by name without knowing the class type. It's like the
* reflection mechanism of Java or C#. This ability is introduced by the @ref
* App::PropertyContainer "PropertyContainer" class and can be used by all
* derived classes. In particular, there are two classes that inherit from
* @ref App::PropertyContainer "PropertyContainer" which are @ref App::Document
* The property framework introduces an intricate system to access properties
* of objects. It provides the ability to:
* 1. define properties in a class and access them by name,
* 2. add properties to a class at runtime and access them by name, and
* 3. access properties of a class by name without knowing the class type.
*
* The first two points are similar to what the dynamic reflection framework of
* C# or Java offer. The third point allows FreeCAD to have App::Property as a
* generic interface to access properties. This is similar to the way that
* Python allows to access properties of a class by name.
*
* This ability is introduced by the @ref App::PropertyContainer
* "PropertyContainer" class and can be used by all derived classes. In
* particular, there are two classes that inherit from @ref
* App::PropertyContainer "PropertyContainer" which are @ref App::Document
* "Document" and @ref App::DocumentObject "DocumentObject". These two classes
* serve different purposes but are both able to hold properties. @ref
* App::PropertyContainer "PropertyContainer" contains the shared logic to do
* so.
*
* @section propframe_static_dynamic_props Static/Dynamic Properties
*
* In C++, it is possible to define properties as part of the class. These can
* be considered "static" properties but this term is typically not used within
* FreeCAD. Properties created in a class cannot be removed from a @ref
@@ -81,13 +92,65 @@
* However, it is also possible to add properties to a @ref
* App::PropertyContainer "PropertyContainer" at runtime. These properties are
* called "dynamic properties" and these properties can be freely added and
* removed by users.
* removed by users. In Python, all properties are dynamic properties.
*
* In Python, all properties are dynamic properties. This makes it difficult
* to understand whether properties are part of a Python class and are
* necessary for the functioning of the class, or whether a user has added
* these properties. Therefore, it is possible to indicate that a property is
* "locked":
* @section propframe_mechanisms Mechanisms
*
* The macros `PROPERTY_HEADER` and `PROPERTY_SOURCE` (and variants) are used
* to define a private static PropertyData member in the class. This data
* structure contains a multi index that maps 1) the property name to the
* property specifications @ref App::PropertyData::PropertySpec "PropertySpec",
* allowing access of properties by name, and 2) maps the offset of a property
* with respect to its container, allowing access to property specifications.
*
* The static function @ref App::PropertyContainer::getPropertyDataPtr()
* "PropertyContainer::getPropertyDataPtr()" is used to access the class-wide
* static @ref App::PropertyData "PropertyData" structure shared by all
* instances of the class. The virtual function @ref
* App::PropertyContainer::getPropertyData()
* "PropertyContainer::getPropertyData()" allows access to the class-level
* static PropertyData based on the dynamic type of the object, even when
* downcasted. This allows for example a @ref App::PropertyInteger
* "PropertyInteger*" downcasted to @ref App::Property "Property*" to access
* all its properties.
*
* Since the @ref App::PropertyData "PropertyData" structure is static in the
* class and maintains static information of properties, such as the group and
* documentation, we need to be able to access a specific instance given a
* property name. This is achieved by looking up the @ref
* App::PropertyData::PropertySpec "PropertySpec" in the index based on the
* property name. The property specification stores an offset to the property
* address given an @ref App::PropertyData::OffsetBase "OffsetBase" which wraps
* the address of a @ref App::PropertyContainer "PropertyContainer". See @ref
* App::PropertyData::findProperty() "PropertyData::findProperty()" and @ref
* App::PropertyData::OffsetBase::getOffsetTo() "OffsetBase::getOffsetTo()".
* The offset of the property relative to the offset base gives us the address
* of the property in the instance. Note that different values for properties
* across property containers are stored in the @ref App::Property "Property" instances.
*
* The reverse is also needed: Given a property in a property container, it is
* possible to compute the offset relative to the base of the property
* container. The index in @ref App::PropertyData "PropertyData" allows us to
* acquire the property spec and provides us with the static data associated
* with the property.
*
* Dynamic properties are stored in their own @ref App::DynamicProperty
* "DynamicProperty" container. It can be added with the function @ref
* App::PropertyContainer::addDynamicProperty()
* "PropertyContainer::addDynamicProperty()" and removed with @ref
* App::PropertyContainer::removeDynamicProperty()
* "PropertyContainer::removeDynamicProperty()". The rest of the interface of
* dynamic properties is virtually the same. In general, in FreeCAD, it is
* difficult for users to know whether a property is dynamic or static and they
* typically do not need to be concerned with this distinction.
*
* @section Locking Dynamic Properties
*
* Since in Python all properties are dynamic properties, it is difficult to
* understand whether properties are part of a Python class and are necessary
* for the functioning of the class, or whether a user has added these
* properties. Therefore, it is possible to indicate that a property is
* "locked":
*
* @code
* prop->setStatus(Property::LockDynamic, true);
@@ -107,13 +170,12 @@
*
* @section Examples
*
* Here some little examples how to use it:
* Here some little examples how to use the property framework:
*
* @code
* // search in PropertyList
* Property *prop = _pcFeature->getPropertyByName(attr);
* if(prop)
* {
* // Acquire a property by name and return the value as a Python object.
* Property *prop = feature->getPropertyByName(nameProperty);
* if (prop) {
* return prop->getPyObject();
* }
* @endcode
@@ -121,18 +183,19 @@
* or:
*
* @code
* // Restore properties from a reader.
* void PropertyContainer::Restore(Base::Reader &reader)
* {
* reader.readElement("Properties");
* int Cnt = reader.getAttributeAsInteger("Count");
*
* for(int i=0 ;i<Cnt ;i++)
* {
* for(int i=0 ;i<Cnt ;i++) {
* reader.readElement("Property");
* string PropName = reader.getAttribute("name");
* Property* prop = getPropertyByName(PropName.c_str());
* if(prop)
* if(prop) {
* prop->Restore(reader);
* }
*
* reader.readEndElement("Property");
* }