travian/src/Executor.ts

187 lines
5.8 KiB
TypeScript

import { markPage, sleepMicro, timestamp, waitForLoad } from './utils';
import { AbortTaskError, ActionError, GrabError, TryLaterError, VillageNotFound } from './Errors';
import { TaskQueueRenderer } from './TaskQueueRenderer';
import { createActionHandler } from './Action/ActionController';
import { ConsoleLogger, Logger } from './Logger';
import { GrabberManager } from './Grabber/GrabberManager';
import { Scheduler } from './Scheduler';
import { Statistics } from './Statistics';
import { ExecutionStorage } from './Storage/ExecutionStorage';
import { Action } from './Queue/ActionQueue';
import { Task } from './Queue/TaskProvider';
import { createTaskHandler } from './Task/TaskMap';
import { VillageStateRepository } from './VillageState';
export interface ExecutionSettings {
pauseTs: number;
}
export class Executor {
private readonly version: string;
private readonly scheduler: Scheduler;
private readonly villageStateRepository: VillageStateRepository;
private grabbers: GrabberManager;
private statistics: Statistics;
private executionState: ExecutionStorage;
private logger: Logger;
constructor(
version: string,
scheduler: Scheduler,
villageStateRepository: VillageStateRepository,
statistics: Statistics
) {
this.version = version;
this.scheduler = scheduler;
this.villageStateRepository = villageStateRepository;
this.grabbers = new GrabberManager(scheduler);
this.statistics = statistics;
this.executionState = new ExecutionStorage();
this.logger = new ConsoleLogger(this.constructor.name);
}
async run() {
await waitForLoad();
await sleepMicro();
this.renderInfo();
const sleep = createExecutionLoopSleeper();
while (true) {
await sleep();
if (!this.isPaused()) {
await this.doTaskProcessingStep();
}
}
}
private renderInfo() {
try {
markPage('Executor', this.version);
this.renderTaskQueue();
} catch (e) {
this.logger.warn(e);
}
}
private isPaused(): boolean {
const { pauseTs } = this.executionState.getExecutionSettings();
return pauseTs > timestamp();
}
private renderTaskQueue() {
new TaskQueueRenderer().render(this.scheduler.getTaskItems());
}
private async doTaskProcessingStep() {
const currentTs = timestamp();
const task = this.scheduler.nextTask(currentTs);
// текущего таска нет, очищаем очередь действий по таску
if (!task) {
this.logger.info('NO ACTIVE TASK');
this.scheduler.clearActions();
return;
}
const actionCommand = this.scheduler.nextAction();
this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', actionCommand);
this.runGrabbers();
try {
if (actionCommand) {
return await this.processActionCommand(actionCommand, task);
}
if (task) {
return await this.processTaskCommand(task);
}
} catch (e) {
this.handleError(e, task);
}
}
private async processActionCommand(cmd: Action, task: Task) {
const actionHandler = createActionHandler(cmd.name, this.scheduler, this.villageStateRepository);
this.logger.info('PROCESS ACTION', cmd.name, actionHandler);
if (cmd.args.taskId !== task.id) {
throw new ActionError(`Action task id ${cmd.args.taskId} not equal current task id ${task.id}`);
}
if (actionHandler) {
this.statistics.incrementAction(timestamp());
await actionHandler.run(cmd.args, task);
} else {
this.logger.warn('ACTION NOT FOUND', cmd.name);
}
}
private async processTaskCommand(task: Task) {
const taskHandler = createTaskHandler(task.name, this.scheduler);
this.logger.info('PROCESS TASK', task.name, task, taskHandler);
if (taskHandler) {
await taskHandler.run(task);
} else {
this.logger.warn('TASK NOT FOUND', task.name);
this.scheduler.removeTask(task.id);
}
}
private handleError(err: Error, task: Task) {
this.scheduler.clearActions();
if (err instanceof AbortTaskError) {
this.logger.warn('Abort task', task.id, 'MSG', err.message);
this.scheduler.removeTask(task.id);
return;
}
if (err instanceof VillageNotFound) {
this.logger.error('Village not found, abort task', task.id, 'msg', err.message);
this.scheduler.removeTask(task.id);
return;
}
if (err instanceof TryLaterError) {
this.logger.warn('Try', task.id, 'after', err.seconds, 'msg', err.message);
this.scheduler.postponeTask(task.id, err.seconds);
return;
}
if (err instanceof GrabError) {
this.logger.error('Layout element not found, abort action', err.message);
return;
}
if (err instanceof ActionError) {
this.logger.error('Abort action', err.message);
return;
}
this.logger.error(err.message);
throw err;
}
private runGrabbers() {
try {
this.logger.info('Rug grabbers');
this.grabbers.grab();
} catch (e) {
this.logger.warn('Grabbers fails with', e.message);
}
}
}
function createExecutionLoopSleeper() {
let skipFirstSleep = true;
return async () => {
if (skipFirstSleep) {
skipFirstSleep = false;
} else {
await sleepMicro();
}
};
}