Skip to content

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:

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:

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.custom with 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 client
replicator:set_custom(player_entity, components.player)
-- or you can alternatively use a pair
world: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_entity
world:set(player, components.player, Players.Player1)
world:set(components.player, replecs.custom_handler, function(player: Player)
return player_refs[player]
end)

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 client
replicator:register_custom_id(new_custom)
--- server
local entity = world:entity()
server_replicator:set_custom(entity, new_custom)

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 nil again 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_custom

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")
end
end)