Skip to main content

Traits

The Traits module provides compile-time detection of component lifecycle hooks using C++20 concepts. This enables zero-overhead conditional invocation of optional methods.

Overview

Traits use concepts to detect if a type implements specific methods:

template<typename T>
concept HasOnAcquire = requires(T t) {
{t.onAcquire()} -> std::same_as<void>;
};

At compile time, the ComponentReflector uses these traits to generate appropriate function pointers, avoiding runtime checks.

Available Traits

TraitRequired MethodReturn TypePurpose
HasOnAcquireonAcquire()voidPool acquisition callback
HasOnReleaseonRelease()voidPool release callback
HasOnRemoveonRemove()boolRemoval interception
HasToogleableenable(), disable()voidComponent enable/disable
HasCloneonClone(const T&)voidPost-copy initialization
HasActivatableonActivate(), onDeactivate()voidGameObject activation

Trait Definitions

HasOnAcquire

Detects components that need initialization when acquired from a pool:

template<typename T>
concept HasOnAcquire = requires(T t) {
{t.onAcquire()} -> std::same_as<void>;
};

HasOnRelease

Detects components that need cleanup when released to a pool:

template<typename T>
concept HasOnRelease = requires(T t) {
{t.onRelease()} -> std::same_as<void>;
};

HasOnRemove

Detects components that can intercept removal:

template<typename T>
concept HasOnRemove = requires(T t) {
{t.onRemove()} -> std::convertible_to<bool>;
};

Returns true to allow removal, false to prevent it.

HasToogleable

Detects components that can be enabled/disabled:

template<typename T>
concept HasToogleable = requires(T t) {
{t.disable()} -> std::same_as<void>;
{t.enable()} -> std::same_as<void>;
};

Note: Both methods must be present for the trait to be satisfied.

HasClone

Detects components that need post-copy initialization:

template<typename T>
concept HasClone = requires(T t, const T& source) {
{t.onClone(source)} -> std::same_as<void>;
};

HasActivatable

Detects components that respond to GameObject activation:

template<typename T>
concept HasActivatable = requires(T t) {
{t.onActivate()} -> std::same_as<void>;
{t.onDeactivate()} -> std::same_as<void>;
};

Usage in ComponentReflector

The ComponentReflector uses traits with if constexpr for zero-overhead dispatch:

template<typename T>
static void registerType() {
ComponentOps ops{
.onAcquire = [](void* ptr) {
if constexpr (traits::HasOnAcquire<T>) {
static_cast<T*>(ptr)->onAcquire();
}
},

.onRemove = [](void* ptr) -> bool {
if constexpr (traits::HasOnRemove<T>) {
return static_cast<T*>(ptr)->onRemove();
}
return true;
},

// ... other hooks
};
}

Implementing Traits in Components

Components opt-in to lifecycle hooks by implementing the required methods:

class HealthComponent {
float health_;
float maxHealth_;

public:
// Satisfies HasOnAcquire
void onAcquire() noexcept {
health_ = maxHealth_;
}

// Satisfies HasOnRelease
void onRelease() noexcept {
health_ = maxHealth_;
}
};

// Compile-time verification
static_assert(traits::HasOnAcquire<HealthComponent>);
static_assert(traits::HasOnRelease<HealthComponent>);

Performance

Traits provide zero runtime overhead:

  1. Compile-time detection - No runtime type checks
  2. Dead code elimination - Empty if constexpr branches are removed
  3. Inlining - Lambda bodies are typically inlined
// If T doesn't have onAcquire(), this compiles to nothing:
if constexpr (traits::HasOnAcquire<T>) {
ptr->onAcquire(); // Only compiled if trait is satisfied
}

See Also