Skip to main content

Services

WeaverServices are created through the WeaverServer. Services are simply tables, which makes them able to be created in modules, rather than needing to all be created in the same script, which is why Weaver's structure works so well.

Construction

When creating a service, the only key needed in the table is the Name property, like so:

local Service = Weaver.CreateService({ Name = "Service" })

If the extra parenthesis do not fit your coding style, you can also remove them:

local Service = Weaver.CreateService{ Name = "Service" }

Other values are also able to be specified in this initialization phase, such as the Client table. This table is what exposes certain methods or signals to the client. It's also possible to specify an Attributes table, which are directly tied to Roblox's instance attributes.

local Service = Weaver.CreateService{
Name = "Service";
Attributes = {
isService = true;
};
Client = {
Shoot = Weaver.CreateRemoteSignal();
};
}

Alongside this, you're also free to add any other items to the service's table itself; the only restrictive table are the Client and Attributes tables. The Client table accepts methods and RemoteSignal markers, which are created via WeaverServer.CreateRemoteSignal() and WeaverServer.CreateUnreliableSignal(). Any other type is classified as a RemoteProperty, and will be instantiated as one.

local Service = Weaver.CreateService{
Name = "Service";
Client = {
ImportantCall = Weaver.CreateRemoteSignal();
SomeProperty = 0;

GetSomething = function(self: WeaverExposed): string
return "Something"
end;
};

someSharedValue = 123;
someOtherSharedValue = "hello";
someSharedValues = { 1, 2, 3 };
}
caution

The Client table is shallow, and all other tables inside of it will be converted into a RemoteProperty once Weaver starts.

Signals

Let's say that we don't want to expose signals to the client, rather, we want to have an event that other services can listen to, like a BindableEvent. Weaver implements this through it's built-in class: Signal. You are able to create a Signal by using the module located at Weaver.Util.Signal. Unlike RemoteSignals, these are available as soon as you create them via Signal.new().

First, we create our service, which handles our signal:

local Signal = Weaver.Util.Signal -- Util is a module, and Signal is already required!
local EventFiringService = Weaver.CreateService{
Name = "EventFiringService";
SomethingHappened = Signal.new();
}
function EventFiringService:WeaverStart(): ()
-- Fire SomethingHappened in WeaverStart,
-- as the connections from other services should be ready
self.SomethingHappened:Fire()
end

Then, we create our service which uses this signal:

local EventHandlingService = Weaver.CreateService{ Name = "EventHandlingService" }
function EventHandlingService:WeaverInit(): ()
-- First, we get the service
local EventFiringService = Weaver.GetService("EventFiringService")
-- Then, we connect an event to it
EventFiringService.SomethingHappened:Connect(function(): ()
print("Something happened!")
end)
end
caution

Trying to add Signals to WeaverService.Client will not work. The signal will still exist, but it will not be accessible from Client once Weaver starts, as it cannot be exposed to the client.

Properties

Weaver also has support for RemoteProperties. These are properties which are set by the server, and passed to the client. These properties are also able to be different for every client, while still having a default value. This is great for services which may handle points or currency. As an example, here is a basic money service:

local MoneyService = Weaver.CreateService{ Name = "MoneyService" }
-- This will initialize as a RemoteProperty once Weaver is initialized
-- The value this is set to will be used as the default
MoneyService.Client.Money = 0
MoneyService.Client.AddMoney = Weaver.CreateRemoteSignal()

local Money: RemoteProperty -- This gets initialized in WeaverInit, so it can be used as a shorthand
-- This is in the server table, so this won't clash with the signal's name
function MoneyService:AddMoney(Client: Player, Amount: number): ()
Money:SetFor(Client, Money:GetFor(Client) + Amount)
end
function MoneyService:WeaverInit(): ()
-- Initialize our Money variable
Money = self.Client.Money
-- Hook our AddMoney signal to the AddMoney function aswell,
-- so players can also give themselves money
self.Client.AddMoney:Connect(function(Client: number, Amount: number): ()
self:AddMoney(Client, Amount)
end)
end

This service has a setup where both services and clients can add money to their own values, though you may not want to have a setup like this in production.

Similar logic can be used for something that relies on data; if your game uses DataStores, you may want to set a client's value on a RemoteProperty to their stored value once they load in. This value could be something like the number of kills a client has reached, or their current level and experience points. Do note that RemoteProperties can have all data types that RemoteEvents support.

The methods, signals, properties, and attributes all have their own distinct uses. You should pick which one you want to use based on which is the mose optimal for your own use cases.

Initialization

Trying to utilize services before Weaver has started will result in errors, especially when dealing with RemoteSignals. This is because Weaver needs to construct all of the services internally so that they are ready when WeaverInit and WeaverStart are called. This is because the client could have unexpected behavior dealing with services, as issues related to race conditions may cause certain signals to be missing, should services be initialized early.

Weaver's way of dealing with this is through two methods: WeaverInit and WeaverStart. As outlined in the execution model, all service objects are internally created and handled, then WeaverInit is called, followed by WeaverStart. These methods are declared by adding Service:WeaverInit() and Service:WeaverStart() to your service.

local MyMultiStepService = Weaver.CreateService{
Name = "Service";
Client = {
SomeExposedSignal = Weaver.CreateRemoteSignal();
};
}

function MyMultiStepService:WeaverInit(): ()
print("Init", self.Name)
-- Connect some events here...
self.Client.SomeExposedSignal:Connect(function(client: Player): ()
warn(client, "fired my exposed signal!")
end)
end
function MyMultiStepService:WeaverStart(): ()
print("Start", self.Name)
-- Fire some events or execute some functions here...
local SomeOtherService = Weaver.GetService("SomeOtherService")
SomeOtherService.SomeSignal:Fire(123)
end

-- Output:
-- Init Service
-- Start Service

As seen above, WeaverInit will always be called before WeaverStart. This goes for all services that you create for your WeaverServer. The best practice is to connect handlers for Signals, RemoteSignals, or other RBXScriptSignals in WeaverInit, and then execute functions or handle things that update based on the task scheduler — i.e RunService.PostSimulation — in WeaverStart. This helps to prevent race conditions from occuring, where you may fire a signal before its handler is connected.

Note that anything outside of the Client table is not exposed to any clients. This is to guarantee safety as you can control what clients are able to access. Usually, for formatting purposes, you may also want to define Client outside of WeaverServer.CreateService().

local Service = Weaver.CreateService{ Name = "Service" }
Service.Client.SomeSignal = Weaver.CreateRemoteSignal()

function Service.Client:IncrementNumber(value: number): number
return value + 1
end

If you have ModuleScripts for every WeaverService, you are also able to use WeaverServer.AddServices() to add all of your services in bulk. This makes it easy to load all of your services at once, and it's especially useful if all of your services are in one location.

local Weaver = require(Weaver)

Weaver.AddServices(script.Runtime) -- Just an example of a directory to store your services in
Weaver.Start()

Caveats

When you first create a Weaver service, a few things will not be available:

  • WeaverService.Client will not be initialized until Weaver is started, meaning all RemoteSignals will still be markers, and all RemoteProperties will only be the values that you put in the Client table.
  • Client.Server, which is used to access the Server table from methods within the Client table, will also not be initialized until Weaver has started.
  • All of WeaverService's attribute methods will be unavailable, as Weaver needs to start to initialize the service, which then internally sets the attributes and exposes the methods.

See Weaver's execution model to understand how services are initialized.