Entity Remapping
Replecs will automatically create entities from the server when they dont exist and have a lookup table to translate server entities to client entities.
But there will be cases where you already created the equivalent entity in the client separately, and you want to tell replecs to use that entity for the server entity. Let’s see how we can do this:
Custom Ids
Section titled “Custom Ids”Customs Ids will let you get information about the entity the server is trying to create. And determine which entity to use.
There are two ways you can use custom ids:
Using a component value.
Section titled “Using a component value.”If you can use a component value to determine which entity to use, you can set a custom id with the component.
-
You can use
server_replicator:set_custom(entity, component)to set a custom id for an entity. -
You can also add a pair
replecs.customwith the component as the target.
local Players = game:GetService("Players")local replicator = require("@server/replicator")local components = require("@shared/components")
local player_entity = world:entity()world:add(player_entity, replecs.networked)world:set(player_entity, components.player, Players.Player1)
-- this will use the value of player_component in the clientreplicator:set_custom(player_entity, components.player)
-- or you can alternatively use a pairworld:add(player_entity, jecs.pair(replecs.custom, components.player))Now, we can set a handler using replecs.custom_handler in the client for the player component.
This function will receive the value of the component and return the client entity.
local components = require("@shared/components")
local player_refs: {[Player]: Entity} = {}
local player_entity = world:entity()player_refs[Players.Player1] = player_entityworld:set(player, components.player, Players.Player1)
world:set(components.player, replecs.custom_handler, function(player: Player) return player_refs[player]end)Complex Custom Ids
Section titled “Complex Custom Ids”If you need more complex logic or more information about the entity, you can use a custom parser. This will allow you to get any information about the entity its trying to create, as well as information about other entities.
To start you can create a custom handler by caling replecs.create_custom_id(identifier, handler).
- The first argument is the identifier. This is a string that will be used so the client and server can agree on what to use.
- The second argument is the handler callback. This will be called in the client when the entity is trying to be created.
The handler callback will receive a HandleContext object. This can be used to get entity information.
You can find all the functions in the API Reference.
After that, you need to register the custom id in both the client and server by calling replicator:register_custom_id(custom_id).
The shared replicator has a method that will call replicator:register_custom_id for both the server and client.
local new_custom = replecs.create_custom_id("identifier")
new_custom:handler(function(ctx) -- do something (called in server) print(ctx.component(c.foo)) print(ctx.target(c.bar)) print(ctx.entity(some_server_entity))end)
-- register for both the server and clientreplicator:register_custom_id(new_custom)
--- serverlocal entity = world:entity()server_replicator:set_custom(entity, new_custom)Cancelling Custom Ids
Section titled “Cancelling Custom Ids”Replecs supports cancelling the creation of a custom id. You can do this by returning nil from the handler.
This is not recommended, and if you use it, its worth nothing:
-
This will cause ctx.target() and ctx.entity() from other custom id to return nil if the requested entity aborted the handler. This is not reflected in the types for simplicity. You can return
nilagain to abort the current one too. -
The custom handler will be called again if replecs needs the entity again for applying server updates.
Here’s an example of how to use custom ids to create a building prediction system. This will create a building entity instantly in the client without waiting for the server. This makes the game feel more responsive.
local replecs = require("@pkg/replecs")local world = require("@shared/world")local replicator = require("@shared/replicator")local c = require("@shared/components")
local building_custom = replecs.create_custom_id("building-prediction")replicator:register_custom_id(building_custom)
building_custom:handler(function(ctx) for building, building_uuid in world:query(c.building_uuid):with(c.predicted) do if building_uuid == ctx.component(c.building_uuid) then -- building is no longer predicted world:remove(building, c.predicted) return building end end
-- default to normal behaviour return world:entity()end)
return building_customlocal HttpService = game:GetService("HttpService")
local building_custom = require("@shared/custom/building")
local c = require("@shared/components")local world = require("@shared/world")local replicator = require("@server/replicator")local remotes = require("@shared/remotes")
function create_building(player: Player, transform: CFrame) local building_uuid = HttpService:GenerateGUID(false)
local building = world:entity() world:set(building, c.building_uuid, building_uuid) world:set(building, c.transform, transform)
replicator:set_networked(building) replicator:set_reliable(building, c.transform, transform) replicator:set_reliable(building, c.building_uuid)
-- setting custom id replicator:set_custom(building, building_custom)
-- return the building uuid so the client can know which server entity to use return building_uuidend
remotes.create_building:set_callback(create_building)local world = require("@shared/world")local c = require("@shared/components")local remotes = require("@shared/remotes")local replicator = require("@client/replicator")
-- called when trying to add a buildingfunction build(building: Model, transform: CFrame) local predicted_building = world:entity() world:set(predicted_building, c.transform, transform) world:set(predicted_building, c.model, building:Clone()) world:add(predicted_building, c.predicted)
-- this is to hold the predicted building until the server confirms it world:add(predicted_building, c.prediction_active)
task.spawn(function() local building_uuid = remotes.create_building:invoke_server(transform) if building_uuid then world:set(predicted_building, c.building_uuid, building_uuid) end
world:remove(predicted_building, c.prediction_active) end)end
function delete_predicted() for building in world:query(c.predicted):without(c.prediction_active) do world:delete(building) endend
local updates = collect(remotes.send_updates)
function replecs_client() for buf, variants in updates do replicator:apply_updates(buf, variants) -- if there was a predicted building, the custom id should've claimed it -- delete what wasn't claimed delete_predicted() endend
system(replecs_client)Global Ids
Section titled “Global Ids”In essence, global ids are a worse version of custom ids. These are entities that get marked with a single number from 1 - 245.
There is no extra data associated like a component in Custom Ids.
The reason why they exist is because they are a lot more bandwidth efficient.
They can be represented with a single byte rather than 6 bytes for normal entities. Making it a good fit for cases where bandwidth is crucial.
If packet size is not a concern, custom ids should be always prefered. Its the responsability of the user to give a meaning to the number
To use global ids you need to set replecs.global to the entity with the number you want to use.
local replecs = require("@pkg/replecs")
local entity = world:entity()world:set(entity, replecs.global, 1)In the client, you set a handler globally by doing client_replicator:handle_global(handler).
This handler will receive the number, and should return an entity.
local entity = world:entity()
client_replicator:handle_global(function(id: number) if id == 1 then return entity else error("invalid global id") endend)