Refactoring

This commit is contained in:
Anton Vakhrushev 2020-04-03 12:22:49 +03:00
parent 29485d233d
commit f45f9c88ae
7 changed files with 121 additions and 128 deletions

View File

@ -1,12 +1,11 @@
import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue';
import { GameState } from '../Storage/GameState';
import Scheduler from '../Scheduler';
import { Scheduler } from '../Scheduler';
const actionMap: { [name: string]: Function | undefined } = {};
export function registerAction(constructor: Function) {
console.log('REGISTER ACTION', constructor.name);
actionMap[constructor.name] = constructor;
}

View File

@ -1,7 +1,7 @@
import * as URLParse from 'url-parse';
import { markPage, trimPrefix, uniqId, waitForLoad } from './utils';
import Scheduler from './Scheduler';
import UpgradeBuildingTask from './Task/UpgradeBuildingTask';
import { markPage, uniqId, waitForLoad } from './utils';
import { Scheduler } from './Scheduler';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import { Command } from './Common';
import TaskQueueRenderer from './TaskQueueRenderer';
@ -51,7 +51,7 @@ export default class Dashboard {
}
private onScheduleBuilding(id: string) {
const queueItem = new Command(UpgradeBuildingTask.NAME, {
const queueItem = new Command(UpgradeBuildingTask.name, {
id,
});
this.scheduler.scheduleTask(queueItem);

View File

@ -1,29 +1,29 @@
import { TaskId } from './Storage/TaskQueue';
export class ActionError extends Error {
readonly id: TaskId;
constructor(id: TaskId, msg: string = '') {
readonly taskId: TaskId;
constructor(taskId: TaskId, msg: string = '') {
super(msg);
this.id = id;
this.taskId = taskId;
Object.setPrototypeOf(this, ActionError.prototype);
}
}
export class AbortTaskError extends Error {
readonly id: TaskId;
constructor(id: TaskId, msg: string = '') {
readonly taskId: TaskId;
constructor(taskId: TaskId, msg: string = '') {
super(msg);
this.id = id;
this.taskId = taskId;
Object.setPrototypeOf(this, AbortTaskError.prototype);
}
}
export class TryLaterError extends Error {
readonly seconds: number;
readonly id: TaskId;
constructor(seconds: number, id: TaskId, msg: string = '') {
readonly taskId: TaskId;
constructor(seconds: number, taskId: TaskId, msg: string = '') {
super(msg);
this.id = id;
this.taskId = taskId;
this.seconds = seconds;
Object.setPrototypeOf(this, TryLaterError.prototype);
}
@ -31,10 +31,10 @@ export class TryLaterError extends Error {
export class BuildingQueueFullError extends Error {
readonly seconds: number;
readonly id: TaskId;
constructor(seconds: number, id: TaskId, msg: string = '') {
readonly taskId: TaskId;
constructor(seconds: number, taskId: TaskId, msg: string = '') {
super(msg);
this.id = id;
this.taskId = taskId;
this.seconds = seconds;
Object.setPrototypeOf(this, BuildingQueueFullError.prototype);
}

View File

@ -1,5 +1,5 @@
import { markPage, sleepShort, timestamp } from './utils';
import UpgradeBuildingTask from './Task/UpgradeBuildingTask';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import {
AbortTaskError,
BuildingQueueFullError,
@ -7,14 +7,14 @@ import {
} from './Errors';
import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue';
import ActionQueue from './Storage/ActionQueue';
import { Args, Command } from './Common';
import { Command } from './Common';
import TaskQueueRenderer from './TaskQueueRenderer';
import { ActionController, createAction } from './Action/ActionController';
import TaskController from './Task/TaskController';
import SendOnAdventureTask from './Task/SendOnAdventureTask';
import { createAction } from './Action/ActionController';
import { createTask } from './Task/TaskController';
import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
import { GameState } from './Storage/GameState';
export default class Scheduler {
export class Scheduler {
private readonly version: string;
private taskQueue: TaskQueue;
private actionQueue: ActionQueue;
@ -48,9 +48,9 @@ export default class Scheduler {
}
private scheduleHeroAdventure() {
if (!this.taskQueue.hasNamed(SendOnAdventureTask.NAME)) {
if (!this.taskQueue.hasNamed(SendOnAdventureTask.name)) {
this.taskQueue.push(
new Command(SendOnAdventureTask.NAME, {}),
new Command(SendOnAdventureTask.name, {}),
timestamp()
);
}
@ -62,7 +62,7 @@ export default class Scheduler {
const taskCommand = this.taskQueue.get(currentTs);
// текущего таска нет, очищаем очередь действий по таску
if (taskCommand === undefined) {
if (!taskCommand) {
this.log('NO ACTIVE TASK');
this.actionQueue.clear();
return;
@ -73,40 +73,70 @@ export default class Scheduler {
this.log('CURRENT TASK', taskCommand);
this.log('CURRENT ACTION', actionCommand);
try {
if (actionCommand) {
return await this.processActionCommand(actionCommand, taskCommand);
return await this.processActionCommand(
actionCommand,
taskCommand
);
}
if (taskCommand) {
return await this.processTaskCommand(taskCommand);
}
}
private async processTaskCommand(task: Task) {
const taskController = this.createTaskControllerByName(task.cmd.name);
this.log(
'PROCESS TASK',
taskController?.constructor.name,
task,
taskController
);
if (taskController) {
taskController.run(task);
} catch (e) {
this.handleError(e);
}
}
private async processActionCommand(cmd: Command, task: Task) {
const actionController = this.createActionControllerByName(cmd.name);
this.log(
'PROCESS ACTION',
actionController?.constructor.name,
actionController
);
const actionController = createAction(cmd.name, this.gameState, this);
this.log('PROCESS ACTION', cmd.name, actionController);
if (actionController) {
await this.runAction(actionController, cmd.args, task);
await actionController.run(cmd.args, task);
} else {
this.logError('ACTION NOT FOUND', cmd.name);
}
}
private async processTaskCommand(task: Task) {
const taskController = createTask(task.cmd.name, this);
this.log('PROCESS TASK', task.cmd.name, task, taskController);
if (taskController) {
await taskController.run(task);
} else {
this.logError('TASK NOT FOUND', task.cmd.name);
}
}
private handleError(err: Error) {
this.logWarn('ACTION ABORTED', err.message);
this.actionQueue.clear();
if (err instanceof AbortTaskError) {
this.logWarn('ABORT TASK', err.taskId);
this.completeTask(err.taskId);
return;
}
if (err instanceof TryLaterError) {
this.logWarn('TRY', err.taskId, 'AFTER', err.seconds);
this.taskQueue.postpone(err.taskId, timestamp() + err.seconds);
return;
}
if (err instanceof BuildingQueueFullError) {
this.logWarn('BUILDING QUEUE FULL, TRY ALL AFTER', err.seconds);
this.taskQueue.modify(
t => t.cmd.name === UpgradeBuildingTask.name,
t => t.withTime(timestamp() + err.seconds)
);
return;
}
throw err;
}
getTaskItems(): TaskList {
return this.taskQueue.seeItems();
}
@ -124,59 +154,15 @@ export default class Scheduler {
this.actionQueue.assign(actions);
}
private createTaskControllerByName(
taskName: string
): TaskController | undefined {
switch (taskName) {
case UpgradeBuildingTask.NAME:
return new UpgradeBuildingTask(this);
case SendOnAdventureTask.NAME:
return new SendOnAdventureTask(this);
}
this.logError('TASK NOT FOUND', taskName);
return undefined;
}
private createActionControllerByName(
actionName: string
): ActionController | undefined {
const action = createAction(actionName, this.gameState, this);
if (!action) {
this.logError('ACTION NOT FOUND', actionName);
return undefined;
}
return action;
}
private async runAction(action: ActionController, args: Args, task: Task) {
try {
await action.run(args, task);
} catch (e) {
console.warn('ACTION ABORTED', e.message);
this.actionQueue.clear();
if (e instanceof AbortTaskError) {
console.warn('ABORT TASK', e.id);
this.completeTask(e.id);
}
if (e instanceof TryLaterError) {
console.warn('TRY', task.id, 'AFTER', e.seconds);
this.taskQueue.postpone(task.id, timestamp() + e.seconds);
}
if (e instanceof BuildingQueueFullError) {
console.warn('BUILDING QUEUE FULL, TRY ALL AFTER', e.seconds);
this.taskQueue.modify(
t => t.cmd.name === UpgradeBuildingTask.NAME,
t => t.withTime(timestamp() + e.seconds)
);
}
}
}
private log(...args) {
console.log('SCHEDULER:', ...args);
}
private logWarn(...args) {
console.warn('SCHEDULER:', ...args);
}
private logError(...args) {
console.error(...args);
console.error('SCHEDULER:', ...args);
}
}

View File

@ -1,23 +1,15 @@
import Scheduler from '../Scheduler';
import { Args, Command } from '../Common';
import { Task } from '../Storage/TaskQueue';
import TaskController from './TaskController';
import { TaskController, registerTask } from './TaskController';
import { GoToPageAction } from '../Action/GoToPageAction';
import { GrabHeroAttributesAction } from '../Action/GrabHeroAttributesAction';
import { CompleteTaskAction } from '../Action/CompleteTaskAction';
import { SendOnAdventureAction } from '../Action/SendOnAdventureAction';
import { ClickButtonAction } from '../Action/ClickButtonAction';
export default class SendOnAdventureTask extends TaskController {
static NAME = 'send_on_adventure';
private scheduler: Scheduler;
constructor(scheduler: Scheduler) {
super();
this.scheduler = scheduler;
}
run(task: Task) {
@registerTask
export class SendOnAdventureTask extends TaskController {
async run(task: Task) {
const args: Args = { ...task.cmd.args, taskId: task.id };
this.scheduler.scheduleActions([
new Command(GoToPageAction.name, { ...args, path: 'hero.php' }),

View File

@ -1,5 +1,30 @@
import { Task } from '../Storage/TaskQueue';
import { Scheduler } from '../Scheduler';
export default abstract class TaskController {
abstract run(task: Task);
const taskMap: { [name: string]: Function | undefined } = {};
export function registerTask(constructor: Function) {
taskMap[constructor.name] = constructor;
}
export function createTask(
name: string,
scheduler: Scheduler
): TaskController | undefined {
const storedFunction = taskMap[name];
if (storedFunction === undefined) {
return undefined;
}
const constructor = (storedFunction as unknown) as typeof TaskController;
return new constructor(scheduler);
}
export class TaskController {
protected scheduler: Scheduler;
constructor(scheduler: Scheduler) {
this.scheduler = scheduler;
}
async run(task: Task) {}
}

View File

@ -1,23 +1,14 @@
import Scheduler from '../Scheduler';
import { UpgradeBuildingAction } from '../Action/UpgradeBuildingAction';
import { Args, Command } from '../Common';
import { Task } from '../Storage/TaskQueue';
import TaskController from './TaskController';
import { TaskController, registerTask } from './TaskController';
import { GoToPageAction } from '../Action/GoToPageAction';
import { CheckBuildingRemainingTimeAction } from '../Action/CheckBuildingRemainingTimeAction';
import { CompleteTaskAction } from '../Action/CompleteTaskAction';
export default class UpgradeBuildingTask extends TaskController {
static NAME = 'upgrade_building';
private scheduler: Scheduler;
constructor(scheduler: Scheduler) {
super();
this.scheduler = scheduler;
}
run(task: Task) {
console.log('RUN', UpgradeBuildingTask.NAME, 'with', task);
@registerTask
export class UpgradeBuildingTask extends TaskController {
async run(task: Task) {
const args: Args = { ...task.cmd.args, taskId: task.id };
this.scheduler.scheduleActions([
new Command(GoToPageAction.name, { ...args, path: '/dorf1.php' }),