hadean-old/src/Time.ts

315 lines
6.9 KiB
TypeScript
Raw Normal View History

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) ?
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 --;
}
}
async execTick(seconds: number) {
const doDynamicTick = true;
2021-07-27 17:48:50 -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-27 17:48:50 -04:00
async doTick() {
2021-07-27 17:48:50 -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
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-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 {
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
}