Tutorial: Networked Physics - Pawn Tutorial

Here are my results so far with two complex pawns - vehicle and crane, it’s recorded with bad network profile and here is player on server controls the pawns and client observes the results.

I wonder is that the best result you could expect from that tech right now or in general it has to be better and I have to search for an error in some interpolation/decay stuff?

@MBobbo regarding the walls specifically, pink walls are AActors, not AStaticMeshActors. With debug draw both are rendered red

Very nice that you got both of those working, good job!

You can get better results than that yes, but I’d recommend to aim for the Average profile, not the Bad at the moment.
It’s hard to point towards specific fixes, networked physics is quite a complex area and it’s still experimental so we don’t have all the documentation, settings and features implemented yet.

But it looks to me like the issue for both vehicle and crane is the same thing that sim-proxies forward predict too much, the clients physics simulation is forward predicted, the autonomous proxy is acting in the forward predicted timeline, it’s moving forward sends that input to the server which then move forward and sends the input and state to sim-proxies which then move forward. That creates a time delay so sim-proxies act in the “interpolated timeline" behind the server while autonomous proxies act in the “forward predicted timeline”. And for physics interactions to work they need to act in the same timeline (preferably).

With bad networking you probably receive inputs and states from the server that should have been applied 10-15 frames ago, i.e. the forward predicted timeline is 15 frames ahead of the interpolated timeline. And to get sim-proxies into the forward predicted timeline we trigger a resimulation, sets physics back 15 frames, apply the received input there and then resimulate forward to the forward predicted timeline again. But sim-proxies only have inputs for the interpolated timeline, so for the first resim frame essentially. The next 14 frames during resimulation the sim-proxy will be using the latest received input which means the sim-proxy crane will turn right for 14 frames but we don’t know if it will actually turn right during those frames. Say when you receive the next input from the server the input is that the crane have stopped moving, but on the sim-proxy it has forward predicted moved right for 14 frames, so it has overshot its stopping point. It will rewind 15 frames and then apply input with no movement for 15 frames. Then rendering will cover up that correction by moving it smoothly back to the left from the overshot position.

This means that instant movements with networked physics is quite hard to make look well since everything needs to be forward predicted but if things can stop on a dime they will overshoot and need to get corrected backwards.

From a game design view it’s best to make the crane have momentum, so it doesn’t stop instantly, it slows down gradually to a full stop. That will help along with the following..

To make sim-proxies not fully forward predicted during resimulations the only thing we have at the moment is input decay, which means we decay the input during forward prediction for sim-proxies, so the crane can turn less and less right the further into forward prediction it comes.

I would say you need to balance input decay and you do that via NetworkPhysicsSettingsComponent and the DataAsset along with your implementation function for decaying inputs.

It is sometimes also better to make the input decay curve a flat line at say 0.35 instead of a curve or linear increase, it depends on what your pawn.
One downside with input decay is that it doesn’t scale with network conditions yet, so if you balance it to look good at Bad then it might not look good at Average.

UE 5.8 will see a couple of improvements to input decay and also a new way of keeping sim-proxies not fully forward predicted without decaying inputs.

You can also in the settings DataAsset make it trigger a resimulation when it receives inputs, that will make it resimulate very often but it will most likely also help you with the overshoot issue.

Also if you are making a coop / casual game then resimulation is a bit overkill at the moment, it’s designed for 100% server authority with dedicated servers.

We don’t have a solution for client authority between autonomous proxy and server when the pawn is physics-based at the moment though. But if you were to run client authority the replicated physics objects could use Predictive Interpolation which generally looks much better out of the box.
You can use that replication mode for physics pawns also but you will have input latency depending on your network latency in that case.

So the three things you should look into to start with is:

  • Add momentum to the cranes rotation
  • Balance Input Decay
  • Enable resimulation on received inputs

Thanks! Currently I’m playing with making smoother input (not discrete 0 and 1), decay curve and trigger resimulation - seems like it delivers pretty good results!

Yeah, maybe that’s an overkill for a coop game I’m doing right now, but unfortunately predictive interpolation doesn’t work for me at all, all my attempts were fallen into 2 categories:

  1. No immediate response on a client (so it was looking like it doesn’t work at all ), when I was trying to implement this example Networked Physics and UNetworkPhysicsComponent in Unreal Engine 5.4 - Devtricks (author said that probably something is broken in 5.7 in comparison with his own tests on earlier version)

  2. Physics looks broken and jerky (for example if I’m just set to predictive interpolation in your example)

