# `nupp.runtime.hotreload` The development-only half of Nupp hot reload. Generated watch modules keep their public function values stable and dispatch through one array of implementation functions. A patch is loaded while `stage` owns the registry: `define` validates every candidate and joins its authored upvalues to the cells held by the live implementation. Nothing in a live slot changes until `commit` has a complete candidate vector. The state is process-wide so a host and generated modules may require this module through different loader paths without splitting the registry. Ordinary generated programs never require it. See [Hot reload](../../../../learn/projects/hot-reload/index.html) for what a watch build does with this, and [NEP 6: Hot reload](../../../../reference/neps/0006-hot-reload/index.html) for why a patch is refused rather than applied partially. ## Types ### `Candidate` _type_ ```nupp type hotreload.Candidate = { --- The registration record the slot belongs to, which is private to this module. module: any, index: integer, implementation: hotreload.Implementation, --- The implementation's own upvalue numbering. Committing publishes it into the --- definition, because the next patch joins against this implementation and Lua --- numbers upvalues by first reference, which a changed body reorders. captures: hotreload.Captures } ``` One slot a prepared patch will fill, and the implementation to fill it with. ### `Captures` _type_ ```nupp type hotreload.Captures = { byName: {[string]: integer}, order: {string} } ``` Where a function's captured names sit in its own upvalue list. `order` is sorted, so two implementations of the same function can be compared name by name without either side's upvalue numbering mattering. ### `Definition` _type_ ```nupp type hotreload.Definition = { id: string, signature: string, selfName: string?, captures: hotreload.Captures } ``` What a live slot is known by, beside the implementation filling it. A candidate has to match all four before it may be joined: a changed identity, signature, recursive binding or capture set is a change the running process cannot take, and each is refused by name. ### `Implementation` _type_ ```nupp type hotreload.Implementation = function(...: any): any ``` One function body a slot can hold. Generated code writes these and generated code calls them, so no two slots need agree about what a call takes or answers. What this module does with one is join its upvalues and store it, neither of which reads a parameter. ### `Slots` _type_ ```nupp type hotreload.Slots = {hotreload.Implementation} ``` The implementation array a generated watch module dispatches through. Naming it is what keeps the three functions that pass one around agreeing about what they are passing, and what lets `bySlots` be keyed by it. ### `Staging` _type_ ```nupp type hotreload.Staging = { baseGeneration: integer, candidates: {hotreload.Candidate}, --- Module name and slot index, so one patch cannot fill a slot twice. byKey: {[string]: hotreload.Candidate}, policies: {[any]: {[string]: boolean}} } ``` What `stage` accumulates while the patch chunk runs. It is the registry `define` writes into: private to the staging call, discarded whether the chunk succeeds or raises, and turned into a transaction only once the whole chunk has run without one. ### `Transaction` _type_ ```nupp type hotreload.Transaction = { baseGeneration: integer, generation: integer, serial: integer, candidates: {hotreload.Candidate}, --- Dynamic-custody policies per registration record, keyed the way `candidates` --- names them. policies: {[any]: {[string]: boolean}}, committed: boolean } ``` A validated patch that has changed nothing yet. Everything fallible has already happened by the time one of these exists, which is what lets `commit` publish the whole vector with no failure point in the middle. `serial` distinguishes two transactions prepared from the same generation. ## Functions ### `hotreload.abort` _function_ ```nupp function hotreload.abort(name: string): nil ``` Discards an incomplete registration after its initial chunk fails. A module that already sealed is left alone, so this is safe to call from an error path that cannot tell how far the chunk got. #### Arguments | Name | Type | Description | | --- | --- | --- | | `name` | `string` | the watch module's name | #### Returns | Type | Description | | --- | --- | | `nil` | | ### `hotreload.commit` _function_ ```nupp function hotreload.commit(prepared: hotreload.Transaction): integer?, string? ``` Atomically publishes a prepared candidate vector. Lua cannot run authored code between the direct array writes below, so no dispatch can observe half a patch. #### Arguments | Name | Type | Description | | --- | --- | --- | | `prepared` | `hotreload.Transaction` | what `stage` answered | #### Returns | Type | Description | | --- | --- | | `integer?` | the committed generation, or nil when this transaction is stale | | `string?` | why it is stale, when it is | ### `hotreload.define` _function_ ```nupp function hotreload.define(slots: hotreload.Slots, index: integer, id: string, signature: string, selfName: string?, implementation: hotreload.Implementation): nil ``` Registers an initial implementation, or one candidate in the active stage. A candidate must match the live definition's identity, signature, recursive binding and capture set. Each of those is a change the running process cannot take, and each is refused by name. #### Arguments | Name | Type | Description | | --- | --- | --- | | `slots` | `hotreload.Slots` | the module's slot array | | `index` | `integer` | which slot this fills | | `id` | `string` | the declaration identity the slot was registered under | | `signature` | `string` | the declaration's signature | | `selfName` | `string?` | the recursive binding's name, when the function has one | | `implementation` | `hotreload.Implementation` | the function body to install | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - when identity, signature, or captured lexical cells are incompatible ### `hotreload.generation` _function_ ```nupp function hotreload.generation(): integer ``` Answers the currently published implementation generation. #### Returns | Type | Description | | --- | --- | | `integer` | | ### `hotreload.loaded` _function_ ```nupp function hotreload.loaded(name: string): boolean ``` Answers whether a module has completed its initial watch execution. #### Arguments | Name | Type | Description | | --- | --- | --- | | `name` | `string` | | #### Returns | Type | Description | | --- | --- | | `boolean` | | ### `hotreload.module` _function_ ```nupp function hotreload.module(name: string): hotreload.Slots ``` Answers the direct implementation array for one loaded watch module. Outside staging, naming a module that has not executed registers it, because that is a module about to run its own initial chunk. Inside staging it is a patch naming something that was never loaded, which is refused. #### Arguments | Name | Type | Description | | --- | --- | --- | | `name` | `string` | the watch module's name | #### Returns | Type | Description | | --- | --- | | `hotreload.Slots` | the slot array that module dispatches through | #### Raises - when a patch names a module that has not executed yet ### `hotreload.policy` _function_ ```nupp function hotreload.policy(slots: hotreload.Slots, key: string): nil ``` Records one managed-cell cleanup policy a generated module uses. During staging the candidate set stays private until every live managed cell admits the change, so a policy that still owns a payload cannot be changed out from under it. #### Arguments | Name | Type | Description | | --- | --- | --- | | `slots` | `hotreload.Slots` | the module's slot array | | `key` | `string` | the stable policy key | #### Returns | Type | Description | | --- | --- | | `nil` | | ### `hotreload.resetForTesting` _function_ ```nupp function hotreload.resetForTesting(): nil ``` Clears process state. Intended for compiler/runtime tests only. #### Returns | Type | Description | | --- | --- | | `nil` | | ### `hotreload.seal` _function_ ```nupp function hotreload.seal(name: string): nil ``` Marks a module's initial registration complete after its chunk returns. #### Arguments | Name | Type | Description | | --- | --- | --- | | `name` | `string` | the watch module's name | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - when the module is unknown or has already been sealed ### `hotreload.stage` _function_ ```nupp function hotreload.stage(source: string, baseGeneration: integer): hotreload.Transaction?, string? ``` Loads and validates a patch without changing any live implementation. Everything fallible happens here, so a transaction that comes back can be committed without a failure point in the middle of the vector. #### Arguments | Name | Type | Description | | --- | --- | --- | | `source` | `string` | the patch chunk | | `baseGeneration` | `integer` | the generation the patch was built against | #### Returns | Type | Description | | --- | --- | | `hotreload.Transaction?` | the prepared transaction, or nil when it was rejected | | `string?` | why it was rejected, when it was |