Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Fwk/AppFwk/cafProjectDataModel/cafPdmCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ set(PROJECT_FILES
cafPdmChildField.inl
cafPdmDataValueField.h
cafPdmField.h
cafPdmField.cpp
cafPdmFieldCapability.h
cafPdmFieldHandle.cpp
cafPdmFieldHandle.h
Expand Down
27 changes: 27 additions & 0 deletions Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmField.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Explicit instantiation of the hot PdmField<T> specializations. The matching
// `extern template class PdmField<T>;` declarations live in cafPdmField.h, suppressing
// re-instantiation in every TU that includes the header.

#include "cafPdmField.h"

// PdmFieldHandle::capability<T>() uses dynamic_cast on PdmFieldCapability*, which requires a
// complete polymorphic type at the point of instantiation. Bring it in here so the explicit
// instantiations of setValueWithFieldChanged() can compile.
#include "cafPdmFieldCapability.h"

namespace caf
{
template class PdmField<int>;
template class PdmField<unsigned int>;
template class PdmField<double>;
template class PdmField<float>;
template class PdmField<bool>;
template class PdmField<QString>;
template class PdmField<std::vector<int>>;
template class PdmField<std::vector<double>>;
template class PdmField<std::vector<QString>>;
template class PdmField<std::optional<double>>;
template class PdmField<std::optional<QString>>;
template class PdmField<std::pair<bool, double>>;
template class PdmField<std::pair<bool, QString>>;
} // namespace caf
27 changes: 27 additions & 0 deletions Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmField.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,30 @@
#include "cafPdmDataValueField.h"
#undef PdmDataValueField
#undef CAF_IS_DEFINING_PDM_FIELD

// Suppress per-TU instantiation of the hot PdmField<T> specializations used throughout the
// codebase. The matching `template class PdmField<T>;` lines live in cafPdmField.cpp.
//
// extern template only suppresses out-of-class members and the vtable; in-class inline members
// (value(), operator==, etc.) are unaffected and still inlined where called.
//
// The includes for QString / std::vector / std::optional / std::pair come transitively via
// cafPdmDataValueField.h (QVariant, <vector>, <optional>, etc.). cafFilePath.h is the only
// header we need to add explicitly.

namespace caf
{
extern template class PdmField<int>;
extern template class PdmField<unsigned int>;
extern template class PdmField<double>;
extern template class PdmField<float>;
extern template class PdmField<bool>;
extern template class PdmField<QString>;
extern template class PdmField<std::vector<int>>;
extern template class PdmField<std::vector<double>>;
extern template class PdmField<std::vector<QString>>;
extern template class PdmField<std::optional<double>>;
extern template class PdmField<std::optional<QString>>;
extern template class PdmField<std::pair<bool, double>>;
extern template class PdmField<std::pair<bool, QString>>;
} // namespace caf
140 changes: 140 additions & 0 deletions Fwk/AppFwk/cafProjectDataModel/cafPdmObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class QXmlStreamWriter;
#include "cafPdmXmlObjectHandle.h"
#include "cafPdmXmlObjectHandleMacros.h"

#include <concepts>

namespace caf
{
class PdmFieldHandle;
Expand Down Expand Up @@ -151,6 +153,14 @@ class PdmObjectCapability;

namespace caf
{
class PdmObject;

/// Concept satisfied by any class publicly derived from PdmObject. Used to constrain the
/// macro-free init template methods so misuse produces a clear "constraints not satisfied"
/// diagnostic instead of a deep template error.
template <typename T>
concept PdmObjectDerived = std::derived_from<T, PdmObject>;

class PdmObject : public PdmObjectHandle, public PdmXmlObjectHandle, public PdmUiObjectHandle
{
public:
Expand Down Expand Up @@ -206,6 +216,136 @@ class PdmObject : public PdmObjectHandle, public PdmXmlObjectHandle, public PdmU
/// Consider this method private. Please use the CAF_PDM_InitFieldNoDefault() macro instead.
void addFieldUiNoDefault( PdmFieldHandle* field, const QString& keyword, PdmUiItemInfo* fieldDescription );

/// Macro-free alternative to CAF_PDM_InitObject.
///
/// The DerivedClass template parameter ensures each class gets its own unique
/// static PdmUiItemInfo instance (stored as a function-local static in each
/// template instantiation). All instances of the same DerivedClass share the
/// same static item info, matching the behavior of the macro.
///
/// Example:
/// @code
/// MyClass::MyClass()
/// {
/// initPdmObject<MyClass>( "UI Name", ":/icon.png", "Tooltip", "WhatsThis" );
/// }
/// @endcode
template <PdmObjectDerived DerivedClass>
void initPdmObject( const QString& uiName,
const QString& iconResourceName = {},
const QString& toolTip = {},
const QString& whatsThis = {} )
{
this->isInheritedFromPdmUiObject();
this->isInheritedFromPdmXmlSerializable();
this->registerClassKeyword( classKeyword() );

static caf::PdmUiItemInfo objectDescription( uiName, iconResourceName, toolTip, whatsThis );
this->setUiItemInfo( &objectDescription );
}

/// Macro-free alternative to CAF_PDM_InitField.
///
/// The DerivedClass and Keyword template parameters together ensure each
/// (class, field-keyword) pair gets its own unique static PdmUiItemInfo.
/// The Keyword non-type template parameter also enables compile-time
/// validation that the keyword is a valid XML element name.
///
/// Example:
/// @code
/// MyClass::MyClass()
/// {
/// initField<MyClass, caf::PdmKeyword{ "MyField" }>( &m_field, 42, "UI Name" );
/// }
/// @endcode
template <PdmObjectDerived DerivedClass, caf::PdmKeyword Keyword, typename FieldDataType>
void initField( PdmField<FieldDataType>* field,
const FieldDataType& defaultValue,
const QString& uiName,
const QString& iconResourceName = {},
const QString& toolTip = {},
const QString& whatsThis = {} )
{
static_assert( isFirstCharacterValidInXmlKeyword( Keyword.value ), "First character in keyword is invalid" );
static_assert( !isFirstThreeCharactersXml( Keyword.value ), "Keyword starts with invalid sequence xml" );
static_assert( isValidXmlKeyword( Keyword.value ), "Detected invalid character in keyword" );

// Compile/link-time guard mirroring CAF_PDM_InitField: ensures CAF_PDM_HEADER_INIT and
// CAF_PDM_SOURCE_INIT were added to DerivedClass. Missing HEADER_INIT yields a compile
// error (no such member); missing SOURCE_INIT yields a link error referencing this symbol.
static bool checkingThePresenceOfHeaderAndSourceInitMacros =
DerivedClass::Error_You_forgot_to_add_the_macro_CAF_PDM_XML_HEADER_INIT_and_or_CAF_PDM_XML_SOURCE_INIT_to_your_cpp_file_for_this_class();
Q_UNUSED( checkingThePresenceOfHeaderAndSourceInitMacros )

this->isInheritedFromPdmUiObject();
this->isInheritedFromPdmXmlSerializable();

addXmlCapabilityToField( field );
addUiCapabilityToField( field );
configureCapabilities( field );
registerClassWithField( classKeyword(), field );

static caf::PdmUiItemInfo fieldDescription( uiName, iconResourceName, toolTip, whatsThis, Keyword.value );
addFieldUi( field, QString( Keyword.value ), defaultValue, &fieldDescription );
}

/// Overload of initField() for PdmField<AppEnum<T>> that accepts a raw enum value as the default,
/// matching the convenience provided by CAF_PDM_InitField (which dispatches via overload resolution
/// on addFieldUi). Without this overload, callers would have to write
/// `AppEnum<MyEnum>{ MyEnum::Value }` for the default.
template <PdmObjectDerived DerivedClass, caf::PdmKeyword Keyword, typename EnumType>
void initField( PdmField<AppEnum<EnumType>>* field,
const EnumType& defaultValue,
const QString& uiName,
const QString& iconResourceName = {},
const QString& toolTip = {},
const QString& whatsThis = {} )
{
initField<DerivedClass, Keyword>( field, AppEnum<EnumType>( defaultValue ), uiName, iconResourceName, toolTip, whatsThis );
}

/// Macro-free alternative to CAF_PDM_InitFieldNoDefault.
///
/// Like initField(), but does not assign a default value to the field.
/// See initField() documentation for usage details.
///
/// Example:
/// @code
/// MyClass::MyClass()
/// {
/// initFieldNoDefault<MyClass, caf::PdmKeyword{ "MyField" }>( &m_field, "UI Name" );
/// }
/// @endcode
template <PdmObjectDerived DerivedClass, caf::PdmKeyword Keyword, typename FieldType>
void initFieldNoDefault( FieldType* field,
const QString& uiName,
const QString& iconResourceName = {},
const QString& toolTip = {},
const QString& whatsThis = {} )
{
static_assert( isFirstCharacterValidInXmlKeyword( Keyword.value ), "First character in keyword is invalid" );
static_assert( !isFirstThreeCharactersXml( Keyword.value ), "Keyword starts with invalid sequence xml" );
static_assert( isValidXmlKeyword( Keyword.value ), "Detected invalid character in keyword" );

// Compile/link-time guard mirroring CAF_PDM_InitFieldNoDefault: ensures CAF_PDM_HEADER_INIT
// and CAF_PDM_SOURCE_INIT were added to DerivedClass. Missing HEADER_INIT yields a compile
// error (no such member); missing SOURCE_INIT yields a link error referencing this symbol.
static bool checkingThePresenceOfHeaderAndSourceInitMacros =
DerivedClass::Error_You_forgot_to_add_the_macro_CAF_PDM_XML_HEADER_INIT_and_or_CAF_PDM_XML_SOURCE_INIT_to_your_cpp_file_for_this_class();
Q_UNUSED( checkingThePresenceOfHeaderAndSourceInitMacros )

this->isInheritedFromPdmUiObject();
this->isInheritedFromPdmXmlSerializable();

addXmlCapabilityToField( field );
addUiCapabilityToField( field );
configureCapabilities( field );
registerClassWithField( classKeyword(), field );

static caf::PdmUiItemInfo fieldDescription( uiName, iconResourceName, toolTip, whatsThis, Keyword.value );
addFieldUiNoDefault( field, QString( Keyword.value ), &fieldDescription );
}

protected:
PdmObjectHandle* doCopyObject() const override;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <cstddef>

constexpr bool isLowerCase( char c )
{
return ( c >= 'a' && c <= 'z' );
Expand Down Expand Up @@ -49,3 +51,36 @@ constexpr bool isFirstThreeCharactersXml( const char ( &arr )[N] )
{
return ( N < 3 ) ? false : arr[0] == 'x' && arr[1] == 'm' && arr[2] == 'l';
}

namespace caf
{
/// A structural type suitable for use as a non-type template parameter for PDM keywords.
///
/// By capturing the keyword as a compile-time template argument, this enables
/// compile-time XML keyword validation via static_assert in template functions,
/// providing a macro-free alternative to CAF_PDM_InitField and
/// CAF_PDM_InitFieldNoDefault.
///
/// Example usage:
/// @code
/// MyClass::MyClass()
/// {
/// initField<MyClass, caf::PdmKeyword{ "MyField" }>( &m_field, 0, "UI Name" );
/// }
/// @endcode
template <std::size_t N>
struct PdmKeyword
{
// N is the size of the string literal including the null terminator.
// For a keyword "MyField", N = 8 (7 characters + null terminator).
char value[N]{};

// NOLINTNEXTLINE(google-explicit-constructor): intentionally implicit from string literal
constexpr PdmKeyword( const char ( &str )[N] ) noexcept
{
for ( std::size_t i = 0; i < N; ++i )
value[i] = str[i];
}
};

} // namespace caf
22 changes: 20 additions & 2 deletions Fwk/AppFwk/cafProjectDataModel/cafPdm_CompileBench/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,18 @@ find_package(
qt_standard_project_setup()

set(PROJECT_FILES
bench_basic_fields.cpp bench_enum_fields.cpp bench_container_fields.cpp
bench_filepath_fields.cpp bench_mixed_fields.cpp
bench_basic_fields.cpp
bench_enum_fields.cpp
bench_container_fields.cpp
bench_filepath_fields.cpp
bench_mixed_fields.cpp
bench_heavy_fields.cpp
bench_basic_fields_template.cpp
bench_enum_fields_template.cpp
bench_container_fields_template.cpp
bench_filepath_fields_template.cpp
bench_mixed_fields_template.cpp
bench_heavy_fields_template.cpp
)

# OBJECT library: compiles each file individually without linking. This isolates
Expand All @@ -21,6 +31,14 @@ add_library(${PROJECT_NAME} OBJECT ${PROJECT_FILES})
# Disable unity build for this target — each file must compile independently
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD OFF)

# bench_heavy_fields_template.cpp generates many per-template-instantiation
# sections (one function-local static per (Class, Keyword) pair) and exceeds the
# default COFF section limit. /bigobj raises the limit; impact on compile time
# is negligible.
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /bigobj)
endif()

target_link_libraries(
${PROJECT_NAME} PRIVATE cafProjectDataModel Qt6::Core Qt6::Widgets
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Compile-time benchmark: same as bench_basic_fields.cpp, but uses the macro-free
// initPdmObject/initField template API. Used to measure the compile-time delta of the
// new API versus the CAF_PDM_InitObject/CAF_PDM_InitField macros.

#include "cafPdmField.h"
#include "cafPdmObject.h"

#include <QString>

// clang-format off

#define BENCH_BASIC_OBJECT_T( N ) \
class BenchBasicObjT##N : public caf::PdmObject \
{ \
CAF_PDM_HEADER_INIT; \
public: \
BenchBasicObjT##N() \
{ \
initPdmObject<BenchBasicObjT##N>( "BenchBasicObjT" #N ); \
initField<BenchBasicObjT##N, caf::PdmKeyword{ "IntA" }>( &m_intA, 0, "Int A" ); \
initField<BenchBasicObjT##N, caf::PdmKeyword{ "IntB" }>( &m_intB, 0, "Int B" ); \
initField<BenchBasicObjT##N, caf::PdmKeyword{ "DblA" }>( &m_dblA, 0.0, "Double A" ); \
initField<BenchBasicObjT##N, caf::PdmKeyword{ "DblB" }>( &m_dblB, 0.0, "Double B" ); \
initField<BenchBasicObjT##N, caf::PdmKeyword{ "BoolA" }>( &m_boolA, false, "Bool A" ); \
initField<BenchBasicObjT##N, caf::PdmKeyword{ "BoolB" }>( &m_boolB, false, "Bool B" ); \
initField<BenchBasicObjT##N, caf::PdmKeyword{ "StrA" }>( &m_strA, QString(), "String A" ); \
initField<BenchBasicObjT##N, caf::PdmKeyword{ "StrB" }>( &m_strB, QString(), "String B" ); \
} \
caf::PdmField<int> m_intA; \
caf::PdmField<int> m_intB; \
caf::PdmField<double> m_dblA; \
caf::PdmField<double> m_dblB; \
caf::PdmField<bool> m_boolA; \
caf::PdmField<bool> m_boolB; \
caf::PdmField<QString> m_strA; \
caf::PdmField<QString> m_strB; \
};

BENCH_BASIC_OBJECT_T( 01 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT01, "BenchBasicObjT01" );
BENCH_BASIC_OBJECT_T( 02 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT02, "BenchBasicObjT02" );
BENCH_BASIC_OBJECT_T( 03 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT03, "BenchBasicObjT03" );
BENCH_BASIC_OBJECT_T( 04 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT04, "BenchBasicObjT04" );
BENCH_BASIC_OBJECT_T( 05 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT05, "BenchBasicObjT05" );
BENCH_BASIC_OBJECT_T( 06 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT06, "BenchBasicObjT06" );
BENCH_BASIC_OBJECT_T( 07 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT07, "BenchBasicObjT07" );
BENCH_BASIC_OBJECT_T( 08 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT08, "BenchBasicObjT08" );
BENCH_BASIC_OBJECT_T( 09 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT09, "BenchBasicObjT09" );
BENCH_BASIC_OBJECT_T( 10 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT10, "BenchBasicObjT10" );
BENCH_BASIC_OBJECT_T( 11 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT11, "BenchBasicObjT11" );
BENCH_BASIC_OBJECT_T( 12 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT12, "BenchBasicObjT12" );
BENCH_BASIC_OBJECT_T( 13 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT13, "BenchBasicObjT13" );
BENCH_BASIC_OBJECT_T( 14 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT14, "BenchBasicObjT14" );
BENCH_BASIC_OBJECT_T( 15 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT15, "BenchBasicObjT15" );
BENCH_BASIC_OBJECT_T( 16 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT16, "BenchBasicObjT16" );
BENCH_BASIC_OBJECT_T( 17 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT17, "BenchBasicObjT17" );
BENCH_BASIC_OBJECT_T( 18 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT18, "BenchBasicObjT18" );
BENCH_BASIC_OBJECT_T( 19 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT19, "BenchBasicObjT19" );
BENCH_BASIC_OBJECT_T( 20 ) CAF_PDM_SOURCE_INIT( BenchBasicObjT20, "BenchBasicObjT20" );

// clang-format on
Loading
Loading