Manually replicating a TMap

I’m working on implementing a points matrix, to show how many times each player has scored off another. For example, with 8 players in game, it’d be an 8x8 grid of 64 total numbers. The best datastructure I’ve thought of is a nested TMap, with the key for both being a struct containing the PlayerState and the player’s name for each player in the game. The main reason for the struct is to preserve the data after a player quits.

The issue is that TMap can’t be replicated. Instead, I’m going to need to use replicated function calls to initialize the data and keep it synced up between clients. Since this requires object references outside of UPROPERTYs, I’m somewhat concerned that I’ll end up referencing garbage collected memory, so I’d appreciate another look at my plan to ensure that I’m not working off any faulty assumptions or otherwise doing anything dangerous. I’m nowhere near being able to test any manner of replication yet.

First, the struct and variables:

struct StateOrName
{
	APlayerState *state;
	int playerNameId;
};

inline uint32 GetTypeHash(StateOrName *A)
{
	return A->playerNameId;
}

	UPROPERTY()
	TArray<FString> trackedNames;

	TMap<APlayerState *, StateOrName *> psLookup;

	TMap<StateOrName *, TMap<StateOrName *, int32>> pointMatrix;

The purpose behind trackedNames is to ensure that the FNames are made visible to the garbage collector. playerNameId in the struct is the index of the relevant name in trackedNames.

The planned code flow:

When a player joins:

  1. Starting from the PlayerState’s BeginPlay() function,
  2. Send the current pointMatrix data to them via a replicated function (probably as 2 TArrays)
  3. Update pointMatrix to include the new player.

BeginPlay() should already be called on both the client and the server, however if the client copy doesn’t have the necessary data (playername) at this point, it’ll need to be triggered elsewhere

When a player leaves:

  1. Starting from the PlayerState’s Destroyed() function,
  2. Update every StateOrName and set state to NULL.

I assume the object is still readable, allowing me to add the name to trackedNames now. Otherwise it’ll be set as part of the join.

When a point is scored:

  1. Call a function to replicate to the client
  2. Call the update function
  3. The client-side function then calls the same update function.

Are there any design issues or potential pitfalls with this approach? It should initialize every client to the correct state on join and keep everything synced properly. I’d like to know if I’m misunderstanding something about the garbage collector before I actually begin coding this.

Thanks in advance.