travian/src/Scheduler.ts

198 lines
7.3 KiB
TypeScript

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<Action>): void {
this.actionQueue.assign(actions);
}
clearActions() {
this.actionQueue.clear();
}
}