Rewrite task queue
This commit is contained in:
		
							
								
								
									
										49
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										49
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -345,6 +345,15 @@ | |||||||
|       "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", |       "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "@types/nanoid": { | ||||||
|  |       "version": "2.1.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/nanoid/-/nanoid-2.1.0.tgz", | ||||||
|  |       "integrity": "sha512-xdkn/oRTA0GSNPLIKZgHWqDTWZsVrieKomxJBOQUK9YDD+zfSgmwD5t4WJYra5S7XyhTw7tfvwznW+pFexaepQ==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "@types/node": "*" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "@types/node": { |     "@types/node": { | ||||||
|       "version": "13.9.4", |       "version": "13.9.4", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.4.tgz", |       "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.4.tgz", | ||||||
| @@ -363,12 +372,6 @@ | |||||||
|       "integrity": "sha512-4kHAkbV/OfW2kb5BLVUuUMoumB3CP8rHqlw48aHvFy5tf9ER0AfOonBlX29l/DD68G70DmyhRlSYfQPSYpC5Vw==", |       "integrity": "sha512-4kHAkbV/OfW2kb5BLVUuUMoumB3CP8rHqlw48aHvFy5tf9ER0AfOonBlX29l/DD68G70DmyhRlSYfQPSYpC5Vw==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "@types/uuid": { |  | ||||||
|       "version": "7.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.2.tgz", |  | ||||||
|       "integrity": "sha512-8Ly3zIPTnT0/8RCU6Kg/G3uTICf9sRwYOpUzSIM3503tLIKcnJPRuinHhXngJUy2MntrEf6dlpOHXJju90Qh5w==", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "@webassemblyjs/ast": { |     "@webassemblyjs/ast": { | ||||||
|       "version": "1.9.0", |       "version": "1.9.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", |       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", | ||||||
| @@ -3964,6 +3967,12 @@ | |||||||
|       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", |       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "nanoid": { | ||||||
|  |       "version": "3.0.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.0.2.tgz", | ||||||
|  |       "integrity": "sha512-WOjyy/xu3199NlQiQWlx7VbspSFlGtOxa1bRX9ebmXOnp1fje4bJfjPs1wLQ8jZbJUfD+yceJmw879ZSaVJkdQ==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "nanomatch": { |     "nanomatch": { | ||||||
|       "version": "1.2.9", |       "version": "1.2.9", | ||||||
|       "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", |       "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", | ||||||
| @@ -4789,6 +4798,28 @@ | |||||||
|         "safe-buffer": "^5.1.0" |         "safe-buffer": "^5.1.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "raw-loader": { | ||||||
|  |       "version": "4.0.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.0.tgz", | ||||||
|  |       "integrity": "sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "loader-utils": "^1.2.3", | ||||||
|  |         "schema-utils": "^2.5.0" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "schema-utils": { | ||||||
|  |           "version": "2.6.5", | ||||||
|  |           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", | ||||||
|  |           "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", | ||||||
|  |           "dev": true, | ||||||
|  |           "requires": { | ||||||
|  |             "ajv": "^6.12.0", | ||||||
|  |             "ajv-keywords": "^3.4.1" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "readable-stream": { |     "readable-stream": { | ||||||
|       "version": "2.3.6", |       "version": "2.3.6", | ||||||
|       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", |       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", | ||||||
| @@ -5871,12 +5902,6 @@ | |||||||
|       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", |       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "uuid": { |  | ||||||
|       "version": "7.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.2.tgz", |  | ||||||
|       "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "v8-compile-cache": { |     "v8-compile-cache": { | ||||||
|       "version": "2.0.3", |       "version": "2.0.3", | ||||||
|       "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", |       "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", | ||||||
|   | |||||||
| @@ -24,20 +24,21 @@ | |||||||
|     "@types/chai": "^4.2.11", |     "@types/chai": "^4.2.11", | ||||||
|     "@types/jquery": "^3.3.34", |     "@types/jquery": "^3.3.34", | ||||||
|     "@types/mocha": "^7.0.2", |     "@types/mocha": "^7.0.2", | ||||||
|  |     "@types/nanoid": "^2.1.0", | ||||||
|     "@types/node": "^13.9.4", |     "@types/node": "^13.9.4", | ||||||
|     "@types/url-parse": "^1.4.3", |     "@types/url-parse": "^1.4.3", | ||||||
|     "@types/uuid": "^7.0.2", |  | ||||||
|     "chai": "^4.2.0", |     "chai": "^4.2.0", | ||||||
|     "jquery": "^3.4.1", |     "jquery": "^3.4.1", | ||||||
|     "mocha": "^7.1.1", |     "mocha": "^7.1.1", | ||||||
|     "mocha-junit-reporter": "^1.23.3", |     "mocha-junit-reporter": "^1.23.3", | ||||||
|  |     "nanoid": "^3.0.2", | ||||||
|     "nyc": "^15.0.0", |     "nyc": "^15.0.0", | ||||||
|     "prettier": "^1.19.1", |     "prettier": "^1.19.1", | ||||||
|  |     "raw-loader": "^4.0.0", | ||||||
|     "ts-loader": "^6.2.2", |     "ts-loader": "^6.2.2", | ||||||
|     "ts-node": "^8.8.1", |     "ts-node": "^8.8.1", | ||||||
|     "typescript": "^3.8.3", |     "typescript": "^3.8.3", | ||||||
|     "url-parse": "^1.4.7", |     "url-parse": "^1.4.7", | ||||||
|     "uuid": "^7.0.2", |  | ||||||
|     "webpack": "^4.42.1", |     "webpack": "^4.42.1", | ||||||
|     "webpack-cli": "^3.3.11" |     "webpack-cli": "^3.3.11" | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| export default abstract class Action { |  | ||||||
|     abstract async run(args); |  | ||||||
| } |  | ||||||
							
								
								
									
										6
									
								
								src/Action/ActionController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/Action/ActionController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | import { Args } from '../Common'; | ||||||
|  | import { Task } from '../Storage/TaskQueue'; | ||||||
|  |  | ||||||
|  | export default abstract class ActionController { | ||||||
|  |     abstract async run(args: Args, task: Task); | ||||||
|  | } | ||||||
| @@ -1,9 +1,11 @@ | |||||||
| import Action from './Action'; | import ActionController from './ActionController'; | ||||||
|  | import { Args } from '../Common'; | ||||||
|  | import { Task } from '../Storage/TaskQueue'; | ||||||
|  |  | ||||||
| export default class GoToBuildingAction extends Action { | export default class GoToBuildingAction extends ActionController { | ||||||
|     static NAME = 'go_to_building'; |     static NAME = 'go_to_building'; | ||||||
|  |  | ||||||
|     async run(args): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         window.location.assign('/build.php?id=' + args.id); |         window.location.assign('/build.php?id=' + args.id); | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
| import Action from './Action'; | import ActionController from './ActionController'; | ||||||
|  | import { Args } from '../Common'; | ||||||
|  | import { Task } from '../Storage/TaskQueue'; | ||||||
|  |  | ||||||
| export default class GoToResourceFieldsAction extends Action { | export default class GoToResourceFieldsAction extends ActionController { | ||||||
|     static NAME = 'go_to_resource_fields'; |     static NAME = 'go_to_resource_fields'; | ||||||
|     async run(): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         window.location.assign('/dorf1.php'); |         window.location.assign('/dorf1.php'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| import Action from './Action'; | import ActionController from './ActionController'; | ||||||
| import { Args } from '../Common'; | import { Args } from '../Common'; | ||||||
|  | import { Task } from '../Storage/TaskQueue'; | ||||||
|  |  | ||||||
| export default class StoreRemainingBuildTimeAction extends Action { | export default class StoreRemainingBuildTimeAction extends ActionController { | ||||||
|     static NAME = 'store_remaining_build_time'; |     static NAME = 'store_remaining_build_time'; | ||||||
|  |  | ||||||
|     async run(args: Args): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         const timer = jQuery('.buildDuration .timer'); |         const timer = jQuery('.buildDuration .timer'); | ||||||
|         // if (timer.length === 1) { |         // if (timer.length === 1) { | ||||||
|         //     const remainingSeconds = +timer.val(); |         //     const remainingSeconds = +timer.val(); | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| import Action from './Action'; | import ActionController from './ActionController'; | ||||||
| import { Args } from '../Common'; | import { Args } from '../Common'; | ||||||
| import { TryLaterError } from '../Errors'; | import { TryLaterError } from '../Errors'; | ||||||
| import Scheduler from '../Scheduler'; | import Scheduler from '../Scheduler'; | ||||||
|  | import { Task } from '../Storage/TaskQueue'; | ||||||
|  |  | ||||||
| export default class UpgradeBuildingAction extends Action { | export default class UpgradeBuildingAction extends ActionController { | ||||||
|     static NAME = 'upgrade_building'; |     static NAME = 'upgrade_building'; | ||||||
|     private scheduler: Scheduler; |     private scheduler: Scheduler; | ||||||
|  |  | ||||||
| @@ -12,12 +13,12 @@ export default class UpgradeBuildingAction extends Action { | |||||||
|         this.scheduler = scheduler; |         this.scheduler = scheduler; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async run(args: Args): Promise<any> { |     async run(args: Args, task: Task): Promise<any> { | ||||||
|         const btn = jQuery( |         const btn = jQuery( | ||||||
|             '.upgradeButtonsContainer .section1 button.green.build' |             '.upgradeButtonsContainer .section1 button.green.build' | ||||||
|         ); |         ); | ||||||
|         if (btn.length === 1) { |         if (btn.length === 1) { | ||||||
|             this.scheduler.completeCurrentTask(); |             this.scheduler.completeTask(task.id); | ||||||
|             btn.trigger('click'); |             btn.trigger('click'); | ||||||
|         } else { |         } else { | ||||||
|             throw new TryLaterError(60, 'No upgrade button, try later'); |             throw new TryLaterError(60, 'No upgrade button, try later'); | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import * as URLParse from 'url-parse'; | import * as URLParse from 'url-parse'; | ||||||
| import { markPage, sleep, sleepShort } from './utils'; | import { markPage, sleep, uniqId } from './utils'; | ||||||
| import { v4 as uuid } from 'uuid'; |  | ||||||
| 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'; | ||||||
| @@ -23,7 +22,7 @@ export default class Dashboard { | |||||||
|         console.log('PARSED LOCATION', p); |         console.log('PARSED LOCATION', p); | ||||||
|  |  | ||||||
|         markPage('Dashboard', this.version); |         markPage('Dashboard', this.version); | ||||||
|         new TaskQueueRenderer().render(this.scheduler.taskState()); |         new TaskQueueRenderer().render(this.scheduler.getTaskItems()); | ||||||
|  |  | ||||||
|         if (p.pathname === '/dorf1.php') { |         if (p.pathname === '/dorf1.php') { | ||||||
|             this.showSlotIds('buildingSlot'); |             this.showSlotIds('buildingSlot'); | ||||||
| @@ -35,7 +34,7 @@ export default class Dashboard { | |||||||
|  |  | ||||||
|         if (p.pathname === '/build.php') { |         if (p.pathname === '/build.php') { | ||||||
|             console.log('BUILD PAGE DETECTED'); |             console.log('BUILD PAGE DETECTED'); | ||||||
|             const id = uuid(); |             const id = uniqId(); | ||||||
|             jQuery('.upgradeButtonsContainer .section1').append( |             jQuery('.upgradeButtonsContainer .section1').append( | ||||||
|                 `<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>` |                 `<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>` | ||||||
|             ); |             ); | ||||||
| @@ -43,7 +42,7 @@ export default class Dashboard { | |||||||
|                 const queueItem = new Command(UpgradeBuildingTask.NAME, { |                 const queueItem = new Command(UpgradeBuildingTask.NAME, { | ||||||
|                     id: p.query['id'], |                     id: p.query['id'], | ||||||
|                 }); |                 }); | ||||||
|                 this.scheduler.pushTask(queueItem); |                 this.scheduler.scheduleTask(queueItem); | ||||||
|                 return false; |                 return false; | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
|  | import { TaskId } from './Storage/TaskQueue'; | ||||||
|  |  | ||||||
| export class TryLaterError extends Error { | export class TryLaterError extends Error { | ||||||
|     readonly seconds: number; |     readonly seconds: number; | ||||||
|     constructor(s: number, msg: string = '') { |     readonly id: TaskId; | ||||||
|  |     constructor(seconds: number, id: TaskId, msg: string = '') { | ||||||
|         super(msg); |         super(msg); | ||||||
|         this.seconds = s; |         this.id = id; | ||||||
|         // Set the prototype explicitly. |         this.seconds = seconds; | ||||||
|         Object.setPrototypeOf(this, TryLaterError.prototype); |         Object.setPrototypeOf(this, TryLaterError.prototype); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										125
									
								
								src/Scheduler.ts
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								src/Scheduler.ts
									
									
									
									
									
								
							| @@ -1,12 +1,14 @@ | |||||||
| import { markPage, sleepLong, sleepShort } from './utils'; | import { markPage, sleepLong, sleepShort, timestamp } from './utils'; | ||||||
| import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; | import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; | ||||||
| import GoToBuildingAction from './Action/GoToBuildingAction'; | import GoToBuildingAction from './Action/GoToBuildingAction'; | ||||||
| import UpgradeBuildingAction from './Action/UpgradeBuildingAction'; | import UpgradeBuildingAction from './Action/UpgradeBuildingAction'; | ||||||
| import { TryLaterError } from './Errors'; | import { TryLaterError } from './Errors'; | ||||||
| import { TaskQueue, ImmutableState } from './Storage/TaskQueue'; | import { TaskQueue, TaskList, Task, TaskId } from './Storage/TaskQueue'; | ||||||
| import ActionQueue from './Storage/ActionQueue'; | import ActionQueue from './Storage/ActionQueue'; | ||||||
| import { Args, Command } from './Common'; | import { Args, Command } from './Common'; | ||||||
| import TaskQueueRenderer from './TaskQueueRenderer'; | import TaskQueueRenderer from './TaskQueueRenderer'; | ||||||
|  | import ActionController from './Action/ActionController'; | ||||||
|  | import TaskController from './Task/TaskController'; | ||||||
|  |  | ||||||
| enum SleepType { | enum SleepType { | ||||||
|     Long, |     Long, | ||||||
| @@ -29,31 +31,53 @@ export default class Scheduler { | |||||||
|     async run() { |     async run() { | ||||||
|         await sleepShort(); |         await sleepShort(); | ||||||
|         markPage('Executor', this.version); |         markPage('Executor', this.version); | ||||||
|         new TaskQueueRenderer().render(this.taskQueue.state()); |         new TaskQueueRenderer().render(this.taskQueue.seeItems()); | ||||||
|  |  | ||||||
|         while (true) { |         while (true) { | ||||||
|             await this.sleep(); |             await this.doLoopStep(); | ||||||
|             const actionItem = this.popAction(); |  | ||||||
|             this.log('POP ACTION ITEM', actionItem); |  | ||||||
|             if (actionItem !== null) { |  | ||||||
|                 const action = this.createAction(actionItem); |  | ||||||
|                 this.log('POP ACTION', action); |  | ||||||
|                 if (action) { |  | ||||||
|                     await this.runAction(action, actionItem.args); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 const taskItem = this.getTask(); |  | ||||||
|                 this.log('POP TASK ITEM', taskItem); |  | ||||||
|                 if (taskItem !== null) { |  | ||||||
|                     const task = this.createTask(taskItem); |  | ||||||
|                     this.log('POP TASK', task); |  | ||||||
|                     if (task !== null) { |  | ||||||
|                         task.run(taskItem.args); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private async doLoopStep() { | ||||||
|  |         await this.sleep(); | ||||||
|  |         const currentTs = timestamp(); | ||||||
|  |         const taskCommand = this.taskQueue.get(currentTs); | ||||||
|  |  | ||||||
|  |         // текущего таска нет, очищаем очередь действий по таску | ||||||
|  |         if (taskCommand === undefined) { | ||||||
|  |             this.log('NO ACTIVE TASK'); | ||||||
|  |             this.actionQueue.clear(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const actionCommand = this.popActionCommand(); | ||||||
|  |  | ||||||
|  |         this.log('CURRENT TASK', taskCommand); | ||||||
|  |         this.log('CURRENT ACTION', actionCommand); | ||||||
|  |  | ||||||
|  |         if (actionCommand) { | ||||||
|  |             return await this.processActionCommand(actionCommand, taskCommand); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (taskCommand) { | ||||||
|  |             return await this.processTaskCommand(taskCommand); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async processActionCommand(cmd: Command, task: Task) { | ||||||
|  |         const actionController = this.createActionControllerByName(cmd.name); | ||||||
|  |         this.log('PROCESS ACTION CTR', actionController); | ||||||
|  |         if (actionController) { | ||||||
|  |             await this.runAction(actionController, cmd.args, task); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async processTaskCommand(task: Task) { | ||||||
|  |         const taskController = this.createTaskControllerByName(task.cmd.name); | ||||||
|  |         this.log('PROCESS TASK CTR', taskController, task); | ||||||
|  |         taskController?.run(task); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private async sleep() { |     private async sleep() { | ||||||
|         if (this.sleepType === SleepType.Long) { |         if (this.sleepType === SleepType.Long) { | ||||||
|             await sleepLong(); |             await sleepLong(); | ||||||
| @@ -67,69 +91,64 @@ export default class Scheduler { | |||||||
|         this.sleepType = SleepType.Long; |         this.sleepType = SleepType.Long; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     taskState(): ImmutableState { |     getTaskItems(): TaskList { | ||||||
|         return this.taskQueue.state(); |         return this.taskQueue.seeItems(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pushTask(task: Command): void { |     completeTask(id: TaskId) { | ||||||
|  |         this.taskQueue.complete(id); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     scheduleTask(task: Command): void { | ||||||
|         this.log('PUSH TASK', task); |         this.log('PUSH TASK', task); | ||||||
|         this.taskQueue.push(task); |         this.taskQueue.push(task, timestamp()); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pushAction(action: Command): void { |  | ||||||
|         this.log('PUSH ACTION', action); |  | ||||||
|         this.actionQueue.push(action); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     scheduleActions(actions: Array<Command>): void { |     scheduleActions(actions: Array<Command>): void { | ||||||
|         this.actionQueue.assign(actions); |         this.actionQueue.assign(actions); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     completeCurrentTask() { |     private createTaskControllerByName( | ||||||
|         this.taskQueue.next(); |         taskName: string | ||||||
|     } |     ): TaskController | undefined { | ||||||
|  |         switch (taskName) { | ||||||
|     private getTask(): Command | null { |  | ||||||
|         return this.taskQueue.current() || this.taskQueue.next(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private createTask(taskItem: Command) { |  | ||||||
|         switch (taskItem.name) { |  | ||||||
|             case UpgradeBuildingTask.NAME: |             case UpgradeBuildingTask.NAME: | ||||||
|                 return new UpgradeBuildingTask(this); |                 return new UpgradeBuildingTask(this); | ||||||
|         } |         } | ||||||
|         this.log('UNKNOWN TASK', taskItem.name); |         this.log('UNKNOWN TASK', taskName); | ||||||
|         return null; |         return undefined; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private popAction() { |     private popActionCommand(): Command | undefined { | ||||||
|         const actionItem = this.actionQueue.pop(); |         const actionItem = this.actionQueue.pop(); | ||||||
|         if (actionItem === undefined) { |         if (actionItem === undefined) { | ||||||
|             return null; |             return undefined; | ||||||
|         } |         } | ||||||
|         this.log('UNKNOWN ACTION', actionItem.name); |         this.log('UNKNOWN ACTION', actionItem.name); | ||||||
|         return actionItem; |         return actionItem; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private createAction(actionItem: Command) { |     private createActionControllerByName( | ||||||
|         if (actionItem.name === GoToBuildingAction.NAME) { |         actonName: string | ||||||
|  |     ): ActionController | undefined { | ||||||
|  |         if (actonName === GoToBuildingAction.NAME) { | ||||||
|             return new GoToBuildingAction(); |             return new GoToBuildingAction(); | ||||||
|         } |         } | ||||||
|         if (actionItem.name === UpgradeBuildingAction.NAME) { |         if (actonName === UpgradeBuildingAction.NAME) { | ||||||
|             return new UpgradeBuildingAction(this); |             return new UpgradeBuildingAction(this); | ||||||
|         } |         } | ||||||
|         return null; |         return undefined; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async runAction(action, args: Args) { |     private async runAction(action: ActionController, args: Args, task: Task) { | ||||||
|         try { |         try { | ||||||
|             await action.run(args); |             await action.run(args, task); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|             console.warn('ACTION ABORTED', e.message); |             console.warn('ACTION ABORTED', e.message); | ||||||
|             if (e instanceof TryLaterError) { |             if (e instanceof TryLaterError) { | ||||||
|                 console.warn('TRY AFTER', e.seconds); |                 console.warn('TRY AFTER', e.seconds); | ||||||
|                 this.actionQueue.clear(); |                 this.actionQueue.clear(); | ||||||
|                 this.taskQueue.postpone(e.seconds); |                 this.taskQueue.postpone(task.id, e.seconds); | ||||||
|                 this.nextSleepLong(); |                 this.nextSleepLong(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,110 +1,88 @@ | |||||||
| import { Command } from '../Common'; | import { Command } from '../Common'; | ||||||
|  | import { uniqId } from '../utils'; | ||||||
|  |  | ||||||
| const QUEUE_NAME = 'task_queue:v2'; | const QUEUE_NAME = 'task_queue:v3'; | ||||||
|  |  | ||||||
| class CommandWithTime { | export type TaskId = string; | ||||||
|     readonly cmd: Command; |  | ||||||
|  | function uniqTaskId(): TaskId { | ||||||
|  |     return uniqId(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class Task { | ||||||
|  |     readonly id: TaskId; | ||||||
|     readonly ts: number; |     readonly ts: number; | ||||||
|     constructor(cmd: Command, ts: number) { |     readonly cmd: Command; | ||||||
|         this.cmd = cmd; |     constructor(id: TaskId, ts: number, cmd: Command) { | ||||||
|  |         this.id = id; | ||||||
|         this.ts = ts; |         this.ts = ts; | ||||||
|  |         this.cmd = cmd; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     withTime(ts: number): Task { | ||||||
|  |         return new Task(this.id, ts, this.cmd); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export class State { | export type TaskList = Array<Task>; | ||||||
|     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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     postpone(ds: number): State { |  | ||||||
|         const current = this.current; |  | ||||||
|         let items = this.items.slice(); |  | ||||||
|         if (current) { |  | ||||||
|             const cmd = new CommandWithTime(current.cmd, current.ts + ds); |  | ||||||
|             items.push(cmd); |  | ||||||
|         } |  | ||||||
|         return new State(null, items); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export class ImmutableState { |  | ||||||
|     readonly current: CommandWithTime | null; |  | ||||||
|     readonly items: Array<CommandWithTime>; |  | ||||||
|     constructor(state: State) { |  | ||||||
|         this.current = state.current; |  | ||||||
|         this.items = state.items; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export class TaskQueue { | export class TaskQueue { | ||||||
|     push(cmd: Command, ts: number | null = null) { |     private static normalize(items: TaskList): TaskList { | ||||||
|         this.log('PUSH TASK', cmd, ts); |         return items.sort((x, y) => x.ts - y.ts); | ||||||
|         const state = this.getState(); |  | ||||||
|         this.flushState(state.push(cmd, ts || this.defaultTs())); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     current(): Command | null { |     push(cmd: Command, ts: number): Task { | ||||||
|         let current = this.getState().current; |         const id = uniqTaskId(); | ||||||
|         return current ? current.cmd : null; |         const task = new Task(id, ts, cmd); | ||||||
|  |         this.log('PUSH TASK', id, ts, cmd); | ||||||
|  |         let items = this.getItems(); | ||||||
|  |         items.push(task); | ||||||
|  |         this.flushItems(items); | ||||||
|  |         return task; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     next(): Command | null { |     get(ts: number): Task | undefined { | ||||||
|         let state = this.getState().next(); |         const readyItems = this.getItems().filter(t => t.ts <= ts); | ||||||
|         let current = state.current ? state.current.cmd : null; |         if (readyItems.length === 0) { | ||||||
|         this.flushState(state); |             return undefined; | ||||||
|         return current; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     postpone(ds: number) { |  | ||||||
|         const state = this.getState().postpone(ds); |  | ||||||
|         this.flushState(state); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     state(): ImmutableState { |  | ||||||
|         return new ImmutableState(this.getState()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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, []); |  | ||||||
|         } |         } | ||||||
|  |         return readyItems[0]; | ||||||
|         const s = JSON.parse(serialized) as State; |  | ||||||
|  |  | ||||||
|         this.log('STATE', s); |  | ||||||
|  |  | ||||||
|         return new State(s.current, s.items); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private flushState(state: State): void { |     complete(id: TaskId) { | ||||||
|         localStorage.setItem(QUEUE_NAME, JSON.stringify(state)); |         const [_, items] = this.shiftTask(id); | ||||||
|  |         this.flushItems(items); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     postpone(id: TaskId, deltaSeconds: number) { | ||||||
|  |         const [task, items] = this.shiftTask(id); | ||||||
|  |         if (task) { | ||||||
|  |             items.push(task.withTime(task.ts + deltaSeconds)); | ||||||
|  |         } | ||||||
|  |         this.flushItems(items); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     seeItems(): TaskList { | ||||||
|  |         return this.getItems(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private shiftTask(id: TaskId): [Task | undefined, TaskList] { | ||||||
|  |         const items = this.getItems(); | ||||||
|  |         const task = items.find(t => t.id === id); | ||||||
|  |         const tail = items.filter(t => t.id !== id); | ||||||
|  |         return [task, tail]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private getItems(): TaskList { | ||||||
|  |         const serialized = localStorage.getItem(QUEUE_NAME); | ||||||
|  |         return serialized !== null ? (JSON.parse(serialized) as TaskList) : []; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private flushItems(items: TaskList): void { | ||||||
|  |         localStorage.setItem( | ||||||
|  |             QUEUE_NAME, | ||||||
|  |             JSON.stringify(TaskQueue.normalize(items)) | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private log(...args) { |     private log(...args) { | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								src/Task/TaskController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/Task/TaskController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import { Task } from '../Storage/TaskQueue'; | ||||||
|  |  | ||||||
|  | export default abstract class TaskController { | ||||||
|  |     abstract run(task: Task); | ||||||
|  | } | ||||||
| @@ -1,18 +1,22 @@ | |||||||
| import Scheduler from '../Scheduler'; | import Scheduler from '../Scheduler'; | ||||||
| import GoToBuildingAction from '../Action/GoToBuildingAction'; | import GoToBuildingAction from '../Action/GoToBuildingAction'; | ||||||
| import UpgradeBuildingAction from '../Action/UpgradeBuildingAction'; | import UpgradeBuildingAction from '../Action/UpgradeBuildingAction'; | ||||||
| import { Args, Command } from '../Common'; | import { Command } from '../Common'; | ||||||
|  | import { Task } from '../Storage/TaskQueue'; | ||||||
|  | import TaskController from './TaskController'; | ||||||
|  |  | ||||||
| export default class UpgradeBuildingTask { | export default class UpgradeBuildingTask extends TaskController { | ||||||
|     static NAME = 'upgrade_building'; |     static NAME = 'upgrade_building'; | ||||||
|     private scheduler: Scheduler; |     private scheduler: Scheduler; | ||||||
|  |  | ||||||
|     constructor(scheduler: Scheduler) { |     constructor(scheduler: Scheduler) { | ||||||
|  |         super(); | ||||||
|         this.scheduler = scheduler; |         this.scheduler = scheduler; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     run(args: Args) { |     run(task: Task) { | ||||||
|         console.log('RUN', UpgradeBuildingTask.NAME, 'with', args); |         console.log('RUN', UpgradeBuildingTask.NAME, 'with', task); | ||||||
|  |         const args = { ...task.cmd.args, taskId: task.id }; | ||||||
|         this.scheduler.scheduleActions([ |         this.scheduler.scheduleActions([ | ||||||
|             new Command(GoToBuildingAction.NAME, args), |             new Command(GoToBuildingAction.NAME, args), | ||||||
|             new Command(UpgradeBuildingAction.NAME, args), |             new Command(UpgradeBuildingAction.NAME, args), | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| import { ImmutableState } from './Storage/TaskQueue'; | import { TaskList } from './Storage/TaskQueue'; | ||||||
|  | import { uniqId } from './utils'; | ||||||
|  |  | ||||||
| const ID = 'id-832654376836436939356'; | const ID = uniqId(); | ||||||
|  |  | ||||||
| export default class TaskQueueRenderer { | export default class TaskQueueRenderer { | ||||||
|     render(state: ImmutableState) { |     render(tasks: TaskList) { | ||||||
|         const ul = jQuery('<ul></ul>') |         const ul = jQuery('<ul></ul>') | ||||||
|             .attr({ id: ID }) |             .attr({ id: ID }) | ||||||
|             .css({ |             .css({ | ||||||
| @@ -15,18 +16,16 @@ export default class TaskQueueRenderer { | |||||||
|                 'z-index': '9999', |                 'z-index': '9999', | ||||||
|                 padding: '8px 6px', |                 padding: '8px 6px', | ||||||
|             }); |             }); | ||||||
|         if (state.current) { |         tasks.forEach(task => { | ||||||
|             let cmd = state.current.cmd; |  | ||||||
|             ul.append( |             ul.append( | ||||||
|                 jQuery('<li></li>').text( |                 jQuery('<li></li>').text( | ||||||
|                     'Current: ' + cmd.name + ' ' + JSON.stringify(cmd.args) |                     task.ts + | ||||||
|                 ) |                         ' ' + | ||||||
|             ); |                         task.cmd.name + | ||||||
|         } |                         ' ' + | ||||||
|         state.items.forEach(c => { |                         JSON.stringify(task.cmd.args) + | ||||||
|             ul.append( |                         ' ' + | ||||||
|                 jQuery('<li></li>').text( |                         task.id | ||||||
|                     c.cmd.name + ' ' + JSON.stringify(c.cmd.args) |  | ||||||
|                 ) |                 ) | ||||||
|             ); |             ); | ||||||
|         }); |         }); | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								src/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/utils.ts
									
									
									
									
									
								
							| @@ -1,3 +1,7 @@ | |||||||
|  | import { customAlphabet } from 'nanoid'; | ||||||
|  |  | ||||||
|  | const smallIdGenerator = customAlphabet('1234567890abcdef', 6); | ||||||
|  |  | ||||||
| export function sleep(ms: number) { | export function sleep(ms: number) { | ||||||
|     return new Promise(resolve => setTimeout(resolve, ms)); |     return new Promise(resolve => setTimeout(resolve, ms)); | ||||||
| } | } | ||||||
| @@ -14,6 +18,14 @@ export async function sleepLong() { | |||||||
|     return await sleep(ms); |     return await sleep(ms); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function uniqId(): string { | ||||||
|  |     return 'id' + smallIdGenerator(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function timestamp(): number { | ||||||
|  |     return Math.floor(Date.now() / 1000); | ||||||
|  | } | ||||||
|  |  | ||||||
| export function markPage(text: string, version: string) { | export function markPage(text: string, version: string) { | ||||||
|     jQuery('body').append( |     jQuery('body').append( | ||||||
|         '<div style="' + |         '<div style="' + | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
|     "target": "es5", |     "target": "es5", | ||||||
|     "module": "commonjs", |     "module": "commonjs", | ||||||
|     "strictNullChecks": true, |     "strictNullChecks": true, | ||||||
|     "types": ["node", "url-parse", "jquery", "uuid", "mocha", "chai"] |     "types": ["node", "url-parse", "jquery", "nanoid", "mocha", "chai"] | ||||||
|   }, |   }, | ||||||
|   "include": [ |   "include": [ | ||||||
|     "./src/**/*" |     "./src/**/*" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user