Advanced: Replication In-Depth
FactsDB is designed with multiplayer support as a first-class feature. Its architecture leverages standard, efficient Unreal Engine networking patterns to ensure that your game state is synchronized correctly and performantly.
The Replication Model
The core of the system follows a standard Server-Authoritative model.
- Changes are made on the Server: All modifications to replicated facts should occur on the server. The
UFactsComponent::UpdateFact
function will fail if called on a client for a replicated component. - Server Updates Data: The server updates the
FFactInstance
within theReplicatedFacts
container (FFactContainer
). - Engine Replicates Changes: Unreal Engine's networking system, using the highly efficient
FFastArraySerializer
, detects the change and sends only the modified data (a "delta") to the relevant clients. - Clients Receive Updates: On the client, the
FFactContainer
receives the new data and updates its localFFactInstance
. - Clients React to Changes: The replication process automatically triggers the
OnFactValueChanged
andOnGlobalFactValueChanged
delegates on the client, allowing client-side systems (like UI) to react to the new state.
Controlling What Gets Replicated
You have two levels of control over replication: at the Component level and at the individual Fact level. They work together to determine the final behavior.
1. Component-Level: EFactReplicationType
This property on the UFactsComponent
determines the overall replication strategy for its ReplicatedFacts
container.
-
Global
(Default)- Behavior: The component and its replicated facts are visible and sent to all connected clients.
- Use Case: Data that everyone needs to know, such as a player's health, name, or position in a shared world. The state of world objects like doors or objectives.
-
OwnerOnly
- Behavior: The component and its replicated facts are only sent to the owning client of the Actor. Other clients will not receive this data.
- Use Case: Data that is only relevant to the player controlling the character, but still needs to be server-authoritative. Examples include a player's personal inventory, ammo count for their current weapon, or cooldown timers for their abilities. This is a crucial optimization for reducing network traffic.
-
Local
- Behavior: The component does not replicate at all. All its facts, even those in the
ReplicatedFacts
container, are treated as local to the machine they are on. - Use Case: For purely cosmetic systems or client-side logic that doesn't need to be synchronized. For example, a client-side effect controller that uses facts to manage its state.
- Behavior: The component does not replicate at all. All its facts, even those in the
2. Fact-Level: bReplicate
This is a boolean property on the FFactDefinition
struct within your UDataTable
. It determines whether a fact is placed in the ReplicatedFacts
container or the LocalFacts
container upon initialization.
bReplicate = true
(Default)- The fact will be placed in the
ReplicatedFacts
container. Its replication will then be governed by the component'sEFactReplicationType
.
- The fact will be placed in the
bReplicate = false
- The fact will be placed in the
LocalFacts
container. It will never be replicated, regardless of the component's settings. - Use Case: For temporary, intermediate values on the server that clients don't need, or for purely client-side state information within a replicated component.
- The fact will be placed in the
How They Interact: A Decision Table
UFactsComponent ReplicationType | FFactDefinition bReplicate | Resulting Behavior |
---|---|---|
Global | true | Fact is replicated to all clients. |
Global | false | Fact is local to each machine (server/client). Not replicated. |
OwnerOnly | true | Fact is replicated to the owning client only. |
OwnerOnly | false | Fact is local to each machine. Not replicated. |
Local | true | Fact is local. ReplicationType overrides the fact's setting. |
Local | false | Fact is local. |
Initial Replication (OnInitialStateReceived
)
When a client first becomes aware of a replicated UFactsComponent
(e.g., when a player enters the network relevancy range of another player), the server sends a full initial state of all replicated facts.
Receiving these one by one and firing OnFactValueChanged
for each can be inefficient and lead to "pop-in" effects on UI. To solve this, the UFactsComponent
provides the OnInitialStateReceived
delegate.
- This delegate fires once on the client after the entire initial set of replicated facts has been received.
- Individual
OnFactValueChanged
events are suppressed during this initial dump. They will only begin firing for subsequent changes.
Always use OnInitialStateReceived
to perform the initial setup of your UI. Bind to this event to populate all health bars, ammo counts, etc. Use OnFactValueChanged
only for updating the UI after that initial setup is complete.
// In a client-side UI widget's BeginPlay or Initialize
void UMyPlayerHUD::InitializeForTarget(UFactsComponent* PlayerFactsComponent)
{
if (PlayerFactsComponent)
{
// Bind to both delegates
PlayerFactsComponent->OnInitialStateReceivedNative.AddUObject(this, &UMyPlayerHUD::HandleInitialState);
PlayerFactsComponent->OnFactValueChangedNative.AddUObject(this, &UMyPlayerHUD::HandleHealthChanged);
}
}
// Fired once when the component is first replicated to us.
void UMyPlayerHUD::HandleInitialState(UFactsComponent* SourceComponent)
{
// Do a full UI refresh here
UpdateHealthBar(SourceComponent);
UpdateAmmoCount(SourceComponent);
UpdateStatusEffectIcons(SourceComponent);
}
// Fired for every subsequent change after the initial state.
void UMyPlayerHUD::HandleHealthChanged(const FFactTag& FactTag, const FInstancedStruct& OldValue, const FInstancedStruct& NewValue)
{
if (FactTag == FGameplayTag::RequestGameplayTag(TEXT("Fact.Player.Stat.Health")))
{
// Do a targeted update for just the health bar
const float NewHealth = NewValue.Get<FFactFloat>().Value;
HealthBarWidget->SetPercent(NewHealth / 100.0f);
}
}