Pluginy
PluginHost, manifest, uprawnienia, punkty rozszerzeń, zależności z detekcją cykli i budżety błędów. Jak pisać pluginy bezpiecznie.
Model pluginu#
Plugin to manifest + funkcje aktywacji. PluginHost zarządza cyklem życia, zależnościami i uprawnieniami.
ts
import { PluginHost, type PluginDefinition } from '@engine'
const host = new PluginHost({
engineVersion: '0.1.0',
logger: engine.getLogger(),
events: engine.getEvents(),
services: { assets: engine.getAssets() },
maxFailures: 3
})
const plugin: PluginDefinition = {
manifest: {
id: 'my-plugin',
version: '1.0.0',
engine: '>=0.1.0',
name: 'Mój plugin',
permissions: ['systems', 'events']
},
activate(context) {
context.registerSystem({
name: 'spawn',
update(dt) {
context.events.emit('spawn:tick', { dt })
}
})
},
deactivate() {}
}
host.install(plugin)
host.activate('my-plugin')
Manifest i uprawnienia#
ts
type PluginManifest = {
id: string
version: string
engine: string
name?: string
description?: string
author?: string
dependencies?: string[]
permissions?: PluginPermission[]
}
type PluginPermission =
| 'systems'
| 'importers'
| 'panels'
| 'events'
| 'scenes'
| 'assets'
| 'storage'
Manifest to nie piaskownica
Uprawnienia ograniczają API kontekstu, ale kod pluginu działa w tym samym realmie co gra. Manifest jest bramką API, nie sandboxem — instaluj tylko pluginy z zaufanych źródeł.
API hosta#
ts
install(plugin); activate(id); activateAll(); deactivate(id); uninstall(id); dispose()
update(deltaTime); get(id); has(id); list(); summary()
getSystems(); enableSystem(owner, name); getImporters(); getPanels()
parseVersion(input); compareVersions(a, b); isValidRange(range); satisfiesRange(version, range)
Przepływ aktywacji#
install()waliduje manifest i unikalnośćid— nic więcej. Katalog może zainstalować cały zestaw przed aktywacją.activate()sprawdza zakresengine, aktywuje zależności (DFS z detekcją cykli), buduje kontekst z capability-gatingiem i wołaactivate(context).- Rejestracje trafiają do rejestrów z właścicielem (
owner = id), więc dezaktywacja usuwa je jednym przebiegiem. adapter.updatewołaplugins.update(deltaTime)po scenie.- Wyjątek systemu jest liczony; po
maxFailuressystem zostaje automatycznie wyłączony. deactivate()idzie od zależnych do zależności: hook pluginu, disposery LIFO, czyszczenie rejestrów.
Punkty rozszerzeń#
| Rejestr | Co dodaje | Przykład |
|---|---|---|
SystemRegistry | Systemy z kolejnością i budżetem błędów | Spawn, AI, pogoda |
ImporterRegistry | Importery plików (wbudowany tiled) | Własny format map |
PanelRegistry | Panele edytora (mount/unmount) | Inspector, statystyki |
events | Zdarzenia w przestrzeni nazw pluginu | my-plugin:* |
Przykład: system pogody#
ts
const weatherPlugin: PluginDefinition = {
manifest: {
id: 'weather',
version: '1.0.0',
engine: '>=0.1.0',
permissions: ['systems', 'events']
},
activate(context) {
let timer = 0
context.registerSystem({
name: 'weather',
update(dt) {
timer += dt
if (timer > 30) {
timer = 0
context.events.emit('weather:change', {
type: context.random.pick(['rain', 'clear', 'fog'])
})
}
}
})
}
}
Diagnostyka pluginów#
Raport diagnostyczny silnika zawiera sekcję plugins z licznikami i audytem uprawnień:
ts
const report = engine.getDiagnosticsReport()
console.log(report.plugins)
console.log(host.summary())
Wersje#
ts
import { satisfiesRange } from '@engine'
satisfiesRange('0.1.0', '>=0.1.0')
satisfiesRange('0.2.0', '^0.1.0')
Zakresy wersji są walidowane przy aktywacji — plugin niezgodny z silnikiem nie wystartuje.