import { TaskQueue } from './Queue/TaskQueue'; import { BalanceHeroResourcesTask } from './Handler/Task/BalanceHeroResourcesTask'; import { Logger } from './Logger'; import { GrabVillageStateTask } from './Handler/Task/GrabVillageStateTask'; import { ActionQueue } from './Queue/ActionQueue'; import { UpdateResourceContractsTask } from './Handler/Task/UpdateResourceContractsTask'; import { SendResourcesTask } from './Handler/Task/SendResourcesTask'; import { Args } from './Queue/Args'; import { VillageRepositoryInterface } from './Village/VillageRepository'; import { VillageFactory } from './Village/VillageFactory'; import { RunVillageProductionTask } from './Handler/Task/RunVillageProductionTask'; import { isProductionTask } from './Handler/TaskMap'; import { around } from './Helpers/Random'; import { timestamp } from './Helpers/Time'; import { Action, ImmutableActionList } from './Queue/Action'; import { ImmutableTaskList, Task, withTime } from './Queue/Task'; import { TaskId, uniqTaskId } from './Queue/TaskId'; interface NextExecution { task?: Task; action?: Action; } export class Scheduler { private readonly taskQueue: TaskQueue; private readonly actionQueue: ActionQueue; private readonly villageRepository: VillageRepositoryInterface; private readonly villageControllerFactory: VillageFactory; private readonly logger: Logger; constructor( taskQueue: TaskQueue, actionQueue: ActionQueue, villageRepository: VillageRepositoryInterface, villageControllerFactory: VillageFactory, logger: Logger ) { this.taskQueue = taskQueue; this.actionQueue = actionQueue; this.villageRepository = villageRepository; this.villageControllerFactory = villageControllerFactory; this.logger = logger; // this.taskQueue.push(GrabVillageStateTask.name, {}, timestamp()); // this.taskQueue.push(UpdateResourceContractsTask.name, {}, timestamp()); // this.taskQueue.push(BalanceHeroResourcesTask.name, {}, timestamp()); const villages = this.villageRepository.all(); for (let village of villages) { this.createUniqTaskTimer(5 * 60, RunVillageProductionTask.name, { villageId: village.id, }); } this.createUniqTaskTimer(10 * 60, GrabVillageStateTask.name); // @todo Только если деревень больше одной // this.createUniqTaskTimer(10 * 60, SendResourcesTask.name); this.createUniqTaskTimer(10 * 60, BalanceHeroResourcesTask.name); this.createUniqTaskTimer(20 * 60, UpdateResourceContractsTask.name); // @todo Нужна отдельная настройка для запуска задачи // this.createUniqTaskTimer(60 * 60, SendOnAdventureTask.name); } private createUniqTaskTimer(seconds: number, name: string, args: Args = {}) { const firstDelay = around(seconds - 10, 0.2); const intervalTime = around(seconds, 0.2) * 1000; this.scheduleUniqTask(name, args, timestamp() + firstDelay); setInterval(() => this.scheduleUniqTask(name, args, timestamp()), intervalTime); } getTaskItems(): ImmutableTaskList { return this.taskQueue.seeItems(); } getActionItems(): ImmutableActionList { return this.actionQueue.seeItems(); } nextTask(ts: number): NextExecution { const task = this.taskQueue.get(ts); // Task not found - next task not isReady or queue is empty if (!task) { this.clearActions(); return {}; } const action = this.actionQueue.pop(); // Action not found - task is new if (!action) { return { task: this.replaceTask(task) }; } // Task in action not equals current task it - rerun task if (action.args.taskId !== task.id) { this.clearActions(); return { task: this.replaceTask(task) }; } return { task, action }; } private replaceTask(task: Task): Task | undefined { if (task.name === RunVillageProductionTask.name && task.args.villageId) { const villageId = task.args.villageId; // First stage - plan new tasks if needed. const controllerForPlan = this.villageControllerFactory.getById(villageId).controller(); controllerForPlan.planTasks(); // Second stage - select isReady for production task. // We recreate controller, because need new village state. const controllerForSelect = this.villageControllerFactory .getById(villageId) .controller(); const villageTask = controllerForSelect.getReadyProductionTask(); if (villageTask) { this.removeTask(task.id); const newTask = new Task(villageTask.id, 0, villageTask.name, { ...villageTask.args, villageId: controllerForSelect.getVillageId(), }); this.taskQueue.add(newTask); return newTask; } } return task; } scheduleTask(name: string, args: Args, ts?: number | undefined): void { if (isProductionTask(name) && args.villageId) { const controller = this.villageControllerFactory.getById(args.villageId).controller(); controller.addTask(name, args); } else { this.logger.info('Schedule task', name, args, ts); this.taskQueue.add(new Task(uniqTaskId(), ts || timestamp(), name, args)); } } scheduleUniqTask(name: string, args: Args, ts?: number | undefined): void { let alreadyHasTask; if (args.villageId) { alreadyHasTask = this.taskQueue.has( (t) => t.name === name && t.args.villageId === args.villageId ); } else { alreadyHasTask = this.taskQueue.has((t) => t.name === name); } if (!alreadyHasTask) { this.scheduleTask(name, args, ts); } } removeTask(taskId: TaskId) { this.taskQueue.remove((t) => t.id === taskId); this.actionQueue.clear(); } completeTask(taskId: TaskId) { const task = this.taskQueue.findById(taskId); const villageId = task ? task.args.villageId : undefined; if (villageId) { const controller = this.villageControllerFactory.getById(villageId).controller(); controller.removeTask(taskId); } this.removeTask(taskId); } postponeTask(taskId: TaskId, seconds: number) { const task = this.taskQueue.seeItems().find((t) => t.id === taskId); if (!task) { return; } if (isProductionTask(task.name) && task.args.villageId) { const controller = this.villageControllerFactory .getById(task.args.villageId) .controller(); controller.postponeTask(taskId, seconds); this.removeTask(taskId); } else { const modifyTime = withTime(timestamp() + seconds); this.taskQueue.modify((t) => t.id === taskId, modifyTime); } } scheduleActions(actions: Array): void { this.actionQueue.assign(actions); } clearActions() { this.actionQueue.clear(); } }