Predictive interpolation is not meant for use on autonomous proxy, it’s only meant for sim-proxies.
For client-side authority you’d need to make the client not replicate physics and then either make a component yourself that sends states from the client to the server and applies it on the server or “hack” it by sending states inside the inputs via NetworkPhysicsComponent to the server so the server applies states.
But then you need to make sure that sim-proxies doesn’t apply those inputs and instead that they run predictive interpolation.

Hi again! I’m currently trying to push the approach further and check if I can create the following setups:

I have APhysicsPawn (APawn) with NetworkComponent

I have APhysicsCube (AActor) with NetworkComponent

The goal is to be able to apply some forces to this cube from client’s APhysicsPawn.

What I tried:

  1. APhysicsPawn passes Chaos::FPhysicsObjectHandle CubeBody into it’s own AsyncPhysicsInput and applies forces there. It works on server, but on client side I see constant corrections back to the server, as I understand because of the fact that APhysicsCube is an AActor and it’s not owned by client

  2. Then I tried to do the following (doesn’t work as well):

a) define ForceInput for FNetInputPhysicsCube;

b) When APhysicsPawn wants to interact with that, client sends request to the server to set ownership for this client and get local inputs:

 TargetCube->SetOwner(GetController());

TargetCube->NetComp->SetIsRelayingLocalInputs(true);

c) Once this is done, on tick of APhysicsPawn I send desired Force to APhysicsCube

d) On tick of APhysicsCube this force goes into its async component

Does the second approach look more-less correct in theory or it’s not the way it will work? It that possible at all in general? :slight_smile:

Also I wonder what’s the proper approach for implementing discrete stuff like jumps? I see that in general it has to rely on some counter like int32 JumpCount that is incrementing in GameThread when player wants to jump and then performing comparison in physics thread with the previous value.

However for comparison I have to store previous jump count somewhere, is that the case where I have to use states structs? Because if I store that in SimCallback the jumps doesn’t work great with any network delays

UPD: no, seems like my jumps are fine. It was the significant drops in FPS when resim is triggered (because of network physics settings set this way on a vehicles), even when vehicle is stationary without inputs. So when I have 60 components in the scene with network component it all goes crazy. Seems like I need some custom sleep logic for most of these components (they are building blocks I have to be able to apply forces to).

Oh, so many nuances :slight_smile:

Hello, the NetworkPhysicsComponent should only be used for complex assets, like your character, vehicles and your crane for example.
It’s a very heavy component at the moment since there is not much optimizations done yet, it will continuously send inputs and states over the network for every physics frame even when nothing changes. So you can’t scale a game with this yet, having 60 of them will most likely not work well.

When it comes to applying forces to other objects you don’t need the NetworkPhysicsComponent.
SetIsRelayingLocalInputs is meant to be used when you have a complex asset that the character is able to control, like your crane for example it could be an AActor instead of an APawn. And you sit your character inside the crane and then take ownership of it and set SetIsRelayingLocalInputs which makes the NetworkPhysicsComponent able to send inputs from your client to the server.

As mentioned before, networked physics is currently designed around dedicated servers and full server authority. So your approach of taking ownership of a physics object that you want to apply a force to is not the flow it’s designed for at the moment.
Instead the idea is that you apply the force to the other FPhysicsObject on the physics thread both on client and on the server. When it’s done correct, since client and server are in sync, the force gets applied on the correct frame and things stay in sync.

There are multiple ways of deciding to apply the force and to do it on the physics thead. You can do it from the game thread also in a certain way but it’s not really recommended.

  1. Do a trace on the physics thread to find physics particles and apply the force to those physics particles. This is still experimental though and might change but you find physics thread trace API here: FGenericPhysicsInterface_Internal. If you sync the ChaosMover plugin you should find examples or it being used there if you search.
    • You can do this when you hit your input to shoot for example
    • You can also do this from other logic than a complex character, you can have a trampoline on the ground which does traces on the physics thread and applies a force or impulse to physics objects.

2. You can register for ISimCallbackObject.OnContactModification_Internal and find particles you collide with to apply a force to on the physics thread.

