Refactoring
This commit is contained in:
		| @@ -1,12 +1,11 @@ | |||||||
| import { Args } from '../Common'; | import { Args } from '../Common'; | ||||||
| import { Task } from '../Storage/TaskQueue'; | import { Task } from '../Storage/TaskQueue'; | ||||||
| import { GameState } from '../Storage/GameState'; | import { GameState } from '../Storage/GameState'; | ||||||
| import Scheduler from '../Scheduler'; | import { Scheduler } from '../Scheduler'; | ||||||
|  |  | ||||||
| const actionMap: { [name: string]: Function | undefined } = {}; | const actionMap: { [name: string]: Function | undefined } = {}; | ||||||
|  |  | ||||||
| export function registerAction(constructor: Function) { | export function registerAction(constructor: Function) { | ||||||
|     console.log('REGISTER ACTION', constructor.name); |  | ||||||
|     actionMap[constructor.name] = constructor; |     actionMap[constructor.name] = constructor; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import * as URLParse from 'url-parse'; | import * as URLParse from 'url-parse'; | ||||||
| import { markPage, trimPrefix, uniqId, waitForLoad } from './utils'; | import { markPage, uniqId, waitForLoad } from './utils'; | ||||||
| import Scheduler from './Scheduler'; | import { Scheduler } from './Scheduler'; | ||||||
| import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; | import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||||
| import { Command } from './Common'; | import { Command } from './Common'; | ||||||
| import TaskQueueRenderer from './TaskQueueRenderer'; | import TaskQueueRenderer from './TaskQueueRenderer'; | ||||||
|  |  | ||||||
| @@ -51,7 +51,7 @@ export default class Dashboard { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private onScheduleBuilding(id: string) { |     private onScheduleBuilding(id: string) { | ||||||
|         const queueItem = new Command(UpgradeBuildingTask.NAME, { |         const queueItem = new Command(UpgradeBuildingTask.name, { | ||||||
|             id, |             id, | ||||||
|         }); |         }); | ||||||
|         this.scheduler.scheduleTask(queueItem); |         this.scheduler.scheduleTask(queueItem); | ||||||
|   | |||||||
| @@ -1,29 +1,29 @@ | |||||||
| import { TaskId } from './Storage/TaskQueue'; | import { TaskId } from './Storage/TaskQueue'; | ||||||
|  |  | ||||||
| export class ActionError extends Error { | export class ActionError extends Error { | ||||||
|     readonly id: TaskId; |     readonly taskId: TaskId; | ||||||
|     constructor(id: TaskId, msg: string = '') { |     constructor(taskId: TaskId, msg: string = '') { | ||||||
|         super(msg); |         super(msg); | ||||||
|         this.id = id; |         this.taskId = taskId; | ||||||
|         Object.setPrototypeOf(this, ActionError.prototype); |         Object.setPrototypeOf(this, ActionError.prototype); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class AbortTaskError extends Error { | export class AbortTaskError extends Error { | ||||||
|     readonly id: TaskId; |     readonly taskId: TaskId; | ||||||
|     constructor(id: TaskId, msg: string = '') { |     constructor(taskId: TaskId, msg: string = '') { | ||||||
|         super(msg); |         super(msg); | ||||||
|         this.id = id; |         this.taskId = taskId; | ||||||
|         Object.setPrototypeOf(this, AbortTaskError.prototype); |         Object.setPrototypeOf(this, AbortTaskError.prototype); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class TryLaterError extends Error { | export class TryLaterError extends Error { | ||||||
|     readonly seconds: number; |     readonly seconds: number; | ||||||
|     readonly id: TaskId; |     readonly taskId: TaskId; | ||||||
|     constructor(seconds: number, id: TaskId, msg: string = '') { |     constructor(seconds: number, taskId: TaskId, msg: string = '') { | ||||||
|         super(msg); |         super(msg); | ||||||
|         this.id = id; |         this.taskId = taskId; | ||||||
|         this.seconds = seconds; |         this.seconds = seconds; | ||||||
|         Object.setPrototypeOf(this, TryLaterError.prototype); |         Object.setPrototypeOf(this, TryLaterError.prototype); | ||||||
|     } |     } | ||||||
| @@ -31,10 +31,10 @@ export class TryLaterError extends Error { | |||||||
|  |  | ||||||
| export class BuildingQueueFullError extends Error { | export class BuildingQueueFullError extends Error { | ||||||
|     readonly seconds: number; |     readonly seconds: number; | ||||||
|     readonly id: TaskId; |     readonly taskId: TaskId; | ||||||
|     constructor(seconds: number, id: TaskId, msg: string = '') { |     constructor(seconds: number, taskId: TaskId, msg: string = '') { | ||||||
|         super(msg); |         super(msg); | ||||||
|         this.id = id; |         this.taskId = taskId; | ||||||
|         this.seconds = seconds; |         this.seconds = seconds; | ||||||
|         Object.setPrototypeOf(this, BuildingQueueFullError.prototype); |         Object.setPrototypeOf(this, BuildingQueueFullError.prototype); | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										142
									
								
								src/Scheduler.ts
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								src/Scheduler.ts
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| import { markPage, sleepShort, timestamp } from './utils'; | import { markPage, sleepShort, timestamp } from './utils'; | ||||||
| import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; | import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; | ||||||
| import { | import { | ||||||
|     AbortTaskError, |     AbortTaskError, | ||||||
|     BuildingQueueFullError, |     BuildingQueueFullError, | ||||||
| @@ -7,14 +7,14 @@ import { | |||||||
| } from './Errors'; | } from './Errors'; | ||||||
| import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue'; | import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue'; | ||||||
| import ActionQueue from './Storage/ActionQueue'; | import ActionQueue from './Storage/ActionQueue'; | ||||||
| import { Args, Command } from './Common'; | import { Command } from './Common'; | ||||||
| import TaskQueueRenderer from './TaskQueueRenderer'; | import TaskQueueRenderer from './TaskQueueRenderer'; | ||||||
| import { ActionController, createAction } from './Action/ActionController'; | import { createAction } from './Action/ActionController'; | ||||||
| import TaskController from './Task/TaskController'; | import { createTask } from './Task/TaskController'; | ||||||
| import SendOnAdventureTask from './Task/SendOnAdventureTask'; | import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; | ||||||
| import { GameState } from './Storage/GameState'; | import { GameState } from './Storage/GameState'; | ||||||
|  |  | ||||||
| export default class Scheduler { | export class Scheduler { | ||||||
|     private readonly version: string; |     private readonly version: string; | ||||||
|     private taskQueue: TaskQueue; |     private taskQueue: TaskQueue; | ||||||
|     private actionQueue: ActionQueue; |     private actionQueue: ActionQueue; | ||||||
| @@ -48,9 +48,9 @@ export default class Scheduler { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private scheduleHeroAdventure() { |     private scheduleHeroAdventure() { | ||||||
|         if (!this.taskQueue.hasNamed(SendOnAdventureTask.NAME)) { |         if (!this.taskQueue.hasNamed(SendOnAdventureTask.name)) { | ||||||
|             this.taskQueue.push( |             this.taskQueue.push( | ||||||
|                 new Command(SendOnAdventureTask.NAME, {}), |                 new Command(SendOnAdventureTask.name, {}), | ||||||
|                 timestamp() |                 timestamp() | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
| @@ -62,7 +62,7 @@ export default class Scheduler { | |||||||
|         const taskCommand = this.taskQueue.get(currentTs); |         const taskCommand = this.taskQueue.get(currentTs); | ||||||
|  |  | ||||||
|         // текущего таска нет, очищаем очередь действий по таску |         // текущего таска нет, очищаем очередь действий по таску | ||||||
|         if (taskCommand === undefined) { |         if (!taskCommand) { | ||||||
|             this.log('NO ACTIVE TASK'); |             this.log('NO ACTIVE TASK'); | ||||||
|             this.actionQueue.clear(); |             this.actionQueue.clear(); | ||||||
|             return; |             return; | ||||||
| @@ -73,40 +73,70 @@ export default class Scheduler { | |||||||
|         this.log('CURRENT TASK', taskCommand); |         this.log('CURRENT TASK', taskCommand); | ||||||
|         this.log('CURRENT ACTION', actionCommand); |         this.log('CURRENT ACTION', actionCommand); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|             if (actionCommand) { |             if (actionCommand) { | ||||||
|             return await this.processActionCommand(actionCommand, taskCommand); |                 return await this.processActionCommand( | ||||||
|  |                     actionCommand, | ||||||
|  |                     taskCommand | ||||||
|  |                 ); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (taskCommand) { |             if (taskCommand) { | ||||||
|                 return await this.processTaskCommand(taskCommand); |                 return await this.processTaskCommand(taskCommand); | ||||||
|             } |             } | ||||||
|     } |         } catch (e) { | ||||||
|  |             this.handleError(e); | ||||||
|     private async processTaskCommand(task: Task) { |  | ||||||
|         const taskController = this.createTaskControllerByName(task.cmd.name); |  | ||||||
|         this.log( |  | ||||||
|             'PROCESS TASK', |  | ||||||
|             taskController?.constructor.name, |  | ||||||
|             task, |  | ||||||
|             taskController |  | ||||||
|         ); |  | ||||||
|         if (taskController) { |  | ||||||
|             taskController.run(task); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async processActionCommand(cmd: Command, task: Task) { |     private async processActionCommand(cmd: Command, task: Task) { | ||||||
|         const actionController = this.createActionControllerByName(cmd.name); |         const actionController = createAction(cmd.name, this.gameState, this); | ||||||
|         this.log( |         this.log('PROCESS ACTION', cmd.name, actionController); | ||||||
|             'PROCESS ACTION', |  | ||||||
|             actionController?.constructor.name, |  | ||||||
|             actionController |  | ||||||
|         ); |  | ||||||
|         if (actionController) { |         if (actionController) { | ||||||
|             await this.runAction(actionController, cmd.args, task); |             await actionController.run(cmd.args, task); | ||||||
|  |         } else { | ||||||
|  |             this.logError('ACTION NOT FOUND', cmd.name); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private async processTaskCommand(task: Task) { | ||||||
|  |         const taskController = createTask(task.cmd.name, this); | ||||||
|  |         this.log('PROCESS TASK', task.cmd.name, task, taskController); | ||||||
|  |         if (taskController) { | ||||||
|  |             await taskController.run(task); | ||||||
|  |         } else { | ||||||
|  |             this.logError('TASK NOT FOUND', task.cmd.name); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private handleError(err: Error) { | ||||||
|  |         this.logWarn('ACTION ABORTED', err.message); | ||||||
|  |         this.actionQueue.clear(); | ||||||
|  |  | ||||||
|  |         if (err instanceof AbortTaskError) { | ||||||
|  |             this.logWarn('ABORT TASK', err.taskId); | ||||||
|  |             this.completeTask(err.taskId); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (err instanceof TryLaterError) { | ||||||
|  |             this.logWarn('TRY', err.taskId, 'AFTER', err.seconds); | ||||||
|  |             this.taskQueue.postpone(err.taskId, timestamp() + err.seconds); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (err instanceof BuildingQueueFullError) { | ||||||
|  |             this.logWarn('BUILDING QUEUE FULL, TRY ALL AFTER', err.seconds); | ||||||
|  |             this.taskQueue.modify( | ||||||
|  |                 t => t.cmd.name === UpgradeBuildingTask.name, | ||||||
|  |                 t => t.withTime(timestamp() + err.seconds) | ||||||
|  |             ); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         throw err; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     getTaskItems(): TaskList { |     getTaskItems(): TaskList { | ||||||
|         return this.taskQueue.seeItems(); |         return this.taskQueue.seeItems(); | ||||||
|     } |     } | ||||||
| @@ -124,59 +154,15 @@ export default class Scheduler { | |||||||
|         this.actionQueue.assign(actions); |         this.actionQueue.assign(actions); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private createTaskControllerByName( |  | ||||||
|         taskName: string |  | ||||||
|     ): TaskController | undefined { |  | ||||||
|         switch (taskName) { |  | ||||||
|             case UpgradeBuildingTask.NAME: |  | ||||||
|                 return new UpgradeBuildingTask(this); |  | ||||||
|             case SendOnAdventureTask.NAME: |  | ||||||
|                 return new SendOnAdventureTask(this); |  | ||||||
|         } |  | ||||||
|         this.logError('TASK NOT FOUND', taskName); |  | ||||||
|         return undefined; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private createActionControllerByName( |  | ||||||
|         actionName: string |  | ||||||
|     ): ActionController | undefined { |  | ||||||
|         const action = createAction(actionName, this.gameState, this); |  | ||||||
|         if (!action) { |  | ||||||
|             this.logError('ACTION NOT FOUND', actionName); |  | ||||||
|             return undefined; |  | ||||||
|         } |  | ||||||
|         return action; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private async runAction(action: ActionController, args: Args, task: Task) { |  | ||||||
|         try { |  | ||||||
|             await action.run(args, task); |  | ||||||
|         } catch (e) { |  | ||||||
|             console.warn('ACTION ABORTED', e.message); |  | ||||||
|             this.actionQueue.clear(); |  | ||||||
|             if (e instanceof AbortTaskError) { |  | ||||||
|                 console.warn('ABORT TASK', e.id); |  | ||||||
|                 this.completeTask(e.id); |  | ||||||
|             } |  | ||||||
|             if (e instanceof TryLaterError) { |  | ||||||
|                 console.warn('TRY', task.id, 'AFTER', e.seconds); |  | ||||||
|                 this.taskQueue.postpone(task.id, timestamp() + e.seconds); |  | ||||||
|             } |  | ||||||
|             if (e instanceof BuildingQueueFullError) { |  | ||||||
|                 console.warn('BUILDING QUEUE FULL, TRY ALL AFTER', e.seconds); |  | ||||||
|                 this.taskQueue.modify( |  | ||||||
|                     t => t.cmd.name === UpgradeBuildingTask.NAME, |  | ||||||
|                     t => t.withTime(timestamp() + e.seconds) |  | ||||||
|                 ); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private log(...args) { |     private log(...args) { | ||||||
|         console.log('SCHEDULER:', ...args); |         console.log('SCHEDULER:', ...args); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private logWarn(...args) { | ||||||
|  |         console.warn('SCHEDULER:', ...args); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private logError(...args) { |     private logError(...args) { | ||||||
|         console.error(...args); |         console.error('SCHEDULER:', ...args); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,23 +1,15 @@ | |||||||
| import Scheduler from '../Scheduler'; |  | ||||||
| import { Args, Command } from '../Common'; | import { Args, Command } from '../Common'; | ||||||
| import { Task } from '../Storage/TaskQueue'; | import { Task } from '../Storage/TaskQueue'; | ||||||
| import TaskController from './TaskController'; | import { TaskController, registerTask } from './TaskController'; | ||||||
| import { GoToPageAction } from '../Action/GoToPageAction'; | import { GoToPageAction } from '../Action/GoToPageAction'; | ||||||
| import { GrabHeroAttributesAction } from '../Action/GrabHeroAttributesAction'; | import { GrabHeroAttributesAction } from '../Action/GrabHeroAttributesAction'; | ||||||
| import { CompleteTaskAction } from '../Action/CompleteTaskAction'; | import { CompleteTaskAction } from '../Action/CompleteTaskAction'; | ||||||
| import { SendOnAdventureAction } from '../Action/SendOnAdventureAction'; | import { SendOnAdventureAction } from '../Action/SendOnAdventureAction'; | ||||||
| import { ClickButtonAction } from '../Action/ClickButtonAction'; | import { ClickButtonAction } from '../Action/ClickButtonAction'; | ||||||
|  |  | ||||||
| export default class SendOnAdventureTask extends TaskController { | @registerTask | ||||||
|     static NAME = 'send_on_adventure'; | export class SendOnAdventureTask extends TaskController { | ||||||
|     private scheduler: Scheduler; |     async run(task: Task) { | ||||||
|  |  | ||||||
|     constructor(scheduler: Scheduler) { |  | ||||||
|         super(); |  | ||||||
|         this.scheduler = scheduler; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     run(task: Task) { |  | ||||||
|         const args: Args = { ...task.cmd.args, taskId: task.id }; |         const args: Args = { ...task.cmd.args, taskId: task.id }; | ||||||
|         this.scheduler.scheduleActions([ |         this.scheduler.scheduleActions([ | ||||||
|             new Command(GoToPageAction.name, { ...args, path: 'hero.php' }), |             new Command(GoToPageAction.name, { ...args, path: 'hero.php' }), | ||||||
|   | |||||||
| @@ -1,5 +1,30 @@ | |||||||
| import { Task } from '../Storage/TaskQueue'; | import { Task } from '../Storage/TaskQueue'; | ||||||
|  | import { Scheduler } from '../Scheduler'; | ||||||
|  |  | ||||||
| export default abstract class TaskController { | const taskMap: { [name: string]: Function | undefined } = {}; | ||||||
|     abstract run(task: Task); |  | ||||||
|  | export function registerTask(constructor: Function) { | ||||||
|  |     taskMap[constructor.name] = constructor; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function createTask( | ||||||
|  |     name: string, | ||||||
|  |     scheduler: Scheduler | ||||||
|  | ): TaskController | undefined { | ||||||
|  |     const storedFunction = taskMap[name]; | ||||||
|  |     if (storedFunction === undefined) { | ||||||
|  |         return undefined; | ||||||
|  |     } | ||||||
|  |     const constructor = (storedFunction as unknown) as typeof TaskController; | ||||||
|  |     return new constructor(scheduler); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class TaskController { | ||||||
|  |     protected scheduler: Scheduler; | ||||||
|  |  | ||||||
|  |     constructor(scheduler: Scheduler) { | ||||||
|  |         this.scheduler = scheduler; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async run(task: Task) {} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,23 +1,14 @@ | |||||||
| import Scheduler from '../Scheduler'; |  | ||||||
| import { UpgradeBuildingAction } from '../Action/UpgradeBuildingAction'; | import { UpgradeBuildingAction } from '../Action/UpgradeBuildingAction'; | ||||||
| import { Args, Command } from '../Common'; | import { Args, Command } from '../Common'; | ||||||
| import { Task } from '../Storage/TaskQueue'; | import { Task } from '../Storage/TaskQueue'; | ||||||
| import TaskController from './TaskController'; | import { TaskController, registerTask } from './TaskController'; | ||||||
| import { GoToPageAction } from '../Action/GoToPageAction'; | import { GoToPageAction } from '../Action/GoToPageAction'; | ||||||
| import { CheckBuildingRemainingTimeAction } from '../Action/CheckBuildingRemainingTimeAction'; | import { CheckBuildingRemainingTimeAction } from '../Action/CheckBuildingRemainingTimeAction'; | ||||||
| import { CompleteTaskAction } from '../Action/CompleteTaskAction'; | import { CompleteTaskAction } from '../Action/CompleteTaskAction'; | ||||||
|  |  | ||||||
| export default class UpgradeBuildingTask extends TaskController { | @registerTask | ||||||
|     static NAME = 'upgrade_building'; | export class UpgradeBuildingTask extends TaskController { | ||||||
|     private scheduler: Scheduler; |     async run(task: Task) { | ||||||
|  |  | ||||||
|     constructor(scheduler: Scheduler) { |  | ||||||
|         super(); |  | ||||||
|         this.scheduler = scheduler; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     run(task: Task) { |  | ||||||
|         console.log('RUN', UpgradeBuildingTask.NAME, 'with', task); |  | ||||||
|         const args: Args = { ...task.cmd.args, taskId: task.id }; |         const args: Args = { ...task.cmd.args, taskId: task.id }; | ||||||
|         this.scheduler.scheduleActions([ |         this.scheduler.scheduleActions([ | ||||||
|             new Command(GoToPageAction.name, { ...args, path: '/dorf1.php' }), |             new Command(GoToPageAction.name, { ...args, path: '/dorf1.php' }), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user