Skip to main content

Services

Services act as the authoritative backend for your game. They handle data management, physics calculations, server-side validation, and state management.

Every Service is a singleton, meaning only one instance of it exists per server.

Defining a Service

A standard Service requires, at minimum, a table to hold its structure. The CLI loren make service command generates the following boilerplate:

local PlayerDataService = {
Dependencies = {},

-- Signals are declared as an array of names (strings).
-- Loren swaps this array for ready-to-fire Signal objects on ignition.
Signals = {},

Client = {},
Middleware = {}
}

function PlayerDataService:LorenIgnite()
-- Synchronous setup
end

function PlayerDataService:LorenBurn()
-- Asynchronous execution
end

return PlayerDataService

The Client Table

By default, everything inside a Service is strictly private to the server. To allow a Client Controller to interact with your Service, you must explicitly define functions inside the Client table.

The framework intercepts calls to these functions and automatically manages the underlying RemoteEvent data packing.

Method Structure

When a Client invokes a Service method, the framework guarantees that the first argument received by the server is the Player object who made the request.

-- The client only passes 'itemId'
function PlayerDataService.Client:PurchaseItem(player, itemId)
-- The server securely receives the player context
local hasFunds = self.Server:CheckFunds(player)

if hasFunds then
return true, "Item purchased."
else
return false, "Insufficient funds."
end
end
The Server Context

Notice the use of self.Server in the example above. Methods executed within the Client table have their self context shifted. To access the top-level Service functions and variables, you must route through self.Server.

Firing Signals

Signals provide a highly optimized alternative to standard RemoteEvents. First declare the signal name as a string in the Signals array. During ignition, Loren replaces that array with live Signal objects you can fire by name.

local PlayerDataService = {
Signals = {"PointsUpdated"}, -- declare the name here
}

function PlayerDataService:AwardPoints(player, amount)
-- Logic to award points...

-- Notify the specific player
self.Signals.PointsUpdated:Fire(player, amount)

-- Or notify every connected client
self.Signals.PointsUpdated:FireAll(amount)
end
Server context

The example above fires from a top-level Service method, so self.Signals is correct. Inside a Client method self is the Client table — reach signals through self.Server.Signals instead.

See the Signal API reference for every method (Fire, FireAll, Connect, Once).