3. You can do the trace on the game thread and then queue up a lambda function to run on a specified physics frame. You need to do this on the client and network the physics frame and physics object to the server (via your pawn) so the server can queue up the same lambda for the same frame.

Number 1 and 2 are best since they are decided on the physics thread and will resimulate correctly.
Number 3 is a client authoritative flow and it won’t resim correctly since the decision is made on the game thread, but the lambda will play out again during resimulation at least on the frame it was scheduled for.

Here is a code example of alternative 3:

APlayerController* Controller;

FVector ForceVector;

FPhysicsSolver* Solver;

const int32 ServerFrame = Controller->GetPhysicsTimestamp().ServerFrame;

{
	const int32 CorrespondingFrame = ServerFrame - FrameOffset;

	FConstPhysicsObjectHandle PhysicsObject = RootComponent->GetPhysicsObjectByName(NAME_None);

	Solver->EnqueueCommandScheduled_External(CorrespondingFrame, [PhysicsObject]

	{

		FWritePhysicsObjectInterface_Internal Interface = FPhysicsObjectInternalInterface::GetWrite();

		if (FPBDRigidParticleHandle* Handle = Interface.GetRigidParticle(PhysicsObject))

		{
			Handle->AddForce(ForceVector);
		}
	});
}

You’d need to then send the ServerFrame and the AActor (that you get the rootcomponent and physics object from) from client to server and then schedule that same lambda for the CorrespondingFrame on the server too.

I’m going to write more tutorials eventually when I get time about some fundamental knowledge and then some gameplay implementations.

Thank you so much for your response! I will try to implement approach number 3! What’s the FrameOffset in this context btw? Let’s say I have average network profile and I have ServerFrame 4256 and LocalFrame 4060, should I basically send 4060 for client (execute lambda now) and 4256 for server (this force was applied on client when for server it was 4256)

Regarding approach #1, I was trying to do something like that in my NetworkPhysicsPawn in presimulate callback (assuming I’m calculating GetWorldForcePosn() input in game thread and then send it to network component, the physics particle is constant for simplicity, so I don’t need to do traces)

This works fine for a server, but on a client it’s being played back to the server’s position. I though that this could work because for the root body of the pawn it works just great, but seems like for others bodies server doesn’t apply that forces. Is that possible to utilize these callbacks for such logic or I have to define some additional logic like “send that input on server too, so the server will apply that in physics thread for this body on its side”?

void APhysicsPawn::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	const bool bIsLocallyControlled = IsLocallyControlled();

	if (bIsLocallyControlled)
	{
		if (FAsyncInputPhysicsPawn* AsyncInput = PhysicsPawnAsync->GetProducerInputData_External())
		{
            // other input states here
			AsyncInput->Vector2Input = GetForceWorldPosn();	

		}	
	
	}
	
}




void FPhysicsPawnAdvAsync::OnPreSimulate_Internal()
{

// skipped the pawn movement logic for the pawn root body, works fine

// Object that we want to push, TargetObjectHandle_Internal is defined on PostInitializeComponents

Chaos::FPBDRigidParticleHandle* ParticleHandlePush = Interface.GetRigidParticle(TargetObjectHandle_Internal);

if (ParticleHandlePush && Vector2Input_Internal.Size()>0.5)
{

// Skipped the logic that calculates the force from Vector2Input_Internal

	ParticleHandlePush->AddForce(Force, true);
	ParticleHandlePush->AddTorque(WorldTorque, true);
	
}

}

That’s the result I’ve achieved with the following code (approach 3, I hope I understood it right) server of the left, client on the right, so for average network profile it works nearly perfect I would say, for bad one obviously quite bad, but it’s quite extreme I guess.


void APhysicsPawn::ApplyForceToActor(AActor* TargetActor, FVector WorldForcePosn,FVector WorldForceNormal)
{
	if (!TargetActor || !GetController()) return;

	APlayerController* PC = Cast<APlayerController>(GetController());
	if (!PC) return;


	const int32 ServerFrame = PC->GetPhysicsTimestamp().ServerFrame;
	const int32 ClientFrame = PC->GetPhysicsTimestamp().LocalFrame;


	UE_LOG(LogTemp, Warning, TEXT("Server %d, Client %d"), ServerFrame, ClientFrame);

	
	if (!HasAuthority())
	{
		EnqueuePhysicsForceCommand(TargetActor, WorldForcePosn, WorldForceNormal, ClientFrame);
	}

	Server_ApplyForceToActor(TargetActor, WorldForcePosn, WorldForceNormal, ServerFrame);
}


