diff --git a/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp b/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp index e82a03c604b32871baf9ffb818881ba1fd1195e4..08651d743f304a858d7a444657d69f692b09a85a 100644 --- a/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp +++ b/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp @@ -19,24 +19,34 @@ UClientTransformReplication::UClientTransformReplication() // Naive direct transform replication (replace with input rep?) void UClientTransformReplication::UpdateState(float DeltaTime) { + // We only perform the state update on the local owner of the actor. This is our (client-)authority const auto* OwningActor = GetOwner(); if (OwningActor && OwningActor->HasLocalNetOwner()) { + // Only do this if we actually replicate the actor if (GetIsReplicated()) { const FVector Loc = OwningActor->GetActorLocation(); const FRotator Rot = OwningActor->GetActorRotation(); + // Only update state if the local state changed if (!Loc.Equals(ReplicatedTransform.Position) || !Rot.Equals(ReplicatedTransform.Rotation)) { + // Factor in NetUpdateRate ControllerNetUpdateCount += DeltaTime; - if (ControllerNetUpdateCount >= (1.0f / ControllerNetUpdateRate)) // todo save inverse? + if (ControllerNetUpdateCount >= (1.0f / ControllerNetUpdateRate)) { ControllerNetUpdateCount = 0.0f; + // The local state has changed and we're within the update rate. Apply the new local state to the + // replicated variable - this just saves the new local state on the local net owner. ReplicatedTransform.Position = Loc; ReplicatedTransform.Rotation = Rot; - if (GetNetMode() == NM_Client) // why do we differentiate here between netmode and authority? + + // If we are running as a client, push the state to the server by calling the respective RPC. + // This is required in case we are both the server and the local net owner, in which case simply + // updating the local state is enough. + if (GetNetMode() == NM_Client) { ServerSendControllerTransformRpc(ReplicatedTransform); } @@ -47,26 +57,35 @@ void UClientTransformReplication::UpdateState(float DeltaTime) } void UClientTransformReplication::TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) + FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Perform a state update (if required) each tick. A state update means that we check whether + // our transform changed. If it did, send the new one to the server if we're within our NetUpdateRate. UpdateState(DeltaTime); } -void UClientTransformReplication::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +// this function just defines how our properties are replicated +void UClientTransformReplication::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); - // Skipping the owner with this as the owner will use the controllers location directly + // Skipping the owner with this as the owner will use the local state directly DOREPLIFETIME_CONDITION(UClientTransformReplication, ReplicatedTransform, COND_SkipOwner); DOREPLIFETIME(UClientTransformReplication, ControllerNetUpdateRate); } +// Apply the state update on the server void UClientTransformReplication::ServerSendControllerTransformRpc_Implementation(FVRTransformRep NewTransform) { // Store new transform and trigger OnRep_Function ReplicatedTransform = NewTransform; + // We are on the server here. If we are additionally the local net owner, i.e. the source of the stateupdate, + // it means we already directly apply our transform, as we are either a listen server or running in standalone. + // Therefore only apply the On_Rep function if we are not the local net owner. + // The OnRep function is doing the actual state update. if (!GetOwner()->HasLocalNetOwner()) OnRep_ReplicatedTransform(); } @@ -75,4 +94,4 @@ bool UClientTransformReplication::ServerSendControllerTransformRpc_Validate(FVRT { return true; // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? -} \ No newline at end of file +} diff --git a/Source/RWTHVRToolkit/Private/Pawn/ReplicatedMotionControllerComponent.cpp b/Source/RWTHVRToolkit/Private/Pawn/ReplicatedMotionControllerComponent.cpp index 552f22b80befa51febebb07bd15408ca63906a69..21da54cb34f52fff5596771438fb5204dc108e8a 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/ReplicatedMotionControllerComponent.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/ReplicatedMotionControllerComponent.cpp @@ -16,8 +16,7 @@ UReplicatedMotionControllerComponent::UReplicatedMotionControllerComponent() ControllerNetUpdateCount = 0.0f; } -// Naive direct transform replication (replace with input rep?) - +// See UClientTransformReplication::UpdateState void UReplicatedMotionControllerComponent::UpdateState(float DeltaTime) { if (GetOwner()->HasLocalNetOwner()) @@ -36,7 +35,7 @@ void UReplicatedMotionControllerComponent::UpdateState(float DeltaTime) ReplicatedTransform.Position = Loc; ReplicatedTransform.Rotation = Rot; - if (GetNetMode() == NM_Client) // why do we differentiate here between netmode and authority? + if (GetNetMode() == NM_Client) { ServerSendControllerTransformRpc(ReplicatedTransform); } @@ -58,6 +57,7 @@ void UReplicatedMotionControllerComponent::GetLifetimeReplicatedProps( { Super::GetLifetimeReplicatedProps(OutLifetimeProps); + // Disable the basic built in replication of relative transform data, as we are using our own transform sync/state update DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeLocation); DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeRotation); DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D); @@ -67,6 +67,7 @@ void UReplicatedMotionControllerComponent::GetLifetimeReplicatedProps( DOREPLIFETIME(UReplicatedMotionControllerComponent, ControllerNetUpdateRate); } +// See UClientTransformReplication::ServerSendControllerTransformRpc_Implementation void UReplicatedMotionControllerComponent::ServerSendControllerTransformRpc_Implementation( FVRTransformRep NewTransform) { diff --git a/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp index 4b9f8add37f4f1c86577eb95bedffe15159887b5..26e599357e557abc6263e0766b0adba5520ef1ec 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/VirtualRealityPawn.cpp @@ -60,6 +60,8 @@ void AVirtualRealityPawn::Tick(float DeltaSeconds) * The alternative would be to do this only on the server on possess and check for player state/type, * as connections now send their playertype over. */ +// This pawn's controller has changed! This is called on both server and owning client. If we are the owning client +// and the master, request that the DCRA is attached to us. void AVirtualRealityPawn::NotifyControllerChanged() { Super::NotifyControllerChanged(); @@ -71,6 +73,8 @@ void AVirtualRealityPawn::NotifyControllerChanged() if (UVirtualRealityUtilities::IsRoomMountedMode() && (UVirtualRealityUtilities::IsMaster() || GetNetMode()) == NM_Standalone) { + // If we are also the authority (standalone or listen server), directly attach it to us. + // If we are not (client), ask the server to do it. if (HasAuthority()) AttachDCRAtoPawn(); else @@ -205,6 +209,8 @@ void AVirtualRealityPawn::UpdateRightHandForDesktopInteraction() const } // Todo rewrite this in some other way or attach it differently, this is horrible +// Executed on the server only: Finds and attaches the CaveSetup Actor, which contains the DCRA to the Pawn. +// It is only executed on the server because attachments are synced to all clients, but not from client to server. void AVirtualRealityPawn::AttachDCRAtoPawn() { if (!CaveSetupActorClass || !CaveSetupActorClass->IsValidLowLevelFast()) @@ -212,6 +218,8 @@ void AVirtualRealityPawn::AttachDCRAtoPawn() UE_LOGFMT(Toolkit, Warning, "No CaveSetup Actor class set in pawn!"); return; } + + // Find our CaveSetup actor TArray<AActor*> FoundActors; UGameplayStatics::GetAllActorsOfClass(GetWorld(), CaveSetupActorClass, FoundActors); @@ -249,8 +257,10 @@ void AVirtualRealityPawn::SetupMotionControllerSources() RightHand->SetTrackingMotionSource(MotionControllerSourceRight); } +// Requests the server to perform the attachment, as only the server can sync this to all the other clients. void AVirtualRealityPawn::ServerAttachDCRAtoPawnRpc_Implementation() { + // We're on the server here - attach the actor to the pawn. AttachDCRAtoPawn(); } diff --git a/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h b/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h index daa57c4e478b48064e8ffa3da617ab7eb2eb20d9..3c6eda9c9641694f6b3492f92ed626bc59d2aee8 100644 --- a/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h +++ b/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h @@ -46,7 +46,7 @@ protected: UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedTransform, Category = "Networking") FVRTransformRep ReplicatedTransform; - // Called whenever ReplicatedTransform is replicated to clients. Not called on Server/Owning client + // Called whenever ReplicatedTransform is replicated to clients. Not called on Owning client, manually called on server UFUNCTION() virtual void OnRep_ReplicatedTransform() { @@ -61,6 +61,7 @@ protected: UFUNCTION(Unreliable, Server, WithValidation) void ServerSendControllerTransformRpc(FVRTransformRep NewTransform); + // Function called each tick that checks if we need to send a new state from our local net owner to the server. void UpdateState(float DeltaTime); public: diff --git a/Source/RWTHVRToolkit/Public/Pawn/ReplicatedCameraComponent.h b/Source/RWTHVRToolkit/Public/Pawn/ReplicatedCameraComponent.h index 8807531cc134733fbb6d8c8590dde1157a618be0..7657f0c9ed4075f6f9d6ef7fd46104d4eb87e6a6 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/ReplicatedCameraComponent.h +++ b/Source/RWTHVRToolkit/Public/Pawn/ReplicatedCameraComponent.h @@ -24,6 +24,10 @@ protected: // Full transform update replication ///////////////////////////////////////////////////////////////////////////////////////////////////// + /* + * See UClientTransformReplication for a description of the replication functions, they work exactly the same way. + */ + // Rate to update the position to the server, 100htz is default (same as replication rate, should also hit every tick). UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Networking", meta = (ClampMin = "0", UIMin = "0")) diff --git a/Source/RWTHVRToolkit/Public/Pawn/ReplicatedMotionControllerComponent.h b/Source/RWTHVRToolkit/Public/Pawn/ReplicatedMotionControllerComponent.h index 8df745d1791b986ea28192d9a62520ccf07a4afb..030f9a00c6f25acd6db9963d683d768c617d0608 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/ReplicatedMotionControllerComponent.h +++ b/Source/RWTHVRToolkit/Public/Pawn/ReplicatedMotionControllerComponent.h @@ -35,6 +35,9 @@ protected: UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedTransform, Category = "Networking") FVRTransformRep ReplicatedTransform; + /* + * See UClientTransformReplication for a description of the replication functions, they work exactly the same way. + */ void UpdateState(float DeltaTime); UFUNCTION()