I’m working on a system that keeps track of friends server-side. IsFriendsWith is fine for small servers, but I need this to scale well for hundreds of players.
Has anyone had problems polling GetFriendsAsync periodically for all players on the server? I’m thinking I could set it up so it polls roughly every 15-60 seconds. Ideally there would be some way to set up signals for this, but polling seems like the only way right now.
With faster polling rates I could ignore players that aren’t online to reduce the memory footprint for this system, but I need this to be reliable in cases where friends join within seconds of going online.
Here are a few of my uses cases:
Reliable friend-based permissions for various game mechanics: Who can enter home, who can send ride requests, etc. This information needs to be ready to go all the time with no yielding.
Change player name color based on if they are a friend without any API calls client-side.
“Friend joined” notifications per-server.
“Friend joined” notifications cross-server. This depends on if I run into MessagingService limitations.
I could make it refresh sooner when the client detects StarterGui:GetCore("PlayerFriendedEvent") or StarterGui:GetCore("PlayerUnfriendedEvent"). I dislike these events because they pass Player objects though; I’m hopeful that someday I will be able to configure my game to not replicate players by default. There are both privacy and scalability problems when hundreds (or thousands) of players are replicated by default:
When a player is added it would start polling GetFriendsAsync and stop when they left. Each time it refreshes it would check for friended-ness changes.
My first implementation would check for friends client-side and replicate it to the server; If both clients agreed then the server would consider them friends. It just does’t scale well for the client to check friended-ness on hundreds of players when they join.
What I would do is store players’ friends in a table when they join and refresh the table whenever these StarterGui:GetCore("PlayerFriendedEvent") or StarterGui:GetCore("PlayerUnfriendedEvent") events you mentioned fire.
IsFriendsWith is fine for small servers, but I need this to scale well for hundreds of players.
I’m not convinced. Have you tested this? I want to see some numbers before even considering solutions that should already be happening internally.
The API of IsFriendsWith seems to be designed for your use-cases. The docs even say that the result is cached as necessary. Roblox clearly wants it to be used this way, so they must be pulling some tricks to make it fast. If not, then it would be another reason why high cap servers aren’t “ready” yet.
There are a few ways IsFriendsWith could be used, but it falls short of what I need in most cases.
Client polling - Periodically check IsFriendsWith for all players in the server.
The number of calls for the client while polling is numPlayers - 1.
Client: Signals - Connect to PlayerFriendedEvent/PlayerUnfriendedEvent, and call IsFriendsWith for all players in the server initially and on PlayerAdded.
I want the “Friend Joined” message to be instant, and I’m worried this will be too busy for huge servers as players join and leave. It seems like it would be more efficient to keep track of their friends list (which has a maximum of 200), instead of checking for thousands of players as they join and leave.
There’s added implementation complexity because the client needs to replicate this information to the server, and the server needs to check that both clients agree before considering them friends. It’s also possible for two exploiters to lie about their friended-ness, although I’m not sure how that could be abused. Mainly, I just don’t think it’s a good idea for all players to be replicated in massive servers, and I don’t think I should need to spawn hundreds of threads that call IsFriendsWith when the client is already busy setting up the game.
I want to develop systems that can scale nicely with thousands of players in a server.
Server: Check when-needed - Check IsFriendsWith when the server needs to know if two players are friends.
The main problem is that this would yield game code, which can introduce edge cases and delays. My entire code base is designed to avoid yielding wherever possible (only yielding for necessary Roblox APIs.) It’s so much easier to have a custom system that can check friended-ness in a non-yielding way. This would also result in an unreasonable number of threads/requests when players chat. I also need an event-based friend interface for use-cases like house permissions (so the player gets kicked from the home if they are unfriended.)
Server: Polling - Periodically check IsFriendsWith for all players in the server.
The calls needed is numPlayers * (numPlayers - 1) / 2.
GetFriendsAsync seems like it would be more efficient because there are fewer API calls, although it would need to iterate over significantly more players while allocating/gcing tables created by FriendPages:GetCurrentPage().
IsFriendsWith also doesn’t solve the “Your friend joined a different server” use-case, although I’m not yet sure if this would run into MessagingService limitations. If I need to keep track of friends in other servers anyways, I may as well create a system that manages each player’s friends for the whole server.
What I’m saying is, use IsFriendsWith as late as possible. Call it when a player walks into a house. This eliminates the need to poll entirely, because you’re doing the check exactly at the point it is required. This assumes that IsFriendsWith scales well (we have yet to determine that it doesn’t). If IsFriendsWith and GetFriendsAsync are cooperative internally, then calling GetFriendsAsync once, when the player enters the server, may aid IsFriendsWith caching and reduce the chance of yielding. Once again, it’s hard to say whether this actually happens without actual data.
My entire code base is designed to avoid yielding wherever possible
Well that messes things up. The only thing that comes to mind is to call IsFriendsWith speculatively. e.g. Call it before a player walks anywhere near a door, and hope it resolves before they get there.
As for cross-server notifications, GetFriendsOnline is an interesting API. Not only will it tell you the friends of the player, it will tell you where they are, down to the job ID. If you call this once, when a player enters a server, you could check which servers their friends are in, and send messages to them directly.
In general, my gripe is that it seems like you’re trying to get friend associations for no reason other than to have them. This is wasteful; if a player never tries to enter any houses, then there will be no reason to know who they’re friends with. The amount of work being done to keep these associations fresh versus how often they will be used doesn’t seem worth it.
This would normally work for most uses like that. Creating a central event-based friend system that does polling just seems much more stable and convenient to me, especially when some of the use cases need to know about players in other servers or send notifications. I want messages from friends to stand out in chat, and add various privacy/parental settings that allow players to only see chat/text from their friends.
That would help prevent delays. I’m not exactly sure how the house system will be designed though; The game has pretty big servers so I might implement it as a seamless barrier that un-replicates people who don’t have the same person’s home active (the game already has custom replication for everything.) It would need to quickly create of a list of homes you’re allowed to enter based on permissions/friended-ness so you can join friends easily:
This would be perfect! The server could connect to its own id and listen for friends it detects. The only problem is that GetFriendsOnline only works client-side for some reason. I currently use GetFriendsOnline for my game’s friends menu, so players can send messages or follow friends into a different server (it just verifies that they are a friend before sending.) Perhaps I could have the client do polling for cross-server notifications specifically (with per-client limits), but a hacked client could still send fake job ids; I could verify it with GetPlayerPlaceInstanceAsync though.
It’s certainly wasteful to check for offline friends and friends who are playing different games. It could also be disastrous if the 200 friend limit was raised significantly. I really just need to know which of their friends are playing my game and when they join/leave so that I can implement responsive event-based social features.
“Your friend joined” notifications are the main reason friend checks can’t be lazy.