bool APhysicsPawn::Server_ApplyForceToActor_Validate(AActor* TargetActor, FVector ForceVector, FVector ForceNormal, int32 InteractionFrame)
{
	return true; 
}

void APhysicsPawn::Server_ApplyForceToActor_Implementation(AActor* TargetActor, FVector ForceVector, FVector ForceNormal, int32 InteractionFrame)
{
	EnqueuePhysicsForceCommand(TargetActor, ForceVector, ForceNormal, InteractionFrame);
}

void APhysicsPawn::EnqueuePhysicsForceCommand(AActor* TargetActor, FVector ForceVector, FVector ForceNormal, int32 TargetFrame)
{
	if (!TargetActor) return;

	UPrimitiveComponent* RootPrim = Cast<UPrimitiveComponent>(TargetActor->GetRootComponent());
	if (!RootPrim) return;

	FPhysScene* PhysScene = RootPrim->GetWorld()->GetPhysicsScene();
	if (!PhysScene) return;

	Chaos::FPhysicsSolver* Solver = PhysScene->GetSolver();
	if (!Solver) return;


	Chaos::FConstPhysicsObjectHandle PhysicsObject = RootPrim->GetPhysicsObjectByName(NAME_None);
	if (!PhysicsObject) return;

	float PushStrength = CharacterUtilsComponent->MovementParams.ForcePush;


	Solver->EnqueueCommandScheduled_External(TargetFrame,
		[PhysicsObject, ForceVector, ForceNormal,PushStrength]() 
		{
			Chaos::FWritePhysicsObjectInterface_Internal Interface = Chaos::FPhysicsObjectInternalInterface::GetWrite();

			if (Chaos::FPBDRigidParticleHandle* Handle = Interface.GetRigidParticle(PhysicsObject))
			{
				Chaos::FVec3 WorldCenterOfMass = Chaos::FParticleUtilitiesGT::GetCoMWorldPosition(Handle);
				Chaos::FVec3 Force = -ForceNormal.GetSafeNormal() * PushStrength;

				const Chaos::FVec3 WorldTorque = Chaos::FVec3::CrossProduct(ForceVector - WorldCenterOfMass, Force);

				Handle->AddForce(Force, true);
				Handle->AddTorque(WorldTorque, true);
			}
		});
}

@mizarates
When it comes to the frame numbers we had a presentation at Unreal Fest which describes it. The video got uploaded the other day, here: https://www.youtube.com/watch?v=_jRLlTDqoGI

It starts out with the physics-based character and then talks about some fundamentals about networked physics.

Your implementation of option 3 looks correct yes.

Your implementation for option 1 is still dependent on the decision being made on the game thread not the physics thread. For it to work both client and server needs to run the same logic to apply the force, I’m guessing when you do it “on the server” it’s a listen server player and it works? And it doesn’t work when it’s a normal client that runs the logic (since the server doens’t actually perform that logic or perform it based on the sim-proxy which might be desynced which they normally are a bit)

1 Like

Thank you Markus, it was a great talk, enjoyed it a lot :slight_smile: Especially the part with timelines

Yes, my implementation for option 1 works on listen server player, and for normal client server doesn’t apply the force to that object (while applying it correctly for the root body in the same scope)

Btw seems like approach #3 doesn’t work great (if my implementation is correct) for moving the objects that are colliding with other stuff in the scene, hm. I’ve created the system where I specify the point on the target body and push it towards Pawn.

On a video you could see that the body doesn’t move smooth and at some points there are resim triggered. So maybe my implementation was not correct previously and the fact that the body was floating on a constraint hide that jitters

void APhysicsPawn::ApplyForceToActor(AActor* TargetActor, FForceRopeInterface RopeInterface)
{
if (!TargetActor || !GetController()) return;

APlayerController* PC = Cast<APlayerController>(GetController());
if (!PC) return;


const int32 ServerFrame = PC->GetPhysicsTimestamp().ServerFrame;
const int32 ClientFrame = PC->GetPhysicsTimestamp().LocalFrame;


if (!HasAuthority())
{
	EnqueuePhysicsForceCommand(TargetActor, RopeInterface, ClientFrame);
}

Server_ApplyForceToActor(TargetActor, RopeInterface,ServerFrame);

}

