Rebuild village production queue system
This commit is contained in:
parent
f3a1e67906
commit
8bea617f5b
@ -5,6 +5,6 @@ import { Task } from '../Queue/TaskProvider';
|
||||
@registerAction
|
||||
export class CompleteTaskAction extends ActionController {
|
||||
async run(args: Args, task: Task): Promise<any> {
|
||||
this.scheduler.removeTask(task.id);
|
||||
this.scheduler.completeTask(task.id);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import { StatisticsStorage } from './Storage/StatisticsStorage';
|
||||
import { VillageRepository } from './VillageRepository';
|
||||
import { VillageStateRepository } from './VillageState';
|
||||
import { LogStorage } from './Storage/LogStorage';
|
||||
import { VillageControllerFactory } from './VillageControllerFactory';
|
||||
import { GrabberManager } from './Grabber/GrabberManager';
|
||||
|
||||
export class Container {
|
||||
private readonly version: string;
|
||||
@ -40,16 +42,35 @@ export class Container {
|
||||
return this._statistics;
|
||||
}
|
||||
|
||||
private _villageControllerFactory: VillageControllerFactory | undefined;
|
||||
|
||||
get villageControllerFactory(): VillageControllerFactory {
|
||||
this._villageControllerFactory =
|
||||
this._villageControllerFactory ||
|
||||
(() => {
|
||||
return new VillageControllerFactory(this.villageRepository);
|
||||
})();
|
||||
return this._villageControllerFactory;
|
||||
}
|
||||
|
||||
private _scheduler: Scheduler | undefined;
|
||||
|
||||
get scheduler(): Scheduler {
|
||||
this._scheduler =
|
||||
this._scheduler ||
|
||||
(() => {
|
||||
const taskProvider = DataStorageTaskProvider.create();
|
||||
const taskQueue = new TaskQueue(taskProvider, new ConsoleLogger(TaskQueue.name));
|
||||
const taskQueue = new TaskQueue(
|
||||
DataStorageTaskProvider.create('tasks:v1'),
|
||||
new ConsoleLogger(TaskQueue.name)
|
||||
);
|
||||
const actionQueue = new ActionQueue();
|
||||
return new Scheduler(taskQueue, actionQueue, this.villageRepository, new ConsoleLogger(Scheduler.name));
|
||||
return new Scheduler(
|
||||
taskQueue,
|
||||
actionQueue,
|
||||
this.villageRepository,
|
||||
this.villageControllerFactory,
|
||||
new ConsoleLogger(Scheduler.name)
|
||||
);
|
||||
})();
|
||||
return this._scheduler;
|
||||
}
|
||||
@ -60,11 +81,22 @@ export class Container {
|
||||
this._villageStateRepository =
|
||||
this._villageStateRepository ||
|
||||
(() => {
|
||||
return new VillageStateRepository(this.villageRepository, this.scheduler);
|
||||
return new VillageStateRepository(this.villageRepository, this.villageControllerFactory);
|
||||
})();
|
||||
return this._villageStateRepository;
|
||||
}
|
||||
|
||||
private _grabberManager: GrabberManager | undefined;
|
||||
|
||||
get grabberManager(): GrabberManager {
|
||||
this._grabberManager =
|
||||
this._grabberManager ||
|
||||
(() => {
|
||||
return new GrabberManager(this.villageControllerFactory);
|
||||
})();
|
||||
return this._grabberManager;
|
||||
}
|
||||
|
||||
private _executor: Executor | undefined;
|
||||
|
||||
get executor(): Executor {
|
||||
@ -75,7 +107,15 @@ export class Container {
|
||||
new ConsoleLogger(Executor.name),
|
||||
new StorageLogger(new LogStorage(), LogLevel.warning),
|
||||
]);
|
||||
return new Executor(this.version, this.scheduler, this.villageStateRepository, this.statistics, logger);
|
||||
return new Executor(
|
||||
this.version,
|
||||
this.scheduler,
|
||||
this.villageStateRepository,
|
||||
this.villageControllerFactory,
|
||||
this.grabberManager,
|
||||
this.statistics,
|
||||
logger
|
||||
);
|
||||
})();
|
||||
return this._executor;
|
||||
}
|
||||
@ -86,7 +126,12 @@ export class Container {
|
||||
this._controlPanel =
|
||||
this._controlPanel ||
|
||||
(() => {
|
||||
return new ControlPanel(this.version, this.scheduler, this.villageStateRepository);
|
||||
return new ControlPanel(
|
||||
this.version,
|
||||
this.scheduler,
|
||||
this.villageStateRepository,
|
||||
this.villageControllerFactory
|
||||
);
|
||||
})();
|
||||
return this._controlPanel;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { VillageState, VillageStateRepository } from './VillageState';
|
||||
import { Task } from './Queue/TaskProvider';
|
||||
import { Action } from './Queue/ActionQueue';
|
||||
import { createStore } from './DashboardView/Store';
|
||||
import { VillageControllerFactory } from './VillageControllerFactory';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@ -53,11 +54,18 @@ export class ControlPanel {
|
||||
private readonly scheduler: Scheduler;
|
||||
private readonly villageStateRepository: VillageStateRepository;
|
||||
private readonly logger: Logger;
|
||||
private villageControllerFactory: VillageControllerFactory;
|
||||
|
||||
constructor(version: string, scheduler: Scheduler, villageStateRepository: VillageStateRepository) {
|
||||
constructor(
|
||||
version: string,
|
||||
scheduler: Scheduler,
|
||||
villageStateRepository: VillageStateRepository,
|
||||
villageControllerFactory: VillageControllerFactory
|
||||
) {
|
||||
this.version = version;
|
||||
this.scheduler = scheduler;
|
||||
this.villageStateRepository = villageStateRepository;
|
||||
this.villageControllerFactory = villageControllerFactory;
|
||||
this.logger = new ConsoleLogger(this.constructor.name);
|
||||
}
|
||||
|
||||
@ -128,9 +136,10 @@ export class ControlPanel {
|
||||
DataStorage.onChange(() => state.refresh());
|
||||
|
||||
const getBuildingsInQueue = () =>
|
||||
this.scheduler
|
||||
.getTaskItems()
|
||||
.filter(t => t.name === UpgradeBuildingTask.name && t.args.villageId === villageId)
|
||||
this.villageControllerFactory
|
||||
.create(villageId)
|
||||
.getTasks()
|
||||
.filter(t => t.name === UpgradeBuildingTask.name)
|
||||
.map(t => t.args.buildId || 0);
|
||||
|
||||
if (p.pathname === '/dorf1.php') {
|
||||
@ -151,7 +160,11 @@ export class ControlPanel {
|
||||
}
|
||||
|
||||
if (isBuildingPage()) {
|
||||
const buildPage = new BuildingPageController(this.scheduler, getBuildingPageAttributes());
|
||||
const buildPage = new BuildingPageController(
|
||||
this.scheduler,
|
||||
getBuildingPageAttributes(),
|
||||
this.villageControllerFactory.create(villageId)
|
||||
);
|
||||
buildPage.run();
|
||||
}
|
||||
|
||||
|
10
src/Core/Contract.ts
Normal file
10
src/Core/Contract.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export enum ContractType {
|
||||
UpgradeBuilding,
|
||||
ImproveTrooper,
|
||||
}
|
||||
|
||||
export interface ContractAttributes {
|
||||
type: ContractType;
|
||||
buildId?: number;
|
||||
unitId?: number;
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { getProductionQueue } from '../Task/TaskMap';
|
||||
|
||||
export enum ProductionQueue {
|
||||
Building = 'building',
|
||||
TrainUnit = 'train_unit',
|
||||
@ -27,3 +29,18 @@ export function translateProductionQueue(queue: ProductionQueue): string {
|
||||
return 'Празднование';
|
||||
}
|
||||
}
|
||||
|
||||
export interface TaskNamePredicate {
|
||||
(name: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* List on non intersected task queue predicates.
|
||||
*/
|
||||
export const TASK_TYPE_PREDICATES: Array<TaskNamePredicate> = ProductionQueueTypes.map(queue => {
|
||||
return (taskName: string) => getProductionQueue(taskName) === queue;
|
||||
});
|
||||
|
||||
export function isProductionTask(taskName: string): boolean {
|
||||
return TASK_TYPE_PREDICATES.reduce((memo, predicate) => memo || predicate(taskName), false);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { Action } from './Queue/ActionQueue';
|
||||
import { Task } from './Queue/TaskProvider';
|
||||
import { createTaskHandler } from './Task/TaskMap';
|
||||
import { VillageStateRepository } from './VillageState';
|
||||
import { VillageControllerFactory } from './VillageControllerFactory';
|
||||
|
||||
export interface ExecutionSettings {
|
||||
pauseTs: number;
|
||||
@ -20,7 +21,8 @@ export class Executor {
|
||||
private readonly version: string;
|
||||
private readonly scheduler: Scheduler;
|
||||
private readonly villageStateRepository: VillageStateRepository;
|
||||
private grabbers: GrabberManager;
|
||||
private villageControllerFactory: VillageControllerFactory;
|
||||
private grabberManager: GrabberManager;
|
||||
private statistics: Statistics;
|
||||
private executionState: ExecutionStorage;
|
||||
private logger: Logger;
|
||||
@ -29,13 +31,16 @@ export class Executor {
|
||||
version: string,
|
||||
scheduler: Scheduler,
|
||||
villageStateRepository: VillageStateRepository,
|
||||
villageControllerFactory: VillageControllerFactory,
|
||||
grabberManager: GrabberManager,
|
||||
statistics: Statistics,
|
||||
logger: Logger
|
||||
) {
|
||||
this.version = version;
|
||||
this.scheduler = scheduler;
|
||||
this.villageStateRepository = villageStateRepository;
|
||||
this.grabbers = new GrabberManager(scheduler);
|
||||
this.villageControllerFactory = villageControllerFactory;
|
||||
this.grabberManager = grabberManager;
|
||||
this.statistics = statistics;
|
||||
this.executionState = new ExecutionStorage();
|
||||
this.logger = logger;
|
||||
@ -49,6 +54,7 @@ export class Executor {
|
||||
|
||||
const sleep = createExecutionLoopSleeper();
|
||||
|
||||
// noinspection InfiniteLoopJS
|
||||
while (true) {
|
||||
await sleep();
|
||||
if (!this.isPaused()) {
|
||||
@ -76,25 +82,22 @@ export class Executor {
|
||||
}
|
||||
|
||||
private async doTaskProcessingStep() {
|
||||
this.runGrabbers();
|
||||
|
||||
const currentTs = timestamp();
|
||||
const task = this.scheduler.nextTask(currentTs);
|
||||
const { task, action } = this.scheduler.nextTask(currentTs);
|
||||
|
||||
// текущего таска нет, очищаем очередь действий по таску
|
||||
if (!task) {
|
||||
this.logger.info('NO ACTIVE TASK');
|
||||
this.scheduler.clearActions();
|
||||
return;
|
||||
}
|
||||
|
||||
const actionCommand = this.scheduler.nextAction();
|
||||
|
||||
this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', actionCommand);
|
||||
|
||||
this.runGrabbers();
|
||||
this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', action);
|
||||
|
||||
try {
|
||||
if (actionCommand) {
|
||||
return await this.processActionCommand(actionCommand, task);
|
||||
if (task && action) {
|
||||
return await this.processActionCommand(action, task);
|
||||
}
|
||||
|
||||
if (task) {
|
||||
@ -105,27 +108,24 @@ export class Executor {
|
||||
}
|
||||
}
|
||||
|
||||
private async processActionCommand(cmd: Action, task: Task) {
|
||||
const actionHandler = createActionHandler(cmd.name, this.scheduler, this.villageStateRepository);
|
||||
this.logger.info('PROCESS ACTION', cmd.name, actionHandler);
|
||||
if (cmd.args.taskId !== task.id) {
|
||||
throw new ActionError(`Action task id ${cmd.args.taskId} not equal current task id ${task.id}`);
|
||||
}
|
||||
private async processActionCommand(action: Action, task: Task) {
|
||||
const actionHandler = createActionHandler(action.name, this.scheduler, this.villageStateRepository);
|
||||
this.logger.info('Process action', action.name, actionHandler);
|
||||
if (actionHandler) {
|
||||
this.statistics.incrementAction(timestamp());
|
||||
await actionHandler.run(cmd.args, task);
|
||||
await actionHandler.run(action.args, task);
|
||||
} else {
|
||||
this.logger.warn('ACTION NOT FOUND', cmd.name);
|
||||
this.logger.error('Action not found', action.name);
|
||||
}
|
||||
}
|
||||
|
||||
private async processTaskCommand(task: Task) {
|
||||
const taskHandler = createTaskHandler(task.name, this.scheduler);
|
||||
this.logger.info('PROCESS TASK', task.name, task, taskHandler);
|
||||
this.logger.info('Process task', task.name, task, taskHandler);
|
||||
if (taskHandler) {
|
||||
await taskHandler.run(task);
|
||||
} else {
|
||||
this.logger.warn('TASK NOT FOUND', task.name);
|
||||
this.logger.error('Task handler not created', task.name);
|
||||
this.scheduler.removeTask(task.id);
|
||||
}
|
||||
}
|
||||
@ -174,7 +174,7 @@ export class Executor {
|
||||
private runGrabbers() {
|
||||
try {
|
||||
this.logger.info('Rug grabbers');
|
||||
this.grabbers.grab();
|
||||
this.grabberManager.grab();
|
||||
} catch (e) {
|
||||
this.logger.warn('Grabbers fails with', e.message);
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import { getBuildingPageAttributes, isBuildingPage } from '../Page/PageDetectors';
|
||||
import { grabContractResources, hasContractResources } from '../Page/BuildingPage/BuildingPage';
|
||||
import { ContractType } from '../Scheduler';
|
||||
import { ContractType } from '../Core/Contract';
|
||||
|
||||
export class BuildingContractGrabber extends Grabber {
|
||||
grab(): void {
|
||||
@ -19,12 +18,10 @@ export class BuildingContractGrabber extends Grabber {
|
||||
return;
|
||||
}
|
||||
|
||||
const villageId = grabActiveVillageId();
|
||||
const contract = grabContractResources();
|
||||
|
||||
this.scheduler.updateResources(contract, {
|
||||
this.controller.updateResources(contract, {
|
||||
type: ContractType.UpgradeBuilding,
|
||||
villageId,
|
||||
buildId: building.buildId,
|
||||
});
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import { getBuildingPageAttributes, isForgePage } from '../Page/PageDetectors';
|
||||
import { ContractType } from '../Scheduler';
|
||||
import { ContractType } from '../Core/Contract';
|
||||
import { grabImprovementContracts, grabRemainingSeconds } from '../Page/BuildingPage/ForgePage';
|
||||
import { VillageStorage } from '../Storage/VillageStorage';
|
||||
import { ProductionQueue } from '../Core/ProductionQueue';
|
||||
import { timestamp } from '../utils';
|
||||
|
||||
@ -13,29 +11,26 @@ export class ForgePageGrabber extends Grabber {
|
||||
return;
|
||||
}
|
||||
|
||||
const villageId = grabActiveVillageId();
|
||||
|
||||
this.grabContracts(villageId);
|
||||
this.grabTimer(villageId);
|
||||
this.grabContracts();
|
||||
this.grabTimer();
|
||||
}
|
||||
|
||||
private grabContracts(villageId: number): void {
|
||||
private grabContracts(): void {
|
||||
const { buildId } = getBuildingPageAttributes();
|
||||
const contracts = grabImprovementContracts();
|
||||
|
||||
for (let { resources, unitId } of contracts) {
|
||||
this.scheduler.updateResources(resources, {
|
||||
this.controller.updateResources(resources, {
|
||||
type: ContractType.ImproveTrooper,
|
||||
villageId,
|
||||
buildId,
|
||||
unitId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private grabTimer(villageId: number): void {
|
||||
const state = new VillageStorage(villageId);
|
||||
private grabTimer(): void {
|
||||
const storage = this.controller.getStorage();
|
||||
const seconds = grabRemainingSeconds();
|
||||
state.storeQueueTaskEnding(ProductionQueue.UpgradeUnit, seconds ? seconds + timestamp() : 0);
|
||||
storage.storeQueueTaskEnding(ProductionQueue.UpgradeUnit, seconds ? seconds + timestamp() : 0);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Scheduler } from '../Scheduler';
|
||||
import { VillageController } from '../VillageController';
|
||||
|
||||
export abstract class Grabber {
|
||||
protected scheduler: Scheduler;
|
||||
protected controller: VillageController;
|
||||
|
||||
constructor(scheduler: Scheduler) {
|
||||
this.scheduler = scheduler;
|
||||
constructor(controller: VillageController) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
abstract grab(): void;
|
||||
|
@ -3,28 +3,35 @@ import { VillageResourceGrabber } from './VillageResourceGrabber';
|
||||
import { VillageOverviewPageGrabber } from './VillageOverviewPageGrabber';
|
||||
import { HeroPageGrabber } from './HeroPageGrabber';
|
||||
import { MarketPageGrabber } from './MarketPageGrabber';
|
||||
import { Scheduler } from '../Scheduler';
|
||||
import { BuildingContractGrabber } from './BuildingContractGrabber';
|
||||
import { ForgePageGrabber } from './ForgePageGrabber';
|
||||
import { GuildHallPageGrabber } from './GuildHallPageGrabber';
|
||||
import { VillageControllerFactory } from '../VillageControllerFactory';
|
||||
|
||||
export class GrabberManager {
|
||||
private readonly grabbers: Array<Grabber> = [];
|
||||
private factory: VillageControllerFactory;
|
||||
|
||||
constructor(scheduler: Scheduler) {
|
||||
this.grabbers = [];
|
||||
this.grabbers.push(new VillageResourceGrabber(scheduler));
|
||||
this.grabbers.push(new VillageOverviewPageGrabber(scheduler));
|
||||
this.grabbers.push(new HeroPageGrabber(scheduler));
|
||||
this.grabbers.push(new MarketPageGrabber(scheduler));
|
||||
this.grabbers.push(new BuildingContractGrabber(scheduler));
|
||||
this.grabbers.push(new ForgePageGrabber(scheduler));
|
||||
this.grabbers.push(new GuildHallPageGrabber(scheduler));
|
||||
constructor(factory: VillageControllerFactory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
grab() {
|
||||
for (let grabber of this.grabbers) {
|
||||
const grabbers = this.createGrabbers();
|
||||
for (let grabber of grabbers) {
|
||||
grabber.grab();
|
||||
}
|
||||
}
|
||||
|
||||
private createGrabbers(): Array<Grabber> {
|
||||
const controller = this.factory.getActive();
|
||||
const grabbers: Array<Grabber> = [];
|
||||
grabbers.push(new VillageResourceGrabber(controller));
|
||||
grabbers.push(new VillageOverviewPageGrabber(controller));
|
||||
grabbers.push(new HeroPageGrabber(controller));
|
||||
grabbers.push(new MarketPageGrabber(controller));
|
||||
grabbers.push(new BuildingContractGrabber(controller));
|
||||
grabbers.push(new ForgePageGrabber(controller));
|
||||
grabbers.push(new GuildHallPageGrabber(controller));
|
||||
return grabbers;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import { VillageStorage } from '../Storage/VillageStorage';
|
||||
import { isGuildHallPage } from '../Page/PageDetectors';
|
||||
import { grabRemainingSeconds } from '../Page/BuildingPage/GuildHallPage';
|
||||
import { ProductionQueue } from '../Core/ProductionQueue';
|
||||
@ -12,9 +10,8 @@ export class GuildHallPageGrabber extends Grabber {
|
||||
return;
|
||||
}
|
||||
|
||||
const villageId = grabActiveVillageId();
|
||||
const state = new VillageStorage(villageId);
|
||||
const seconds = grabRemainingSeconds();
|
||||
state.storeQueueTaskEnding(ProductionQueue.Celebration, seconds ? seconds + timestamp() : 0);
|
||||
const storage = this.controller.getStorage();
|
||||
storage.storeQueueTaskEnding(ProductionQueue.Celebration, seconds ? seconds + timestamp() : 0);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import { VillageStorage } from '../Storage/VillageStorage';
|
||||
import { isMarketSendResourcesPage } from '../Page/PageDetectors';
|
||||
import { grabIncomingMerchants } from '../Page/BuildingPage/MarketPage';
|
||||
|
||||
@ -10,8 +8,7 @@ export class MarketPageGrabber extends Grabber {
|
||||
return;
|
||||
}
|
||||
|
||||
const villageId = grabActiveVillageId();
|
||||
const state = new VillageStorage(villageId);
|
||||
state.storeIncomingMerchants(grabIncomingMerchants());
|
||||
const storage = this.controller.getStorage();
|
||||
storage.storeIncomingMerchants(grabIncomingMerchants());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { grabActiveVillageId, grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock';
|
||||
import { VillageStorage } from '../Storage/VillageStorage';
|
||||
import { grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock';
|
||||
import { parseLocation, timestamp } from '../utils';
|
||||
import { GrabError } from '../Errors';
|
||||
import { BuildingQueueInfo } from '../Game';
|
||||
@ -13,8 +12,7 @@ export class VillageOverviewPageGrabber extends Grabber {
|
||||
return;
|
||||
}
|
||||
|
||||
const villageId = grabActiveVillageId();
|
||||
const storage = new VillageStorage(villageId);
|
||||
const storage = this.controller.getStorage();
|
||||
storage.storeResourcesPerformance(grabResourcesPerformance());
|
||||
storage.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault());
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { grabActiveVillageId } from '../Page/VillageBlock';
|
||||
import { grabVillageResources, grabVillageResourceStorage } from '../Page/ResourcesBlock';
|
||||
import { VillageStorage } from '../Storage/VillageStorage';
|
||||
|
||||
export class VillageResourceGrabber extends Grabber {
|
||||
grab(): void {
|
||||
const villageId = grabActiveVillageId();
|
||||
const state = new VillageStorage(villageId);
|
||||
state.storeResources(grabVillageResources());
|
||||
state.storeResourceStorage(grabVillageResourceStorage());
|
||||
const storage = this.controller.getStorage();
|
||||
storage.storeResources(grabVillageResources());
|
||||
storage.storeResourceStorage(grabVillageResourceStorage());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { notify, split } from '../utils';
|
||||
import { notify } from '../utils';
|
||||
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
|
||||
import { Scheduler } from '../Scheduler';
|
||||
import { TrainTroopTask } from '../Task/TrainTroopTask';
|
||||
@ -17,15 +17,18 @@ import { createResearchButtons } from './BuildingPage/ForgePage';
|
||||
import { ForgeImprovementTask } from '../Task/ForgeImprovementTask';
|
||||
import { createCelebrationButtons } from './BuildingPage/GuildHallPage';
|
||||
import { CelebrationTask } from '../Task/CelebrationTask';
|
||||
import { VillageController } from '../VillageController';
|
||||
|
||||
export class BuildingPageController {
|
||||
private scheduler: Scheduler;
|
||||
private readonly attributes: BuildingPageAttributes;
|
||||
private villageController: VillageController;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(scheduler: Scheduler, attributes: BuildingPageAttributes) {
|
||||
constructor(scheduler: Scheduler, attributes: BuildingPageAttributes, villageController: VillageController) {
|
||||
this.scheduler = scheduler;
|
||||
this.attributes = attributes;
|
||||
this.villageController = villageController;
|
||||
this.logger = new ConsoleLogger(this.constructor.name);
|
||||
}
|
||||
|
||||
@ -56,7 +59,7 @@ export class BuildingPageController {
|
||||
}
|
||||
|
||||
if (isMarketSendResourcesPage()) {
|
||||
createSendResourcesButton((res, crd) => this.onSendResources(res, crd));
|
||||
createSendResourcesButton((res, crd) => this.onSendResources(crd));
|
||||
}
|
||||
|
||||
if (isForgePage()) {
|
||||
@ -64,7 +67,7 @@ export class BuildingPageController {
|
||||
}
|
||||
|
||||
if (isGuildHallPage()) {
|
||||
createCelebrationButtons((res, idx) => this.onCelebration(res, idx));
|
||||
createCelebrationButtons(res => this.onCelebration(res));
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,14 +75,20 @@ export class BuildingPageController {
|
||||
const buildId = this.attributes.buildId;
|
||||
const categoryId = this.attributes.categoryId;
|
||||
const villageId = grabActiveVillageId();
|
||||
this.scheduler.scheduleTask(BuildBuildingTask.name, { villageId, buildId, categoryId, buildTypeId, resources });
|
||||
this.villageController.addTask(BuildBuildingTask.name, {
|
||||
villageId,
|
||||
buildId,
|
||||
categoryId,
|
||||
buildTypeId,
|
||||
resources,
|
||||
});
|
||||
notify(`Building ${buildId} scheduled`);
|
||||
}
|
||||
|
||||
private onScheduleUpgradeBuilding(resources: Resources) {
|
||||
const buildId = this.attributes.buildId;
|
||||
const villageId = grabActiveVillageId();
|
||||
this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId, resources });
|
||||
this.villageController.addTask(UpgradeBuildingTask.name, { villageId, buildId, resources });
|
||||
notify(`Upgrading ${buildId} scheduled`);
|
||||
}
|
||||
|
||||
@ -94,11 +103,11 @@ export class BuildingPageController {
|
||||
troopResources: resources,
|
||||
resources: resources.scale(trainCount),
|
||||
};
|
||||
this.scheduler.scheduleTask(TrainTroopTask.name, args);
|
||||
this.villageController.addTask(TrainTroopTask.name, args);
|
||||
notify(`Training ${trainCount} troopers scheduled`);
|
||||
}
|
||||
|
||||
private onSendResources(resources: Resources, coordinates: Coordinates) {
|
||||
private onSendResources(coordinates: Coordinates) {
|
||||
const villageId = grabActiveVillageId();
|
||||
const targetVillage = grabVillageList().find(v => v.crd.eq(coordinates));
|
||||
this.scheduler.scheduleTask(SendResourcesTask.name, {
|
||||
@ -114,7 +123,7 @@ export class BuildingPageController {
|
||||
|
||||
private onResearch(resources: Resources, unitId: number) {
|
||||
const villageId = grabActiveVillageId();
|
||||
this.scheduler.scheduleTask(ForgeImprovementTask.name, {
|
||||
this.villageController.addTask(ForgeImprovementTask.name, {
|
||||
villageId,
|
||||
buildTypeId: this.attributes.buildTypeId,
|
||||
buildId: this.attributes.buildId,
|
||||
@ -124,9 +133,9 @@ export class BuildingPageController {
|
||||
notify(`Researching ${unitId} scheduled`);
|
||||
}
|
||||
|
||||
private onCelebration(resources: Resources, idx: number) {
|
||||
private onCelebration(resources: Resources) {
|
||||
const villageId = grabActiveVillageId();
|
||||
this.scheduler.scheduleTask(CelebrationTask.name, {
|
||||
this.villageController.addTask(CelebrationTask.name, {
|
||||
villageId,
|
||||
buildTypeId: this.attributes.buildTypeId,
|
||||
buildId: this.attributes.buildId,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { DataStorage } from '../DataStorage';
|
||||
import { Task, TaskList, TaskProvider, uniqTaskId } from './TaskProvider';
|
||||
|
||||
const NAMESPACE = 'tasks:v1';
|
||||
const QUEUE_NAME = 'queue';
|
||||
|
||||
export class DataStorageTaskProvider implements TaskProvider {
|
||||
@ -11,8 +10,8 @@ export class DataStorageTaskProvider implements TaskProvider {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
static create() {
|
||||
return new DataStorageTaskProvider(new DataStorage(NAMESPACE));
|
||||
static create(namespace: string) {
|
||||
return new DataStorageTaskProvider(new DataStorage(namespace));
|
||||
}
|
||||
|
||||
getTasks(): TaskList {
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Args } from './Args';
|
||||
import { uniqId } from '../utils';
|
||||
import { ResourcesInterface } from '../Core/Resources';
|
||||
|
||||
export type TaskId = string;
|
||||
|
||||
let idSequence = 1;
|
||||
let lastTimestamp: number | null = null;
|
||||
let lastTimestamp: number | undefined = undefined;
|
||||
|
||||
export function uniqTaskId(): TaskId {
|
||||
const ts = Math.floor(Date.now() / 1000);
|
||||
@ -38,3 +39,15 @@ export interface TaskProvider {
|
||||
getTasks(): TaskList;
|
||||
setTasks(tasks: TaskList): void;
|
||||
}
|
||||
|
||||
export interface TaskTransformer {
|
||||
(task: Task): Task;
|
||||
}
|
||||
|
||||
export function withTime(ts: number): TaskTransformer {
|
||||
return (task: Task) => new Task(task.id, ts, task.name, task.args);
|
||||
}
|
||||
|
||||
export function withResources(resources: ResourcesInterface): TaskTransformer {
|
||||
return (task: Task) => new Task(task.id, task.ts, task.name, { ...task.args, resources });
|
||||
}
|
||||
|
@ -11,14 +11,10 @@ export class TaskQueue {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
push(name: string, args: Args, ts: number): Task {
|
||||
const id = uniqTaskId();
|
||||
const task = new Task(id, ts, name, args);
|
||||
this.logger.info('PUSH TASK', id, ts, name, args);
|
||||
add(task: Task) {
|
||||
let items = this.getItems();
|
||||
items.push(task);
|
||||
this.flushItems(items);
|
||||
return task;
|
||||
}
|
||||
|
||||
get(ts: number): Task | undefined {
|
||||
@ -29,6 +25,11 @@ export class TaskQueue {
|
||||
return readyItems[0];
|
||||
}
|
||||
|
||||
findById(taskId: TaskId): Task | undefined {
|
||||
const [matched, _] = this.split(t => t.id === taskId);
|
||||
return matched.shift();
|
||||
}
|
||||
|
||||
has(predicate: (t: Task) => boolean): boolean {
|
||||
const [matched, _] = this.split(predicate);
|
||||
return matched.length > 0;
|
||||
@ -53,11 +54,6 @@ export class TaskQueue {
|
||||
return this.getItems();
|
||||
}
|
||||
|
||||
private shiftTask(id: TaskId): [Task | undefined, TaskList] {
|
||||
const [a, b] = this.split(t => t.id === id);
|
||||
return [a.shift(), b];
|
||||
}
|
||||
|
||||
private split(predicate: (t: Task) => boolean): [TaskList, TaskList] {
|
||||
const matched: TaskList = [];
|
||||
const other: TaskList = [];
|
||||
|
263
src/Scheduler.ts
263
src/Scheduler.ts
@ -1,5 +1,4 @@
|
||||
import { timestamp } from './utils';
|
||||
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
|
||||
import { TaskQueue } from './Queue/TaskQueue';
|
||||
import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
|
||||
import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask';
|
||||
@ -7,49 +6,49 @@ import { Logger } from './Logger';
|
||||
import { GrabVillageState } from './Task/GrabVillageState';
|
||||
import { Action, ActionQueue, ImmutableActionList } from './Queue/ActionQueue';
|
||||
import { UpdateResourceContracts } from './Task/UpdateResourceContracts';
|
||||
import { Resources, ResourcesInterface } from './Core/Resources';
|
||||
import { SendResourcesTask } from './Task/SendResourcesTask';
|
||||
import { Args } from './Queue/Args';
|
||||
import { ImmutableTaskList, Task, TaskId } from './Queue/TaskProvider';
|
||||
import { ForgeImprovementTask } from './Task/ForgeImprovementTask';
|
||||
import { getProductionQueue } from './Task/TaskMap';
|
||||
import { ImmutableTaskList, Task, TaskId, uniqTaskId, withTime } from './Queue/TaskProvider';
|
||||
import { MARKET_ID } from './Core/Buildings';
|
||||
import { VillageRepositoryInterface } from './VillageRepository';
|
||||
import { ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue';
|
||||
import { isProductionTask } from './Core/ProductionQueue';
|
||||
import { VillageControllerFactory } from './VillageControllerFactory';
|
||||
import { RunVillageProductionTask } from './Task/RunVillageProductionTask';
|
||||
|
||||
export enum ContractType {
|
||||
UpgradeBuilding,
|
||||
ImproveTrooper,
|
||||
}
|
||||
|
||||
interface ContractAttributes {
|
||||
type: ContractType;
|
||||
villageId?: number;
|
||||
buildId?: number;
|
||||
unitId?: number;
|
||||
export interface NextExecution {
|
||||
task?: Task;
|
||||
action?: Action;
|
||||
}
|
||||
|
||||
export class Scheduler {
|
||||
private taskQueue: TaskQueue;
|
||||
private actionQueue: ActionQueue;
|
||||
private villageRepository: VillageRepositoryInterface;
|
||||
private villageControllerFactory: VillageControllerFactory;
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
taskQueue: TaskQueue,
|
||||
actionQueue: ActionQueue,
|
||||
villageRepository: VillageRepositoryInterface,
|
||||
villageControllerFactory: VillageControllerFactory,
|
||||
logger: Logger
|
||||
) {
|
||||
this.taskQueue = taskQueue;
|
||||
this.actionQueue = actionQueue;
|
||||
this.villageRepository = villageRepository;
|
||||
this.villageControllerFactory = villageControllerFactory;
|
||||
this.logger = logger;
|
||||
|
||||
// this.taskQueue.push(GrabVillageState.name, {}, timestamp());
|
||||
// this.taskQueue.push(UpdateResourceContracts.name, {}, timestamp());
|
||||
// this.taskQueue.push(BalanceHeroResourcesTask.name, {}, timestamp());
|
||||
|
||||
const villages = this.villageRepository.all();
|
||||
for (let village of villages) {
|
||||
this.createUniqTaskTimer(5 * 60, RunVillageProductionTask.name, { villageId: village.id });
|
||||
}
|
||||
|
||||
this.createUniqTaskTimer(5 * 60, GrabVillageState.name);
|
||||
this.createUniqTaskTimer(10 * 60, BalanceHeroResourcesTask.name);
|
||||
this.createUniqTaskTimer(20 * 60, UpdateResourceContracts.name);
|
||||
@ -69,25 +68,67 @@ export class Scheduler {
|
||||
return this.actionQueue.seeItems();
|
||||
}
|
||||
|
||||
nextTask(ts: number) {
|
||||
return this.taskQueue.get(ts);
|
||||
nextTask(ts: number): NextExecution {
|
||||
const task = this.taskQueue.get(ts);
|
||||
|
||||
// Task not found - next task not ready or queue is empty
|
||||
if (!task) {
|
||||
this.clearActions();
|
||||
return {};
|
||||
}
|
||||
|
||||
nextAction() {
|
||||
return this.actionQueue.pop();
|
||||
const action = this.actionQueue.pop();
|
||||
|
||||
// Action not found - task is new
|
||||
if (!action) {
|
||||
return { task: this.replaceTask(task) };
|
||||
}
|
||||
|
||||
// Task in action not equals current task it - rerun task
|
||||
if (action.args.taskId !== task.id) {
|
||||
this.clearActions();
|
||||
return { task: this.replaceTask(task) };
|
||||
}
|
||||
|
||||
return { task, action };
|
||||
}
|
||||
|
||||
private replaceTask(task: Task): Task | undefined {
|
||||
if (task.name === RunVillageProductionTask.name && task.args.villageId) {
|
||||
const villageId = task.args.villageId;
|
||||
const controller = this.villageControllerFactory.create(villageId);
|
||||
const villageTask = controller.getReadyProductionTask();
|
||||
if (villageTask) {
|
||||
this.removeTask(task.id);
|
||||
const newTask = new Task(villageTask.id, 0, villageTask.name, {
|
||||
...villageTask.args,
|
||||
villageId: controller.villageId,
|
||||
});
|
||||
this.taskQueue.add(newTask);
|
||||
return newTask;
|
||||
}
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
scheduleTask(name: string, args: Args, ts?: number | undefined): void {
|
||||
this.logger.info('PUSH TASK', name, args, ts);
|
||||
let insertedTs = calculateInsertTime(this.taskQueue.seeItems(), name, args, ts);
|
||||
this.taskQueue.push(name, args, insertedTs);
|
||||
if (args.villageId) {
|
||||
this.reorderVillageTasks(args.villageId);
|
||||
if (isProductionTask(name) && args.villageId) {
|
||||
const controller = this.villageControllerFactory.create(args.villageId);
|
||||
controller.addTask(name, args);
|
||||
} else {
|
||||
this.logger.info('Schedule task', name, args, ts);
|
||||
this.taskQueue.add(new Task(uniqTaskId(), ts || timestamp(), name, args));
|
||||
}
|
||||
}
|
||||
|
||||
scheduleUniqTask(name: string, args: Args, ts?: number | undefined): void {
|
||||
let alreadyHasTask = this.taskQueue.has(t => t.name === name);
|
||||
let alreadyHasTask;
|
||||
if (args.villageId) {
|
||||
alreadyHasTask = this.taskQueue.has(t => t.name === name && t.args.villageId === args.villageId);
|
||||
} else {
|
||||
alreadyHasTask = this.taskQueue.has(t => t.name === name);
|
||||
}
|
||||
|
||||
if (!alreadyHasTask) {
|
||||
this.scheduleTask(name, args, ts);
|
||||
}
|
||||
@ -98,55 +139,30 @@ export class Scheduler {
|
||||
this.actionQueue.clear();
|
||||
}
|
||||
|
||||
completeTask(taskId: TaskId) {
|
||||
const task = this.taskQueue.findById(taskId);
|
||||
const villageId = task ? task.args.villageId : undefined;
|
||||
if (villageId) {
|
||||
const controller = this.villageControllerFactory.create(villageId);
|
||||
controller.removeTask(taskId);
|
||||
}
|
||||
this.removeTask(taskId);
|
||||
}
|
||||
|
||||
postponeTask(taskId: TaskId, seconds: number) {
|
||||
const task = this.taskQueue.seeItems().find(t => t.id === taskId);
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
|
||||
const villageId = task.args.villageId;
|
||||
const modifyTime = (t: Task) => withTime(t, timestamp() + seconds);
|
||||
|
||||
let predicateUsed = false;
|
||||
|
||||
for (let taskTypePred of TASK_TYPE_PREDICATES) {
|
||||
if (taskTypePred(task.name) && villageId) {
|
||||
this.taskQueue.modify(t => sameVillage(villageId, t.args) && taskTypePred(t.name), modifyTime);
|
||||
predicateUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!predicateUsed) {
|
||||
if (isProductionTask(task.name) && task.args.villageId) {
|
||||
const controller = this.villageControllerFactory.create(task.args.villageId);
|
||||
controller.postponeTask(taskId, seconds);
|
||||
this.removeTask(taskId);
|
||||
} else {
|
||||
const modifyTime = withTime(timestamp() + seconds);
|
||||
this.taskQueue.modify(t => t.id === taskId, modifyTime);
|
||||
}
|
||||
|
||||
if (villageId) {
|
||||
this.reorderVillageTasks(villageId);
|
||||
}
|
||||
}
|
||||
|
||||
updateResources(resources: Resources, attr: ContractAttributes): void {
|
||||
if (attr.type === ContractType.UpgradeBuilding && attr.villageId && attr.buildId) {
|
||||
const count = this.taskQueue.modify(
|
||||
t =>
|
||||
t.name === UpgradeBuildingTask.name &&
|
||||
t.args.villageId === attr.villageId &&
|
||||
t.args.buildId === attr.buildId,
|
||||
t => withResources(t, resources)
|
||||
);
|
||||
this.logger.info('Update', count, 'upgrade contracts', attr, resources);
|
||||
}
|
||||
if (attr.type === ContractType.ImproveTrooper && attr.villageId && attr.buildId && attr.unitId) {
|
||||
const count = this.taskQueue.modify(
|
||||
t =>
|
||||
t.name === ForgeImprovementTask.name &&
|
||||
t.args.villageId === attr.villageId &&
|
||||
t.args.buildId === attr.buildId &&
|
||||
t.args.unitId === attr.unitId,
|
||||
t => withResources(t, resources)
|
||||
);
|
||||
this.logger.info('Update', count, 'improve contracts', attr, resources);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleActions(actions: Array<Action>): void {
|
||||
@ -157,31 +173,6 @@ export class Scheduler {
|
||||
this.actionQueue.clear();
|
||||
}
|
||||
|
||||
getVillageRequiredResources(villageId: number): Resources {
|
||||
const tasks = this.taskQueue
|
||||
.seeItems()
|
||||
.filter(t => sameVillage(villageId, t.args) && t.args.resources && t.name !== SendResourcesTask.name);
|
||||
const first = tasks.shift();
|
||||
if (first && first.args.resources) {
|
||||
return Resources.fromObject(first.args.resources);
|
||||
}
|
||||
return Resources.zero();
|
||||
}
|
||||
|
||||
getTotalVillageRequiredResources(villageId: number): Resources {
|
||||
const tasks = this.taskQueue
|
||||
.seeItems()
|
||||
.filter(t => sameVillage(villageId, t.args) && t.args.resources && t.name !== SendResourcesTask.name);
|
||||
return tasks.reduce((acc, t) => acc.add(t.args.resources!), Resources.zero());
|
||||
}
|
||||
|
||||
getResourceShipmentVillageIds(villageId: number): Array<number> {
|
||||
const tasks = this.taskQueue
|
||||
.seeItems()
|
||||
.filter(t => t.name === SendResourcesTask.name && t.args.villageId === villageId && t.args.targetVillageId);
|
||||
return tasks.map(t => t.args.targetVillageId!);
|
||||
}
|
||||
|
||||
dropResourceTransferTasks(fromVillageId: number, toVillageId: number): void {
|
||||
this.taskQueue.remove(
|
||||
t =>
|
||||
@ -205,92 +196,4 @@ export class Scheduler {
|
||||
tabId: 5,
|
||||
});
|
||||
}
|
||||
|
||||
getProductionQueueTasks(villageId: number, queue: ProductionQueue): ReadonlyArray<Task> {
|
||||
return this.taskQueue
|
||||
.seeItems()
|
||||
.filter(t => t.args.villageId === villageId && getProductionQueue(t.name) === queue);
|
||||
}
|
||||
|
||||
private reorderVillageTasks(villageId: number) {
|
||||
const tasks = this.taskQueue.seeItems();
|
||||
|
||||
for (let i = 0; i < TASK_TYPE_PREDICATES.length; ++i) {
|
||||
const taskTypePred = TASK_TYPE_PREDICATES[i];
|
||||
const lowTaskTypePredicates = TASK_TYPE_PREDICATES.slice(i + 1);
|
||||
const lastTaskTs = lastTaskTime(tasks, t => taskTypePred(t.name) && sameVillage(villageId, t.args));
|
||||
if (lastTaskTs) {
|
||||
for (let pred of lowTaskTypePredicates) {
|
||||
this.taskQueue.modify(
|
||||
t => pred(t.name) && sameVillage(villageId, t.args),
|
||||
t => withTime(t, lastTaskTs + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface TaskNamePredicate {
|
||||
(name: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* List on non intersected task queue predicates.
|
||||
*/
|
||||
const TASK_TYPE_PREDICATES: Array<TaskNamePredicate> = ProductionQueueTypes.map(queue => {
|
||||
return (taskName: string) => getProductionQueue(taskName) === queue;
|
||||
});
|
||||
|
||||
function sameVillage(villageId: number | undefined, args: Args) {
|
||||
return villageId !== undefined && args.villageId === villageId;
|
||||
}
|
||||
|
||||
function withTime(task: Task, ts: number): Task {
|
||||
return new Task(task.id, ts, task.name, task.args);
|
||||
}
|
||||
|
||||
function withResources(task: Task, resources: ResourcesInterface): Task {
|
||||
return new Task(task.id, task.ts, task.name, { ...task.args, resources });
|
||||
}
|
||||
|
||||
function lastTaskTime(tasks: ImmutableTaskList, predicate: (t: Task) => boolean): number | undefined {
|
||||
const queuedTaskIndex = findLastIndex(tasks, predicate);
|
||||
if (queuedTaskIndex === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return tasks[queuedTaskIndex].ts;
|
||||
}
|
||||
|
||||
function findLastIndex(tasks: ImmutableTaskList, predicate: (t: Task) => boolean): number | undefined {
|
||||
const count = tasks.length;
|
||||
const indexInReversed = tasks
|
||||
.slice()
|
||||
.reverse()
|
||||
.findIndex(predicate);
|
||||
if (indexInReversed < 0) {
|
||||
return undefined;
|
||||
}
|
||||
return count - 1 - indexInReversed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates insert time for new task based on task queue.
|
||||
*/
|
||||
function calculateInsertTime(tasks: ImmutableTaskList, name: string, args: Args, ts: number | undefined): number {
|
||||
const villageId = args.villageId;
|
||||
let insertedTs = ts;
|
||||
|
||||
if (villageId && !insertedTs) {
|
||||
for (let taskTypePred of TASK_TYPE_PREDICATES) {
|
||||
const sameVillageAndTypePred = (t: Task) =>
|
||||
taskTypePred(name) && taskTypePred(t.name) && sameVillage(villageId, t.args);
|
||||
insertedTs = lastTaskTime(tasks, sameVillageAndTypePred);
|
||||
if (insertedTs) {
|
||||
insertedTs += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return insertedTs || timestamp();
|
||||
}
|
||||
|
@ -6,14 +6,16 @@ import { IncomingMerchant } from '../Core/Market';
|
||||
import { VillageSettings, VillageSettingsDefaults } from '../Core/Village';
|
||||
import { ProductionQueue } from '../Core/ProductionQueue';
|
||||
import { getNumber } from '../utils';
|
||||
import { Task, TaskList, uniqTaskId } from '../Queue/TaskProvider';
|
||||
|
||||
const RESOURCES_KEY = 'res';
|
||||
const CAPACITY_KEY = 'cap';
|
||||
const PERFORMANCE_KEY = 'perf';
|
||||
const BUILDING_QUEUE_KEY = 'bq';
|
||||
const INCOMING_MERCHANTS_KEY = 'im';
|
||||
const RESOURCES_KEY = 'resources';
|
||||
const CAPACITY_KEY = 'capacity';
|
||||
const PERFORMANCE_KEY = 'performance';
|
||||
const BUILDING_QUEUE_INFO_KEY = 'building_queue_info';
|
||||
const INCOMING_MERCHANTS_KEY = 'incoming_merchants';
|
||||
const SETTINGS_KEY = 'settings';
|
||||
const QUEUE_ENDING_TIME_KEY = 'qet';
|
||||
const QUEUE_ENDING_TIME_KEY = 'queue_ending_time';
|
||||
const TASK_LIST_KEY = 'tasks';
|
||||
|
||||
const ResourceOptions = {
|
||||
factory: () => new Resources(0, 0, 0, 0),
|
||||
@ -52,11 +54,11 @@ export class VillageStorage {
|
||||
}
|
||||
|
||||
storeBuildingQueueInfo(info: BuildingQueueInfo): void {
|
||||
this.storage.set(BUILDING_QUEUE_KEY, info);
|
||||
this.storage.set(BUILDING_QUEUE_INFO_KEY, info);
|
||||
}
|
||||
|
||||
getBuildingQueueInfo(): BuildingQueueInfo {
|
||||
let plain = this.storage.get(BUILDING_QUEUE_KEY);
|
||||
let plain = this.storage.get(BUILDING_QUEUE_INFO_KEY);
|
||||
let res = new BuildingQueueInfo(0);
|
||||
return Object.assign(res, plain) as BuildingQueueInfo;
|
||||
}
|
||||
@ -103,4 +105,48 @@ export class VillageStorage {
|
||||
const key = this.queueKey(queue);
|
||||
this.storage.set(key, ts);
|
||||
}
|
||||
|
||||
getTasks(): Array<Task> {
|
||||
return this.storage.getTypedList<Task>(TASK_LIST_KEY, {
|
||||
factory: () => new Task(uniqTaskId(), 0, '', {}),
|
||||
});
|
||||
}
|
||||
|
||||
addTask(task: Task): void {
|
||||
const tasks = this.getTasks();
|
||||
tasks.push(task);
|
||||
this.storeTaskList(tasks);
|
||||
}
|
||||
|
||||
modifyTasks(predicate: (t: Task) => boolean, modifier: (t: Task) => Task): number {
|
||||
const [matched, other] = this.split(predicate);
|
||||
const modified = matched.map(modifier);
|
||||
const modifiedCount = modified.length;
|
||||
this.storeTaskList(modified.concat(other));
|
||||
return modifiedCount;
|
||||
}
|
||||
|
||||
removeTasks(predicate: (t: Task) => boolean): number {
|
||||
const [_, other] = this.split(predicate);
|
||||
const result = other.length;
|
||||
this.storeTaskList(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
private split(predicate: (t: Task) => boolean): [TaskList, TaskList] {
|
||||
const matched: TaskList = [];
|
||||
const other: TaskList = [];
|
||||
this.getTasks().forEach(t => {
|
||||
if (predicate(t)) {
|
||||
matched.push(t);
|
||||
} else {
|
||||
other.push(t);
|
||||
}
|
||||
});
|
||||
return [matched, other];
|
||||
}
|
||||
|
||||
private storeTaskList(tasks: Array<Task>): void {
|
||||
this.storage.set(TASK_LIST_KEY, tasks);
|
||||
}
|
||||
}
|
||||
|
11
src/Task/RunVillageProductionTask.ts
Normal file
11
src/Task/RunVillageProductionTask.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { TaskController, ActionDefinition } from './TaskController';
|
||||
import { scanAllVillagesBundle } from './ActionBundles';
|
||||
import { Task } from '../Queue/TaskProvider';
|
||||
import { registerTask } from './TaskMap';
|
||||
|
||||
@registerTask()
|
||||
export class RunVillageProductionTask extends TaskController {
|
||||
defineActions(task: Task): Array<ActionDefinition> {
|
||||
return [];
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { TaskController, ActionDefinition } from './TaskController';
|
||||
import { ActionDefinition, TaskController } from './TaskController';
|
||||
import { GoToPageAction } from '../Action/GoToPageAction';
|
||||
import { UpgradeBuildingTask } from './UpgradeBuildingTask';
|
||||
import { ImmutableTaskList, Task } from '../Queue/TaskProvider';
|
||||
@ -11,10 +11,9 @@ export class UpdateResourceContracts extends TaskController {
|
||||
defineActions(task: Task): Array<ActionDefinition> {
|
||||
const tasks = this.scheduler.getTaskItems();
|
||||
|
||||
const paths = [...this.walkUpgradeTasks(tasks), ...this.walkImprovementTask(tasks)];
|
||||
const uniq = uniqPaths(paths);
|
||||
const paths = uniqPaths([...this.walkUpgradeTasks(tasks), ...this.walkImprovementTask(tasks)]);
|
||||
|
||||
return uniq.map(p => [GoToPageAction.name, { path: path(p.name, p.query) }]);
|
||||
return paths.map(p => [GoToPageAction.name, { path: path(p.name, p.query) }]);
|
||||
}
|
||||
|
||||
private walkUpgradeTasks(tasks: ImmutableTaskList): PathList {
|
||||
|
93
src/VillageController.ts
Normal file
93
src/VillageController.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { Task, TaskId, uniqTaskId, withResources, withTime } from './Queue/TaskProvider';
|
||||
import { VillageStorage } from './Storage/VillageStorage';
|
||||
import { Args } from './Queue/Args';
|
||||
import { isProductionTask, ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue';
|
||||
import { Resources } from './Core/Resources';
|
||||
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
|
||||
import { ForgeImprovementTask } from './Task/ForgeImprovementTask';
|
||||
import { ContractType, ContractAttributes } from './Core/Contract';
|
||||
import { timestamp } from './utils';
|
||||
import { getProductionQueue } from './Task/TaskMap';
|
||||
|
||||
export class VillageController {
|
||||
private readonly _villageId: number;
|
||||
private readonly _storage: VillageStorage;
|
||||
|
||||
constructor(villageId: number, storage: VillageStorage) {
|
||||
this._villageId = villageId;
|
||||
this._storage = storage;
|
||||
}
|
||||
|
||||
get villageId() {
|
||||
return this._villageId;
|
||||
}
|
||||
|
||||
getStorage(): VillageStorage {
|
||||
return this._storage;
|
||||
}
|
||||
|
||||
addTask(name: string, args: Args) {
|
||||
if (!isProductionTask(name)) {
|
||||
throw new Error(`Task "${name}" is not production task`);
|
||||
}
|
||||
if (args.villageId !== this._villageId) {
|
||||
throw new Error(`Task village id (${args.villageId}) not equal controller village id (${this._villageId}`);
|
||||
}
|
||||
const task = new Task(uniqTaskId(), 0, name, { villageId: this._villageId, ...args });
|
||||
this._storage.addTask(task);
|
||||
}
|
||||
|
||||
getTasks(): Array<Task> {
|
||||
return this._storage.getTasks();
|
||||
}
|
||||
|
||||
removeTask(taskId: TaskId) {
|
||||
this._storage.removeTasks(t => t.id === taskId);
|
||||
}
|
||||
|
||||
getTasksInProductionQueue(queue: ProductionQueue): Array<Task> {
|
||||
return this._storage.getTasks().filter(task => getProductionQueue(task.name) === queue);
|
||||
}
|
||||
|
||||
getReadyProductionTask(): Task | undefined {
|
||||
let sortedTasks: Array<Task> = [];
|
||||
for (let queue of ProductionQueueTypes) {
|
||||
const tasks = this.getTasksInProductionQueue(queue);
|
||||
sortedTasks = sortedTasks.concat(tasks);
|
||||
}
|
||||
return sortedTasks.shift();
|
||||
}
|
||||
|
||||
postponeTask(taskId: TaskId, seconds: number) {
|
||||
const modifyTime = withTime(timestamp() + seconds);
|
||||
this._storage.modifyTasks(task => task.id === taskId, modifyTime);
|
||||
}
|
||||
|
||||
updateResources(resources: Resources, attr: ContractAttributes): void {
|
||||
if (attr.type === ContractType.UpgradeBuilding && attr.buildId) {
|
||||
const predicate = (t: Task) => t.name === UpgradeBuildingTask.name && t.args.buildId === attr.buildId;
|
||||
this._storage.modifyTasks(predicate, withResources(resources));
|
||||
}
|
||||
if (attr.type === ContractType.ImproveTrooper && attr.buildId && attr.unitId) {
|
||||
const predicate = (t: Task) =>
|
||||
t.name === ForgeImprovementTask.name &&
|
||||
t.args.buildId === attr.buildId &&
|
||||
t.args.unitId === attr.unitId;
|
||||
this._storage.modifyTasks(predicate, withResources(resources));
|
||||
}
|
||||
}
|
||||
|
||||
getVillageRequiredResources(): Resources {
|
||||
const tasks = this._storage.getTasks().filter(t => t.args.resources);
|
||||
const first = tasks.shift();
|
||||
if (first && first.args.resources) {
|
||||
return Resources.fromObject(first.args.resources);
|
||||
}
|
||||
return Resources.zero();
|
||||
}
|
||||
|
||||
getTotalVillageRequiredResources(): Resources {
|
||||
const tasks = this._storage.getTasks().filter(t => t.args.resources);
|
||||
return tasks.reduce((acc, t) => acc.add(t.args.resources!), Resources.zero());
|
||||
}
|
||||
}
|
21
src/VillageControllerFactory.ts
Normal file
21
src/VillageControllerFactory.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { VillageController } from './VillageController';
|
||||
import { VillageStorage } from './Storage/VillageStorage';
|
||||
import { VillageRepository } from './VillageRepository';
|
||||
|
||||
export class VillageControllerFactory {
|
||||
private villageRepository: VillageRepository;
|
||||
|
||||
constructor(villageRepository: VillageRepository) {
|
||||
this.villageRepository = villageRepository;
|
||||
}
|
||||
|
||||
create(villageId: number): VillageController {
|
||||
const village = this.villageRepository.get(villageId);
|
||||
return new VillageController(village.id, new VillageStorage(village.id));
|
||||
}
|
||||
|
||||
getActive(): VillageController {
|
||||
const village = this.villageRepository.getActive();
|
||||
return this.create(village.id);
|
||||
}
|
||||
}
|
@ -1,12 +1,30 @@
|
||||
import { Village } from './Core/Village';
|
||||
import { grabVillageList } from './Page/VillageBlock';
|
||||
import { VillageNotFound } from './Errors';
|
||||
|
||||
export interface VillageRepositoryInterface {
|
||||
all(): Array<Village>;
|
||||
getActive(): Village;
|
||||
}
|
||||
|
||||
export class VillageRepository implements VillageRepositoryInterface {
|
||||
all(): Array<Village> {
|
||||
return grabVillageList();
|
||||
}
|
||||
|
||||
get(villageId: number): Village {
|
||||
const village = this.all().find(vlg => vlg.id === villageId);
|
||||
if (!village) {
|
||||
throw new VillageNotFound('Active village not found');
|
||||
}
|
||||
return village;
|
||||
}
|
||||
|
||||
getActive(): Village {
|
||||
const village = this.all().find(vlg => vlg.active);
|
||||
if (!village) {
|
||||
throw new VillageNotFound('Active village not found');
|
||||
}
|
||||
return village;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Village, VillageSettings } from './Core/Village';
|
||||
import { Scheduler } from './Scheduler';
|
||||
import { Resources } from './Core/Resources';
|
||||
import { VillageStorage } from './Storage/VillageStorage';
|
||||
import { calcGatheringTimings, GatheringTime } from './Core/GatheringTimings';
|
||||
@ -8,6 +7,8 @@ import { VillageNotFound } from './Errors';
|
||||
import { ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue';
|
||||
import { Task } from './Queue/TaskProvider';
|
||||
import { timestamp } from './utils';
|
||||
import { VillageControllerFactory } from './VillageControllerFactory';
|
||||
import { VillageController } from './VillageController';
|
||||
|
||||
interface VillageStorageState {
|
||||
resources: Resources;
|
||||
@ -132,14 +133,13 @@ function taskResourceReducer(resources: Resources, task: Task) {
|
||||
}
|
||||
|
||||
function createProductionQueueState(
|
||||
villageId: number,
|
||||
queue: ProductionQueue,
|
||||
storage: VillageStorage,
|
||||
scheduler: Scheduler
|
||||
controller: VillageController
|
||||
): VillageProductionQueueState {
|
||||
const storage = controller.getStorage();
|
||||
const resources = storage.getResources();
|
||||
const performance = storage.getResourcesPerformance();
|
||||
const tasks = scheduler.getProductionQueueTasks(villageId, queue);
|
||||
const tasks = controller.getTasksInProductionQueue(queue);
|
||||
|
||||
const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero());
|
||||
const allTaskResources = tasks.reduce(taskResourceReducer, Resources.zero());
|
||||
@ -156,33 +156,33 @@ function createProductionQueueState(
|
||||
};
|
||||
}
|
||||
|
||||
function createAllProductionQueueStates(villageId: number, storage: VillageStorage, scheduler: Scheduler) {
|
||||
function createAllProductionQueueStates(controller: VillageController) {
|
||||
let result: { [queue: string]: VillageProductionQueueState } = {};
|
||||
for (let queue of ProductionQueueTypes) {
|
||||
result[queue] = createProductionQueueState(villageId, queue, storage, scheduler);
|
||||
result[queue] = createProductionQueueState(queue, controller);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function calcFrontierResources(villageId: number, scheduler: Scheduler): Resources {
|
||||
function calcFrontierResources(controller: VillageController): Resources {
|
||||
let result = Resources.zero();
|
||||
for (let queue of ProductionQueueTypes) {
|
||||
const tasks = scheduler.getProductionQueueTasks(villageId, queue);
|
||||
const tasks = controller.getTasksInProductionQueue(queue);
|
||||
const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero());
|
||||
result = result.add(firstTaskResources);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function createVillageOwnState(village: Village, scheduler: Scheduler): VillageOwnState {
|
||||
const storage = new VillageStorage(village.id);
|
||||
function createVillageOwnState(village: Village, controller: VillageController): VillageOwnState {
|
||||
const storage = controller.getStorage();
|
||||
const resources = storage.getResources();
|
||||
const resourceStorage = storage.getResourceStorage();
|
||||
const performance = storage.getResourcesPerformance();
|
||||
const buildQueueInfo = storage.getBuildingQueueInfo();
|
||||
const requiredResources = scheduler.getVillageRequiredResources(village.id);
|
||||
const frontierResources = calcFrontierResources(village.id, scheduler);
|
||||
const totalRequiredResources = scheduler.getTotalVillageRequiredResources(village.id);
|
||||
const requiredResources = controller.getVillageRequiredResources();
|
||||
const frontierResources = calcFrontierResources(controller);
|
||||
const totalRequiredResources = controller.getTotalVillageRequiredResources();
|
||||
|
||||
return {
|
||||
id: village.id,
|
||||
@ -196,24 +196,24 @@ function createVillageOwnState(village: Village, scheduler: Scheduler): VillageO
|
||||
buildRemainingSeconds: buildQueueInfo.seconds,
|
||||
incomingResources: calcIncomingResources(storage),
|
||||
settings: storage.getSettings(),
|
||||
queues: createAllProductionQueueStates(village.id, storage, scheduler),
|
||||
queues: createAllProductionQueueStates(controller),
|
||||
};
|
||||
}
|
||||
|
||||
function createVillageOwnStates(villages: Array<Village>, scheduler: Scheduler): VillageOwnStateDictionary {
|
||||
function createVillageOwnStates(
|
||||
villages: Array<Village>,
|
||||
villageControllerFactory: VillageControllerFactory
|
||||
): VillageOwnStateDictionary {
|
||||
const result: VillageOwnStateDictionary = {};
|
||||
for (let village of villages) {
|
||||
result[village.id] = createVillageOwnState(village, scheduler);
|
||||
const villageController = villageControllerFactory.create(village.id);
|
||||
result[village.id] = createVillageOwnState(village, villageController);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function createVillageState(
|
||||
state: VillageOwnState,
|
||||
ownStates: VillageOwnStateDictionary,
|
||||
scheduler: Scheduler
|
||||
): VillageState {
|
||||
const villageIds = scheduler.getResourceShipmentVillageIds(state.id);
|
||||
function createVillageState(state: VillageOwnState, ownStates: VillageOwnStateDictionary): VillageState {
|
||||
const villageIds = Object.keys(ownStates).map(k => +k);
|
||||
const commitments = villageIds.reduce((memo, shipmentVillageId) => {
|
||||
const shipmentVillageState = ownStates[shipmentVillageId];
|
||||
const shipmentVillageRequired = shipmentVillageState.required;
|
||||
@ -224,26 +224,29 @@ function createVillageState(
|
||||
return { ...state, commitments, shipment: villageIds };
|
||||
}
|
||||
|
||||
function getVillageStates(villages: Array<Village>, scheduler: Scheduler): Array<VillageState> {
|
||||
const ownStates = createVillageOwnStates(villages, scheduler);
|
||||
return villages.map(village => createVillageState(ownStates[village.id], ownStates, scheduler));
|
||||
function getVillageStates(
|
||||
villages: Array<Village>,
|
||||
villageControllerFactory: VillageControllerFactory
|
||||
): Array<VillageState> {
|
||||
const ownStates = createVillageOwnStates(villages, villageControllerFactory);
|
||||
return villages.map(village => createVillageState(ownStates[village.id], ownStates));
|
||||
}
|
||||
|
||||
export class VillageStateRepository {
|
||||
private villageRepository: VillageRepositoryInterface;
|
||||
private scheduler: Scheduler;
|
||||
private villageControllerFactory: VillageControllerFactory;
|
||||
|
||||
constructor(villageRepository: VillageRepositoryInterface, scheduler: Scheduler) {
|
||||
constructor(villageRepository: VillageRepositoryInterface, villageControllerFactory: VillageControllerFactory) {
|
||||
this.villageRepository = villageRepository;
|
||||
this.scheduler = scheduler;
|
||||
this.villageControllerFactory = villageControllerFactory;
|
||||
}
|
||||
|
||||
getAllVillageStates(): Array<VillageState> {
|
||||
return getVillageStates(this.villageRepository.all(), this.scheduler);
|
||||
return getVillageStates(this.villageRepository.all(), this.villageControllerFactory);
|
||||
}
|
||||
|
||||
getVillageState(villageId: number): VillageState {
|
||||
const states = getVillageStates(this.villageRepository.all(), this.scheduler);
|
||||
const states = getVillageStates(this.villageRepository.all(), this.villageControllerFactory);
|
||||
const needle = states.find(s => s.id === villageId);
|
||||
if (!needle) {
|
||||
throw new VillageNotFound(`Village ${villageId} not found`);
|
||||
|
Loading…
Reference in New Issue
Block a user