Rewrite task queue and action queue
This commit is contained in:
		| @@ -1,9 +1,11 @@ | ||||
| import Action from './Action'; | ||||
| import { Args } from '../Common'; | ||||
| import { TryLaterError } from '../Errors'; | ||||
|  | ||||
| export default class UpgradeBuildingAction extends Action { | ||||
|     static NAME = 'upgrade_building'; | ||||
|  | ||||
|     async run(args): Promise<any> { | ||||
|     async run(args: Args): Promise<any> { | ||||
|         const btn = jQuery( | ||||
|             '.upgradeButtonsContainer .section1 button.green.build' | ||||
|         ); | ||||
| @@ -11,6 +13,7 @@ export default class UpgradeBuildingAction extends Action { | ||||
|             btn.trigger('click'); | ||||
|         } else { | ||||
|             console.log('NO UPGRADE BUTTON'); | ||||
|             throw new TryLaterError(60); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|   | ||||
							
								
								
									
										13
									
								
								src/Common.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Common.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| export interface Args { | ||||
|     [name: string]: any; | ||||
| } | ||||
|  | ||||
| export class Command { | ||||
|     readonly name: string; | ||||
|     readonly args: Args; | ||||
|  | ||||
|     constructor(name: string, args: Args) { | ||||
|         this.name = name; | ||||
|         this.args = args; | ||||
|     } | ||||
| } | ||||
| @@ -1,9 +1,9 @@ | ||||
| import * as URLParse from 'url-parse'; | ||||
| import { markPage, sleepShort } from './utils'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import { QueueItem } from './Queue'; | ||||
| import Scheduler from './Scheduler'; | ||||
| import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; | ||||
| import { Command } from './Common'; | ||||
|  | ||||
| export default class Dashboard { | ||||
|     private scheduler: Scheduler; | ||||
| @@ -27,7 +27,7 @@ export default class Dashboard { | ||||
|                 `<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>` | ||||
|             ); | ||||
|             jQuery(`#${id}`).on('click', () => { | ||||
|                 const queueItem = new QueueItem(UpgradeBuildingTask.NAME, { | ||||
|                 const queueItem = new Command(UpgradeBuildingTask.NAME, { | ||||
|                     id: p.query['id'], | ||||
|                 }); | ||||
|                 this.scheduler.pushTask(queueItem); | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/Errors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Errors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| export class TryLaterError extends Error { | ||||
|     readonly seconds: number; | ||||
|     constructor(s: number) { | ||||
|         super(); | ||||
|         this.seconds = s; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										52
									
								
								src/Queue.ts
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								src/Queue.ts
									
									
									
									
									
								
							| @@ -1,52 +0,0 @@ | ||||
| export class QueueItem { | ||||
|     readonly name: string; | ||||
|  | ||||
|     readonly args; | ||||
|  | ||||
|     constructor(name: string, args: { [name: string]: any }) { | ||||
|         this.name = name; | ||||
|         this.args = args; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export class Queue { | ||||
|     private readonly name; | ||||
|  | ||||
|     constructor(name: string) { | ||||
|         this.name = name; | ||||
|     } | ||||
|  | ||||
|     pop() { | ||||
|         const serialized = localStorage.getItem(this.name); | ||||
|         if (serialized === null) { | ||||
|             return null; | ||||
|         } | ||||
|         const items = JSON.parse(serialized) as Array<QueueItem>; | ||||
|         if (items.length === 0) { | ||||
|             return null; | ||||
|         } | ||||
|         const first = items.shift(); | ||||
|  | ||||
|         this.flush(items); | ||||
|  | ||||
|         if (first === undefined) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         return new QueueItem(first.name || '', first.args || {}); | ||||
|     } | ||||
|  | ||||
|     push(item: QueueItem): void { | ||||
|         const serialized = localStorage.getItem(this.name); | ||||
|         const items = serialized | ||||
|             ? (JSON.parse(serialized) as Array<QueueItem>) | ||||
|             : []; | ||||
|         items.push(item); | ||||
|         this.flush(items); | ||||
|     } | ||||
|  | ||||
|     private flush(items) { | ||||
|         console.log('SET NEW QUEUE', this.name, items); | ||||
|         localStorage.setItem(this.name, JSON.stringify(items)); | ||||
|     } | ||||
| } | ||||
| @@ -1,19 +1,19 @@ | ||||
| import { markPage, sleepLong, sleepShort } from './utils'; | ||||
| import { Queue, QueueItem } from './Queue'; | ||||
| import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; | ||||
| import GoToBuildingAction from './Action/GoToBuildingAction'; | ||||
| import UpgradeBuildingAction from './Action/UpgradeBuildingAction'; | ||||
|  | ||||
| const ACTION_QUEUE = 'action_queue'; | ||||
| const TASK_QUEUE = 'task_queue'; | ||||
| import { TryLaterError } from './Errors'; | ||||
| import TaskQueue from './Storage/TaskQueue'; | ||||
| import ActionQueue from './Storage/ActionQueue'; | ||||
| import { Args, Command } from './Common'; | ||||
|  | ||||
| export default class Scheduler { | ||||
|     taskQueue: Queue; | ||||
|     actionQueue: Queue; | ||||
|     taskQueue: TaskQueue; | ||||
|     actionQueue: ActionQueue; | ||||
|  | ||||
|     constructor() { | ||||
|         this.taskQueue = new Queue(TASK_QUEUE); | ||||
|         this.actionQueue = new Queue(ACTION_QUEUE); | ||||
|         this.taskQueue = new TaskQueue(); | ||||
|         this.actionQueue = new ActionQueue(); | ||||
|     } | ||||
|  | ||||
|     async run() { | ||||
| @@ -27,7 +27,7 @@ export default class Scheduler { | ||||
|                 const action = this.createAction(actionItem); | ||||
|                 this.log('POP ACTION', action); | ||||
|                 if (action) { | ||||
|                     await action.run(actionItem.args); | ||||
|                     await this.runAction(action, actionItem.args); | ||||
|                 } | ||||
|             } else { | ||||
|                 const taskItem = this.popTask(); | ||||
| @@ -43,25 +43,25 @@ export default class Scheduler { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pushTask(task: QueueItem): void { | ||||
|     pushTask(task: Command): void { | ||||
|         this.log('PUSH TASK', task); | ||||
|         this.taskQueue.push(task); | ||||
|     } | ||||
|  | ||||
|     pushAction(action: QueueItem): void { | ||||
|     pushAction(action: Command): void { | ||||
|         this.log('PUSH ACTION', action); | ||||
|         this.actionQueue.push(action); | ||||
|     } | ||||
|  | ||||
|     private popTask() { | ||||
|         const taskItem = this.taskQueue.pop(); | ||||
|         if (taskItem === null) { | ||||
|             return null; | ||||
|         } | ||||
|         return taskItem; | ||||
|     scheduleActions(actions: Array<Command>): void { | ||||
|         this.actionQueue.assign(actions); | ||||
|     } | ||||
|  | ||||
|     private createTask(taskItem: QueueItem) { | ||||
|     private popTask(): Command | null { | ||||
|         return this.taskQueue.current() || this.taskQueue.next(); | ||||
|     } | ||||
|  | ||||
|     private createTask(taskItem: Command) { | ||||
|         switch (taskItem.name) { | ||||
|             case UpgradeBuildingTask.NAME: | ||||
|                 return new UpgradeBuildingTask(this); | ||||
| @@ -72,14 +72,14 @@ export default class Scheduler { | ||||
|  | ||||
|     private popAction() { | ||||
|         const actionItem = this.actionQueue.pop(); | ||||
|         if (actionItem === null) { | ||||
|         if (actionItem === undefined) { | ||||
|             return null; | ||||
|         } | ||||
|         this.log('UNKNOWN ACTION', actionItem.name); | ||||
|         return actionItem; | ||||
|     } | ||||
|  | ||||
|     private createAction(actionItem: QueueItem) { | ||||
|     private createAction(actionItem: Command) { | ||||
|         if (actionItem.name === GoToBuildingAction.NAME) { | ||||
|             return new GoToBuildingAction(); | ||||
|         } | ||||
| @@ -89,7 +89,18 @@ export default class Scheduler { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     private async runAction(action, args: Args) { | ||||
|         try { | ||||
|             await action.run(args); | ||||
|         } catch (e) { | ||||
|             console.warn('ACTION ERROR', e); | ||||
|             if (e instanceof TryLaterError) { | ||||
|                 console.warn('TRY AFTER', e.seconds); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private log(...args) { | ||||
|         console.log('SCHEDULER', ...args); | ||||
|         console.log('SCHEDULER:', ...args); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										57
									
								
								src/Storage/ActionQueue.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/Storage/ActionQueue.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import { Command } from '../Common'; | ||||
|  | ||||
| const QUEUE_NAME = 'action_queue:v2'; | ||||
|  | ||||
| class State { | ||||
|     items: Array<Command>; | ||||
|     constructor(items: Array<Command>) { | ||||
|         this.items = items; | ||||
|     } | ||||
|  | ||||
|     pop(): Command | undefined { | ||||
|         return this.items.shift(); | ||||
|     } | ||||
|  | ||||
|     push(cmd: Command) { | ||||
|         this.items.push(cmd); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default class ActionQueue { | ||||
|     pop(): Command | undefined { | ||||
|         const state = this.getState(); | ||||
|         const first = state.pop(); | ||||
|         this.flushState(state); | ||||
|         return first; | ||||
|     } | ||||
|  | ||||
|     push(cmd: Command): void { | ||||
|         const state = this.getState(); | ||||
|         state.push(cmd); | ||||
|         this.flushState(state); | ||||
|     } | ||||
|  | ||||
|     assign(items: Array<Command>): void { | ||||
|         this.flushState(new State(items)); | ||||
|     } | ||||
|  | ||||
|     private getState(): State { | ||||
|         const serialized = localStorage.getItem(QUEUE_NAME); | ||||
|         if (serialized === null) { | ||||
|             return new State([]); | ||||
|         } | ||||
|  | ||||
|         let parsed = JSON.parse(serialized) as State; | ||||
|         this.log('STATE', parsed); | ||||
|  | ||||
|         return new State(parsed.items); | ||||
|     } | ||||
|  | ||||
|     private flushState(state: State): void { | ||||
|         localStorage.setItem(QUEUE_NAME, JSON.stringify(state)); | ||||
|     } | ||||
|  | ||||
|     private log(...args) { | ||||
|         console.log('ACTION QUEUE:', ...args); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										85
									
								
								src/Storage/TaskQueue.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/Storage/TaskQueue.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| import { Command } from '../Common'; | ||||
|  | ||||
| const QUEUE_NAME = 'task_queue:v2'; | ||||
|  | ||||
| class CommandWithTime { | ||||
|     readonly cmd: Command; | ||||
|     readonly ts: number; | ||||
|     constructor(cmd: Command, ts: number) { | ||||
|         this.cmd = cmd; | ||||
|         this.ts = ts; | ||||
|     } | ||||
| } | ||||
|  | ||||
| class State { | ||||
|     current: CommandWithTime | null; | ||||
|     items: Array<CommandWithTime>; | ||||
|     constructor( | ||||
|         current: CommandWithTime | null, | ||||
|         items: Array<CommandWithTime> | ||||
|     ) { | ||||
|         items.sort((x: CommandWithTime, y: CommandWithTime) => x.ts - y.ts); | ||||
|         this.current = current; | ||||
|         this.items = items; | ||||
|     } | ||||
|  | ||||
|     push(cmd: Command, ts: number): State { | ||||
|         const items = this.items.slice(); | ||||
|         items.push(new CommandWithTime(cmd, ts)); | ||||
|         return new State(this.current, items); | ||||
|     } | ||||
|  | ||||
|     next(): State { | ||||
|         const items = this.items.slice(); | ||||
|         const first = items.shift(); | ||||
|         if (first === undefined) { | ||||
|             return new State(null, []); | ||||
|         } | ||||
|         return new State(first, items); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default class TaskQueue { | ||||
|     push(cmd: Command, ts: number | null = null) { | ||||
|         this.log('PUSH TASK', cmd, ts); | ||||
|         const state = this.getState(); | ||||
|         this.flushState(state.push(cmd, ts || this.defaultTs())); | ||||
|     } | ||||
|  | ||||
|     current(): Command | null { | ||||
|         let current = this.getState().current; | ||||
|         return current ? current.cmd : null; | ||||
|     } | ||||
|  | ||||
|     next(): Command | null { | ||||
|         let state = this.getState().next(); | ||||
|         let current = state.current ? state.current.cmd : null; | ||||
|         this.flushState(state); | ||||
|         return current; | ||||
|     } | ||||
|  | ||||
|     private defaultTs(): number { | ||||
|         return Math.floor(Date.now() / 1000); | ||||
|     } | ||||
|  | ||||
|     private getState(): State { | ||||
|         const serialized = localStorage.getItem(QUEUE_NAME); | ||||
|         if (serialized === null) { | ||||
|             return new State(null, []); | ||||
|         } | ||||
|  | ||||
|         const s = JSON.parse(serialized) as State; | ||||
|  | ||||
|         this.log('STATE', s); | ||||
|  | ||||
|         return new State(s.current, s.items); | ||||
|     } | ||||
|  | ||||
|     private flushState(state: State): void { | ||||
|         localStorage.setItem(QUEUE_NAME, JSON.stringify(state)); | ||||
|     } | ||||
|  | ||||
|     private log(...args) { | ||||
|         console.log('TASK QUEUE:', ...args); | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| import Scheduler from '../Scheduler'; | ||||
| import GoToBuildingAction from '../Action/GoToBuildingAction'; | ||||
| import UpgradeBuildingAction from '../Action/UpgradeBuildingAction'; | ||||
| import { QueueItem } from '../Queue'; | ||||
| import { Args, Command } from '../Common'; | ||||
|  | ||||
| export default class UpgradeBuildingTask { | ||||
|     static NAME = 'upgrade_building'; | ||||
| @@ -11,11 +11,11 @@ export default class UpgradeBuildingTask { | ||||
|         this.scheduler = scheduler; | ||||
|     } | ||||
|  | ||||
|     run(args) { | ||||
|     run(args: Args) { | ||||
|         console.log('RUN', UpgradeBuildingTask.NAME, 'with', args); | ||||
|         this.scheduler.pushAction(new QueueItem(GoToBuildingAction.NAME, args)); | ||||
|         this.scheduler.pushAction( | ||||
|             new QueueItem(UpgradeBuildingAction.NAME, args) | ||||
|         ); | ||||
|         this.scheduler.scheduleActions([ | ||||
|             new Command(GoToBuildingAction.NAME, args), | ||||
|             new Command(UpgradeBuildingAction.NAME, args), | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user