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);
}
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);
}
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
}
}