feat: cache capability

This adds the option to build a cache from the database so that it doesn’t have
to be re-calculated every time. The first provided cache is a collections of new
applications and uses for skills.
This commit is contained in:
Lukas Obermann
2023-08-17 17:02:31 +02:00
parent 58c37c440e
commit 45cf7bfd3e
4 changed files with 165 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
import type { CacheConfig } from "../cacheConfig.js"
import type { SkillApplication, SkillUse } from "../types/_Activatable.js"
import type { ActivatableIdentifier } from "../types/_IdentifierGroup.js"
export type NewApplication = {
source: ActivatableIdentifier
data: SkillApplication
requiredSkillRating?: number
}
export type Use = {
source: ActivatableIdentifier
data: SkillUse
}
export type NewApplicationsAndUsesCache = {
newApplications: Record<number, NewApplication[]>
uses: Record<number, Use[]>
}
export const config: CacheConfig<NewApplicationsAndUsesCache> = {
builder(database) {
type WithApplicationsAndUses = {
skill_applications?: SkillApplication[]
skill_uses?: SkillUse[]
}
const cache: NewApplicationsAndUsesCache = {
newApplications: {},
uses: {},
}
const addNewApplicationsAndUses = (
activatable: WithApplicationsAndUses,
activatableId: ActivatableIdentifier,
) => {
if (activatable.skill_applications) {
for (const newApplication of activatable.skill_applications) {
const pair: NewApplication = { source: activatableId, data: newApplication }
if (newApplication.skill.tag === "Single") {
const previous = (cache.newApplications[newApplication.skill.single.id.skill] ??= [])
previous.push(pair)
} else {
for (const skill of newApplication.skill.multiple.list) {
const previous = (cache.newApplications[skill.id.skill] ??= [])
previous.push(pair)
}
if (newApplication.skill.multiple.required_skill_rating !== undefined) {
pair.requiredSkillRating = newApplication.skill.multiple.required_skill_rating
}
}
}
}
if (activatable.skill_uses) {
for (const use of activatable.skill_uses) {
const pair: Use = { source: activatableId, data: use }
if (use.skill.tag === "Single") {
const previous = (cache.uses[use.skill.single.id.skill] ??= [])
previous.push(pair)
} else {
for (const skill of use.skill.multiple.list) {
const previous = (cache.uses[skill.id.skill] ??= [])
previous.push(pair)
}
}
}
}
}
const entries: [
[id: number, data: WithApplicationsAndUses][],
(numericId: number) => ActivatableIdentifier,
][] = [
[database.advancedCombatSpecialAbilities, n => ({ tag: "AdvancedCombatSpecialAbility", advanced_combat_special_ability: n })],
[database.advancedKarmaSpecialAbilities, n => ({ tag: "AdvancedKarmaSpecialAbility", advanced_karma_special_ability: n })],
[database.advancedMagicalSpecialAbilities, n => ({ tag: "AdvancedMagicalSpecialAbility", advanced_magical_special_ability: n })],
[database.advancedSkillSpecialAbilities, n => ({ tag: "AdvancedSkillSpecialAbility", advanced_skill_special_ability: n })],
[database.advantages, n => ({ tag: "Advantage", advantage: n })],
[database.blessedTraditions, n => ({ tag: "BlessedTradition", blessed_tradition: n })],
[database.ceremonialItemSpecialAbilities, n => ({ tag: "CeremonialItemSpecialAbility", ceremonial_item_special_ability: n })],
[database.combatSpecialAbilities, n => ({ tag: "CombatSpecialAbility", combat_special_ability: n })],
[database.combatStyleSpecialAbilities, n => ({ tag: "CombatStyleSpecialAbility", combat_style_special_ability: n })],
[database.fatePointSpecialAbilities, n => ({ tag: "FatePointSpecialAbility", fate_point_special_ability: n })],
[database.generalSpecialAbilities, n => ({ tag: "GeneralSpecialAbility", general_special_ability: n })],
[database.liturgicalStyleSpecialAbilities, n => ({ tag: "LiturgicalStyleSpecialAbility", liturgical_style_special_ability: n })],
[database.magicStyleSpecialAbilities, n => ({ tag: "MagicStyleSpecialAbility", magic_style_special_ability: n })],
[database.magicalSpecialAbilities, n => ({ tag: "MagicalSpecialAbility", magical_special_ability: n })],
[database.magicalTraditions, n => ({ tag: "MagicalTradition", magical_tradition: n })],
[database.sexSpecialAbilities, n => ({ tag: "SexSpecialAbility", sex_special_ability: n })],
]
entries.forEach(([entityEntries, createId]) => {
entityEntries.forEach(([numericId, activatable]) => {
addNewApplicationsAndUses(activatable, createId(numericId))
})
})
return cache
}
}
+5
View File
@@ -0,0 +1,5 @@
import { ValidResults } from "./main.js"
export type CacheConfig<T> = {
builder: (data: ValidResults) => T
}
+9
View File
@@ -0,0 +1,9 @@
import * as NewApplicationsAndUses from "../cache/newApplicationsAndUses.js"
export type CacheMap = {
newApplicationsAndUses: NewApplicationsAndUses.NewApplicationsAndUsesCache
}
export const cacheMap = {
newApplicationsAndUses: NewApplicationsAndUses.config
}
+50
View File
@@ -1,3 +1,7 @@
import { mkdir, readFile, writeFile } from "node:fs/promises"
import { dirname } from "node:path"
import { CacheConfig } from "./cacheConfig.js"
import { CacheMap, cacheMap } from "./config/cache.js"
import { TypeId, TypeMap } from "./config/types.js"
import "./helpers/array.js"
import { mapSecond } from "./helpers/pair.js"
@@ -134,3 +138,49 @@ export const getRawResults = async (
const rawResultMap = await getRawValidationResults(entityDirPaths, options)
return Object.fromEntries(rawResultMap) as unknown as RawResults
}
/**
* A dictionary of cache types and their associated location.
*/
export type CachePaths = {
[K in keyof CacheMap]: string
}
export type CacheOptions = {
/**
* Whether to pretty-print the JSON. Default is `false`.
*/
pretty?: boolean
}
/**
* Builds the cache and writes it to the specified paths in JSON format.
* @param cachePaths The absolute paths to write each cache to.
* @param validResults The data to build the cache from. Usually the result of
* `getAllValidData`.
* @param options Configuration options for building the cache.
*/
export const buildCache = async (cachePaths: CachePaths, validResults: ValidResults, options: CacheOptions = {}): Promise<void> => {
const { pretty = false } = options
for (const [cacheName, cachePath] of Object.entries(cachePaths)) {
const cacheConfig: CacheConfig<unknown> = cacheMap[cacheName as keyof CacheMap]
const cacheData = cacheConfig.builder(validResults)
await mkdir(dirname(cachePath), { recursive: true })
await writeFile(cachePath, JSON.stringify(cacheData, null, pretty ? 2 : undefined), "utf-8")
}
}
/**
* Reads the cache from the specified paths in JSON format.
* @param cachePaths The absolute paths to read each cache from.
* @returns
*/
export const getCache = async (cachePaths: CachePaths): Promise<CacheMap> => {
const cache: Partial<CacheMap> = {}
for (const [cacheName, cachePath] of Object.entries(cachePaths)) {
const cacheConfig: CacheConfig<unknown> = cacheMap[cacheName as keyof CacheMap]
const cacheData = JSON.parse(await readFile(cachePath, "utf-8"))
cache[cacheName as keyof CacheMap] = cacheData
}
return cache as CacheMap
}