Rewrite task queue
This commit is contained in:
parent
e215b5ca93
commit
017433fdf9
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/**/*"
|
||||||
|
Loading…
Reference in New Issue
Block a user