Extract scheduler to separate component
This commit is contained in:
		| @@ -1,30 +1,30 @@ | ||||
| import * as URLParse from 'url-parse'; | ||||
| import { getNumber, uniqId, waitForLoad } from '../utils'; | ||||
| import { Scheduler } from '../Scheduler'; | ||||
| import { BuildPage } from '../Page/BuildPage'; | ||||
| import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; | ||||
| import { grabActiveVillageId, grabVillageList } from '../Page/VillageBlock'; | ||||
| import { getNumber, uniqId, waitForLoad } from './utils'; | ||||
| import { Scheduler } from './Scheduler'; | ||||
| import { BuildPage } from './Page/BuildPage'; | ||||
| import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||
| import { grabActiveVillageId, grabVillageList } from './Page/VillageBlock'; | ||||
| import { | ||||
|     grabResourceDeposits, | ||||
|     onResourceSlotCtrlClick, | ||||
|     showBuildingSlotIds, | ||||
|     showResourceSlotIds, | ||||
| } from '../Page/SlotBlock'; | ||||
| } from './Page/SlotBlock'; | ||||
| import Vue from 'vue'; | ||||
| import DashboardApp from './Components/DashboardApp.vue'; | ||||
| import { ResourcesToLevel } from '../Task/ResourcesToLevel'; | ||||
| import { ConsoleLogger, Logger } from '../Logger'; | ||||
| import { VillageState } from '../State/VillageState'; | ||||
| import { StateGrabberManager } from '../State/StateGrabberManager'; | ||||
| import DashboardApp from './DashboardView/Dashboard.vue'; | ||||
| import { ResourcesToLevel } from './Task/ResourcesToLevel'; | ||||
| import { ConsoleLogger, Logger } from './Logger'; | ||||
| import { VillageState } from './State/VillageState'; | ||||
| import { StateGrabberManager } from './State/StateGrabberManager'; | ||||
| 
 | ||||
| interface QuickAction { | ||||
|     label: string; | ||||
|     cb: () => void; | ||||
| } | ||||
| 
 | ||||
