Simplify ReserveServer

As a Roblox developer, it is currently too hard to teleport two players from different servers to the same reserved servers without possible edge cases.

If you want to teleport multiple players to the same ReservedServer based off of a key, this is how you might do it

local TS = game:GetService("TeleportService")
local Players = game:GetService("Players")
local DSS = game:GetService("DataStoreService")
local DS = DSS:GetGlobalDataStore()

return function(key)
    local code = DS:GetAsync(key)
    if type(code) ~= "string" then
        code = TS:ReserveServer(game.PlaceId)
        DS:SetAsync(key,code)
    end
    return code
end

You’d use a function like this :point_up: to translate the key into a ReservedServer code that is stored using DataStore.

The problem with this is that DataStore is Asynchronous.
If two players try to teleport to the same server at once, there’s a chance that the ReservedServer code is overwritten. If this happens, the players will teleport to two seperate ReservedServers.

The overly complicated solution to this would be to use something with session locking, like ProfileService. (not entirely sure how ProfileService has session locking if datastore is async)
You’d create a new profile for the key and store the ReservedServer code in that profile.
This seems a bit much for something so simple. Why can’t Roblox handle this for us?
I wish I could just do something like

TeleportService:TeleportToPrivateServer(4419893655,"whatever i want",{game.Players.dispeller})

I also don’t see why ReservedServer Access Codes and PrivateServerIds have to be two different things. Maybe make PrivateServerId only visible on the server side and use that for everything.


If Roblox is able to address this issue, it would improve my development experience because Reserved servers would be easier to use properly.

Use Case

I could see this as being useful for developers who want to create cross-server dueling systems.

Personally, I’m working on a game with infinitely generated terrain.
When the player reaches the edge of the map, they get teleported to another reserved server.
The world is basically a grid of reserved servers that players teleport between. Each server has a world generated with the coordinate as the seed.
image
in the above image, I tried to illustrate a scenario where this edge case could occur.
Red and Green are players and the arrows show the direction they’re traveling.

3 Likes

Wouldn’t UpdateAsync be able to resolve your edge case here? There are many recommendations on the Developer Hub mentioning that if it’s possible that two instances can update the key at the same time, it should be used. UpdateAsync performs data validation and gets called multiple times until it can save, whereas a GetAsync-SetAsync pattern will just set a value and do no extra work.

Assuming that two instances call UpdateAsync and you’re met with the edge case of the key being overwritten and also assuming that the value appropriately updates and is passed as the function parameter between each attempt to write to the DataStore, you can reject the write request if the key turns up non-nil.

local TeleportService = game:GetService("TeleportService")
local DataStoreService = game:GetService("DataStoreService")

local GlobalDataStore = DataStoreService:GetGlobalDataStore()

return function(key)
    -- ReserveServer yields and UpdateAsync cannot yield, so we make one outside.
    -- Codes and instances generated by RS can be issued infinitely at no cost.
    local sessionCode = TeleportService:ReserveServer(game.PlaceId)

    GlobalDataStore:UpdateAsync(key, function(oldKey)
        -- Returning nil cancels the write
        if oldKey then return nil end

        -- Returning a value attempts to use this as the key's new value
        return sessionCode
    end)

    return sessionCode
end

ProfileService only implements UpdateAsync for any of its interactions with DataStore; no SetAsync and no GetAsync. This is also how it’s able to nail down session locking. Metadata is attached to data managed with ProfileService, which session locking is part of. With UpdateAsync, write requests are essentially queued up. As one goes through, another one starts processing and can check the metadata for a locked session before proceeding to save or cancel a save.

1 Like

I totally forgot UpdateAsync was a thing lol
(that’s what I get for just using DataStore2)

I still think it would be nice to simplify Reserved Servers, but this makes things more bearable

Thanks!

1 Like