Skip to content

Commit

Permalink
Data: Fix huge perf bugs in randbat tests, part 1 (#10616)
Browse files Browse the repository at this point in the history
  • Loading branch information
larry-the-table-guy authored Nov 9, 2024
1 parent 3e23daf commit a86d043
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 51 deletions.
4 changes: 1 addition & 3 deletions data/random-battles/gen3/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,7 @@ export class RandomGen3Teams extends RandomGen4Teams {

// Develop additional move lists
const badWithSetup = ['knockoff', 'rapidspin', 'toxic'];
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
7 changes: 4 additions & 3 deletions data/random-battles/gen4/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export class RandomGen4Teams extends RandomGen5Teams {
Steel: (movePool, moves, abilities, types, counter, species) => (!counter.get('Steel') && species.id === 'metagross'),
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
};
this.cachedStatusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
}

cullMovePool(
Expand Down Expand Up @@ -164,9 +167,7 @@ export class RandomGen4Teams extends RandomGen5Teams {

// Develop additional move lists
const badWithSetup = ['pursuit', 'toxic'];
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
9 changes: 5 additions & 4 deletions data/random-battles/gen5/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export class RandomGen5Teams extends RandomGen6Teams {
),
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
};
// Nature Power is Earthquake this gen
this.cachedStatusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
.map(move => move.id);
}

cullMovePool(
Expand Down Expand Up @@ -177,10 +181,7 @@ export class RandomGen5Teams extends RandomGen6Teams {

// Develop additional move lists
const badWithSetup = ['healbell', 'pursuit', 'toxic'];
// Nature Power is Earthquake this gen
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
7 changes: 4 additions & 3 deletions data/random-battles/gen6/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ export class RandomGen6Teams extends RandomGen7Teams {
Steel: (movePool, moves, abilities, types, counter, species) => (!counter.get('Steel') && species.baseStats.atk >= 100),
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
};
this.cachedStatusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
}

cullMovePool(
Expand Down Expand Up @@ -196,9 +199,7 @@ export class RandomGen6Teams extends RandomGen7Teams {

// Develop additional move lists
const badWithSetup = ['defog', 'dragontail', 'haze', 'healbell', 'nuzzle', 'pursuit', 'rapidspin', 'toxic'];
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
10 changes: 6 additions & 4 deletions data/random-battles/gen7/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ function sereneGraceBenefits(move: Move) {

export class RandomGen7Teams extends RandomGen8Teams {
randomSets: {[species: string]: RandomTeamsTypes.RandomSpeciesData} = require('./sets.json');
protected cachedStatusMoves: ID[];

constructor(format: Format | string, prng: PRNG | PRNGSeed | null) {
super(format, prng);
Expand Down Expand Up @@ -141,6 +142,10 @@ export class RandomGen7Teams extends RandomGen8Teams {
Steel: (movePool, moves, abilities, types, counter, species) => (!counter.get('Steel') && species.baseStats.atk >= 100),
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
};
// Nature Power is Tri Attack this gen
this.cachedStatusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
.map(move => move.id);
}

newQueryMoves(
Expand Down Expand Up @@ -311,10 +316,7 @@ export class RandomGen7Teams extends RandomGen8Teams {

// Develop additional move lists
const badWithSetup = ['defog', 'dragontail', 'haze', 'healbell', 'nuzzle', 'pursuit', 'rapidspin', 'toxic'];
// Nature Power is Tri Attack this gen
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
50 changes: 38 additions & 12 deletions data/random-battles/gen8/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function sereneGraceBenefits(move: Move) {
}

export class RandomGen8Teams {
dex: ModdedDex;
readonly dex: ModdedDex;
gen: number;
factoryTier: string;
format: Format;
Expand All @@ -116,6 +116,11 @@ export class RandomGen8Teams {
*/
moveEnforcementCheckers: {[k: string]: MoveEnforcementChecker};

/** Used by .getPools() */
private poolsCacheKey: [string | undefined, number | undefined, RuleTable | undefined, boolean] | undefined;
private cachedPool: number[] | undefined;
private cachedSpeciesPool: Species[] | undefined;

constructor(format: Format | string, prng: PRNG | PRNGSeed | null) {
format = Dex.formats.get(format);
this.dex = Dex.forFormat(format);
Expand Down Expand Up @@ -232,6 +237,9 @@ export class RandomGen8Teams {
return abilities.includes('Huge Power') && movePool.includes('aquajet');
},
};
this.poolsCacheKey = undefined;
this.cachedPool = undefined;
this.cachedSpeciesPool = undefined;
}

setSeed(prng?: PRNG | PRNGSeed) {
Expand Down Expand Up @@ -526,19 +534,18 @@ export class RandomGen8Teams {
return team;
}

randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Picks `n` random pokemon--no repeats, even among formes
// Also need to either normalize for formes or select formes at random
// Unreleased are okay but no CAP
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}

private getPools(requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Memoize pool and speciesPool because, at least during tests, they are constructed with the same parameters
// hundreds of times and are expensive to compute.
const isNotCustom = !ruleTable;

const pool: number[] = [];
let pool: number[] = [];
let speciesPool: Species[] = [];
if (isNotCustom) {
const ck = this.poolsCacheKey;
if (ck && this.cachedPool && this.cachedSpeciesPool &&
ck[0] === requiredType && ck[1] === minSourceGen && ck[2] === ruleTable && ck[3] === requireMoves) {
speciesPool = this.cachedSpeciesPool.slice();
pool = this.cachedPool.slice();
} else if (isNotCustom) {
speciesPool = [...this.dex.species.all()];
for (const species of speciesPool) {
if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue;
Expand All @@ -552,6 +559,9 @@ export class RandomGen8Teams {
if (num <= 0 || pool.includes(num)) continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
} else {
const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent'];
const nonexistentBanReason = ruleTable.check('nonexistent');
Expand Down Expand Up @@ -596,7 +606,23 @@ export class RandomGen8Teams {
if (pool.includes(num)) continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
}
return {pool, speciesPool};
}

randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Picks `n` random pokemon--no repeats, even among formes
// Also need to either normalize for formes or select formes at random
// Unreleased are okay but no CAP
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}

const {pool, speciesPool} = this.getPools(requiredType, minSourceGen, ruleTable, requireMoves);
const isNotCustom = !ruleTable;

const hasDexNumber: {[k: string]: number} = {};
for (let i = 0; i < n; i++) {
Expand Down
56 changes: 41 additions & 15 deletions data/random-battles/gen9/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function sereneGraceBenefits(move: Move) {
}

export class RandomTeams {
dex: ModdedDex;
readonly dex: ModdedDex;
gen: number;
factoryTier: string;
format: Format;
Expand All @@ -164,6 +164,12 @@ export class RandomTeams {
*/
moveEnforcementCheckers: {[k: string]: MoveEnforcementChecker};

/** Used by .getPools() */
private poolsCacheKey: [string | undefined, number | undefined, RuleTable | undefined, boolean] | undefined;
private cachedPool: number[] | undefined;
private cachedSpeciesPool: Species[] | undefined;
protected cachedStatusMoves: ID[];

constructor(format: Format | string, prng: PRNG | PRNGSeed | null) {
format = Dex.formats.get(format);
this.dex = Dex.forFormat(format);
Expand Down Expand Up @@ -233,6 +239,10 @@ export class RandomTeams {
),
Water: (movePool, moves, abilities, types, counter) => (!counter.get('Water') && !types.includes('Ground')),
};
this.poolsCacheKey = undefined;
this.cachedPool = undefined;
this.cachedSpeciesPool = undefined;
this.cachedStatusMoves = this.dex.moves.all().filter(move => move.category === 'Status').map(move => move.id);
}

setSeed(prng?: PRNG | PRNGSeed) {
Expand Down Expand Up @@ -476,9 +486,7 @@ export class RandomTeams {
}

// Develop additional move lists
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// Team-based move culls
if (teamDetails.screens) {
Expand Down Expand Up @@ -1972,19 +1980,18 @@ export class RandomTeams {
return team;
}

randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Picks `n` random pokemon--no repeats, even among formes
// Also need to either normalize for formes or select formes at random
// Unreleased are okay but no CAP
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}

private getPools(requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Memoize pool and speciesPool because, at least during tests, they are constructed with the same parameters
// hundreds of times and are expensive to compute.
const isNotCustom = !ruleTable;

const pool: number[] = [];
let pool: number[] = [];
let speciesPool: Species[] = [];
if (isNotCustom) {
const ck = this.poolsCacheKey;
if (ck && this.cachedPool && this.cachedSpeciesPool &&
ck[0] === requiredType && ck[1] === minSourceGen && ck[2] === ruleTable && ck[3] === requireMoves) {
speciesPool = this.cachedSpeciesPool.slice();
pool = this.cachedPool.slice();
} else if (isNotCustom) {
speciesPool = [...this.dex.species.all()];
for (const species of speciesPool) {
if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue;
Expand All @@ -1998,6 +2005,9 @@ export class RandomTeams {
if (num <= 0 || pool.includes(num)) continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
} else {
const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent'];
const nonexistentBanReason = ruleTable.check('nonexistent');
Expand Down Expand Up @@ -2042,7 +2052,23 @@ export class RandomTeams {
if (pool.includes(num)) continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
}
return {pool, speciesPool};
}

randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Picks `n` random pokemon--no repeats, even among formes
// Also need to either normalize for formes or select formes at random
// Unreleased are okay but no CAP
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}

const {pool, speciesPool} = this.getPools(requiredType, minSourceGen, ruleTable, requireMoves);
const isNotCustom = !ruleTable;

const hasDexNumber: {[k: string]: number} = {};
for (let i = 0; i < n; i++) {
Expand Down
4 changes: 1 addition & 3 deletions data/random-battles/gen9baby/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ export class RandomBabyTeams extends RandomTeams {
}

// Create list of all status moves to be used later
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// Team-based move culls
if (teamDetails.screens && movePool.length >= this.maxMoveCount + 2) {
Expand Down
9 changes: 5 additions & 4 deletions sim/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,13 +618,14 @@ export const Teams = new class Teams {
getGenerator(format: Format | string, seed: PRNG | PRNGSeed | null = null) {
let TeamGenerator;
format = Dex.formats.get(format);
if (toID(format).includes('gen9computergeneratedteams')) {
const formatID = toID(format);
if (formatID.includes('gen9computergeneratedteams')) {
TeamGenerator = require(Dex.forFormat(format).dataDir + '/cg-teams').default;
} else if (toID(format).includes('gen9superstaffbrosultimate')) {
} else if (formatID.includes('gen9superstaffbrosultimate')) {
TeamGenerator = require(`../data/mods/gen9ssb/random-teams`).default;
} else if (toID(format).includes('gen9babyrandombattle')) {
} else if (formatID.includes('gen9babyrandombattle')) {
TeamGenerator = require(`../data/random-battles/gen9baby/teams`).default;
} else if (toID(format).includes('gen9randombattle') && format.ruleTable?.has('+pokemontag:cap')) {
} else if (formatID.includes('gen9randombattle') && format.ruleTable?.has('+pokemontag:cap')) {
TeamGenerator = require(`../data/random-battles/gen9cap/teams`).default;
} else {
TeamGenerator = require(`../data/random-battles/${format.mod}/teams`).default;
Expand Down

0 comments on commit a86d043

Please sign in to comment.