Lifecycle
Every Service and every Controller goes through the exact same three-phase lifecycle when Owl.Start() runs. Understanding the order and why it's ordered that way avoids a whole category of "why is this nil" bugs during startup.
Owl.Start()
│
├── [Phase 1] OwlInit() - sequential, in dependency order
│ Internal setup, fetching references.
│ ⚠️ Do not connect events here.
│
├── [Phase 2] OwlStart() - parallel, all at once
│ Event connections, loops, main logic.
│ ✅ The entire framework is guaranteed operational here.
│
└── [Destruction] OwlDestroy()
Memory cleanup, Trove destruction.Phase 1 - OwlInit
Runs sequentially, following the dependency graph built from each module's Dependencies table (see Dependencies): if InventoryService depends on DataService, DataService:OwlInit() is guaranteed to have already returned before InventoryService:OwlInit() starts.
This is where you fetch references to other Services or Controllers and do any setup that doesn't depend on the rest of the framework being live yet:
function InventoryService:OwlInit()
self.DataService = Owl.GetService("DataService")
endDon't connect events, start loops or fire Signals during
OwlInit. At this point, other Services may not have finished their ownOwlInityet the guarantee only covers your declared dependencies, not every other module in the game. Save anything that assumes "the whole framework is up" forOwlStart.
Phase 2 - OwlStart
Runs in parallel across every Service and Controller by this point, every single one has already finished OwlInit, so there's nothing left to wait for. This is where the actual logic lives: event connections, loops, timers, the first Fire/SetFor calls.
function InventoryService:OwlStart()
self.DataService.PointsChanged:Connect(function(plr, total)
self:SyncInventoryValue(plr, total)
end)
endDestruction - OwlDestroy
Called when a Service or Controller is torn down most commonly a Component instance being removed (see Components), rather than a top-level Service, which normally lives for the whole server session. Use it to disconnect connections and clean up state, typically via a Trove instance created during OwlInit or OwlStart.
function InventoryService:OwlDestroy()
self._trove:Destroy()
endWhy sequential Init but parallel Start?
OwlInit is sequential specifically to prevent a Service from reaching into another Service before that one has finished setting itself up, the exact race condition Knit doesn't protect against (see Owl vs Knit).
OwlStart is parallel because, by definition, every module has already cleared OwlInit by the time any OwlStart runs, there's nothing left to wait for so running them concurrently just makes startup faster.
Where to go next
- Dependencies - exactly how the
OwlInitordering is computed fromDependenciestables. - Automatic Hooks - hooks like
OwlOnPlayerAddedthat piggyback on this same lifecycle. - Services and Controllers - full anatomy of the modules this lifecycle applies to.