Controllers¶
Controllers are named client-side systems. They coordinate UI, input, presentation, and calls to server service proxies.
Definition¶
Recognized fields:
| Field | Required | Description |
|---|---|---|
Name |
Yes | Non-empty unique controller name |
Logger |
No | Custom logger; Vanguard creates one when omitted |
Controllers do not have service priority. Their init hooks run concurrently.
Lifecycle¶
function InventoryController:VanguardInit()
self.Items = {}
end
function InventoryController:VanguardStart()
self.InventoryService = Vanguard.GetService("InventoryService")
self:LoadItems()
end
All controller init hooks must complete before start hooks are scheduled. Start hooks are not awaited.
KnitInit and KnitStart remain supported as migration aliases.
Calling Services¶
function InventoryController:LoadItems()
self.InventoryService:GetItems():andThen(function(items)
self.Items = items
end):catch(function(err)
self.Logger:Warn(`Could not load inventory: {err}`)
end)
end
With the default ServicePromises = true, every remote function returns a Vanguard Promise.
With ServicePromises = false, the call yields and returns directly:
Promise mode is recommended because it makes network failures explicit and keeps yielding behavior visible at the call site.
Listening to Remote Signals¶
function InventoryController:VanguardStart()
local InventoryService = Vanguard.GetService("InventoryService")
InventoryService.InventoryChanged:Connect(function(items)
self.Items = items
end)
end
On the client, remote signal callbacks receive only server-sent payload values. The Player argument exists only for server callbacks receiving client events.
Reading Remote Properties¶
local currentState = MatchService.State:Get()
local subscription = MatchService.State:Observe(function(state)
self.State = state
end)
Observe immediately fetches the current value asynchronously, then reports later updates. Disconnect it during controller teardown when your controller implements its own teardown lifecycle.
Automatic Registration¶
Controller modules loaded by Vanguard may return either:
- an object already created with
CreateController; or - a plain table with a valid
Name.
Loading Controllers¶
LoadControllers and LoadControllersDeep are aliases.
The shallow form requires direct ModuleScript children. The deep form walks all descendants. Modules are processed in full-path order.
Failed modules are logged and skipped without preventing other modules from loading.
Controllers must be loaded before Start.
Controller Lookup¶
local HUDController = Vanguard.GetController("HUDController")
local controllers = Vanguard.GetControllers()
An already-registered controller can be retrieved before startup. Unknown controller lookup requires startup and then produces an error listing known controllers.
GetControllers requires startup to have begun. The returned registry is frozen by default during startup when FreezeControllers = true.
Service Proxy Lookup¶
Client GetService requires Start to have been called. The first lookup waits for the server's remote folder, verifies Vanguard's network protocol, builds a proxy, and caches it. Later lookups return that same proxy.
If no server service folder with that name appears within RemoteTimeout, lookup errors.
Logging¶
Controllers receive a logger scoped to their name:
Creation Rules¶
- Names must be non-empty strings.
- Names must be unique in the client runtime.
- controllers cannot be created after
Startbegins. - controller init errors reject startup.
- controller start errors occur in spawned runtime tasks and do not roll startup back.
Design Guidance¶
- Keep server authority in services, never controllers.
- Put UI and input state in controllers.
- Keep remote calls behind controller methods when multiple UI elements use them.
- Store remote connections in a Cleaner when the controller has a custom teardown path.
- Use components for repeated behavior attached to tagged Instances.