Extract scheduler to separate component

This commit is contained in:
Anton Vakhrushev 2020-04-18 11:07:44 +03:00
parent c285338705
commit 631151ab0b
10 changed files with 200 additions and 158 deletions

View File

@ -1,30 +1,30 @@
import * as URLParse from 'url-parse'; import * as URLParse from 'url-parse';
import { getNumber, uniqId, waitForLoad } from '../utils'; import { getNumber, uniqId, waitForLoad } from './utils';
import { Scheduler } from '../Scheduler'; import { Scheduler } from './Scheduler';
import { BuildPage } from '../Page/BuildPage'; import { BuildPage } from './Page/BuildPage';
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import { grabActiveVillageId, grabVillageList } from '../Page/VillageBlock'; import { grabActiveVillageId, grabVillageList } from './Page/VillageBlock';
import { import {
grabResourceDeposits, grabResourceDeposits,
onResourceSlotCtrlClick, onResourceSlotCtrlClick,
showBuildingSlotIds, showBuildingSlotIds,
showResourceSlotIds, showResourceSlotIds,
} from '../Page/SlotBlock'; } from './Page/SlotBlock';
import Vue from 'vue'; import Vue from 'vue';
import DashboardApp from './Components/DashboardApp.vue'; import DashboardApp from './DashboardView/Dashboard.vue';
import { ResourcesToLevel } from '../Task/ResourcesToLevel'; import { ResourcesToLevel } from './Task/ResourcesToLevel';
import { ConsoleLogger, Logger } from '../Logger'; import { ConsoleLogger, Logger } from './Logger';
import { VillageState } from '../State/VillageState'; import { VillageState } from './State/VillageState';
import { StateGrabberManager } from '../State/StateGrabberManager'; import { StateGrabberManager } from './State/StateGrabberManager';
interface QuickAction { interface QuickAction {
label: string; label: string;
cb: () => void; cb: () => void;
} }
export class Dashboard { export class ControlPanel {
private readonly version: string; private readonly version: string;
private scheduler: Scheduler; private readonly scheduler: Scheduler;
private grabbers: StateGrabberManager; private grabbers: StateGrabberManager;
private readonly logger: Logger; private readonly logger: Logger;

View File

@ -71,7 +71,7 @@
</template> </template>
<script> <script>
import { path } from '../../utils'; import { path } from '../utils';
export default { export default {
data() { data() {

134
src/Executor.ts Normal file
View File

@ -0,0 +1,134 @@
import { markPage, sleepMicro, timestamp, waitForLoad } from './utils';
import { AbortTaskError, ActionError, BuildingQueueFullError, TryLaterError } from './Errors';
import { Task } from './Storage/TaskQueue';
import { Command } from './Common';
import { TaskQueueRenderer } from './TaskQueueRenderer';
import { createAction } from './Action/ActionController';
import { createTask } from './Task/TaskController';
import { ConsoleLogger, Logger } from './Logger';
import { StateGrabberManager } from './State/StateGrabberManager';
import { Scheduler } from './Scheduler';
export class Executor {
private readonly version: string;
private readonly scheduler: Scheduler;
private grabbers: StateGrabberManager;
private logger: Logger;
constructor(version: string, scheduler: Scheduler) {
this.version = version;
this.scheduler = scheduler;
this.grabbers = new StateGrabberManager();
this.logger = new ConsoleLogger(this.constructor.name);
}
async run() {
await waitForLoad();
await sleepMicro();
markPage('Executor', this.version);
this.renderTaskQueue();
setInterval(() => this.renderTaskQueue(), 5 * 1000);
while (true) {
await this.doTaskProcessingStep();
}
}
private renderTaskQueue() {
this.logger.log('RENDER TASK QUEUE');
new TaskQueueRenderer().render(this.scheduler.getTaskItems());
}
private async doTaskProcessingStep() {
await sleepMicro();
const currentTs = timestamp();
const taskCommand = this.scheduler.nextTask(currentTs);
// текущего таска нет, очищаем очередь действий по таску
if (!taskCommand) {
this.logger.log('NO ACTIVE TASK');
this.scheduler.clearActions();
return;
}
const actionCommand = this.scheduler.nextAction();
this.logger.log('CURRENT TASK', taskCommand);
this.logger.log('CURRENT ACTION', actionCommand);
try {
if (actionCommand) {
return await this.processActionCommand(actionCommand, taskCommand);
}
if (taskCommand) {
return await this.processTaskCommand(taskCommand);
}
} catch (e) {
this.handleError(e);
}
}
private async processActionCommand(cmd: Command, task: Task) {
this.runGrabbers();
const actionController = createAction(cmd.name, this.scheduler);
this.logger.log('PROCESS ACTION', cmd.name, actionController);
if (actionController) {
await actionController.run(cmd.args, task);
} else {
this.logger.warn('ACTION NOT FOUND', cmd.name);
}
}
private async processTaskCommand(task: Task) {
const taskController = createTask(task.name, this.scheduler);
this.logger.log('PROCESS TASK', task.name, task, taskController);
if (taskController) {
await taskController.run(task);
} else {
this.logger.warn('TASK NOT FOUND', task.name);
this.scheduler.completeTask(task.id);
}
}
private handleError(err: Error) {
this.scheduler.clearActions();
if (err instanceof AbortTaskError) {
this.logger.warn('ABORT TASK', err.taskId);
this.scheduler.completeTask(err.taskId);
this.scheduler.clearActions();
return;
}
if (err instanceof TryLaterError) {
this.logger.warn('TRY', err.taskId, 'AFTER', err.seconds);
this.scheduler.postponeTask(err.taskId, err.seconds);
return;
}
if (err instanceof BuildingQueueFullError) {
this.logger.warn('BUILDING QUEUE FULL, TRY ALL AFTER', err.seconds);
this.scheduler.postponeBuildingsInVillage(err.villageId, err.seconds);
return;
}
if (err instanceof ActionError) {
this.logger.warn('ACTION ABORTED', err.message);
return;
}
this.logger.error(err.message);
throw err;
}
private runGrabbers() {
try {
this.logger.log('Rug grabbers');
this.grabbers.grab();
} catch (e) {
this.logger.warn('Grabbers fails with', e.message);
}
}
}

View File

@ -1,54 +1,27 @@
import { markPage, sleepMicro, timestamp, waitForLoad } from './utils'; import { timestamp } from './utils';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import { AbortTaskError, ActionError, BuildingQueueFullError, TryLaterError } from './Errors'; import { TaskId, TaskList, TaskQueue } from './Storage/TaskQueue';
import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue';
import { ActionQueue } from './Storage/ActionQueue';
import { Args, Command } from './Common'; import { Args, Command } from './Common';
import { TaskQueueRenderer } from './TaskQueueRenderer';
import { createAction } from './Action/ActionController';
import { createTask } from './Task/TaskController';
import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask';
import { ConsoleLogger, Logger } from './Logger'; import { ConsoleLogger, Logger } from './Logger';
import { BuildBuildingTask } from './Task/BuildBuildingTask'; import { BuildBuildingTask } from './Task/BuildBuildingTask';
import { GrabVillageState } from './Task/GrabVillageState'; import { GrabVillageState } from './Task/GrabVillageState';
import { StateGrabberManager } from './State/StateGrabberManager'; import { ActionQueue } from './Storage/ActionQueue';
export class Scheduler { export class Scheduler {
private readonly version: string;
private taskQueue: TaskQueue; private taskQueue: TaskQueue;
private actionQueue: ActionQueue; private actionQueue: ActionQueue;
private grabbers: StateGrabberManager;
private logger: Logger; private logger: Logger;
constructor(version: string) { constructor() {
this.version = version;
this.taskQueue = new TaskQueue(); this.taskQueue = new TaskQueue();
this.actionQueue = new ActionQueue(); this.actionQueue = new ActionQueue();
this.grabbers = new StateGrabberManager();
this.logger = new ConsoleLogger(this.constructor.name); this.logger = new ConsoleLogger(this.constructor.name);
}
async run() {
await waitForLoad();
await sleepMicro();
markPage('Executor', this.version);
this.renderTaskQueue();
setInterval(() => this.renderTaskQueue(), 5 * 1000);
this.scheduleUniqTask(3600, SendOnAdventureTask.name); this.scheduleUniqTask(3600, SendOnAdventureTask.name);
this.scheduleUniqTask(1200, BalanceHeroResourcesTask.name); this.scheduleUniqTask(1200, BalanceHeroResourcesTask.name);
this.scheduleUniqTask(180, GrabVillageState.name); this.scheduleUniqTask(180, GrabVillageState.name);
while (true) {
await this.doTaskProcessingStep();
}
}
private renderTaskQueue() {
this.logger.log('RENDER TASK QUEUE');
new TaskQueueRenderer().render(this.taskQueue.seeItems());
} }
private scheduleUniqTask(seconds: number, name: string, args: Args = {}) { private scheduleUniqTask(seconds: number, name: string, args: Args = {}) {
@ -61,111 +34,16 @@ export class Scheduler {
setInterval(taskScheduler, seconds * 1000); setInterval(taskScheduler, seconds * 1000);
} }
private async doTaskProcessingStep() {
await sleepMicro();
const currentTs = timestamp();
const taskCommand = this.taskQueue.get(currentTs);
// текущего таска нет, очищаем очередь действий по таску
if (!taskCommand) {
this.logger.log('NO ACTIVE TASK');
this.actionQueue.clear();
return;
}
const actionCommand = this.actionQueue.pop();
this.logger.log('CURRENT TASK', taskCommand);
this.logger.log('CURRENT ACTION', actionCommand);
try {
if (actionCommand) {
return await this.processActionCommand(actionCommand, taskCommand);
}
if (taskCommand) {
return await this.processTaskCommand(taskCommand);
}
} catch (e) {
this.handleError(e);
}
}
private async processActionCommand(cmd: Command, task: Task) {
this.runGrabbers();
const actionController = createAction(cmd.name, this);
this.logger.log('PROCESS ACTION', cmd.name, actionController);
if (actionController) {
await actionController.run(cmd.args, task);
} else {
this.logger.warn('ACTION NOT FOUND', cmd.name);
}
}
private async processTaskCommand(task: Task) {
const taskController = createTask(task.name, this);
this.logger.log('PROCESS TASK', task.name, task, taskController);
if (taskController) {
await taskController.run(task);
} else {
this.logger.warn('TASK NOT FOUND', task.name);
this.taskQueue.complete(task.id);
}
}
private handleError(err: Error) {
this.actionQueue.clear();
if (err instanceof AbortTaskError) {
this.logger.warn('ABORT TASK', err.taskId);
this.completeTask(err.taskId);
return;
}
if (err instanceof TryLaterError) {
this.logger.warn('TRY', err.taskId, 'AFTER', err.seconds);
this.taskQueue.postpone(err.taskId, timestamp() + err.seconds);
return;
}
if (err instanceof BuildingQueueFullError) {
this.logger.warn('BUILDING QUEUE FULL, TRY ALL AFTER', err.seconds);
this.taskQueue.modify(
t => t.name === BuildBuildingTask.name && t.args.villageId === err.villageId,
t => t.withTime(timestamp() + err.seconds)
);
this.taskQueue.modify(
t => t.name === UpgradeBuildingTask.name && t.args.villageId === err.villageId,
t => t.withTime(timestamp() + err.seconds)
);
return;
}
if (err instanceof ActionError) {
this.logger.warn('ACTION ABORTED', err.message);
return;
}
this.logger.error(err.message);
throw err;
}
private runGrabbers() {
try {
this.logger.log('Rug grabbers');
this.grabbers.grab();
} catch (e) {
this.logger.warn('Grabbers fails with', e.message);
}
}
getTaskItems(): TaskList { getTaskItems(): TaskList {
return this.taskQueue.seeItems(); return this.taskQueue.seeItems();
} }
completeTask(id: TaskId) { nextTask(ts: number) {
this.taskQueue.complete(id); return this.taskQueue.get(ts);
this.actionQueue.clear(); }
nextAction() {
return this.actionQueue.pop();
} }
scheduleTask(name: string, args: Args): void { scheduleTask(name: string, args: Args): void {
@ -173,12 +51,36 @@ export class Scheduler {
this.taskQueue.push(name, args, timestamp()); this.taskQueue.push(name, args, timestamp());
} }
completeTask(id: TaskId) {
this.taskQueue.complete(id);
this.actionQueue.clear();
}
removeTask(id: TaskId) { removeTask(id: TaskId) {
this.taskQueue.remove(id); this.taskQueue.remove(id);
this.actionQueue.clear(); this.actionQueue.clear();
} }
postponeTask(id: TaskId, deltaTs: number) {
this.taskQueue.postpone(id, timestamp() + deltaTs);
}
postponeBuildingsInVillage(villageId: number, seconds: number) {
this.taskQueue.modify(
t => t.name === BuildBuildingTask.name && t.args.villageId === villageId,
t => t.withTime(timestamp() + seconds)
);
this.taskQueue.modify(
t => t.name === UpgradeBuildingTask.name && t.args.villageId === villageId,
t => t.withTime(timestamp() + seconds)
);
}
scheduleActions(actions: Array<Command>): void { scheduleActions(actions: Array<Command>): void {
this.actionQueue.assign(actions); this.actionQueue.assign(actions);
} }
clearActions() {
this.actionQueue.clear();
}
} }

View File

@ -19,7 +19,7 @@ class State {
} }
export class ActionQueue { export class ActionQueue {
private readonly logger; private readonly logger: Logger;
constructor() { constructor() {
this.logger = new ConsoleLogger(this.constructor.name); this.logger = new ConsoleLogger(this.constructor.name);

View File

@ -1,18 +1,24 @@
import { ConsoleLogger } from './Logger';
import { ModeDetector } from './ModeDetector'; import { ModeDetector } from './ModeDetector';
import { Scheduler } from './Scheduler'; import { Scheduler } from './Scheduler';
import { Dashboard } from './Dashboard/Dashboard'; import { Executor } from './Executor';
import { ControlPanel } from './ControlPanel';
import TxtVersion from '!!raw-loader!./version.txt'; import TxtVersion from '!!raw-loader!./version.txt';
console.log('TRAVIAN AUTOMATION', TxtVersion); const logger = new ConsoleLogger('Travian');
const md = new ModeDetector(); logger.log('TRAVIAN AUTOMATION', TxtVersion);
if (md.isAuto()) {
md.setAuto(); const modeDetector = new ModeDetector();
console.log('AUTO MANAGEMENT ON'); const scheduler = new Scheduler();
const scheduler = new Scheduler(TxtVersion);
scheduler.run(); if (modeDetector.isAuto()) {
modeDetector.setAuto();
logger.log('AUTO MANAGEMENT ON');
const executor = new Executor(TxtVersion, scheduler);
executor.run();
} else { } else {
console.log('NORMAL MODE'); logger.log('NORMAL MODE');
const dashboard = new Dashboard(TxtVersion, new Scheduler()); const controlPanel = new ControlPanel(TxtVersion, scheduler);
dashboard.run(); controlPanel.run();
} }