Rebuild village production queue system

This commit is contained in:
Anton Vakhrushev 2020-05-24 17:23:13 +03:00
parent f3a1e67906
commit 8bea617f5b
26 changed files with 520 additions and 336 deletions

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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
View File

@ -0,0 +1,10 @@
export enum ContractType {
UpgradeBuilding,
ImproveTrooper,
}
export interface ContractAttributes {
type: ContractType;
buildId?: number;
unitId?: number;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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,
});
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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());

View File

@ -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());
}
}

View File

@ -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,

View File

@ -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 {

View File

@ -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 });
}

View File

@ -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 = [];

View File

@ -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();
}

View File

@ -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);
}
}

View 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 [];
}
}

View File

@ -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
View 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());
}
}

View 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);
}
}

View File

@ -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;
}
}

View File

@ -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`);