Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
990 changes: 372 additions & 618 deletions Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::CollectData(APlayerController*
DataPack.ActiveEffects = AbilityComponent->GetActiveEffectsString();
DataPack.NBActiveEffects = AbilityComponent->GetActiveEffects().Num();
DataPack.ActiveEffectData = AbilityComponent->GetActiveEffectsDataString();
DataPack.NBActiveEffectData = AbilityComponent->ActiveEffectsData.Num();
DataPack.NBActiveEffectData = AbilityComponent->ActiveEffectIDs.Num();
DataPack.ActiveAbilities = AbilityComponent->GetActiveAbilitiesString();
DataPack.NBActiveAbilities = AbilityComponent->GetActiveAbilities().Num();
DataPack.NBCachedOperationPayloads = AbilityComponent->BoundQueueV2.OperationPayloads.Num();

AbilityComponent->GMCMovementComponent->SV_SwapServerState();
}
Expand All @@ -55,7 +56,7 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::DrawData(APlayerController* Own
// Abilities
CanvasContext.Printf(TEXT("{blue}[server] {yellow}Granted Abilities (%d): {white}%s%s"), DataPack.NBGrantedAbilities, *DataPack.GrantedAbilities.Left(MaxCharDisplayAbilities), DataPack.GrantedAbilities.Len() > MaxCharDisplayAbilities ? TEXT("...") : TEXT(""));
// Show client-side data
if (AbilityComponent)
if (AbilityComponent) // Todo: Stop having every dang thing check for AbilityComponent being null
{
if (DataPack.NBGrantedAbilities != AbilityComponent->GetGrantedAbilities().Num())
{
Expand Down Expand Up @@ -113,20 +114,31 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::DrawData(APlayerController* Own
if (AbilityComponent)
{
if (DataPack.NBActiveEffects != AbilityComponent->GetActiveEffects().Num())
CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects: {red} [INCOHERENCY] {white}%s"), *AbilityComponent->GetActiveEffectsString());
CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects: {red} [INCOHERENCY] {white}%s\n"), *AbilityComponent->GetActiveEffectsString());
else
CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects: {white}%s"), *AbilityComponent->GetActiveEffectsString());
CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects: {white}%s\n"), *AbilityComponent->GetActiveEffectsString());
}

// Active Effects Data
CanvasContext.Printf(TEXT("{blue}[server] {yellow}Active Effects Data: {white}%s"), *DataPack.ActiveEffectData);
// Show client-side data
if (AbilityComponent)
{
if (DataPack.NBActiveEffectData != AbilityComponent->ActiveEffectsData.Num())
CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects Data: {red} [INCOHERENCY] {white}%s"), *AbilityComponent->GetActiveEffectsDataString());
if (DataPack.NBActiveEffectData != AbilityComponent->ActiveEffectIDs.Num())
CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects Data: {red} [INCOHERENCY] {white}%s\n"), *AbilityComponent->GetActiveEffectsDataString());
else
CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects Data: {white}%s\n"), *AbilityComponent->GetActiveEffectsDataString());
}

// Cached Operations Data
CanvasContext.Printf(TEXT("{blue}[server] {yellow}Cached Operations: {white}%d"), DataPack.NBCachedOperationPayloads);
// Show client-side data
if (AbilityComponent)
{
if (DataPack.NBActiveEffectData != AbilityComponent->ActiveEffectIDs.Num())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Incorrect Cached Operations Incoherency Check

The Gameplay Debugger's "Cached Operations" display incorrectly checks for data incoherency. It compares DataPack.NBActiveEffectData with AbilityComponent->ActiveEffectIDs.Num() instead of DataPack.NBCachedOperationPayloads with AbilityComponent->BoundQueueV2.OperationPayloads.Num(). This leads to inaccurate incoherency detection for cached operations.

Fix in Cursor Fix in Web

CanvasContext.Printf(TEXT("{green}[client] {yellow}Cached Operations: {red} [INCOHERENCY] {white}%d\n"), AbilityComponent->BoundQueueV2.OperationPayloads.Num());
else
CanvasContext.Printf(TEXT("{green}[client] {yellow}Active Effects Data: {white}%s"), *AbilityComponent->GetActiveEffectsDataString());
CanvasContext.Printf(TEXT("{green}[client] {yellow}Cached Operations: {white}%d\n"), AbilityComponent->BoundQueueV2.OperationPayloads.Num());
}

}
Expand All @@ -152,6 +164,7 @@ void FGameplayDebuggerCategory_GMCAbilitySystem::FRepData::Serialize(FArchive& A
Ar << NBActiveEffects;
Ar << NBActiveEffectData;
Ar << NBActiveAbilities;
Ar << NBCachedOperationPayloads;
}

#endif
1 change: 0 additions & 1 deletion Source/GMCAbilitySystem/Private/Utility/GMASBoundQueue.cpp

This file was deleted.

165 changes: 165 additions & 0 deletions Source/GMCAbilitySystem/Private/Utility/GMASBoundQueueV2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#include "Utility/GMASBoundQueueV2.h"

#include <Utility/GMASBoundQueueV2.h>

#include "GMCAbilitySystem.h"
#include "GMCMovementUtilityComponent.h"


bool FGMASBoundQueueV2::IsValidGMASOperation(const FInstancedStruct& Data) const
{
// Check if the operation is a valid type
if (!OperationData.IsValid()) return false;

const FGMASBoundQueueV2OperationBaseData* BaseData = OperationData.GetPtr<FGMASBoundQueueV2OperationBaseData>();
if (!BaseData)
{
UE_LOG(LogGMCAbilitySystem, Error, TEXT("OperationData is not a valid type"));
return false;
}

return true;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Function Uses Incorrect Variable for Validation

The IsValidGMASOperation function incorrectly uses the member variable OperationData for its validation checks (e.g., IsValid() and GetPtr<>()) instead of the Data parameter. This leads to improper validation of input operations.

Fix in Cursor Fix in Web


bool FGMASBoundQueueV2::IsValidClientOperation(const FInstancedStruct& Data) const
{
if (Data.IsValid())
{
const UScriptStruct* StructType = Data.GetScriptStruct();
if (ValidClientInputOperationTypes.Contains(StructType))
{
return true;
}
UE_LOG(LogGMCAbilitySystem, Error, TEXT("Client operation type %s is not allowed by server as client input"), *StructType->GetName());
}
return false;
}

void FGMASBoundQueueV2::ClearStaleOperationData()
{
const int MaximumFreshMoveIndex = GMCMoveCounter - GMCMovementComponent->MoveHistoryMaxSize;
for (auto It = OperationDataCacheExpiration.CreateIterator(); It; ++It)
{
const int64 MoveAddedAt = It->ModeAddedAt;
if (MoveAddedAt < MaximumFreshMoveIndex)
{
const int OperationID = It->OperationID;
if (!OperationPayloads.Contains(OperationID))
{
UE_LOG(LogGMCAbilitySystem, Warning, TEXT("OperationID %d not found in OperationPayloads, but still in cache expiration map"), OperationID);
continue;
}
// Remove stale operation data
OperationPayloads.Remove(OperationID);
It.RemoveCurrent();
}
}
}

void FGMASBoundQueueV2::BindToGMC(UGMC_MovementUtilityCmp* MovementComponent)
{
OperationData = FInstancedStruct::Make<FGMASBoundQueueV2OperationBaseData>();

BI_OperationData = MovementComponent->BindInstancedStruct(
OperationData,
EGMC_PredictionMode::ClientAuth_InputOutput,
EGMC_CombineMode::CombineIfUnchanged,
EGMC_SimulationMode::None,
EGMC_InterpolationFunction::TargetValue);

GMCMovementComponent = MovementComponent;
}

void FGMASBoundQueueV2::GenPreLocalMoveExecution()
{
// UE_LOG(LogTemp, Warning, TEXT("OperationDataType: %s"), *OperationData.GetScriptStruct()->GetName());
// Client Logic
if (GMCMovementComponent->GetNetMode() == NM_Client ||
GMCMovementComponent->GetNetMode() == NM_Standalone ||
GMCMovementComponent->IsLocallyControlledListenServerPawn() ||
GMCMovementComponent->IsLocallyControlledDedicatedServerPawn())
{
// Get a pending operation
if (ClientQueuedOperations.Num() > 0)
{
const int OperationIDToProcess = ClientQueuedOperations.Pop();
if (OperationPayloads.Contains(OperationIDToProcess))
{
FInstancedStruct OperationPayload;
OperationPayload.InitializeAs<FGMASBoundQueueV2OperationBaseData>(OperationIDToProcess);
OperationData = OperationPayload;
}
}
else
{
OperationData = FInstancedStruct::Make<FGMASBoundQueueV2OperationBaseData>();
}
}
}

void FGMASBoundQueueV2::GenAncillaryTick(const float DeltaTime)
{

if (GMCMovementComponent->GetNetMode() >= NM_Client)
{
GMCMoveCounter++;
ClearStaleOperationData();
}

// Tick all Server Queued Operations
for (auto It = ServerQueuedBoundOperationsGracePeriods.CreateIterator(); It; ++It)
{
It.Value() -= DeltaTime;

if (It.Value() <= 0)
{
if (OperationPayloads.Contains(It.Key()))
{
OnServerOperationForced.Broadcast(OperationPayloads[It.Key()]);
OperationPayloads.Remove(It.Key());
}
It.RemoveCurrent();
}
}
}

void FGMASBoundQueueV2::CacheOperationPayload(const int OperationID, const FInstancedStruct& Payload)
{
OperationPayloads.Add(OperationID, Payload);
OperationDataCacheExpiration.Add({OperationID, GMCMoveCounter});
}

void FGMASBoundQueueV2::QueueClientOperation(const int OperationID)
{
ClientQueuedOperations.Add(OperationID);
}

void FGMASBoundQueueV2::QueueServerOperation(const int OperationID, const float Timeout)
{
if (!OperationPayloads.Contains(OperationID))
{
UE_LOG(LogTemp, Error, TEXT("Tried to queue server operation, but server operation %d not found in payloads"), OperationID);
return;
}

const FInstancedStruct QueuedOperation = OperationPayloads[OperationID];

// Add to server timeout map
ServerQueuedBoundOperationsGracePeriods.Add(OperationID, Timeout);

// Notify
OnServerOperationAdded.Broadcast(OperationID, QueuedOperation);
}

void FGMASBoundQueueV2::ServerAcknowledgeOperation(int ID)
{
if (OperationPayloads.Contains(ID))
{
OperationPayloads.Remove(ID);
}

if (ServerQueuedBoundOperationsGracePeriods.Contains(ID))
{
ServerQueuedBoundOperationsGracePeriods.Remove(ID);
}
}
Loading