TriAxis-Games / RealtimeMeshComponent

Unreal Engine 5 plugin component for rendering runtime generated content.
https://rmc.triaxis.games/
Other
1.57k stars 412 forks source link

Actor vs Actor Component: Which is ideal? #287

Closed hermesonbf closed 1 month ago

hermesonbf commented 1 month ago

Suppose I want to spawn 16 x 16 chunks around the player in a multiplayer game similar to Minecraft, the chunks appear around the player in chunk meshes created by RealtimeMeshComponent, is it better to have 1 actor "ChunkManager" actor that creates / destroys StaticMeshComponents for the chunks inside itself or to create 1 actor for each chunk and create / destroy as the players move around in the world? I am trying to guess which method is more ideal for a multiplayer game since I guess coding in the wrong method may cause extra work to migrate the code to the correct solution later.

connorjak commented 1 month ago

IIRC, meshes are culled per-actor? That would mean separate actors for chunks are good. There is some overhead in having lots of actors. Also, replication (if you're using that) is probably more suited to replicate properties per-actor. I don't use replication myself so take that with a grain of salt.

CitizenCEO commented 1 month ago

best way to check this would be to do some stress tests, but my guess is that 1 actor has bit more overhead than 1 component. Again, it depends. It always depends.

hermesonbf commented 1 month ago

All the examples I found on the internet of people creating "Minecraft like" procedural games with chunks in Unreal use actors for chunks so I guess it is safer to use them instead of 1 actors with N components inside, maybe later in development this latter method will lead to regret with something related to multiplayer Idk, apparently individual chunk components inside a single actor do not have individual relevancy so if the server spawns 30 chunks inside this one actor it will be sent to all the clients by default or extra logic will have to be created to handle this while with individual actors they only replicate creation / destruction based on distance to each client automatically, that's just a hypothesis on why actors may be better for something like this

viniciusaportela commented 1 month ago

I have done some stress tests on the past, it's not perfect but it gived me some insights:

10.000 Actors with one mesh component

Category PMC RMC RMC + 1 Polygroup DMC
Creation 961.723800 300.668200 293.594500 1052.288700
Mesh Setup 2019.371700 2870.816100 2363.835600 1506.531400
Total Draw Calls ~20k ~20k ~20k ~20k
Mesh Draw Calls 11.600 11.300 11.800 11.250
Memory Usage 68,337.48 kb 37,634.53 kb 39,079.67 kb 57,556.23 kb
Frame Time 60 ms 23.5 ms 40 ms 56 ms
Game Time 50 ms 10.7 ms 40 ms 40.8 ms
Draw Time 40 ms 22.5 ms 7.40 ms 33.93 ms
RHIT Time 22 ms 14 ms 14.5 ms 19.83 ms
GPU Time 18.96 ms 7 ms 11.5 ms 14.42 ms
FPS Shipping 22 fps 128 fps 68 fps 48 fps

1 Actor - Many PolyGroups

Possibly has a memory leak problem which make this method inviable

Category 10.000
Creation -
Mesh Setup 125,905 ms
Total Draw Calls 24745
Mesh Draw Calls ?
Memory Usage Unknown
Frame Time 19.75 ms
Game Time 7.49 ms
Draw Time 11.28 ms
RHIT Time 11.05 ms
GPU Time 6.58 ms
FPS Shipping ?

1 Actor - 10.000 Sections - Static

Category RMC
Creation 0.115900 ms
Mesh Setup 29,143 ms
Total Draw Calls 190
Mesh Draw Calls 5.5
Memory Usage 6,328.51 kb
Frame Time 8.33 ms
Game Time 6.02 ms
Draw Time 3.87 ms
RHIT Time 3.38 ms
GPU Time 2.41 ms
FPS Shipping 321 fps

1 Actor - 2.601 Cubes into 1 Section and 1 Group

Category RMC
Creation 0.098600 ms
Mesh Setup 22.012700 ms
Total Draw Calls 187
Mesh Draw Calls 3.5
Memory Usage 1,646.33 kb
Frame Time 8.33 ms
Game Time 6.59 ms
Draw Time 4.17 ms
RHIT Time 3.58 ms
GPU Time 2.25 ms
FPS Shipping -

Create Polygroup Cost

Around 5ms

Create Section Cost

Around 3ms

Create Group Cost

With polygroups: around 5ms Without polygroups: around 3ms

Remove one cube from a StreamSet in one section

Around 7ms

Notes

I noticed some things while doing the test, each section group is a draw call, so creating many sections in the same group will merge the draw calls (but I think they should have the same properties (visibility and collision) for that to work, not tested), but there is a cost for each section you add to the RMC, which can make it a little slow (29 seconds) when adding many sections (10.000) for one component. I Think draw calls would be a greater concern than the triangles, so the ideal is finding a sweet spot where you don't have sections that are not too big (to not make it slower to update) and neither too small (to prevent having many draw calls)

Also, the culling is per Primitive Component, so if you had only one actor which many components the culling would work normally

On the network side, I think it's pratically impossible to rely on the replication system if you want to replicate the terrain data it will spawn actors extremally slow