From 14d296e5a9986a09f2251e120bc348d3280c0e81 Mon Sep 17 00:00:00 2001 From: Dennis Postma Date: Wed, 12 Feb 2025 03:16:46 +0100 Subject: [PATCH] Date manager refactor --- src/managers/dateManager.ts | 100 +++++++++++------------------------- src/services/dateService.ts | 45 ++++++++++++++++ 2 files changed, 74 insertions(+), 71 deletions(-) create mode 100644 src/services/dateService.ts diff --git a/src/managers/dateManager.ts b/src/managers/dateManager.ts index c2917e7..ecbfd6d 100644 --- a/src/managers/dateManager.ts +++ b/src/managers/dateManager.ts @@ -3,106 +3,64 @@ import Logger, { LoggerType } from '@/application/logger' import { World } from '@/entities/world' import SocketManager from '@/managers/socketManager' import WorldRepository from '@/repositories/worldRepository' -import { Server } from 'socket.io' +import DateService from '@/services/dateService' + +interface GameTimeConfig { + readonly GAME_SPEED: number + readonly UPDATE_INTERVAL: number +} class DateManager { - private static readonly CONFIG = { + private static readonly CONFIG: GameTimeConfig = { GAME_SPEED: 8, // 24 game hours / 3 real hours - UPDATE_INTERVAL: 1000 // 1 minute - } as const + UPDATE_INTERVAL: 1000 * 6 // 1 minute + } private readonly logger = Logger.type(LoggerType.APP) + private readonly worldRepository = new WorldRepository() private currentDate = new Date() private intervalId: NodeJS.Timeout | null = null public async boot(): Promise { - await this.loadDate() - this.startDateLoop() - this.logger.info('Date manager loaded') - } - - public getCurrentDate(): Date { - return this.currentDate + try { + this.currentDate = (await this.worldRepository.getFirst())?.date ?? new Date() + this.startDateLoop() + this.logger.info('Date manager loaded') + } catch (error) { + this.logger.error(`Failed to boot date manager: ${error instanceof Error ? error.message : String(error)}`) + } } public async setTime(timeString: string): Promise { try { - const newDate = this.parseTimeString(timeString) - if (!newDate) return + const newDate = DateService.parseTimeString(timeString) + if (!newDate) throw new Error('Invalid time format') this.currentDate = newDate - this.emitDate() - await this.saveDate() + await this.synchronize() } catch (error) { - this.handleError('Failed to set time', error) + this.logger.error(`Failed to set time: ${error instanceof Error ? error.message : String(error)}`) throw error } } - public cleanup(): void { - this.intervalId && clearInterval(this.intervalId) - } - - private async loadDate(): Promise { - try { - const worldRepository = new WorldRepository() - const world = await worldRepository.getFirst() - this.currentDate = world?.date ?? new Date() - } catch (error) { - this.handleError('Failed to load date', error) - this.currentDate = new Date() - } - } - - private parseTimeString(timeString: string): Date | null { - const timeOnlyPattern = /^\d{1,2}:\d{2}(:\d{2})?$/ - - if (timeOnlyPattern.test(timeString)) { - const [hours, minutes] = timeString.split(':').map(Number) - if (!hours || !minutes) return null - const newDate = new Date(this.currentDate) - newDate.setHours(hours, minutes) - return newDate - } - - const fullDate = new Date(timeString) - return isNaN(fullDate.getTime()) ? null : fullDate - } - private startDateLoop(): void { - this.intervalId = setInterval(() => { - this.advanceGameTime() - this.emitDate() - void this.saveDate() + this.intervalId = setInterval(async () => { + const { GAME_SPEED, UPDATE_INTERVAL } = DateManager.CONFIG + this.currentDate = new Date(this.currentDate.getTime() + DateService.calculateGameTimeAdvance(GAME_SPEED, UPDATE_INTERVAL)) + await this.synchronize() }, DateManager.CONFIG.UPDATE_INTERVAL) } - private advanceGameTime(): void { - const advanceMilliseconds = DateManager.CONFIG.GAME_SPEED * DateManager.CONFIG.UPDATE_INTERVAL - this.currentDate = new Date(this.currentDate.getTime() + advanceMilliseconds) - } - - private emitDate(): void { - const io = SocketManager.getIO() - io?.emit(SocketEvent.DATE, this.currentDate) - } - - private async saveDate(): Promise { + private async synchronize(): Promise { try { - const worldRepository = new WorldRepository() - - let world = await worldRepository.getFirst() - if (!world) world = new World() - + SocketManager.getIO().emit(SocketEvent.DATE, this.currentDate) + const world = (await this.worldRepository.getFirst()) ?? new World() await world.setDate(this.currentDate).save() } catch (error) { - this.handleError('Failed to save date', error) + this.logger.error(`Failed to sync date state: ${error instanceof Error ? error.message : String(error)}`) } } - - private handleError(message: string, error: unknown): void { - this.logger.error(`${message}: ${error instanceof Error ? error.message : String(error)}`) - } } export default new DateManager() diff --git a/src/services/dateService.ts b/src/services/dateService.ts new file mode 100644 index 0000000..c0125f7 --- /dev/null +++ b/src/services/dateService.ts @@ -0,0 +1,45 @@ +interface TimeComponents { + hours: number + minutes: number +} + +export class DateService { + public parseTimeString(timeString: string): Date | null { + const timeRegex = /^(\d{1,2}):(\d{2})(?::\d{2})?$/ + const match = timeString.match(timeRegex) + + if (match && match[1] && match[2]) { + const components: TimeComponents = { + hours: parseInt(match[1], 10), + minutes: parseInt(match[2], 10) + } + + return this.isValidTimeComponents(components) ? this.createDateWithComponents(components, new Date()) : null + } + + return this.parseFullDate(timeString) + } + + public isValidTimeComponents(components: TimeComponents): boolean { + const { hours, minutes } = components + return hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60 + } + + public createDateWithComponents(components: TimeComponents, baseDate: Date): Date { + const { hours, minutes } = components + const newDate = new Date(baseDate) + newDate.setHours(hours, minutes, 0, 0) + return newDate + } + + public parseFullDate(dateString: string): Date | null { + const date = new Date(dateString) + return isNaN(date.getTime()) ? null : date + } + + public calculateGameTimeAdvance(gameSpeed: number, updateInterval: number): number { + return gameSpeed * updateInterval + } +} + +export default new DateService()