Rewrite task queue and action queue
This commit is contained in:
parent
3b7b86fa11
commit
c6e535856a
@ -1,9 +1,11 @@
|
|||||||
import Action from './Action';
|
import Action from './Action';
|
||||||
|
import { Args } from '../Common';
|
||||||
|
import { TryLaterError } from '../Errors';
|
||||||
|
|
||||||
export default class UpgradeBuildingAction extends Action {
|
export default class UpgradeBuildingAction extends Action {
|
||||||
static NAME = 'upgrade_building';
|
static NAME = 'upgrade_building';
|
||||||
|
|
||||||
async run(args): Promise<any> {
|
async run(args: Args): Promise<any> {
|
||||||
const btn = jQuery(
|
const btn = jQuery(
|
||||||
'.upgradeButtonsContainer .section1 button.green.build'
|
'.upgradeButtonsContainer .section1 button.green.build'
|
||||||
);
|
);
|
||||||
@ -11,6 +13,7 @@ export default class UpgradeBuildingAction extends Action {
|
|||||||
btn.trigger('click');
|
btn.trigger('click');
|
||||||
} else {
|
} else {
|
||||||
console.log('NO UPGRADE BUTTON');
|
console.log('NO UPGRADE BUTTON');
|
||||||
|
throw new TryLaterError(60);
|
||||||
}
|
}
|
||||||
return null;
|
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 * as URLParse from 'url-parse';
|
||||||
import { markPage, sleepShort } from './utils';
|
import { markPage, sleepShort } from './utils';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { QueueItem } from './Queue';
|
|
||||||
import Scheduler from './Scheduler';
|
import Scheduler from './Scheduler';
|
||||||
import UpgradeBuildingTask from './Task/UpgradeBuildingTask';
|
import UpgradeBuildingTask from './Task/UpgradeBuildingTask';
|
||||||
|
import { Command } from './Common';
|
||||||
|
|
||||||
export default class Dashboard {
|
export default class Dashboard {
|
||||||
private scheduler: Scheduler;
|
private scheduler: Scheduler;
|
||||||
@ -27,7 +27,7 @@ export default class Dashboard {
|
|||||||
`<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>`
|
`<div style="padding: 8px"><a id="${id}" href="#">В очередь</a></div>`
|
||||||
);
|
);
|
||||||
jQuery(`#${id}`).on('click', () => {
|
jQuery(`#${id}`).on('click', () => {
|
||||||
const queueItem = new QueueItem(UpgradeBuildingTask.NAME, {
|
const queueItem = new Command(UpgradeBuildingTask.NAME, {
|
||||||
id: p.query['id'],
|
id: p.query['id'],
|
||||||
});
|
});
|
||||||
this.scheduler.pushTask(queueItem);
|
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 { markPage, sleepLong, sleepShort } from './utils';
|
||||||
import { Queue, QueueItem } from './Queue';
|
|
||||||
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';
|
||||||
const ACTION_QUEUE = 'action_queue';
|
import TaskQueue from './Storage/TaskQueue';
|
||||||
const TASK_QUEUE = 'task_queue';
|
import ActionQueue from './Storage/ActionQueue';
|
||||||
|
import { Args, Command } from './Common';
|
||||||
|
|
||||||
export default class Scheduler {
|
export default class Scheduler {
|
||||||
taskQueue: Queue;
|
taskQueue: TaskQueue;
|
||||||
actionQueue: Queue;
|
actionQueue: ActionQueue;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.taskQueue = new Queue(TASK_QUEUE);
|
this.taskQueue = new TaskQueue();
|
||||||
this.actionQueue = new Queue(ACTION_QUEUE);
|
this.actionQueue = new ActionQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
@ -27,7 +27,7 @@ export default class Scheduler {
|
|||||||
const action = this.createAction(actionItem);
|
const action = this.createAction(actionItem);
|
||||||
this.log('POP ACTION', action);
|
this.log('POP ACTION', action);
|
||||||
if (action) {
|
if (action) {
|
||||||
await action.run(actionItem.args);
|
await this.runAction(action, actionItem.args);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const taskItem = this.popTask();
|
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.log('PUSH TASK', task);
|
||||||
this.taskQueue.push(task);
|
this.taskQueue.push(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
pushAction(action: QueueItem): void {
|
pushAction(action: Command): void {
|
||||||
this.log('PUSH ACTION', action);
|
this.log('PUSH ACTION', action);
|
||||||
this.actionQueue.push(action);
|
this.actionQueue.push(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
private popTask() {
|
scheduleActions(actions: Array<Command>): void {
|
||||||
const taskItem = this.taskQueue.pop();
|
this.actionQueue.assign(actions);
|
||||||
if (taskItem === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return taskItem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTask(taskItem: QueueItem) {
|
private popTask(): Command | null {
|
||||||
|
return this.taskQueue.current() || this.taskQueue.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTask(taskItem: Command) {
|
||||||
switch (taskItem.name) {
|
switch (taskItem.name) {
|
||||||
case UpgradeBuildingTask.NAME:
|
case UpgradeBuildingTask.NAME:
|
||||||
return new UpgradeBuildingTask(this);
|
return new UpgradeBuildingTask(this);
|
||||||
@ -72,14 +72,14 @@ export default class Scheduler {
|
|||||||
|
|
||||||
private popAction() {
|
private popAction() {
|
||||||
const actionItem = this.actionQueue.pop();
|
const actionItem = this.actionQueue.pop();
|
||||||
if (actionItem === null) {
|
if (actionItem === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
this.log('UNKNOWN ACTION', actionItem.name);
|
this.log('UNKNOWN ACTION', actionItem.name);
|
||||||
return actionItem;
|
return actionItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createAction(actionItem: QueueItem) {
|
private createAction(actionItem: Command) {
|
||||||
if (actionItem.name === GoToBuildingAction.NAME) {
|
if (actionItem.name === GoToBuildingAction.NAME) {
|
||||||
return new GoToBuildingAction();
|
return new GoToBuildingAction();
|
||||||
}
|
}
|
||||||
@ -89,7 +89,18 @@ export default class Scheduler {
|
|||||||
return null;
|
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) {
|
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 Scheduler from '../Scheduler';
|
||||||
import GoToBuildingAction from '../Action/GoToBuildingAction';
|
import GoToBuildingAction from '../Action/GoToBuildingAction';
|
||||||
import UpgradeBuildingAction from '../Action/UpgradeBuildingAction';
|
import UpgradeBuildingAction from '../Action/UpgradeBuildingAction';
|
||||||
import { QueueItem } from '../Queue';
|
import { Args, Command } from '../Common';
|
||||||
|
|
||||||
export default class UpgradeBuildingTask {
|
export default class UpgradeBuildingTask {
|
||||||
static NAME = 'upgrade_building';
|
static NAME = 'upgrade_building';
|
||||||
@ -11,11 +11,11 @@ export default class UpgradeBuildingTask {
|
|||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
run(args) {
|
run(args: Args) {
|
||||||
console.log('RUN', UpgradeBuildingTask.NAME, 'with', args);
|
console.log('RUN', UpgradeBuildingTask.NAME, 'with', args);
|
||||||
this.scheduler.pushAction(new QueueItem(GoToBuildingAction.NAME, args));
|
this.scheduler.scheduleActions([
|
||||||
this.scheduler.pushAction(
|
new Command(GoToBuildingAction.NAME, args),
|
||||||
new QueueItem(UpgradeBuildingAction.NAME, args)
|
new Command(UpgradeBuildingAction.NAME, args),
|
||||||
);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user