UNPKG

@jovian/type-tools

Version:

TypeTools is a Typescript library for providing extensible tooling runtime validations and type helpers.

133 lines (131 loc) 3.77 kB
import { Entity } from './ix.entity'; export class HalfLifed extends Entity { /** half-life in seconds */ hl: number = 86400; hlInvertedMs: number = 0; last: number = Date.now(); threshold: number = 0.000001; ticker: { type: string; data: any } = null; valueData: number = 0; frozen = false; v?: number; afterUpdate: ((self: HalfLifed, value: number) => any)[] = []; afterEachTick: ((self: HalfLifed, value: number) => any)[] = []; constructor(init: Partial<HalfLifed>) { super('halflifed'); Object.assign(this, init); if (this.hl < 0.01) { this.hl = 0.01; } this.hlInvertedMs = 1 / (this.hl * 1000); // turn into ms and invert if (init.v !== undefined) { this.value = init.v; } this.addOnDestroy(() => { this.unlinkTicker(); this.afterUpdate.length = 0; this.afterEachTick.length = 0; }); } get value() { this.update(); return this.valueData; } set value(v) { this.valueData = v; this.last = Date.now(); } add(v: number) { if (!Number.isFinite(v) || v <= 0) { return this; } this.update(); this.valueData += v; this.trim(); return this.afterValueChange(); } sub(v: number) { if (!Number.isFinite(v) || v <= 0) { return this; } this.update(); this.valueData -= v; this.trim(); return this.afterValueChange(); } mul(v: number) { if (!Number.isFinite(v) || v < 0) { return this; } this.update(); this.valueData *= v; this.trim(); return this.afterValueChange(); } div(v: number) { if (!Number.isFinite(v) || v <= 0) { return this; } this.update(); this.valueData /= v; this.trim(); return this.afterValueChange(); } reset(v = 0) { this.value = v; this.trim(); return this.afterValueChange(); } afterValueChange() { for (const afterUpdateCb of this.afterUpdate) { afterUpdateCb(this, this.valueData); } return this; } update() { if (this.frozen) { return this; } const now = Date.now(); if (this.valueData > 0) { const deltaMs = now - this.last; if (deltaMs > 0) { this.valueData = this.valueData * Math.pow(0.5, deltaMs * this.hlInvertedMs); this.trim(); } } this.last = now; return this; } linkTicker(callbackkMap: {[id: string]: HalfLifed}) { this.unlinkTicker(); callbackkMap[this.ixId] = this; this.ticker = { type: 'linked', data: callbackkMap, }; return this; } trim() { if (this.valueData < this.threshold || !Number.isFinite(this.valueData)) { this.valueData = 0; } return this; } unlinkTicker() { if (!this.ticker) { return this; } if (this.ticker.type === 'default') { clearInterval(this.ticker.data); } else if (this.ticker.type === 'linked') { const callbackMap: {[id: string]: HalfLifed} = this.ticker.data; if (callbackMap[this.ixId]) { delete callbackMap[this.ixId]; } } this.ticker = null; return this; } tick() { this.update(); if (this.afterEachTick.length > 0) { for (const afterEachTick of this.afterEachTick) { afterEachTick(this, this.valueData); } } return this; } startTicker(type = 'default', intervalMs = 1000, afterEachTick: (self: HalfLifed, value: number) => any = null) { this.unlinkTicker(); if (!type) { type = 'default'; } if (type === 'default') { if (afterEachTick) { this.afterEachTick.push(afterEachTick); } this.ticker = { type: 'default', data: setInterval(() => { this.tick(); }, intervalMs) }; } return this; } afterTick(afterTickCallback: (self: HalfLifed, value: number) => any) { this.afterEachTick.push(afterTickCallback); return this; } }