How does the networking work?
Last updated
Last updated
Certain inventory data can become quite large, especially with how many containers and items you allow in an inventory. To optimize, we try to simplify things. Players only receive data from components that they request data from and data from their own components, but also any update from components they are currently interacting with.
The component keeps track of the players that are currently interacting with a component with the Listeners array, then whenever PlayerA moves, adds, removes or interacts with an item in some way, the component sends RPC calls to the other "listeners" so their widgets and data get updated.
For functions that modifies a components data, such as moving an item or modifying an items stack count or modifying any other data, you must remember that clients only have authority over their own component, they do not have authority over other components and thus can't call functions on other components. This is simply how networking in Unreal Engine works. Because of this, most functions require you to call the function on the component the client has authority over. Even though the item you are trying to modify is not on that component. This is why, when you look at some of the source code, the functions are getting the component the item belongs to through its UniqueID.ParentComponent and modifying that component's data through the component the client has authority over.
To simplify the code, any function that attempts to automate the replication for you stores its logic in a function with the same name but with a Internal_ prefix.
For most functions that modify data in some way, only a single version of that function is available for you to call, and that function handles the replication for you. This is to simplify the amount of functions exposed as it can get very daunting to try and handle every replication scenario and making sure your calling the right function. If you wish to modify how the replication is handled, you can always go into the functions, modify them, or enable the server/client/internal functions to be BlueprintCallable (Example would be AC_Inventory -> Internal_AdjustContainerSize).
Because of how large some RPC's can get, I did not find multicasts to be acceptable for most of the functions. A lot of the RPC's are just simply not relevant for most clients anyways, and managing relevancy is a lot more complicated than a simple array of actors.
RepNotify's also can not be used because there is no way to ensure their order. When you label a RPC as "Reliable", Unreal will make sure all reliable RPC's get replicated in the order you called them. Variables will always get replicated, even with packet loss, so they are in a way "reliable", but not truly reliable. Variables will replicate whenever they get an opportunity to, and they have a lower priority than RPC's. RepNotify's also always re-sync the entire array even if one element is updated. So if you'd just update the item count for one item, the entire inventory would be re-replicated.
IFP does not automatically replicate any inventory data between clients other than their own inventory component. This means that actors in the level, like a chest, for clients will just contain the class defaults.
For a client, they must call C_RequestServerDataFromOtherComponent and they will then be added as a listener. Then eventually you will need to manually call S_RemoveListener.
An example can be found in BP_ItemActor in the interaction events.
The UniqueID is a simple method to quickly find containers or items, or ensuring containers or items are valid, and for linking some object to an item (Such as the item components's). This is covered in multiple sections in the documentation, but here are some things to consider for networking.
Clients and servers must stay in sync when it comes to UniqueID's. This leaves you with two options;
Pre-generate the UniqueID's on the server, then pass them to the client and have your logic assign that UniqueID to the item. This is not advised as it can get bad for networking and it is generally cumbersome to program and manage it.
Use GenerateUniqueIDWithSeed. Now the only thing you need to ensure is that the client and server are using the same seed and they'll both generate the same UniqueID.
UniqueID's scale extremely well with RPC's. It is advised to use UniqueID's as often as possible.
There's no way to evaluate from a UniqueID if it was assigned to an item or container without going through all items and containers until you find a match. It is not recommended to add more information to this struct as it's meant to be small and simple, so that it gets replicated extremely quickly and cheaply.
UniqueID's from components the client does not have information on will not work. For example, two players are interacting with a chest and Player-1 moves an item from their inventory to the chest, Player-2 will not have the information necessary to move the item on their side, since they do not have the information needed from Player-1's inventory component. Some functions simply need more information than the UniqueID can provide.