Skip to content

Commit

Permalink
Chunk initial session settings replication
Browse files Browse the repository at this point in the history
  • Loading branch information
mircearoata committed Jan 1, 2025
1 parent 2f502d6 commit 2138786
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ void ASessionSettingsSubsystem::Init() {
check(SessionSettingsManager);

if (HasAuthority()) {
FGameModeEvents::GameModePostLoginEvent.AddUObject(this, &ASessionSettingsSubsystem::GameModePostLogin);
// We have to chunk the replication of a map, and the server must decide how much data to send,
// so we cannot send the list of missing setting names, as that could, itself, overflow the buffer (though arguably unlikely)
// For this, we only populate the settings names list on server to ensure it gets replicated,
// and then we can use this order to decide which settings to send in each chunk, and the client can know which settings the values belong to
SessionSettingsManager->GetAllSessionSettings().GetKeys(SessionSettingsNames);
}

OnOptionUpdatedDelegate = FOnOptionUpdated::CreateUObject(this, &ASessionSettingsSubsystem::OnSessionSettingUpdated);
Expand Down Expand Up @@ -48,31 +52,9 @@ void ASessionSettingsSubsystem::PushSettingToSessionSettings(const FString& StrI
SessionSettingsManager->ForceSetOptionValue(StrID, value, this);
}

void ASessionSettingsSubsystem::GameModePostLogin(AGameModeBase* GameMode, APlayerController* PlayerController) const {
if (!PlayerController || PlayerController->GetWorld() != GetWorld())
return;

AFGPlayerController* FGPlayerController = Cast<AFGPlayerController>(PlayerController);
if (!FGPlayerController) {
UE_LOG(LogSatisfactoryModLoader, Warning, TEXT("Skipping client Mod Session Settings initialization, PlayerController is not an AFGPlayerController"));
return;
}
// AFGGameMode::PostLogin sets the remote call objects on the player controller
// before calling Super, so the RCOs are valid here
SendAllSessionSettings(FGPlayerController);
}

void ASessionSettingsSubsystem::SendAllSessionSettings(AFGPlayerController* PlayerController) const {
USMLSessionSettingsRemoteCallObject* RCO = PlayerController->GetRemoteCallObjectOfClass<USMLSessionSettingsRemoteCallObject>();

USessionSettingsManager* SessionSettingsManager = GetWorld()->GetSubsystem<USessionSettingsManager>();
TMap<FString, UFGUserSettingApplyType*> Settings = SessionSettingsManager->GetAllSessionSettings();
for (TPair<FString, UFGUserSettingApplyType*> Setting : Settings)
{
FVariant AppliedValue = Setting.Value->GetAppliedValue();
FString ValueString = USessionSettingsManager::VariantToString(AppliedValue);
RCO->Client_SendSessionSetting(Setting.Key, ValueString);
}
void ASessionSettingsSubsystem::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const {
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ASessionSettingsSubsystem, SessionSettingsNames);
}

void ASessionSettingsSubsystem::Multicast_SessionSettingUpdated_Implementation(const FString& StrID, const FString& value) {
Expand All @@ -82,6 +64,26 @@ void ASessionSettingsSubsystem::Multicast_SessionSettingUpdated_Implementation(c
PushSettingToSessionSettings(StrID, USessionSettingsManager::StringToVariant(value));
}

void ASessionSettingsSubsystem::OnRep_SessionSettingsNames() {
if (!HasPendingSessionSettingsRequest && !HasAuthority()) {
// We're on a client, so there should be only one player controller
// and we only care to request the settings, so even if there are more, it doesn't matter
AFGPlayerController* FirstPlayerController = GetWorld()->GetFirstPlayerController<AFGPlayerController>();
if (FirstPlayerController) {
USMLSessionSettingsRemoteCallObject* RCO = FirstPlayerController->GetRemoteCallObjectOfClass<USMLSessionSettingsRemoteCallObject>();
if (RCO) {
ReceivedSessionSettings = 0;
RCO->Server_RequestInitialSessionSettings(0);
HasPendingSessionSettingsRequest = true;
}
}
}
if (!HasPendingSessionSettingsRequest) {
// Retry until the RCO is available
GetWorld()->GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &ASessionSettingsSubsystem::OnRep_SessionSettingsNames));
}
}

USMLSessionSettingsRemoteCallObject::USMLSessionSettingsRemoteCallObject() {
this->mForceNetField_USMLSessionSettingsRemoteCallObject = false;
}
Expand All @@ -104,16 +106,47 @@ bool USMLSessionSettingsRemoteCallObject::Server_RequestSessionSettingUpdate_Val
return SessionSettingsManager != NULL && SessionSettingsManager->FindSessionSetting(SessionSettingName) != NULL;
}

void USMLSessionSettingsRemoteCallObject::Client_SendSessionSetting_Implementation(const FString& SessionSettingName, const FString& ValueString)
{
void USMLSessionSettingsRemoteCallObject::Server_RequestInitialSessionSettings_Implementation(const int& CurrentIndex) {
ASessionSettingsSubsystem* SessionSettingsSubsystem = ASessionSettingsSubsystem::Get(GetWorld());
fgcheck(SessionSettingsSubsystem);

// Current values for session settings are primitives, so they don't take up much space
// But if we support more complex types, we may need to limit the size of the data sent, not the number of settings
int NumSettingsToSend = FMath::Min(SessionSettingsSubsystem->SessionSettingsNames.Num() - CurrentIndex, 512);
TArray<FString> SessionSettingsNamesToSend;
SessionSettingsNamesToSend.Reserve(NumSettingsToSend);
for (int i = 0; i < NumSettingsToSend; i++) {
SessionSettingsNamesToSend.Add(SessionSettingsSubsystem->SessionSettingsNames[CurrentIndex + i]);
}

USessionSettingsManager* SessionSettingsManager = GetWorld()->GetSubsystem<USessionSettingsManager>();
check(SessionSettingsManager);
fgcheck(SessionSettingsManager);

TArray<FString> SettingValues;
SettingValues.Reserve(NumSettingsToSend);
for (const FString& SettingName : SessionSettingsNamesToSend) {
FVariant Value = SessionSettingsManager->FindSessionSetting(SettingName)->GetAppliedValue();
FString ValueString = USessionSettingsManager::VariantToString(Value);
SettingValues.Add(ValueString);
}

SessionSettingsManager->ForceSetOptionValue(SessionSettingName, USessionSettingsManager::StringToVariant(ValueString), this);
Client_RespondInitialSessionSettings(SettingValues);
}

bool USMLSessionSettingsRemoteCallObject::Client_SendSessionSetting_Validate(const FString& SessionSettingName, const FString& ValueString)
void USMLSessionSettingsRemoteCallObject::Client_RespondInitialSessionSettings_Implementation(const TArray<FString>& SessionSettings)
{
const USessionSettingsManager* SessionSettingsManager = GetWorld()->GetSubsystem<USessionSettingsManager>();
return SessionSettingsManager != NULL && !SessionSettingName.IsEmpty() && !ValueString.IsEmpty();
ASessionSettingsSubsystem* SessionSettingsSubsystem = ASessionSettingsSubsystem::Get(GetWorld());
fgcheck(SessionSettingsSubsystem);

USessionSettingsManager* SessionSettingsManager = GetWorld()->GetSubsystem<USessionSettingsManager>();
fgcheck(SessionSettingsManager);

for (int i = 0; i < SessionSettings.Num(); i++) {
SessionSettingsSubsystem->PushSettingToSessionSettings(SessionSettingsSubsystem->SessionSettingsNames[SessionSettingsSubsystem->ReceivedSessionSettings + i], USessionSettingsManager::StringToVariant(SessionSettings[i]));
}
SessionSettingsSubsystem->ReceivedSessionSettings += SessionSettings.Num();
SessionSettingsSubsystem->HasPendingSessionSettingsRequest = false;
if (SessionSettingsSubsystem->ReceivedSessionSettings < SessionSettingsSubsystem->SessionSettingsNames.Num()) {
Server_RequestInitialSessionSettings(SessionSettingsSubsystem->ReceivedSessionSettings);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,24 @@ class SML_API ASessionSettingsSubsystem : public AModSubsystem {
void OnSessionSettingUpdated(const FString StrID, FVariant value);
void PushSettingToSessionSettings( const FString& StrID, FVariant value );

private:
void GameModePostLogin(AGameModeBase* GameMode, APlayerController* PlayerController) const;
void SendAllSessionSettings(AFGPlayerController* PlayerController) const;

virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;

private:
UFUNCTION(NetMulticast, Reliable)
void Multicast_SessionSettingUpdated(const FString& StrID, const FString& ValueString);

FOnOptionUpdated OnOptionUpdatedDelegate;

UFUNCTION()
void OnRep_SessionSettingsNames();

UPROPERTY(ReplicatedUsing=OnRep_SessionSettingsNames)
TArray<FString> SessionSettingsNames;

bool HasPendingSessionSettingsRequest = false;
int ReceivedSessionSettings = 0;

friend class USMLSessionSettingsRemoteCallObject;
};

UCLASS(NotBlueprintable)
Expand All @@ -38,10 +48,15 @@ class SML_API USMLSessionSettingsRemoteCallObject : public UFGRemoteCallObject {
UFUNCTION(Server, Reliable, WithValidation)
void Server_RequestSessionSettingUpdate(const FString& SessionSettingName, const FString& ValueString);

UFUNCTION(Client, Reliable, WithValidation)
void Client_SendSessionSetting(const FString& SessionSettingName, const FString& ValueString);

private:
UFUNCTION(Server, Reliable)
void Server_RequestInitialSessionSettings(const int& CurrentIndex);

UFUNCTION(Client, Reliable)
void Client_RespondInitialSessionSettings(const TArray<FString>& SessionSettingsValues);

UPROPERTY(Replicated)
bool mForceNetField_USMLSessionSettingsRemoteCallObject;

friend class ASessionSettingsSubsystem;
};

0 comments on commit 2138786

Please sign in to comment.