In-progress join and interactable foliage replication

Hi there!

I just implemented basic host / join functionality for my survival game and now I stumbled accross problems with in-progress joining.

For example, if the server cuts down trees (instanced foliage meshes get replaced with an actor) and the client joins after that, the client can still see the tree, can’t interact with it as this is server authorative, but he can see and interact with the actor.

How can I ensure replication (or just transfer the current state) to the client when he joins after action has been taken.

This is especially for foliage, but I’d be glad to get some heads-up for similar in-progress join replication problems, if something comes to your mind :wink:

Thanks and cheers!

Is there some prebuilt functionality for this or do I need to implement this myself?

Can the state of an instanced static mesh / foliage actor be saved and sent to the clients on join so they can apply the state to their instance?

Hi, I’ve never used “Save Game”, so I don’t know how to do it with that. But I think what defines the instanced static mesh component is the mesh and the array of transforms. The mesh is fixed, so the client only needs to know the array of transforms from the server.

So what I would do is: When a client joins then for each foliage “instanced static mesh component” the server sends the transform array over to the client (maybe as input in a Custom Event). Then the client removes all foliage “instanced static meshes” (for loop → remove instance), so the client has no trees on the map. Then add all the instances the server has sent (for loop over the transforms → add instance, input the transform there). Then the client should have the same trees as the server.

As for the actors (are they set to replicate?), I will have to do some tests today then I should be able to answer that :slight_smile:

As for the actors I think you would normally have all relevant values set to replicate, so it should not matter when a client joins because the server automatically sends replicated values over to the client. What specific problem do you have with the actor?

Heeeey, thanks for responding and answering here! You seem to have drastically more knowledge about ISM than I have ;'D

I didn’t know they had transform arrays. If they depend on them, replicating this should solve the issue. I’ll try this later. So I’d basically GetActorsByClass “InstancedFoliageActor”, get the transform arrays and send them to the joined client so he can apply previous changes. This seems of course to be very much viable for savegames later on too.

I have no particular problem with the actor. I just wanted to point out that if a tree has been cut before join, the spawned actor is correctly replicated to the client, but the ISM is still displayed.

Thank you very much for your help!

Glad I can help and in progress join is something I would have to do in my project sooner or later too :slight_smile:

They don’t exactly have a transform array, but there are nodes called “Get Instance Transform” taking in the instance index and “Get Instance Count” (so a for loop from 0 to the “Get Instance Count”-1).

As far as I understands it the “InstancedFoliageActor” holds all instanced foliage. So GetActorsByClass will return a list containing only one actor. Then from that actor you can use “GetComponentsByClass” with “InstancedStaticMeshComponent”. That will give you an array of Instanced Static Mesh Components (ISMC).

The nodes from the ISMC that could be useful in this setup would be “get Static Mesh”, “Get Instance Count”, “Get Instance Transform”, “Add Instance” and “Remove Instance”.

And if you have for example a tree with static mesh “X”. And you have several procedural foliage spawner and/or foliage painting the mesh “X”, then all the trees of type “X” will be in one ISMC. So if “get Static Mesh” returns “X” then all the instances of “X” will be in this ISMC.

And then you could create a new “Structure” containing a static mesh and an array of transforms and then send an array of this structure over to the client (the static mesh would be sort of a key to get the correct ISMC on the client).

Further I wouldn’t check for changes, instead remove all the “interactable” foliage from the client in one step, and then in the next add it from the server, that way it’s certain that the instance arrays of the server and client are in the same order (cause every time you remove an instance the array in the ISMC gets permuted to stay in order).

Oh ok, the last paragraph answered the next question I had. This all pointed to a lot of looping through things (with about 10k trees currently, that’d mean deleting and spawning 10k transforms at login) and therefore pointed to focusing on the changes solely instead (as they would never reach 10k) but yeah okay.

I might try both though. Is a synced order that relevant? I’d be happy enough if it just seems sync.

Thanks for the help

In the way I replace the tree with an actor, it is needed that the ISMC from server and all clients are in the same order. The “Hit Component” is the ISMC the tree belongs to and the “Hit Item” is the index of the tree in that array. So if the array of the server and client are not in the same order and both remove the tree at the “Hit Item” index of the array, then they may end up removing different trees.

293157-ismc-why-same-order-needed.png

And deleting and spawning 10k trees goes quite fast (at my pc way under one second).

That is good to know. Of course the RemoveInstance has to happen in a Multicast event and has to work with an index so syncing the order is critical here.

