UNPKG

@intuitionrobotics/ts-common

Version:
126 lines 4.71 kB
import { ModuleManager } from "./module-manager.js"; import { BadImplementationException } from "./exceptions.js"; import { merge } from "../utils/merge-tools.js"; import { Logger } from "./logger/Logger.js"; import { validate } from "../validator/validator.js"; import { _clearTimeout, _setTimeout, currentTimeMillies } from "../utils/date-time-tools.js"; import { getConfigEpoch, getModuleConfigSlice } from "./app-config.js"; export class Module extends Logger { name; manager; initiated = false; configValidator; timeoutMap = {}; // Lazily-resolved config: defaults (constructor) <- app-config store slice <- manual // setConfig() overrides. Resolved & memoized on first access, so modules that are // never touched never materialize a config copy. Mutations of the resolved object // (legacy pattern) hit the module's own memoized copy, same as before. moduleDefaultConfig; manualConfig; resolvedConfig; resolvedAtEpoch = -1; get config() { if (!this.resolvedConfig || this.resolvedAtEpoch !== getConfigEpoch()) { this.resolvedAtEpoch = getConfigEpoch(); // merge() deep-clones, so the module owns a private mutable copy this.resolvedConfig = merge(merge(this.moduleDefaultConfig || {}, getModuleConfigSlice(this.getName()) || {}), this.manualConfig || {}); } return this.resolvedConfig; } // noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected constructor(name) { super(name); this.name = this.deduceName(name).replace("_Class", ""); } deduceName(name) { if (name) return name; const tempName = this.constructor["name"]; if (!tempName.endsWith("_Class")) throw new BadImplementationException(`Module class MUST end with '_Class' e.g. MyModule_Class, check class named: ${tempName}`); return tempName; } // // possibly to add // public async debounceSync(handler: TimerHandler, key: string, ms = 0) { // _clearTimeout(this.timeoutMap[key]); // // await new Promise((resolve, reject) => { // this.timeoutMap[key] = setTimeout(async (..._args) => { // try { // await handler(..._args); // resolve(); // } catch (e) { // reject(e); // } // }, ms) as unknown as number; // }); // } debounce(handler, key, ms = 0) { const k = "debounce" + key; _clearTimeout(this.timeoutMap[k]); this.timeoutMap[k] = _setTimeout(handler, ms); } throttle(handler, key, ms = 0) { const k = "throttle" + key; if (this.timeoutMap[k]) return; this.timeoutMap[k] = _setTimeout(() => { handler(); delete this.timeoutMap[k]; }, ms); } throttleV2(handler, key, ms, force = false) { const k = "throttle_v2" + key; const now = currentTimeMillies(); const timeoutMapElement = this.timeoutMap[k]; if (timeoutMapElement && now - timeoutMapElement <= ms && !force) return; handler(); this.timeoutMap[k] = currentTimeMillies(); } setConfigValidator(validator) { this.configValidator = validator; } setDefaultConfig(config) { this.moduleDefaultConfig = config; this.resolvedConfig = undefined; } getName() { return this.name; } setName(name) { this.name = name; } setConfig(config) { this.manualConfig = this.manualConfig ? merge(this.manualConfig, config || {}) : config; this.resolvedConfig = undefined; } setManager(manager) { this.manager = manager; } runAsync = (label, toCall) => { setTimeout(() => { this.logDebug(`Running async: ${label}`); new Promise(toCall) .then(() => { this.logDebug(`Async call completed: ${label}`); }) .catch(reason => this.logError(`Async call error: ${label}`, reason)); }, 0); }; /** * @deprecated Backend (Storm) never calls init() — `Storm.build()` fails if a * registered module overrides it. Move work to the constructor (only if trivially * cheap and config-independent) or to lazy memoized getters that resolve on first * use. Run `thunderstorm-codemod check-module-init` to flag offenders. * The frontend (Thunder) still calls init() eagerly at app boot. */ init() { // ignorance is bliss } validate() { if (this.configValidator) validate(this.config, this.configValidator); } } //# sourceMappingURL=module.js.map