bool APhysicsPawn::Server_ApplyForceToActor_Validate(AActor* TargetActor, FForceRopeInterface RopeInterface, int32 InteractionFrame)
{
return true;
}

void APhysicsPawn::Server_ApplyForceToActor_Implementation(AActor* TargetActor, FForceRopeInterface RopeInterface, int32 InteractionFrame)
{
EnqueuePhysicsForceCommand(TargetActor, RopeInterface, InteractionFrame);
}

void APhysicsPawn::EnqueuePhysicsForceCommand(AActor* TargetActor, FForceRopeInterface RopeInterface, int32 TargetFrame)
{
if (!TargetActor) return;

UPrimitiveComponent* RootPrim = Cast<UPrimitiveComponent>(TargetActor->GetRootComponent());
if (!RootPrim) return;

RootPrim->WakeAllRigidBodies();

FPhysScene* PhysScene = RootPrim->GetWorld()->GetPhysicsScene();
if (!PhysScene) return;

Chaos::FPhysicsSolver* Solver = PhysScene->GetSolver();
if (!Solver) return;


Chaos::FConstPhysicsObjectHandle PhysicsObject = RootPrim->GetPhysicsObjectByName(NAME_None);
if (!PhysicsObject) return;


float PushStrength = RopeInterface.Force;
FVector LocalImpactPoint = RopeInterface.ImpactPoint;


Chaos::FConstPhysicsObjectHandle PawnPhysicsBody = Cast<UPrimitiveComponent>(GetRootComponent())->GetPhysicsObjectByName(NAME_None);

Solver->EnqueueCommandScheduled_External(TargetFrame,
	[PhysicsObject, PawnPhysicsBody, LocalImpactPoint, PushStrength]()
	{
		Chaos::FWritePhysicsObjectInterface_Internal Interface = Chaos::FPhysicsObjectInternalInterface::GetWrite();

		if (Chaos::FPBDRigidParticleHandle* Handle = Interface.GetRigidParticle(PhysicsObject))
		{	

			Chaos::FPBDRigidParticleHandle* HandlePawnBody = Interface.GetRigidParticle(PawnPhysicsBody);


			Chaos::FVec3 CurrentActorPos = Handle->GetX();
			Chaos::FRotation3 CurrentActorRot = Handle->GetR();

			// Calculate world position of the point where to apply the force
			Chaos::FVec3 WorldImpactPoint = CurrentActorPos + CurrentActorRot.RotateVector(LocalImpactPoint);

			// Calculate the normal of the force application (we want to push the body towards us) 
			Chaos::FVec3 ForceNormal = (WorldImpactPoint - HandlePawnBody->GetX()).GetSafeNormal();
			
			Chaos::FVec3 Force = -ForceNormal * PushStrength;

			// Calculate torque
			Chaos::FVec3 WorldCenterOfMass = CurrentActorPos + CurrentActorRot.RotateVector(Handle->CenterOfMass());
			Chaos::FVec3 LeverArm = WorldImpactPoint - WorldCenterOfMass;
			const Chaos::FVec3 WorldTorque = Chaos::FVec3::CrossProduct(LeverArm, Force);

			Handle->AddForce(Force, true);
			Handle->AddTorque(WorldTorque, true);

		
		}
	});

}


UPD:

was trying to simplify the logic by applying the force just in fixed direction (1.0,0,0) but even with that the system doesn’t work as I can see

I’m also a little bit confused about the limitations of the options 1-2-3.

So from your description options 1-2 can support the logic like this

Pawn check for collision via hit events/traces on physics thread -> target object detected -> ApplyForce(TargetObject)

But it doesn’t support the case when I want to perform decision making on game thread (like the force is applied only when player has pressed the button)

The option 3 doesn’t have that limitations since I can pass whatever I need into lambda for physics thread, but with any continuous logic there is a possibility of error since decision is not being made from the same scene state on a client/server + differences in GT/PT frequency (likely my issue)

And the general approach with networked component for pawn overcomes these limitations by introducing inputs buffering/merging/interpolation, but it works only for the root body of the actor, so you can’t really touch other physics bodies in that OnPreSimulate_Internal

Is that correct?

Seems like I’ve managed to get the system to work with extension of the approach from original article. Don’t want to spam too much here with tons of other questions I have, but it could be helpful for someone else :slight_smile:

So my mistake was quite stupid, I was trying to send physics body handles via network and at the end in PreSimulateInternal it was not resolved on servers side correctly. I still not sure that my new approach is 100% safe, but it works fine for me and performs smooth motion even in bad network emulation.

Here is the summary of the code:

In FPhysicsPawnAsync I defined a pointer for the second body I want to work with:

	Chaos::FConstPhysicsObjectHandle PhysicsObject2 = nullptr;

In game thread where I setup the attachment I change this pointer on a client straight away and then call reliable RPC to do the same on a server:

PhysicsPawnAsync->PhysicsObject2 = Cast<UPrimitiveComponent>(RopeAttachmentData.AffectedActor->GetRootComponent())->GetPhysicsObjectByName(NAME_None);

if (!HasAuthority())
{
	Server_FeedActorToAsync(RopeAttachmentData.AffectedActor);
}

Then I access this in OnPreSimulate_Internal and perform any force logic for 2 physics bodies:

void FPhysicsPawnAdvAsync::OnPreSimulate_Internal()
{

// main character logic

if (Chaos::FPBDRigidParticleHandle* Handle = Interface.GetRigidParticle(PhysicsObject2))
{
		// logic with force application 
}

}

So as far as I understand, approach #3 could work fine is the interaction is impulse based (1 frame), otherwise it suffers from all typical issues of applying forces in game thread - the results differ a lot with FPS and with network stuff it becomes even more jerky. But for jumps I guess that could work.

Also there are still some issues with the fact that you have to wake up the bodies somehow and prevent them from sleeping (assuming they are some just random physics bodies in the world and doesn’t have never sleep settings)

Hello, good to see you getting on with the implementation and learning the limitations you need to abide by.

Option 3, as you noticed does not work well with continuous forces no, partly because game can step multiple times for each physics step or even step 0 times between physics steps which results in uneven forces being applied. And also the force for each step needs to be sent via an RPC which can arrive irregularly so the server won’t always apply the same uneven force as the client does meaning they desync and resim.

Your new approach is much better indeed, that way it’s the pawn that controls it on the physics thread via inputs handled by the network physics component which is very realiable since we keep an input buffer on the server and send multiple inputs each time we send so if one packet is lost then the next will have the previous input anyway to fill the holes in the buffer with.

And you only have the network physics component on the pawn, not each object that can simulate, since the component is quite heavy to run.

About sleeping though, you should not need to wake it up manually to be able to apply a force. It should wake up automatically. But there are sleeping bugs with resimulation currently (which is why I forced it to NeverSleep in the tutorial), I’m working on fixing the bugs for UE 5.8.

One thing you can do temporarily is to comment out the code inside the two if-statements that checks bShouldSleep inside FPhysicsReplicationAsync::ResimulationReplication. That should hopefully fix most issues you might see from sleeping/waking with resim.

I didn’t build the engine for source yet (but seems like there is a good chance that I have to with all that new stuff, I see you update network component quite frequently :slight_smile:)

So currently as a work-around for sleeping issues I assigned a physics material with larger sleep timings, so in worst case player see some brief wake up event during the start of interaction and then for some time it’s awake. For pawn and vehicles I made that time as the largest number.

Btw, I’m still trying to figure out the best approach with discrete interaction, like jumps. When I natively add them as part on network inputs (counter) it kinda works 50% of the time but sometimes I see rough correction.

Should I:

  1. Do them through approach #3 with add impulse and reliable RCP?

  2. Do them through general approach from original tutorial but move the jump counter to the states structs?

My current code looks like this:

void FPhysicsPawnAdvAsync::OnPreSimulate_Internal()
{

// JumpInput_Internal bridged via FNetInputPhysicsPawn 

if (JumpInput_Internal != JumpInput_Internal_Prev)
{
	ParticleHandle->SetLinearImpulseVelocity(FVector(0, 0, MovementParams.ForceJump), true);
}

JumpInput_Internal_Prev = JumpInput_Internal; // _Prev Defined in FPhysicsPawnAdvAsync directly, not in States

}

Hi guys I really appreciate your discussion a lot and I try to get along out from the shadows but it’s getting really hard to keep track of all of this. My setup also contains objecthandles that I try to get from gamethread into the physics thread but that’s not even the main issue for me.