Ok, I will not try both like I posted last time. Thanks again for your help! ;D

When and where would you call the sync?

I put together a WIP and the logic should be fine, but there’s something off with timing and networking. In the HandleStartingNewPlayer I call a server event on the Controller that gets all transforms and then calls a client event that clears and adds all instances.

The problem is, that the client event only runs on the server. If an actual client joins, the client event never triggers. Maybe HandleStartingNewPlayer is too early for client calls?

Also the controller doesn’t seem to be the right place to do this. And array handling in BP is kinda weird. I had to move it to a function so I could use a local transform array to create the list, but that seemed to work. Then you just sync one array for each ISMC (multiple foliage types).

Can you see these BPs?

I made a new “Actor Compnent”, added it to the player character and called the synch function on “Event Begin Play” → RPC to server → collect data → RPC run on owning client → apply change. You may check that the owning client is not the server, cause the server already has the data in synch with himself.

Maybe you can add the “Actor Component” to the player controller, so the synch does not get triggered every time the character gets respawned but only when the game starts for the player controller (so only when the client joins).

And also when you call the synch at the begin of the game you want to build in a delay between the server and the client, so the client has time to spawn the actor (cause else the event will not get replicated to the client).

So this is the setup I used, that works in my game.

So first I synch all instances but only those that have a specific static mesh (because you can’t interact with all ISMCs but only with specific ones).

Further I made a custom struct that contains a static mesh and an array of transforms. Then I send an array of this struct, containing all the relevant data, over to the client.

So I would not build the custom event into the loop as in your setup, because I don’t know what will happen if the server sends the next event to the client before the client finished the event, and also I think I read in the documentation that a RPC can only be called twice in one frame.

I can’t seem to get this to work… This is driving me crazy…

Calling the events from the BeginPlay of the Player Pawn causes the RPCs to trigger, but if I print the foliage count, it seems all prints get called from the server only. Therefore the server just syncs with his local copy of the player somehow :’( …

Did you build in a delay between “Event Begin Play” and the RPC to the client?

Yep, I tried delays of up to 2 seconds. Even if I make it multicast and only delete all foliage, only the server ever trifgers it.

Strange… I created a blank level, added a landscape and procedural foliage spawner. Then I created a clean character that only contains the below shown actor component and in progress join works.

Can you show an image of how you call the synch event?

EDIT: You can also add the component to the player controller works also.

Ok, I reproduced this in a seperate project and found HandleStartingNewPlayer seems to be indeed to early to trigger this. Triggering this from the Player Controller BeginPlay on the client makes the call of the client side event work.

The problem now, is the number of foliage instanced. When I reach an instance count of about 1000, the client stops receiving the call. It works fine if the array is smaller. It doesn’t matter wether I use a struct or call it for each mesh, although calling two 1000 instance bulk one after another seems to work fine.

Did you test with high instance counts? Would you happen to have an idea on how to overcome that?

Interesting, just tested it with high instance counts, it also does not work.

What I would do then is use two custom events. One to send the data over to the client piecewise over a couple of frames (timer) and the client appends the data to its array.

Then when all data has been sent, call a second event that tells the client to replace its foliage.

Might be worth a shot, but maybe it’s a but unclean. What happens when the server cuts a tree during syncing of one client? He’ll prolly be out of sync.

Maybe an even more dirty approach would be better? Syncing foliage in a certain radius around the player during gameplay? Would be really annyoing tho if you’re running towards some distance tree and it suddenly disappears ;D

Having some best practices for this would be nice ^^’

off topic: did you see, we both where mentioned in the 4.24 feature highlights among the top 10 karma point earners (Y)

Yeah you’re right. With this approach you would need something like a “IsInSyncProcess” Bool. Then when the server cuts down a tree and “IsInSyncProcess” is true do a small delay before cutting it down (so disable any action that would lead the server to go out of sync while syncing with the client).

Maybe you could create a replicated variable that holds all the transforms and only change this variable when a client joins with a “RepNotify” and doing the sync there. So it would only be updated when a client joins. I think that would be cleaner than using RPCs cause you don’t have to worry about how it gets updated. But I think you would still have a delay so you may still need a “IsInSyncProcess” Bool.

I think syncing it during gameplay would either be very complicated (cause the array permutes) or be a huge drain on your Network Bandwith when you sync it every time a tree gets cut down.

off topic: Yup :slight_smile: