Skip to main content

C++ API and Usage

This guide provides practical C++ code examples for common interactions with the FactsDB runtime system.

Including Headers

To get started, you will typically need to include the following headers in your .cpp files. Make sure your module in MyProject.Build.cs has a dependency on the FactsDB module.

// For the runtime subsystem and components
#include "Subsystems/FactRuntimeSubsystem.h"
#include "Components/FactsComponent.h"

// For core data types
#include "Core/FactTagTypes.h"
#include "Core/FactTypes.h"
#include "Core/FactWrappers.h" // For creating wrapper structs like FFactFloat

Accessing the Subsystem

The UFactRuntimeSubsystem is the primary entry point for all global operations. You can get an instance of it from any UObject that has a valid UWorld context (like an Actor or ActorComponent).

UFactRuntimeSubsystem* FactsSubsystem = UFactRuntimeSubsystem::Get(this);
if (!FactsSubsystem)
{
// Handle error: Subsystem not available
return;
}

Reading a Fact

To read a fact, you need the FFactContextID of the target component and the FFactTag of the fact you want.

// Assume we have a valid ContextID and the subsystem
FFactContextID TargetContext = ...;
UFactRuntimeSubsystem* FactsSubsystem = ...;

// Define the tag for the fact we want to read
const FFactTag HealthFactTag(FGameplayTag::RequestGameplayTag(TEXT("Fact.Player.Stat.Health")));

FInstancedStruct FactValue;
FFactOperationResult Result = FactsSubsystem->GetFact(TargetContext, HealthFactTag, FactValue);

if (Result.bSuccess)
{
// Check if the value is of the type we expect (e.g., FFactFloat)
if (FactValue.GetScriptStruct() == FFactFloat::StaticStruct())
{
const float CurrentHealth = FactValue.Get<FFactFloat>().Value;
UE_LOG(LogTemp, Log, TEXT("Player health is: %f"), CurrentHealth);
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to get health fact: %s"), *Result.Message);
}
Direct Component Access

If you already have a pointer to the UFactsComponent, it's more direct to get the fact from it. The FindFact method is the most performant way as it returns a const pointer and avoids a struct copy.

UFactsComponent* PlayerFactsComponent = ...;
const FFactTag HealthFactTag(FGameplayTag::RequestGameplayTag(TEXT("Fact.Player.Stat.Health")));

if (const FFactInstance* HealthInstance = PlayerFactsComponent->FindFact(HealthFactTag))
{
const float CurrentHealth = HealthInstance->Value.Get<FFactFloat>().Value;
}

Writing a Fact

Writing a fact follows a similar pattern, but requires creating an FInstancedStruct to hold the new value.

// Assume we have a valid ContextID and the subsystem
FFactContextID TargetContext = ...;
UFactRuntimeSubsystem* FactsSubsystem = ...;
const float NewHealthValue = 80.0f;

// 1. Define the tag for the fact we want to update
const FFactTag HealthFactTag(FGameplayTag::RequestGameplayTag(TEXT("Fact.Player.Stat.Health")));

// 2. Create an FInstancedStruct containing the new value
FInstancedStruct NewValueStruct;
NewValueStruct.InitializeAs<FFactFloat>(NewHealthValue);

// 3. Call the update function on the subsystem
FFactOperationResult Result = FactsSubsystem->UpdateFact(TargetContext, HealthFactTag, NewValueStruct);

if (!Result.bSuccess)
{
UE_LOG(LogTemp, Warning, TEXT("Failed to update health fact: %s"), *Result.Message);
}
Authority for Updates

Remember that UpdateFact calls will only succeed on the server for replicated components. You should wrap update logic in an authority check: if (HasAuthority()) { ... }.

Batching Multiple Updates

When you need to update multiple facts in the same frame, it is highly recommended to use the batch update system to prevent firing an excessive number of events. The FScopedFactBatchUpdater RAII helper makes this simple and safe.

void AMyGameMode::ApplyAllPlayerDebuffs()
{
// The constructor calls BeginBatchUpdate() on the subsystem.
FScopedFactBatchUpdater BatchUpdater(this);

// Perform as many updates as you need...
FactsSubsystem->UpdateFact(PlayerContext1, SpeedDebuffTag, SpeedDebuffValue);
FactsSubsystem->UpdateFact(PlayerContext1, DamageDebuffTag, DamageDebuffValue);
FactsSubsystem->UpdateFact(PlayerContext2, SpeedDebuffTag, SpeedDebuffValue);
// ...
// ...

} // <-- The BatchUpdater destructor is called here, which calls EndBatchUpdate().
// All queued change events are now fired in a single batch.

Listening to Fact Changes

You can react to fact changes in C++ by binding to the subsystem's or component's native delegates.

Global Listening (Any Fact, Any Context)

Bind to the subsystem's OnGlobalFactValueChangedNative in a system's initialization phase.

// In some manager's Initialize function
void UMyUIManager::Initialize()
{
UFactRuntimeSubsystem* FactsSubsystem = UFactRuntimeSubsystem::Get(this);
if (FactsSubsystem)
{
FactsSubsystem->OnGlobalFactValueChangedNative.AddUObject(this, &UMyUIManager::HandleGlobalFactChange);
}
}

void UMyUIManager::HandleGlobalFactChange(UFactsComponent* SourceComponent, const FFactTag& FactTag, const FInstancedStruct& OldValue, const FInstancedStruct& NewValue)
{
// Example: Check if any player's health changed
if (FactTag == FGameplayTag::RequestGameplayTag(TEXT("Fact.Player.Stat.Health")))
{
const float Health = NewValue.Get<FFactFloat>().Value;
// Update the UI for the player associated with SourceComponent->GetContextID()
}
}

Local Listening (Facts in a specific component)

If a system only cares about changes on a specific actor, it's more efficient to bind directly to the UFactsComponent's delegate.

// In an Actor's BeginPlay
void AMyPlayerCharacter::BeginPlay()
{
Super::BeginPlay();

FactsComponent = FindComponentByClass<UFactsComponent>();
if (FactsComponent)
{
FactsComponent->OnFactValueChangedNative.AddUObject(this, &AMyPlayerCharacter::HandleLocalFactChange);
}
}

void AMyPlayerCharacter::HandleLocalFactChange(const FFactTag& FactTag, const FInstancedStruct& OldValue, const FInstancedStruct& NewValue)
{
if (FactTag == FGameplayTag::RequestGameplayTag(TEXT("Fact.Player.State.HasKey")))
{
const bool bNowHasKey = NewValue.Get<FFactBool>().Value;
// Play a sound or show a particle effect
}
}