I also try to get stuff like VInterpTo implemented into my addforce vector and stuff behaves clearly not as intended. In my case, my “vehicle” is accelerating backwards before the interpolation goes into the forward direction and on my client side I somehow still don’t receive any input at all. This is really deep debugging maze.

This makes me a bit desperate and I wonder if there is a lazy and absolutely not recommended way to get a kinda client-authorative approach as a workaround? The character movement component has this client-authorative movement checkbox for example (I don’t have a char class). I don’t care about the correction or delay of the others I just need the client to have no latency and correction with his input.

I would need the workaround just for the time being, until the networked physics stuff has progressed in development (with maybe blueprint integration and documentation and stuff)

Actually I found a way. Mr Boberg mentioned somewhere that “replicate physics to autonomous proxy” is the necessary flag on the root component to achieve the “client authorative way”.

To stay with the async physics, the singleplayer setup from the tutorial will do. I will do the interpolated location/rotation/speed sync from autonomous proxy to anybody else by myself then. I’m actually quite happy with the results so far.

However I wonder what it is that I don’t see, since there is a reason to put so much effort in the server authorative latency free network component?

I’ve performed additional tests and unfortunately seems like the tech is still too heavy to be working fine for the purely physics-based game :frowning:

Basically I was trying to create a relatively small building based on standardized blocks (no network physics component, just replicated + resim) and being able to interact with each block via physics and that works fine when playing as a server, but when you are a client and there is a movement propagated from one block to others it triggers resim tasks way too often and performance drops down to very poor numbers.

I was trying to play around with resim thresholds but it doesn’t help much. I know that this multibody force propagation is a typical issue for physics engines, but seems like together with networking the complexity goes exponential and it can not be done without full custom physics solution

(the situation is the same in general for any significant number of physics objects being interacting with each other)

@MBobbo, Hi. I found an unpleasant moment in the code. When an object is destroyed on a simulated proxy via Actor Relevance (Net Cull Distance Squared ). The process crashes. It’s happening here. Could you tell me what I could do to fix this annoying moment?
A crash does not always occur. With some probability. It also occurs when trying to reconnect

void FPhysicsPawnAsync::OnPreSimulate_Internal() 
{
....
	if (!PhysicsObject)
	{
		return;
	}

	Chaos::FWritePhysicsObjectInterface_Internal Interface = Chaos::FPhysicsObjectInternalInterface::GetWrite();
	Chaos::FPBDRigidParticleHandle* ParticleHandle = Interface.GetRigidParticle(PhysicsObject); // <-- HERE CRASH
	if (ParticleHandle == nullptr )
	{
		return;
	}
....
}

Hello, good catch, I’ve missed implementing a callback.

What happens is that we keep a pointer to the FPhysicsObject on the physics thread and when the actor along with its physics object gets destroyed (for example when it goes out of relevancy) the pointer will point to invalid memory unless we manually clear the pointer when the object gets destroyed. It passes the if-statement because it does point to a memory address but that address doesn’t hold the data for the FPhysicsObject anymore.

Here is the implementation with the OnPhysicsObjectUnregistered_Internal callback that you need. I’m updating the tutorial with this too.

.h

class FPhysicsPawnAsync : public Chaos::TSimCallbackObject<FAsyncInputPhysicsPawn, FAsyncOutputPhysicsPawn,
	(Chaos::ESimCallbackOptions::Presimulate | Chaos::ESimCallbackOptions::PhysicsObjectUnregister | Chaos::ESimCallbackOptions::Rewind)>
	, TNetworkPhysicsInputState_Internal<FNetInputPhysicsPawn, FNetStatePhysicsPawn>
{
	friend APhysicsPawn;

	~FPhysicsPawnAsync() {}

	// TSimCallbackObject callbacks
	virtual void OnPostInitialize_Internal() override;
	virtual void ProcessInputs_Internal(int32 PhysicsStep) override;
	virtual void OnPreSimulate_Internal() override;
	virtual void OnPhysicsObjectUnregistered_Internal(Chaos::FConstPhysicsObjectHandle InPhysicsObject) override;
....

.cpp

void FPhysicsPawnAsync::OnPhysicsObjectUnregistered_Internal(Chaos::FConstPhysicsObjectHandle InPhysicsObject)
{
	if (PhysicsObject == InPhysicsObject)
	{
		PhysicsObject = nullptr;
	}
}