Skip to content

Commit

Permalink
Further async improvements (#1070)
Browse files Browse the repository at this point in the history
- Adds a set of asynchronous cloners able to be used in async methods
- Updates setInterval to await the update before processing a new one.
- Updates various BotGen methods to remove nested promises and removing
a few unnecessary for loops.
  • Loading branch information
ArchangelWTF authored Jan 12, 2025
1 parent ed0e3d6 commit aeee1b8
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 40 deletions.
76 changes: 41 additions & 35 deletions project/src/controllers/BotController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ export class BotController {
this.pmcConfig.allPMCsHavePlayerNameWithRandomPrefixChance,
);

const conditionPromises: Promise<void>[] = [];
for (const condition of request.conditions) {
// Map conditions to promises for bot generation
const conditionPromises = request.conditions.map(async (condition) => {
const botGenerationDetails = this.getBotGenerationDetailsForWave(
condition,
pmcProfile,
Expand All @@ -193,14 +193,11 @@ export class BotController {
this.botHelper.isBotPmc(condition.Role),
);

conditionPromises.push(this.generateWithBotDetails(condition, botGenerationDetails, sessionId));
}
// Generate bots for the current condition
await this.generateWithBotDetails(condition, botGenerationDetails, sessionId);
});

await Promise.all(conditionPromises)
.then((p) => Promise.all(p))
.catch((ex) => {
this.logger.error(ex);
});
await Promise.all(conditionPromises);
return [];
}

Expand Down Expand Up @@ -301,26 +298,36 @@ export class BotController {

// Get number of bots we have in cache
const botCacheCount = this.botGenerationCacheService.getCachedBotCount(cacheKey);
const botPromises: Promise<void>[] = [];
if (botCacheCount > botGenerationDetails.botCountToGenerate) {

if (botCacheCount >= botGenerationDetails.botCountToGenerate) {
this.logger.debug(`Cache already has sufficient bots: ${botCacheCount}`);
return;
}

// We're below desired count, add bots to cache
const botsToGenerate = botGenerationDetails.botCountToGenerate - botCacheCount;
const progressWriter = new ProgressWriter(botGenerationDetails.botCountToGenerate);
for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++) {
const detailsClone = this.cloner.clone(botGenerationDetails);
botPromises.push(this.generateSingleBotAndStoreInCache(detailsClone, sessionId, cacheKey));
progressWriter.increment();
}

return await Promise.all(botPromises).then(() => {
this.logger.debug(
`Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${
botGenerationDetails.eventRole ?? botGenerationDetails.role ?? ""
}) ${botGenerationDetails.botDifficulty} bots`,
);
this.logger.debug(`Generating ${botsToGenerate} bots for cacheKey: ${cacheKey}`);

const botGenerationPromises = Array.from({ length: botsToGenerate }, async (_, i) => {
try {
const detailsClone = await this.cloner.cloneAsync(botGenerationDetails);
await this.generateSingleBotAndStoreInCache(detailsClone, sessionId, cacheKey);
progressWriter.increment();
} catch (error) {
this.logger.error(`Failed to generate bot #${i + 1}: ${error.message}`);
}
});

// Use allSettled here, this allows us to continue even if one of the promises is rejected
await Promise.allSettled(botGenerationPromises);

this.logger.debug(
`Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${
botGenerationDetails.eventRole ?? botGenerationDetails.role ?? ""
}) ${botGenerationDetails.botDifficulty} bots`,
);
}

/**
Expand All @@ -335,7 +342,7 @@ export class BotController {
sessionId: string,
cacheKey: string,
): Promise<void> {
const botToCache = this.botGenerator.prepareAndGenerateBot(sessionId, botGenerationDetails);
const botToCache = await this.botGenerator.prepareAndGenerateBot(sessionId, botGenerationDetails);
this.botGenerationCacheService.storeBots(cacheKey, [botToCache]);

// Store bot details in cache so post-raid PMC messages can use data
Expand Down Expand Up @@ -422,19 +429,18 @@ export class BotController {

// Check cache for bot using above key
if (!this.botGenerationCacheService.cacheHasBotWithKey(cacheKey)) {
const botPromises: Promise<void>[] = [];
// No bot in cache, generate new and return one
for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++) {
botPromises.push(this.generateSingleBotAndStoreInCache(botGenerationDetails, sessionId, cacheKey));
}
// No bot in cache, generate new and store in cache
await Promise.all(
Array.from({ length: botGenerationDetails.botCountToGenerate }).map(
async () => await this.generateSingleBotAndStoreInCache(botGenerationDetails, sessionId, cacheKey),
),
);

await Promise.all(botPromises).then(() => {
this.logger.debug(
`Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${
botGenerationDetails.eventRole ?? ""
}) ${botGenerationDetails.botDifficulty} bots`,
);
});
this.logger.debug(
`Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${
botGenerationDetails.eventRole ?? ""
}) ${botGenerationDetails.botDifficulty} bots`,
);
}

const desiredBot = this.botGenerationCacheService.getBot(cacheKey);
Expand Down
7 changes: 5 additions & 2 deletions project/src/generators/BotGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ export class BotGenerator {
* @param botGenerationDetails details on how to generate bots
* @returns constructed bot
*/
public prepareAndGenerateBot(sessionId: string, botGenerationDetails: IBotGenerationDetails): IBotBase {
public async prepareAndGenerateBot(
sessionId: string,
botGenerationDetails: IBotGenerationDetails,
): Promise<IBotBase> {
const preparedBotBase = this.getPreparedBotBase(
botGenerationDetails.eventRole ?? botGenerationDetails.role, // Use eventRole if provided,
botGenerationDetails.side,
Expand All @@ -122,7 +125,7 @@ export class BotGenerator {
const botRole = botGenerationDetails.isPmc
? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC
: botGenerationDetails.role;
const botJsonTemplateClone = this.cloner.clone(this.botHelper.getBotTemplate(botRole));
const botJsonTemplateClone = await this.cloner.cloneAsync(this.botHelper.getBotTemplate(botRole));
if (!botJsonTemplateClone) {
this.logger.error(`Unable to retrieve: ${botRole} bot template, cannot generate bot of this type`);
}
Expand Down
2 changes: 1 addition & 1 deletion project/src/services/CreateProfileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class CreateProfileService {

public async createProfile(sessionID: string, info: IProfileCreateRequestData): Promise<string> {
const account = this.saveServer.getProfile(sessionID).info;
const profileTemplateClone: ITemplateSide = this.cloner.clone(
const profileTemplateClone: ITemplateSide = await this.cloner.cloneAsync(
this.databaseService.getProfiles()[account.edition][info.side.toLowerCase()],
);
const pmcData = profileTemplateClone.character;
Expand Down
4 changes: 2 additions & 2 deletions project/src/utils/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export class App {
await onLoad.onLoad();
}

setInterval(() => {
this.update(this.onUpdateComponents);
setInterval(async () => {
await this.update(this.onUpdateComponents);
}, 5000);
}

Expand Down
1 change: 1 addition & 0 deletions project/src/utils/cloners/ICloner.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface ICloner {
clone<T>(obj: T): T;
cloneAsync<T>(obj: T): Promise<T>;
}
10 changes: 10 additions & 0 deletions project/src/utils/cloners/JsonCloner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,14 @@ export class JsonCloner implements ICloner {
public clone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}

public async cloneAsync<T>(obj: T): Promise<T> {
return new Promise((resolve, reject) => {
try {
resolve(JSON.parse(JSON.stringify(obj)));
} catch (error) {
reject(error);
}
});
}
}
33 changes: 33 additions & 0 deletions project/src/utils/cloners/RecursiveCloner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,37 @@ export class RecursiveCloner implements ICloner {

throw new Error(`Cant clone ${JSON.stringify(obj)}`);
}

public async cloneAsync<T>(obj: T): Promise<T> {
// if null or undefined return it as is
if (obj === null || obj === undefined) return obj;

const typeOfObj = typeof obj;

// no need to clone these types, they are primitives
if (RecursiveCloner.primitives.has(typeOfObj)) {
return obj;
}

// clone the object types
if (typeOfObj === "object") {
if (Array.isArray(obj)) {
const objArr = obj as Array<T>;
const clonedArray = await Promise.all(objArr.map(async (v) => await this.cloneAsync(v)));
return clonedArray as T;
}

const newObj: Record<string, T> = {};
const clonePromises = Object.keys(obj).map(async (key) => {
const value = (obj as Record<string, T>)[key];
newObj[key] = await this.cloneAsync(value);
});

await Promise.all(clonePromises);
return newObj as T;
}

// Handle unsupported types
throw new Error(`Cannot clone ${JSON.stringify(obj)}`);
}
}
10 changes: 10 additions & 0 deletions project/src/utils/cloners/StructuredCloner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,14 @@ export class StructuredCloner implements ICloner {
public clone<T>(obj: T): T {
return structuredClone(obj);
}

public async cloneAsync<T>(obj: T): Promise<T> {
return new Promise((resolve, reject) => {
try {
resolve(structuredClone(obj));
} catch (error) {
reject(error);
}
});
}
}

0 comments on commit aeee1b8

Please sign in to comment.