@intuitionrobotics/ts-common
Version:
126 lines • 4.71 kB
JavaScript
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