2021-06-15 19:15:49 -04:00
|
|
|
import chalk from "chalk";
|
2021-06-14 22:03:55 -04:00
|
|
|
import { Serializable } from "frigid";
|
2021-06-26 03:11:18 -04:00
|
|
|
import { getTheme } from "@themes";
|
2021-06-14 22:03:55 -04:00
|
|
|
|
2021-06-19 12:40:01 -04:00
|
|
|
type AbbreviatedMonthName = string;
|
|
|
|
|
|
2021-06-14 22:03:55 -04:00
|
|
|
const daysInMonth = [
|
2021-06-19 12:40:01 -04:00
|
|
|
31, 28, 31,
|
|
|
|
|
30, 31, 30,
|
|
|
|
|
31, 31, 30,
|
|
|
|
|
31, 30, 31
|
2021-06-14 22:03:55 -04:00
|
|
|
];
|
|
|
|
|
|
2021-06-19 12:40:01 -04:00
|
|
|
const months: AbbreviatedMonthName[] = [
|
|
|
|
|
'Jan', 'Feb', 'Mar',
|
|
|
|
|
'Apr', 'May', 'Jun',
|
|
|
|
|
'Jul', 'Aug', 'Sep',
|
|
|
|
|
'Oct', 'Nov', 'Dec'
|
2021-06-14 22:03:55 -04:00
|
|
|
]
|
|
|
|
|
|
2021-07-27 17:48:50 -04:00
|
|
|
// TODO split ticker util and calendar util...
|
2021-07-23 00:12:02 -04:00
|
|
|
export default class Time extends Serializable {
|
2021-07-27 17:48:50 -04:00
|
|
|
targetTPS: number;
|
2021-06-19 12:40:01 -04:00
|
|
|
paused = true;
|
|
|
|
|
|
|
|
|
|
thing: Tickable;
|
|
|
|
|
|
|
|
|
|
year: number;
|
|
|
|
|
month: number;
|
|
|
|
|
day: number;
|
|
|
|
|
hour: number;
|
|
|
|
|
minute: number;
|
|
|
|
|
|
2021-07-27 17:48:50 -04:00
|
|
|
ticksInSecond: number;
|
|
|
|
|
lastTPSCheckpoint: number;
|
|
|
|
|
tps: number;
|
|
|
|
|
|
|
|
|
|
_boundTick: Function;
|
|
|
|
|
|
2021-06-19 12:40:01 -04:00
|
|
|
constructor(timestamp: number = 0) {
|
|
|
|
|
super();
|
|
|
|
|
this.minute = timestamp;
|
|
|
|
|
this.normalize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
asAge() {
|
|
|
|
|
if(this.year > 1) {
|
|
|
|
|
return this.year + ' years old';
|
|
|
|
|
} else {
|
|
|
|
|
if(this.month > 2) {
|
|
|
|
|
return this.month + ' months old';
|
|
|
|
|
} else {
|
|
|
|
|
if(this.day > 1) {
|
|
|
|
|
return this.day + ' days old';
|
|
|
|
|
} else if(this.day === 1) {
|
|
|
|
|
return '1 day old';
|
|
|
|
|
} else {
|
|
|
|
|
return 'newborn';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
const sym = (this.hour >= 6 && this.hour < 20) ?
|
2021-06-19 18:23:44 -04:00
|
|
|
chalk.ansi256(226).bgAnsi256(27)(' ☼ ') :
|
2021-06-19 12:40:01 -04:00
|
|
|
chalk.ansi256(254).bgAnsi256(17)(' ☾ ')
|
|
|
|
|
|
|
|
|
|
return `${sym} ${
|
|
|
|
|
getTheme().normal(`${
|
2021-06-22 19:25:41 -04:00
|
|
|
this.toString()
|
2021-06-19 12:40:01 -04:00
|
|
|
}`)
|
|
|
|
|
}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toString() {
|
2021-07-27 17:48:50 -04:00
|
|
|
return `${
|
|
|
|
|
this.hour
|
|
|
|
|
}:${
|
|
|
|
|
Math.floor(this.minute).toString().padStart(2, '0')
|
|
|
|
|
} ${
|
|
|
|
|
months[this.month]
|
|
|
|
|
} ${
|
|
|
|
|
this.day + 1
|
|
|
|
|
}, ${
|
|
|
|
|
this.normalizedYear
|
|
|
|
|
} [${
|
|
|
|
|
this.tps
|
|
|
|
|
} / ${
|
|
|
|
|
this.targetTPS
|
|
|
|
|
}]`;
|
2021-06-19 12:40:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctor() {
|
2021-07-28 23:08:45 -04:00
|
|
|
this.targetTPS = 60;
|
2021-06-19 12:40:01 -04:00
|
|
|
this.minute ??= 0;
|
|
|
|
|
this.hour ??= 0;
|
|
|
|
|
this.day ??= 0;
|
|
|
|
|
this.month ??= 0;
|
|
|
|
|
this.year ??= 0;
|
2021-07-27 17:48:50 -04:00
|
|
|
this.tps ??= 0;
|
|
|
|
|
this.lastTPSCheckpoint = ms4();
|
|
|
|
|
this.ticksInSecond = 0;
|
|
|
|
|
this._boundTick = this.doTick.bind(this);
|
2021-06-19 12:40:01 -04:00
|
|
|
}
|
|
|
|
|
|
2021-06-22 19:25:41 -04:00
|
|
|
get second() {
|
|
|
|
|
return Math.floor((this.minute % 1) * 60)
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-19 12:40:01 -04:00
|
|
|
get stamp() {
|
|
|
|
|
let minute = this.minute;
|
|
|
|
|
let hour = this.hour;
|
|
|
|
|
let day = this.day;
|
|
|
|
|
let month = this.month;
|
|
|
|
|
let year = this.year;
|
|
|
|
|
|
|
|
|
|
const daysInYear = daysInMonth.reduce((a, b) => a+b, 0);
|
|
|
|
|
|
|
|
|
|
day += daysInYear * year;
|
|
|
|
|
year = 0;
|
|
|
|
|
while(month > 0) {
|
|
|
|
|
day += daysInMonth[month];
|
|
|
|
|
month --;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hour += day * 24;
|
|
|
|
|
day = 0;
|
|
|
|
|
|
|
|
|
|
minute += hour * 60;
|
|
|
|
|
hour = 0;
|
|
|
|
|
|
|
|
|
|
return minute;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pause() {
|
|
|
|
|
this.paused = true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-27 17:48:50 -04:00
|
|
|
resume() {
|
|
|
|
|
this.paused = false;
|
|
|
|
|
setTimeout(this.doTick.bind(this), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start(tickable: Tickable) {
|
|
|
|
|
this.thing = tickable;
|
2021-06-19 12:40:01 -04:00
|
|
|
this.paused = false;
|
|
|
|
|
setTimeout(this.doTick.bind(this), 0);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-26 13:07:11 -04:00
|
|
|
advanceTime(seconds: number) {
|
2021-06-22 19:25:41 -04:00
|
|
|
this.minute += seconds / 60;
|
2021-06-19 12:40:01 -04:00
|
|
|
this.normalize()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get normalizedYear() {
|
|
|
|
|
if(this.year >= 0) {
|
|
|
|
|
return (this.year + 1).toString().padStart(4, '0') + ' CE';
|
|
|
|
|
} else {
|
|
|
|
|
return Math.abs(this.year).toString().padStart(4, '0') + ' BCE';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
normalize() {
|
2021-06-22 19:25:41 -04:00
|
|
|
// while(t)
|
2021-06-19 12:40:01 -04:00
|
|
|
while(this.minute >= 60) {
|
|
|
|
|
this.minute -= 60;
|
|
|
|
|
this.hour ++;
|
|
|
|
|
}
|
|
|
|
|
while(this.minute < 0) {
|
|
|
|
|
this.minute += 60;
|
|
|
|
|
this.hour --;
|
|
|
|
|
}
|
|
|
|
|
while(this.hour >= 24) {
|
|
|
|
|
this.hour -= 24;
|
|
|
|
|
this.day ++;
|
|
|
|
|
}
|
|
|
|
|
while(this.hour < 0) {
|
|
|
|
|
this.hour += 24;
|
|
|
|
|
this.day --;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while(this.day < 0) {
|
|
|
|
|
this.day += daysInMonth[
|
|
|
|
|
((this.month % months.length) + months.length) % months.length
|
|
|
|
|
];
|
|
|
|
|
this.month --;
|
|
|
|
|
}
|
|
|
|
|
while(this.day >= daysInMonth[this.month % months.length]) {
|
|
|
|
|
this.day -= daysInMonth[this.month % months.length];
|
|
|
|
|
this.month ++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while(this.month >= 12) {
|
|
|
|
|
this.month -= 12;
|
|
|
|
|
this.year ++;
|
|
|
|
|
}
|
|
|
|
|
while(this.month < 0) {
|
|
|
|
|
this.month += 12;
|
|
|
|
|
this.year --;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-29 12:51:31 -04:00
|
|
|
async execTick(seconds: number) {
|
|
|
|
|
const doDynamicTick = true;
|
2021-07-27 17:48:50 -04:00
|
|
|
|
2021-07-29 12:51:31 -04:00
|
|
|
if(doDynamicTick) {
|
|
|
|
|
if(this.thing) {
|
|
|
|
|
await this.thing.tick(seconds);
|
|
|
|
|
}
|
|
|
|
|
this.advanceTime(seconds);
|
|
|
|
|
} else {
|
|
|
|
|
for(let i = 0; i < seconds; i ++) {
|
|
|
|
|
if(this.thing) {
|
|
|
|
|
await this.thing.tick(1);
|
|
|
|
|
}
|
|
|
|
|
this.advanceTime(1);
|
|
|
|
|
}
|
2021-06-19 12:40:01 -04:00
|
|
|
}
|
2021-07-29 12:51:31 -04:00
|
|
|
}
|
2021-07-27 17:48:50 -04:00
|
|
|
|
2021-07-29 12:51:31 -04:00
|
|
|
async doTick() {
|
2021-07-27 17:48:50 -04:00
|
|
|
|
2021-07-29 12:51:31 -04:00
|
|
|
// the higher the multitick, the more lopsided
|
|
|
|
|
// ticks become in realtime, however, they rely
|
|
|
|
|
// on fewer setTimeouts, which helps tick scheduling
|
|
|
|
|
const multitick = 1;
|
|
|
|
|
|
|
|
|
|
if(multitick > 1) {
|
|
|
|
|
const timeout = (1000 / this.targetTPS) * multitick;
|
|
|
|
|
const start = ms4();
|
|
|
|
|
|
|
|
|
|
for(let tickNum = 0; tickNum < multitick; tickNum ++ ) {
|
|
|
|
|
const seconds = 3;
|
|
|
|
|
|
|
|
|
|
this.execTick(seconds);
|
2021-07-27 17:48:50 -04:00
|
|
|
|
2021-07-29 12:51:31 -04:00
|
|
|
this.ticksInSecond ++;
|
|
|
|
|
const end = ms4();
|
|
|
|
|
|
|
|
|
|
if(end > this.lastTPSCheckpoint + 1000) {
|
|
|
|
|
this.lastTPSCheckpoint = end;
|
|
|
|
|
this.tps = this.ticksInSecond;
|
|
|
|
|
this.ticksInSecond = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const end = ms4()
|
|
|
|
|
const elapsed = end - start;
|
|
|
|
|
const wait = timeout - elapsed;
|
|
|
|
|
const normalizedWait = Math.floor(Math.max(wait, 0));
|
|
|
|
|
|
|
|
|
|
// process.stdout.write(`tick took ${elapsed} waiting ${normalizedWait}\n`);
|
|
|
|
|
|
|
|
|
|
if(wait < 0) {
|
|
|
|
|
const ticksOver = (-wait / timeout) + 1;
|
|
|
|
|
if(ticksOver > 1.5)
|
|
|
|
|
console.log(chalk.yellow('Can\'t keep up! Tick took ' + ticksOver.toFixed(2) + ' ticks (' + (timeout - wait).toFixed(4) + 'ms)'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(this.paused) return;
|
|
|
|
|
setTimeout(this._boundTick, normalizedWait);
|
|
|
|
|
} else {
|
|
|
|
|
// this code is from not needing a multi-tick workaround, due to scheduling.
|
|
|
|
|
const seconds = 3;
|
|
|
|
|
const timeout = 1000 / this.targetTPS;
|
|
|
|
|
// const start = ms4()
|
|
|
|
|
const start = ms4();
|
|
|
|
|
|
|
|
|
|
this.execTick(seconds);
|
|
|
|
|
|
|
|
|
|
const end = ms4()
|
|
|
|
|
const elapsed = end - start;
|
|
|
|
|
const wait = timeout - elapsed;
|
|
|
|
|
const normalizedWait = Math.floor(Math.max(wait, 0));
|
|
|
|
|
|
|
|
|
|
// process.stdout.write(`tick took ${elapsed} waiting ${normalizedWait}\n`);
|
|
|
|
|
|
|
|
|
|
if(wait < 0) {
|
|
|
|
|
const ticksOver = (-wait / timeout) + 1;
|
|
|
|
|
if(ticksOver > 1.5)
|
|
|
|
|
console.log(chalk.yellow('Can\'t keep up! Tick took ' + ticksOver.toFixed(2) + ' ticks (' + (timeout - wait).toFixed(4) + 'ms)'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.ticksInSecond ++;
|
|
|
|
|
|
|
|
|
|
if(end > this.lastTPSCheckpoint + 1000) {
|
|
|
|
|
this.lastTPSCheckpoint = end;
|
|
|
|
|
this.tps = this.ticksInSecond;
|
|
|
|
|
this.ticksInSecond = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(this.paused) return;
|
|
|
|
|
setTimeout(this._boundTick, normalizedWait);
|
2021-07-27 17:48:50 -04:00
|
|
|
}
|
|
|
|
|
|
2021-07-29 12:51:31 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-07-27 17:48:50 -04:00
|
|
|
|
2021-06-19 12:40:01 -04:00
|
|
|
}
|
2021-06-14 22:03:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Tickable {
|
2021-07-29 12:51:31 -04:00
|
|
|
tick: (seconds: number) => Promise<void>
|
2021-07-27 17:48:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ms4() {
|
|
|
|
|
const a = process.hrtime()
|
|
|
|
|
return a[0]*10e2 + a[1]/1000000;
|
2021-06-14 22:03:55 -04:00
|
|
|
}
|