feat: initialize mini program map engine
This commit is contained in:
213
miniprogram/engine/renderer/tilePersistentCache.ts
Normal file
213
miniprogram/engine/renderer/tilePersistentCache.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
const STORAGE_KEY = 'cmr-tile-disk-cache-index-v1'
|
||||
const ROOT_DIR = `${wx.env.USER_DATA_PATH}/cmr-tile-cache`
|
||||
const MAX_PERSISTED_TILES = 600
|
||||
const PERSIST_DELAY_MS = 300
|
||||
|
||||
export interface TilePersistentCacheRecord {
|
||||
filePath: string
|
||||
lastAccessedAt: number
|
||||
}
|
||||
|
||||
type TilePersistentCacheIndex = Record<string, TilePersistentCacheRecord>
|
||||
|
||||
let sharedTilePersistentCache: TilePersistentCache | null = null
|
||||
|
||||
function getFileExtension(url: string): string {
|
||||
const matched = url.match(/\.(png|jpg|jpeg|webp)(?:$|\?)/i)
|
||||
if (!matched) {
|
||||
return '.tile'
|
||||
}
|
||||
|
||||
return `.${matched[1].toLowerCase()}`
|
||||
}
|
||||
|
||||
function hashUrl(url: string): string {
|
||||
let hash = 2166136261
|
||||
for (let index = 0; index < url.length; index += 1) {
|
||||
hash ^= url.charCodeAt(index)
|
||||
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)
|
||||
}
|
||||
|
||||
return (hash >>> 0).toString(36)
|
||||
}
|
||||
|
||||
function cloneIndex(rawIndex: any): TilePersistentCacheIndex {
|
||||
const nextIndex: TilePersistentCacheIndex = {}
|
||||
if (!rawIndex || typeof rawIndex !== 'object') {
|
||||
return nextIndex
|
||||
}
|
||||
|
||||
Object.keys(rawIndex).forEach((url) => {
|
||||
const record = rawIndex[url]
|
||||
if (!record || typeof record.filePath !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
nextIndex[url] = {
|
||||
filePath: record.filePath,
|
||||
lastAccessedAt: typeof record.lastAccessedAt === 'number' ? record.lastAccessedAt : 0,
|
||||
}
|
||||
})
|
||||
|
||||
return nextIndex
|
||||
}
|
||||
|
||||
export class TilePersistentCache {
|
||||
fs: WechatMiniprogram.FileSystemManager
|
||||
rootDir: string
|
||||
index: TilePersistentCacheIndex
|
||||
persistTimer: number
|
||||
|
||||
constructor() {
|
||||
this.fs = wx.getFileSystemManager()
|
||||
this.rootDir = ROOT_DIR
|
||||
this.index = {}
|
||||
this.persistTimer = 0
|
||||
|
||||
this.ensureRootDir()
|
||||
this.loadIndex()
|
||||
this.pruneMissingFiles()
|
||||
this.pruneIfNeeded()
|
||||
}
|
||||
|
||||
ensureRootDir(): void {
|
||||
try {
|
||||
this.fs.accessSync(this.rootDir)
|
||||
} catch {
|
||||
this.fs.mkdirSync(this.rootDir, true)
|
||||
}
|
||||
}
|
||||
|
||||
loadIndex(): void {
|
||||
try {
|
||||
this.index = cloneIndex(wx.getStorageSync(STORAGE_KEY))
|
||||
} catch {
|
||||
this.index = {}
|
||||
}
|
||||
}
|
||||
|
||||
getCount(): number {
|
||||
return Object.keys(this.index).length
|
||||
}
|
||||
|
||||
schedulePersist(): void {
|
||||
if (this.persistTimer) {
|
||||
return
|
||||
}
|
||||
|
||||
this.persistTimer = setTimeout(() => {
|
||||
this.persistTimer = 0
|
||||
wx.setStorageSync(STORAGE_KEY, this.index)
|
||||
}, PERSIST_DELAY_MS) as unknown as number
|
||||
}
|
||||
|
||||
pruneMissingFiles(): void {
|
||||
let changed = false
|
||||
|
||||
Object.keys(this.index).forEach((url) => {
|
||||
const record = this.index[url]
|
||||
try {
|
||||
this.fs.accessSync(record.filePath)
|
||||
} catch {
|
||||
delete this.index[url]
|
||||
changed = true
|
||||
}
|
||||
})
|
||||
|
||||
if (changed) {
|
||||
this.schedulePersist()
|
||||
}
|
||||
}
|
||||
|
||||
getCachedPath(url: string): string | null {
|
||||
const record = this.index[url]
|
||||
if (!record) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
this.fs.accessSync(record.filePath)
|
||||
record.lastAccessedAt = Date.now()
|
||||
this.schedulePersist()
|
||||
return record.filePath
|
||||
} catch {
|
||||
delete this.index[url]
|
||||
this.schedulePersist()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
getTargetPath(url: string): string {
|
||||
const existingRecord = this.index[url]
|
||||
if (existingRecord) {
|
||||
existingRecord.lastAccessedAt = Date.now()
|
||||
this.schedulePersist()
|
||||
return existingRecord.filePath
|
||||
}
|
||||
|
||||
const filePath = `${this.rootDir}/${hashUrl(url)}${getFileExtension(url)}`
|
||||
this.index[url] = {
|
||||
filePath,
|
||||
lastAccessedAt: Date.now(),
|
||||
}
|
||||
this.schedulePersist()
|
||||
return filePath
|
||||
}
|
||||
|
||||
markReady(url: string, filePath: string): void {
|
||||
this.index[url] = {
|
||||
filePath,
|
||||
lastAccessedAt: Date.now(),
|
||||
}
|
||||
this.pruneIfNeeded()
|
||||
this.schedulePersist()
|
||||
}
|
||||
|
||||
remove(url: string): void {
|
||||
const record = this.index[url]
|
||||
if (!record) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.fs.unlinkSync(record.filePath)
|
||||
} catch {
|
||||
// Ignore unlink errors for already-missing files.
|
||||
}
|
||||
|
||||
delete this.index[url]
|
||||
this.schedulePersist()
|
||||
}
|
||||
|
||||
pruneIfNeeded(): void {
|
||||
const urls = Object.keys(this.index)
|
||||
if (urls.length <= MAX_PERSISTED_TILES) {
|
||||
return
|
||||
}
|
||||
|
||||
const removableUrls = urls.sort((leftUrl, rightUrl) => {
|
||||
return this.index[leftUrl].lastAccessedAt - this.index[rightUrl].lastAccessedAt
|
||||
})
|
||||
|
||||
while (removableUrls.length > MAX_PERSISTED_TILES) {
|
||||
const nextUrl = removableUrls.shift() as string
|
||||
const record = this.index[nextUrl]
|
||||
if (record) {
|
||||
try {
|
||||
this.fs.unlinkSync(record.filePath)
|
||||
} catch {
|
||||
// Ignore unlink errors for already-missing files.
|
||||
}
|
||||
}
|
||||
delete this.index[nextUrl]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getTilePersistentCache(): TilePersistentCache {
|
||||
if (!sharedTilePersistentCache) {
|
||||
sharedTilePersistentCache = new TilePersistentCache()
|
||||
}
|
||||
|
||||
return sharedTilePersistentCache
|
||||
}
|
||||
Reference in New Issue
Block a user