Network Object (Inventory Item) Removed After Player Removed (even tho looted)

Hey, I have been struggling with a replication and garbage collection issue.

BACKGROUND:

I’m designing a inventory system.

  • All items stored in inventory are UObject. (This because planning that some items will be able to store nested inventories. That’s why Items are not FStruct).
  • Character->InventoryComponent->Inventories->Cells->Items. This is how my system structured.

PROBLEM:

  • on client, local player kills the other player, and take his items. Everything works fine, until other player de-spawn. Then item marked as garbage.
  • Same problem not occuring when server kills client and take his item. Everything works just fine.

DIAGRAM

EXAMPLE VIDEO:

  • Log message (in InventoryComponent::Tick()), prints out Item, its outter and if it marked by garbage collector or not.
  • As seen after second player dies on client and de-spawn, item marked as pending kill.

MY GUESS:

I feel like this is related with the object’s outer however, every time I add a new object I set the outer with following code:

bool URepCell::TryAddItem(UItem* Item, int32 X, int32 Y)
{
	check(Item);

	Item->Rename(nullptr, GetOuter());
...

And yes when move an item, it use RemoveItem() then TryAddItem().

Here is something I noticed, for client this directly set the outter to character but for server it sets for the UInventory and let me note that, I also tried to set outter in InventoryComponent but still didn’t worked.


What are the thoughts? It’s my first time working with multiplayer, so I’m hoping that I’m missing something obvious. Also let me know If I can provide better debug info.

For all help, thanks in advance!

Gameplay relevant actors such as loot must be spawned and replicated by the server… For all clients. All loot management is handled by the Authority Proxy.

Server spawns world item. Player interacts (pickup), RPC server to pickup. Server picks up and adds to inventory, destroying world item.

Player drops item, RPC’s server to drop. Server validates action and drops (spawns a replicated actor), updates inventory accordingly.

For dead player inventory you can create a new actor that holds all the items and spawns at the body. Server must handle this as well.

You also should be storing inventory in the Player State. If a player gets disconnected and rejoins and inventory is stored in controller or pawn, then he will have nothing. Player state persists for a duration for this very reason.

1 Like

PlayerState sounds nice, but one reason I avoided it that I designed whole inventory system to be very modular. It will be used on:

  • Chests
  • Weapon attachments
  • Crafting stations etc.

Simply we can imagine a Rust like game, that inventory more like treated as an object by itself.

I double checked my system and all my workflow seems handled by server. And I disabled all client predictions for now.

Creating a new actor that represents the inventory also sounds nice, but I believe this won’t solve the problem. In same logic after player collect the items, if Actor object removed (which required to be removed eventually), it will again remove my items.

I print both garbage collection values, while on server there are no garbage collection triggered, in the client item marked for garbage collector.

what are they replicating via?
you’re changing the outer but likely it was Replicating by the Owner?

other options is to store them all in the GameState or Subsystem

You can do that with a Parent Actor component. Child classes as needed. I do this with my setup. Loot crates, Weapons (attachment, skins etc), body crates, dropped weapons can keep their attachments, Vehicle storage space that all on team can access, etc and so forth.

Here is the replication chain:

bool UInventoryComponent::ReplicateSubobjects(class UActorChannel* Channel, class FOutBunch* Bunch,
                                              FReplicationFlags* RepFlags)
{
	bool bWroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);

	for (UInventory* Inventory : Inventories)
	{
		for (auto WrappedGrid : Inventory->Cells)
		{
			for (FItemPlacement& ItemPlacement : WrappedGrid->Grid.Items)
			{
				if (ItemPlacement.ItemReference) // Check if the item reference is valid
				{
					bWroteSomething |= 
Channel->ReplicateSubobject(ItemPlacement.ItemReference, *Bunch, *RepFlags);
				}
			}

			bWroteSomething |= Channel->ReplicateSubobject(WrappedGrid, *Bunch, *RepFlags);
		}
		if (Inventory)
		{
			bWroteSomething |= Channel->ReplicateSubobject(Inventory, *Bunch, *RepFlags);
		}
	}

	return bWroteSomething;
}

