0
0
mirror of https://github.com/sp-tarkov/server.git synced 2025-02-13 09:50:43 -05:00

Further async improvements (#1070)

- 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.
This commit is contained in:
Jesse 2025-01-12 18:25:41 +01:00 committed by GitHub
parent ed0e3d64c3
commit aeee1b8446
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 103 additions and 40 deletions

View File

@ -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,
@ -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 [];
}
@ -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`,
);
}
/**
@ -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
@ -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);

View File

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

View File

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

View File

@ -64,8 +64,8 @@ export class App {
await onLoad.onLoad();
}
setInterval(() => {
this.update(this.onUpdateComponents);
setInterval(async () => {
await this.update(this.onUpdateComponents);
}, 5000);
}

View File

@ -1,3 +1,4 @@
export interface ICloner {
clone<T>(obj: T): T;
cloneAsync<T>(obj: T): Promise<T>;
}

View File

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

View File

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

View File

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