From 9cd64cba15791961297aead25474213fc3cfb55c Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Thu, 7 Dec 2023 01:14:32 -0500 Subject: [PATCH] Freeze cached Sim objects We're hunting ~~wabbits~~ validator bugs. Honestly, this has been a long time coming, but Object.freeze perf used to not be good enough for us to use it here. Here's hoping! --- lib/database.ts | 2 +- lib/utils.ts | 16 ++++++++++++++++ sim/dex-abilities.ts | 2 +- sim/dex-conditions.ts | 2 +- sim/dex-data.ts | 8 ++++---- sim/dex-items.ts | 4 ++-- sim/dex-moves.ts | 4 ++-- sim/dex-species.ts | 8 ++++---- sim/dex.ts | 1 + 9 files changed, 32 insertions(+), 15 deletions(-) diff --git a/lib/database.ts b/lib/database.ts index b4aff506bd..d0601a2931 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -153,7 +153,7 @@ export abstract class Database(name: string, primaryKeyName: keyof Row & string | null = null) { + getTable(name: string, primaryKeyName: keyof Row & string | null = null): DatabaseTable { return new DatabaseTable(this, name, primaryKeyName); } close() { diff --git a/lib/utils.ts b/lib/utils.ts index fbdc2986ae..36364950f5 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -335,6 +335,22 @@ export function deepClone(obj: any): any { return clone; } +export function deepFreeze(obj: T): T { + if (obj === null || typeof obj !== 'object') return obj; + // support objects with reference loops + if (Object.isFrozen(obj)) return obj; + + Object.freeze(obj); + if (Array.isArray(obj)) { + for (const elem of obj) deepFreeze(elem); + return obj; + } + for (const key of Object.keys(obj)) { + deepFreeze((obj as any)[key]); + } + return obj; +} + export function levenshtein(s: string, t: string, l: number): number { // Original levenshtein distance function by James Westgate, turned out to be the fastest const d: number[][] = []; diff --git a/sim/dex-abilities.ts b/sim/dex-abilities.ts index 8310c469f9..5d17eea310 100644 --- a/sim/dex-abilities.ts +++ b/sim/dex-abilities.ts @@ -97,7 +97,7 @@ export class DexAbilities { }); } - if (ability.exists) this.abilityCache.set(id, ability); + if (ability.exists) this.abilityCache.set(id, this.dex.deepFreeze(ability)); return ability; } diff --git a/sim/dex-conditions.ts b/sim/dex-conditions.ts index 1060756830..7512f3d000 100644 --- a/sim/dex-conditions.ts +++ b/sim/dex-conditions.ts @@ -673,7 +673,7 @@ export class DexConditions { condition = new Condition({name: id, exists: false}); } - this.conditionCache.set(id, condition); + this.conditionCache.set(id, this.dex.deepFreeze(condition)); return condition; } } diff --git a/sim/dex-data.ts b/sim/dex-data.ts index d0cd8bf291..fc9cd8c216 100644 --- a/sim/dex-data.ts +++ b/sim/dex-data.ts @@ -178,7 +178,7 @@ export class DexNatures { nature = new Nature({name: id, exists: false}); } - if (nature.exists) this.natureCache.set(id, nature); + if (nature.exists) this.natureCache.set(id, this.dex.deepFreeze(nature)); return nature; } @@ -188,7 +188,7 @@ export class DexNatures { for (const id in this.dex.data.Natures) { natures.push(this.getByID(id as ID)); } - this.allCache = natures; + this.allCache = Object.freeze(natures); return this.allCache; } } @@ -278,7 +278,7 @@ export class DexTypes { type = new TypeInfo({name: typeName, id, exists: false, effectType: 'EffectType'}); } - if (type.exists) this.typeCache.set(id, type); + if (type.exists) this.typeCache.set(id, this.dex.deepFreeze(type)); return type; } @@ -302,7 +302,7 @@ export class DexTypes { for (const id in this.dex.data.TypeChart) { types.push(this.getByID(id as ID)); } - this.allCache = types; + this.allCache = Object.freeze(types); return this.allCache; } } diff --git a/sim/dex-items.ts b/sim/dex-items.ts index fe44d43aa8..c374b6145e 100644 --- a/sim/dex-items.ts +++ b/sim/dex-items.ts @@ -202,7 +202,7 @@ export class DexItems { item = new Item({name: id, exists: false}); } - if (item.exists) this.itemCache.set(id, item); + if (item.exists) this.itemCache.set(id, this.dex.deepFreeze(item)); return item; } @@ -212,7 +212,7 @@ export class DexItems { for (const id in this.dex.data.Items) { items.push(this.getByID(id as ID)); } - this.allCache = items; + this.allCache = Object.freeze(items); return this.allCache; } } diff --git a/sim/dex-moves.ts b/sim/dex-moves.ts index 4719fe9980..7b18d2c342 100644 --- a/sim/dex-moves.ts +++ b/sim/dex-moves.ts @@ -654,7 +654,7 @@ export class DexMoves { name: id, exists: false, }); } - if (move.exists) this.moveCache.set(id, move); + if (move.exists) this.moveCache.set(id, this.dex.deepFreeze(move)); return move; } @@ -664,7 +664,7 @@ export class DexMoves { for (const id in this.dex.data.Moves) { moves.push(this.getByID(id as ID)); } - this.allCache = moves; + this.allCache = Object.freeze(moves); return this.allCache; } } diff --git a/sim/dex-species.ts b/sim/dex-species.ts index ed3df9bba1..7552fe8217 100644 --- a/sim/dex-species.ts +++ b/sim/dex-species.ts @@ -397,7 +397,7 @@ export class DexSpecies { } } } - this.speciesCache.set(id, species); + this.speciesCache.set(id, this.dex.deepFreeze(species)); return species; } @@ -516,7 +516,7 @@ export class DexSpecies { exists: false, tier: 'Illegal', doublesTier: 'Illegal', natDexTier: 'Illegal', isNonstandard: 'Custom', }); } - if (species.exists) this.speciesCache.set(id, species); + if (species.exists) this.speciesCache.set(id, this.dex.deepFreeze(species)); return species; } @@ -531,7 +531,7 @@ export class DexSpecies { return new Learnset({exists: false}); } learnsetData = new Learnset(this.dex.data.Learnsets[id]); - this.learnsetCache.set(id, learnsetData); + this.learnsetCache.set(id, this.dex.deepFreeze(learnsetData)); return learnsetData; } @@ -545,7 +545,7 @@ export class DexSpecies { for (const id in this.dex.data.Pokedex) { species.push(this.getByID(id as ID)); } - this.allCache = species; + this.allCache = Object.freeze(species); return this.allCache; } } diff --git a/sim/dex.ts b/sim/dex.ts index 61b1d0cd3f..9f2dd42557 100644 --- a/sim/dex.ts +++ b/sim/dex.ts @@ -122,6 +122,7 @@ export class ModdedDex { textCache: TextTableData | null; deepClone = Utils.deepClone; + deepFreeze = Utils.deepFreeze; readonly formats: DexFormats; readonly abilities: DexAbilities;