I draw this diagram shows replication, And I have feelings that replication not works because originally item created from the Local Player of Server, somehow even though I pass the item into new object it’s replication owner stay same. What do you think about this!?

Maybe instead of passing the class, every time I move it, I should create a copy of it to ensure its replication will be re-writed from its owner?

Clients shouldn’t be spawning anything. Even listen server hosts shouldn’t spawn anything. Only the server should be spawning actors. No multicasting for spawning either.

Maybe I’m missing a point, but client do not spawn anything. Item spawned by server, and replicated to the client

my guess is the Rename function isn’t replicated so it works on server but the client copy gets garbage collected

sadly

  • now set the rename also on client, didn’t worked
  • I also switch to new AddReplicateSubobject workflow, and ensure that RemoveReplicatedSuboject called whenever item removed from one inventory however still not working.
  • I managed to make it work with instead of passing same item’s reference, creating copy of the item every time it moved however I think this is just putting the problem under my bed.

i’ve got a similar system that seems to work (tested but not battle tested yet)

however i do all the replication on the UObject itself, all it has to do as add itself as a ReplicatedSubobject.
this way if i change owner it always works since i just call GetOwner.

another option is to look into Iris or how Lyra does it with its inventory fragments

The problem that, the way I hold my nested UObject system. Maybe I should store my Items and inventories in a global object and reference them somehow better idea.

Can I ask that, If I replicate all my items in a global object, can I still use network relevancy? I mean if 2 players so far from each others I would not like them to replicate. Is this achievable with global container?

Seems like an ownership problem. On character death the items aren’t decoupled from the character. So when the character is destroyed any “owned” actors/objects are destroyed with it.

FYI, UObjects are automatically garbage collected, if they are not hard referenced in object which is not garbage collected.

Is your inventory literally UObject or Actor Obj Ref

To be honest that start to make more sense. I made tons of testing and now tend to believe it’s not duplication problem but more like a garbage collection issue on client.

Let me explain my structure as follow:

  • UInventoryComponent : This is the component that holds all inventories
  • UInventory : This is the UObject that contains all URepCells
  • URepCell : This is the object that contains FFastItemPlacementArray
  • FFastItemPlacementArray : This is a fast array to optimize replicated data. Contains array of FItemPlacement
  • FItemPlacement: Contains X, Y and UItem
  • UItem: UObject Item class.

Item also contains array of UItemComponent which adds additional features to item, such as nested inventory or consumable.

I admit that, this was the not best way to design it… But he is like my child now

Btw now I tried to change outer (with Rename) anytime item moved or replicated. So it now updated as client however this didn’t work as well

you said it works on the server though so if ownership failed and/or you had not hard references it wouldn’t work on the server either?

if feel like this is the issue too. instead of using GetOuter() maybe set the actually Actor Owner. I believe UObjects always need an actor to replicate so it shouldnt be a problem.

Dont forget the Add it to the new owner SubObject List

Also tried this, actually tried both outer, and SubObject list at the same time. And yes you have a point on that, it should also garbaged in server. For a moment it seemed to me possible since object outers not replicated fully.

Now I was reading the network documents of the Unreal, especially the Owner part. And thought, maybe I’m making a mistake in the first place:

I’m starting to replicate an object in Server_Character1. and let’s say it use NET_CONNECTION_1. Even though I attempt to change its ReplicateList or even set the outer, maybe it just not switching the NET_CONNECTION. That explains why it stays on server, because in server objects are not replicated.

if the problem is just Garbage Collection you can still replicate via an Actor but keep a hard reference in an ItemManager class, i use subsystems for this, i think its a good idea regardless as you could ‘lose’ UObjects and get a memory leak

That sounds the way to go, I also talked to some developers of similar inventory systems, and see that people actually use GUIDs that track of the both Items and Inventories.

My only concern, if I use a manager like system can I handle the network relevancy? Since my manager item is only one, and contains all items, is it posible to define only replicate items to another player only if they close enough? ( i.e.,range < 500)