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, this.pmcConfig.allPMCsHavePlayerNameWithRandomPrefixChance,
); );
const conditionPromises: Promise<void>[] = []; // Map conditions to promises for bot generation
for (const condition of request.conditions) { const conditionPromises = request.conditions.map(async (condition) => {
const botGenerationDetails = this.getBotGenerationDetailsForWave( const botGenerationDetails = this.getBotGenerationDetailsForWave(
condition, condition,
pmcProfile, pmcProfile,
@ -193,14 +193,11 @@ export class BotController {
this.botHelper.isBotPmc(condition.Role), 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 []; return [];
} }
@ -301,26 +298,36 @@ export class BotController {
// Get number of bots we have in cache // Get number of bots we have in cache
const botCacheCount = this.botGenerationCacheService.getCachedBotCount(cacheKey); 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; return;
} }
// We're below desired count, add bots to cache // We're below desired count, add bots to cache
const botsToGenerate = botGenerationDetails.botCountToGenerate - botCacheCount;
const progressWriter = new ProgressWriter(botGenerationDetails.botCountToGenerate); 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(`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( this.logger.debug(
`Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${ `Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${
botGenerationDetails.eventRole ?? botGenerationDetails.role ?? "" botGenerationDetails.eventRole ?? botGenerationDetails.role ?? ""
}) ${botGenerationDetails.botDifficulty} bots`, }) ${botGenerationDetails.botDifficulty} bots`,
); );
});
} }
/** /**
@ -335,7 +342,7 @@ export class BotController {
sessionId: string, sessionId: string,
cacheKey: string, cacheKey: string,
): Promise<void> { ): Promise<void> {
const botToCache = this.botGenerator.prepareAndGenerateBot(sessionId, botGenerationDetails); const botToCache = await this.botGenerator.prepareAndGenerateBot(sessionId, botGenerationDetails);
this.botGenerationCacheService.storeBots(cacheKey, [botToCache]); this.botGenerationCacheService.storeBots(cacheKey, [botToCache]);
// Store bot details in cache so post-raid PMC messages can use data // 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 // Check cache for bot using above key
if (!this.botGenerationCacheService.cacheHasBotWithKey(cacheKey)) { if (!this.botGenerationCacheService.cacheHasBotWithKey(cacheKey)) {
const botPromises: Promise<void>[] = []; // No bot in cache, generate new and store in cache
// No bot in cache, generate new and return one await Promise.all(
for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++) { Array.from({ length: botGenerationDetails.botCountToGenerate }).map(
botPromises.push(this.generateSingleBotAndStoreInCache(botGenerationDetails, sessionId, cacheKey)); async () => await this.generateSingleBotAndStoreInCache(botGenerationDetails, sessionId, cacheKey),
} ),
);
await Promise.all(botPromises).then(() => {
this.logger.debug( this.logger.debug(
`Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${ `Generated ${botGenerationDetails.botCountToGenerate} ${botGenerationDetails.role} (${
botGenerationDetails.eventRole ?? "" botGenerationDetails.eventRole ?? ""
}) ${botGenerationDetails.botDifficulty} bots`, }) ${botGenerationDetails.botDifficulty} bots`,
); );
});
} }
const desiredBot = this.botGenerationCacheService.getBot(cacheKey); const desiredBot = this.botGenerationCacheService.getBot(cacheKey);

View File

@ -111,7 +111,10 @@ export class BotGenerator {
* @param botGenerationDetails details on how to generate bots * @param botGenerationDetails details on how to generate bots
* @returns constructed bot * @returns constructed bot
*/ */
public prepareAndGenerateBot(sessionId: string, botGenerationDetails: IBotGenerationDetails): IBotBase { public async prepareAndGenerateBot(
sessionId: string,
botGenerationDetails: IBotGenerationDetails,
): Promise<IBotBase> {
const preparedBotBase = this.getPreparedBotBase( const preparedBotBase = this.getPreparedBotBase(
botGenerationDetails.eventRole ?? botGenerationDetails.role, // Use eventRole if provided, botGenerationDetails.eventRole ?? botGenerationDetails.role, // Use eventRole if provided,
botGenerationDetails.side, botGenerationDetails.side,
@ -122,7 +125,7 @@ export class BotGenerator {
const botRole = botGenerationDetails.isPmc const botRole = botGenerationDetails.isPmc
? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC ? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC
: botGenerationDetails.role; : botGenerationDetails.role;
const botJsonTemplateClone = this.cloner.clone(this.botHelper.getBotTemplate(botRole)); const botJsonTemplateClone = await this.cloner.cloneAsync(this.botHelper.getBotTemplate(botRole));
if (!botJsonTemplateClone) { if (!botJsonTemplateClone) {
this.logger.error(`Unable to retrieve: ${botRole} bot template, cannot generate bot of this type`); 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> { public async createProfile(sessionID: string, info: IProfileCreateRequestData): Promise<string> {
const account = this.saveServer.getProfile(sessionID).info; 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()], this.databaseService.getProfiles()[account.edition][info.side.toLowerCase()],
); );
const pmcData = profileTemplateClone.character; const pmcData = profileTemplateClone.character;

View File

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

View File

@ -1,3 +1,4 @@
export interface ICloner { export interface ICloner {
clone<T>(obj: T): T; 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 { public clone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj)); 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)}`); 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 { public clone<T>(obj: T): T {
return structuredClone(obj); return structuredClone(obj);
} }
public async cloneAsync<T>(obj: T): Promise<T> {
return new Promise((resolve, reject) => {
try {
resolve(structuredClone(obj));
} catch (error) {
reject(error);
}
});
}
} }