Skip to content

Commit

Permalink
feat(cache): add hash based cache invalidation for Textures
Browse files Browse the repository at this point in the history
  • Loading branch information
meszaros-lajos-gyorgy committed Apr 29, 2024
1 parent 698ced1 commit 4694de8
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 8 deletions.
4 changes: 2 additions & 2 deletions src/EntityModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Texture } from '@src/Texture.js'
import { Vector3 } from '@src/Vector3.js'
import { repeat } from '@src/faux-ramda.js'
import { arrayPadRight, fileExists, roundToNDecimals } from '@src/helpers.js'
import { getCacheStats, saveHashOf } from '@services/cache.js'
import { getCacheStats, hashingAlgorithm, saveHashOf } from '@services/cache.js'
import { getNonIndexedVertices } from '@tools/mesh/getVertices.js'

type EntityModelConstructorProps = {
Expand Down Expand Up @@ -108,7 +108,7 @@ export class EntityModel {
)

const ftlData = this.generateFtl(entityName)
const hashOfFtlData = objectHash(ftlData)
const hashOfFtlData = objectHash(ftlData, { algorithm: hashingAlgorithm })

let binaryChanged = false
if (hashOfFtlData !== cachedBinary.hash || !cachedBinary.exists) {
Expand Down
24 changes: 20 additions & 4 deletions src/Texture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ClampToEdgeWrapping, Texture as ThreeJsTextue, UVMapping, MathUtils } f
import { Settings } from '@src/Settings.js'
import { any } from '@src/faux-ramda.js'
import { fileExists } from '@src/helpers.js'
import { createCacheFolderIfNotExists } from '@services/cache.js'
import { createCacheFolderIfNotExists, loadHashOf, getHashOfFile, saveHashOf } from '@services/cache.js'
import { getMetadata, getSharpInstance } from '@services/image.js'

export type TextureConstructorProps = {
Expand Down Expand Up @@ -167,10 +167,18 @@ export class Texture extends ThreeJsTextue {
const convertedSourceFolder = await createCacheFolderIfNotExists(this.sourcePath ?? Texture.targetPath, settings)
const convertedSource = path.join(convertedSourceFolder, newFilename)

const currentHash = await getHashOfFile(originalSource)

if (await fileExists(convertedSource)) {
return [convertedSource, convertedTarget]
const storedHash = await loadHashOf(originalSource, settings)

if (storedHash === currentHash) {
return [convertedSource, convertedTarget]
}
}

await saveHashOf(originalSource, currentHash, settings)

const image = await getSharpInstance(originalSource)

switch (ext) {
Expand Down Expand Up @@ -204,11 +212,19 @@ export class Texture extends ThreeJsTextue {
return [convertedSource, convertedTarget]
}

const currentHash = await getHashOfFile(originalSource)

if (await fileExists(convertedSource)) {
this.alreadyMadeTileable = true
return [convertedSource, convertedTarget]
const storedHash = await loadHashOf(originalSource, settings)

if (storedHash === currentHash) {
this.alreadyMadeTileable = true
return [convertedSource, convertedTarget]
}
}

await saveHashOf(originalSource, currentHash, settings)

const image = await getSharpInstance(originalSource)

const powerOfTwo = MathUtils.floorPowerOfTwo(this._width)
Expand Down
40 changes: 38 additions & 2 deletions src/services/cache.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import crypto from 'node:crypto'
import { createReadStream } from 'node:fs'
import fs from 'node:fs/promises'
import path from 'node:path'
import { Settings } from '@src/Settings.js'
import { fileExists } from '@src/helpers.js'

export const hashingAlgorithm = 'sha1'

/**
* Creates the folder structure inside the project's cache folder for a given path
* (supports nested folders)
Expand All @@ -23,13 +27,17 @@ export const createCacheFolderIfNotExists = async (folder: string, settings: Set
return fullFolder
}

/**
*
* @param filename - a pathname of a file relative to the project's root directory
*/
export const getCacheStats = async (filename: string, settings: Settings) => {
const { dir, base } = path.parse(filename)
const cachedFolder = await createCacheFolderIfNotExists(dir, settings)
const cachedFilename = path.join(cachedFolder, base)
const cacheExists = await fileExists(cachedFilename)

const hashOfCachedFilename = await getHashOf(cachedFilename, settings)
const hashOfCachedFilename = await loadHashOf(cachedFilename, settings)

return {
filename: cachedFilename,
Expand All @@ -41,8 +49,10 @@ export const getCacheStats = async (filename: string, settings: Settings) => {
/**
* This function assumes that the cache folder exists
* and that the hash of the cached file is in sync with the contents of the __hashes.json
*
* @param filename - full path to a file
*/
export const getHashOf = async (filename: string, settings: Settings) => {
export const loadHashOf = async (filename: string, settings: Settings) => {
const hashesFilename = path.resolve(settings.cacheFolder, '__hashes.json')

try {
Expand All @@ -53,6 +63,11 @@ export const getHashOf = async (filename: string, settings: Settings) => {
}
}

/**
* This function does not generate hashes, but merely stores them
*
* @param filename - full path to a file
*/
export const saveHashOf = async (filename: string, hash: string, settings: Settings) => {
const hashesFilename = path.resolve(settings.cacheFolder, '__hashes.json')

Expand All @@ -68,3 +83,24 @@ export const saveHashOf = async (filename: string, hash: string, settings: Setti

await fs.writeFile(hashesFilename, JSON.stringify(hashes), { encoding: 'utf-8' })
}

/**
* @param filename - full path to a file
*/
export const getHashOfFile = async (filename: string): Promise<string> => {
const hash = crypto.createHash(hashingAlgorithm)
const stream = createReadStream(filename)

stream.on('data', (chunk) => {
hash.update(chunk)
})

return new Promise((resolve, reject) => {
stream.on('error', (err) => {
reject(err)
})
stream.on('end', () => {
resolve(hash.digest('hex'))
})
})
}

0 comments on commit 4694de8

Please sign in to comment.