| export class Dashboard { | ||||
| export class ControlPanel { | ||||
|     private readonly version: string; | ||||
|     private scheduler: Scheduler; | ||||
|     private readonly scheduler: Scheduler; | ||||
|     private grabbers: StateGrabberManager; | ||||
|     private readonly logger: Logger; | ||||
| 
 | ||||
| @@ -71,7 +71,7 @@ | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { path } from '../../utils'; | ||||
| import { path } from '../utils'; | ||||
| 
 | ||||
| export default { | ||||
|   data() { | ||||
							
								
								
									
										134
									
								
								src/Executor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/Executor.ts
									
									
									
									
									
										Normal 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										166
									
								
								src/Scheduler.ts
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								src/Scheduler.ts
									
									
									
									
									
								
							| @@ -1,54 +1,27 @@ | ||||
| import { markPage, sleepMicro, timestamp, waitForLoad } from './utils'; | ||||
| import { timestamp } from './utils'; | ||||
| import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||
| import { AbortTaskError, ActionError, BuildingQueueFullError, TryLaterError } from './Errors'; | ||||
| import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue'; | ||||
| import { ActionQueue } from './Storage/ActionQueue'; | ||||
| import { TaskId, TaskList, TaskQueue } from './Storage/TaskQueue'; | ||||
| 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 { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; | ||||
| import { ConsoleLogger, Logger } from './Logger'; | ||||
| import { BuildBuildingTask } from './Task/BuildBuildingTask'; | ||||
| import { GrabVillageState } from './Task/GrabVillageState'; | ||||
| import { StateGrabberManager } from './State/StateGrabberManager'; | ||||
| import { ActionQueue } from './Storage/ActionQueue'; | ||||
|  | ||||
| export class Scheduler { | ||||
|     private readonly version: string; | ||||
|     private taskQueue: TaskQueue; | ||||
|     private actionQueue: ActionQueue; | ||||
|     private grabbers: StateGrabberManager; | ||||
|     private logger: Logger; | ||||
|  | ||||
|     constructor(version: string) { | ||||
|         this.version = version; | ||||
|     constructor() { | ||||
|         this.taskQueue = new TaskQueue(); | ||||
|         this.actionQueue = new ActionQueue(); | ||||
|         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); | ||||
|  | ||||
|         this.scheduleUniqTask(3600, SendOnAdventureTask.name); | ||||
|         this.scheduleUniqTask(1200, BalanceHeroResourcesTask.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 = {}) { | ||||
| @@ -61,111 +34,16 @@ export class Scheduler { | ||||
|         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 { | ||||
|         return this.taskQueue.seeItems(); | ||||
|     } | ||||
|  | ||||
|     completeTask(id: TaskId) { | ||||
|         this.taskQueue.complete(id); | ||||
|         this.actionQueue.clear(); | ||||
|     nextTask(ts: number) { | ||||
|         return this.taskQueue.get(ts); | ||||
|     } | ||||
|  | ||||
|     nextAction() { | ||||
|         return this.actionQueue.pop(); | ||||
|     } | ||||
|  | ||||
|     scheduleTask(name: string, args: Args): void { | ||||
| @@ -173,12 +51,36 @@ export class Scheduler { | ||||
|         this.taskQueue.push(name, args, timestamp()); | ||||
|     } | ||||
|  | ||||
|     completeTask(id: TaskId) { | ||||
|         this.taskQueue.complete(id); | ||||
|         this.actionQueue.clear(); | ||||
|     } | ||||
|  | ||||
|     removeTask(id: TaskId) { | ||||
|         this.taskQueue.remove(id); | ||||
|         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 { | ||||
|         this.actionQueue.assign(actions); | ||||
|     } | ||||
|  | ||||
|     clearActions() { | ||||
|         this.actionQueue.clear(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class State { | ||||
| } | ||||
|  | ||||
| export class ActionQueue { | ||||
|     private readonly logger; | ||||
|     private readonly logger: Logger; | ||||
|  | ||||
|     constructor() { | ||||
|         this.logger = new ConsoleLogger(this.constructor.name); | ||||
|   | ||||
							
								
								
									
										28
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/index.js
									
									
									
									
									
								
							| @@ -1,18 +1,24 @@ | ||||
| import { ConsoleLogger } from './Logger'; | ||||
| import { ModeDetector } from './ModeDetector'; | ||||
| import { Scheduler } from './Scheduler'; | ||||
| import { Dashboard } from './Dashboard/Dashboard'; | ||||
| import { Executor } from './Executor'; | ||||
| import { ControlPanel } from './ControlPanel'; | ||||
| import TxtVersion from '!!raw-loader!./version.txt'; | ||||
|  | ||||
| console.log('TRAVIAN AUTOMATION', TxtVersion); | ||||
| const logger = new ConsoleLogger('Travian'); | ||||
|  | ||||
| const md = new ModeDetector(); | ||||
| if (md.isAuto()) { | ||||
|     md.setAuto(); | ||||
|     console.log('AUTO MANAGEMENT ON'); | ||||
|     const scheduler = new Scheduler(TxtVersion); | ||||
|     scheduler.run(); | ||||
| logger.log('TRAVIAN AUTOMATION', TxtVersion); | ||||
|  | ||||
| const modeDetector = new ModeDetector(); | ||||
| const scheduler = new Scheduler(); | ||||
|  | ||||
| if (modeDetector.isAuto()) { | ||||
|     modeDetector.setAuto(); | ||||
|     logger.log('AUTO MANAGEMENT ON'); | ||||
|     const executor = new Executor(TxtVersion, scheduler); | ||||
|     executor.run(); | ||||
| } else { | ||||
|     console.log('NORMAL MODE'); | ||||
|     const dashboard = new Dashboard(TxtVersion, new Scheduler()); | ||||
|     dashboard.run(); | ||||
|     logger.log('NORMAL MODE'); | ||||
|     const controlPanel = new ControlPanel(TxtVersion, scheduler); | ||||
|     controlPanel.run(); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user