Skip to main content

Replication

The framework is multiplayer-first, which means the interesting rules are about what a client is allowed to believe. This page is worth reading before you ship, because both mistakes below look like UI bugs and neither reproduces in standalone.

What replicates​

A container replicates as a subobject of the component that owns it. Its whole state travels:

PropertyWhy it must
EntryArrayThe contents — a FastArray, so changes are deltas rather than the whole list.
ContainerTypeIts identity. Every lookup in the framework goes through this tag.
ContainerIdWhich instance, when several share a type.
MaxSlotsA client guessing capacity draws the wrong thing.
RulesSo a client can grey out what will be refused, instead of finding out by trying.
LayoutConfigIts shape. Without it a grid cannot be drawn at all.

Promoted UEDItemInstances replicate as subobjects too, registered and unregistered as entries come and go.

The seam

All FastArray specifics are confined to FEDContainerEntryArray. Gameplay uses the container's entry API and never the array, which is what lets the replication backend change — to Iris, for instance — without touching gameplay code.

Authority​

Containers are created on the authority. A client never builds one; it receives them.

Every mutation is server-side. The Try* calls return false off the authority and do nothing. The Request* calls are the client's route in.

Events fire on every machine. The server broadcasts when it changes something; a client broadcasts when the change arrives. UI binds once and works on both.

The route​

A Server RPC only travels an actor chain the calling client owns. Half the containers in a game are not on such an actor — a chest, a corpse, a vendor's stock all belong to the server — so a request made "on the chest" is silently dropped by the engine with nothing but a log line.

UEDContainer::RequestTransaction handles this: it finds a UEDInventoryRouteComponent on something the client does own, usually its own pawn, and sends the request down that pipe instead. The transaction payload names the real containers, so the pipe it travelled through does not matter.

You get this for free. It is worth knowing it exists, because "my chest works on the listen server and not on the client" is otherwise a long afternoon.

Trap 1 — a view bound before its container arrives​

On a client the actor's containers arrive by replication a few frames after the HUD is built. A view bound to the container pointer that exists at that moment is bound to null, and unless something re-resolves it, that panel stays empty for the entire session while the inventory behind it works perfectly.

Bind by role, not by pointer:

// Right: "this actor's hotbar" is true before the container exists.
View->SetSourceInventory(Inventory, HotbarTag);

// Only for a container you already hold, such as a chest being looted.
View->SetSource(ChestContainer);

SetSourceInventory follows the inventory: it attaches the moment the container arrives, and detaches if it goes away.

The gameplay debugger will not catch this

The debugger collects on the server and sends the result to the client. It showing the containers correctly does not prove the client has them. Check the client's own view instead.

Trap 2 — the list arrives before the containers are recognisable​

A container and the list holding it replicate as separate things. The list can arrive first and be searched while every container in it is still nameless, so a lookup by tag at that instant finds nothing.

Handled: the container's type announces itself on arrival and the inventory re-broadcasts OnContainersChanged, so anything waiting for a container by tag gets another look. If you write something that resolves containers by tag on a client, subscribe to that event rather than resolving once.

Relevancy and bandwidth​

The plugin ships no relevancy policy, and that is deliberate. Net update frequency, cull distance, owner-only relevancy and dormancy are properties of an actor, and which of them suit a chest, a corpse or a player character is a decision about your game. Set them on your own actors the way you would for anything else.

What the framework does guarantee is that a change reaches the wire even when you have put the actor to sleep. Every authoritative container change calls UEDContainer::MarkOwnerNetDirty, which flushes the owning actor's dormancy and asks for an update. So a map full of dormant chests costs nothing until one is opened, and opening one wakes it without you wiring anything.

Removed

Earlier builds shipped a UEDInventoryRelevancyComponent and an EDInventory Networking settings section. Both are gone. If you had the component on an actor, delete it and set the same four properties directly — that is all it ever did, and nothing else in the plugin depended on it.

If you are reproducing the old behaviour, be careful with bOnlyRelevantToOwner: it hides the whole actor from every other client, not just its inventory. That is right for a private stash actor and catastrophic for a character.

Testing it​

Automation tests run in a single process, so replication cannot be exercised by them. That is not a gap you can close with more tests — it is a property of the harness. What the suite does assert is the thing that makes replication possible: that a container's identity, capacity, contents, rules and shape are all marked to replicate.

The rest wants a real session. Play in editor with 2 or 3 players and check the client window, not just the server's. When something misbehaves, the first question worth answering is "which window?" — client-only is replication, both is logic, and that one word halves the search.

Where next​