Skip to content

Commit

Permalink
Cache refactoring (#32)
Browse files Browse the repository at this point in the history
* BaseCache class added

* moved static methods to BaseCache class

* fix types and style

* fixed latest circom version

---------

Co-authored-by: Artem Chystiakov <artem.ch31@gmail.com>
  • Loading branch information
mllwchrry and Arvolear authored Oct 7, 2024
1 parent 32ff36c commit f412556
Show file tree
Hide file tree
Showing 21 changed files with 219 additions and 301 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solarity/hardhat-zkit",
"version": "0.4.2",
"version": "0.4.3",
"description": "The ultimate TypeScript environment for Circom development",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
Expand Down
167 changes: 167 additions & 0 deletions src/cache/BaseCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import fsExtra from "fs-extra";

import { Reporter } from "@src/reporter";
import { BaseCacheSchema, BaseCacheType } from "@src/types/cache/base-cache";

/**
* Generic class that manages a cache of file-related entries.
*
* This class is designed to manage a collection of cache entries for files, where each entry
* holds metadata or other relevant data associated with a file. It offers functionality to add, retrieve,
* and remove entries from the cache, as well as utilities to write the cache state to a file and clean
* up stale entries for files that no longer exist on the filesystem.
*
* The cache operates on a single generic type:
* - `T`: Represents the individual cache entry for each file, storing metadata or other details.
*
* Usage:
* Extend this class and specify the appropriate cache entry type to implement specific caching logic.
*/
export class BaseCache<T> {
private _cache!: BaseCacheType<T>;

constructor(
private _cacheVersion: string,
private _cacheSchema: BaseCacheSchema,
cachePath?: string,
) {
if (cachePath) {
this.readFromFile(cachePath);
} else {
this._cache = {
_format: this._cacheVersion,
files: {},
};
}
}

/**
* Clears the current cache data, resetting it to an empty state.
*
* This method initializes the cache with the version format and an empty file collection.
*/
public clearCache() {
this._cache = {
_format: this._cacheVersion,
files: {},
};
}

/**
* Reads cache data from the specified file and populates the cache with the loaded data.
*
* This method attempts to read the cache file from the given `cachePath`. If the file exists, it parses
* the content and validates it against the defined cache schema. If the data is valid, the cache is updated.
* If the data is invalid, an error is logged using the Reporter.
*
* @param cachePath The full path to the cache file from which to read the data.
*/
public readFromFile(cachePath: string) {
let cacheRaw: BaseCacheType<T> = {
_format: this._cacheVersion,
files: {},
};

if (fsExtra.pathExistsSync(cachePath)) {
cacheRaw = fsExtra.readJsonSync(cachePath, {
reviver: (_key: string, value: any): any => {
if (value != null && typeof value === "object" && "__bigintval__" in value) {
return BigInt(value["__bigintval__"]);
}

return value;
},
});
}

// Validate the correctness of the data read from the file using the Zod schema
const result = this._cacheSchema.safeParse(cacheRaw);

if (result.success) {
this._cache = result.data;
this.removeNonExistingFiles();

return;
}

this._cache = {
_format: this._cacheVersion,
files: {},
};

Reporter!.verboseLog("cache", "Errors during ZOD schema parsing: %o", [result.error]);
}

/**
* Removes cache entries for files that no longer exist.
*
* This method helps keep the cache up-to-date by deleting references
* to non-existent files, ensuring that the cache remains valid.
*/
public removeNonExistingFiles() {
Object.keys(this._cache.files).map((absolutePath) => {
if (!fsExtra.pathExistsSync(absolutePath)) {
this.removeEntry(absolutePath);
}
});
}

/**
* Writes the current cache state to the specified file.
*
* @param cacheFilePath The full path to the cache file where the state will be written.
*/
public async writeToFile(cacheFilePath: string) {
const jsonContent = JSON.stringify(
this._cache,
(_key, value) => {
if (typeof value === "bigint") {
return { __bigintval__: value.toString() };
}

return value;
},
2,
);

await fsExtra.outputFile(cacheFilePath, jsonContent);
}

/**
* Adds a file cache entry to the cache data using the specified absolute path
*
* @param absolutePath The absolute path to the circuit file
* @param entry The cache entry to be added for the specified file path
*/
public addFile(absolutePath: string, entry: T) {
this._cache.files[absolutePath] = entry;
}

/**
* Returns all stored cache entries
*
* @returns An array of all stored cache entries
*/
public getEntries(): T[] {
return Object.values(this._cache.files);
}

/**
* Returns the cache entry for the specified file path, or undefined if no entry exists
*
* @param file The absolute path to the circuit file
* @returns The stored cache entry or undefined if no entry is found
*/
public getEntry(file: string): T | undefined {
return this._cache.files[file];
}

/**
* Removes the cache entry for the specified file path from the cache
*
* @param file The absolute path to the circuit file
*/
public removeEntry(file: string) {
delete this._cache.files[file];
}
}
145 changes: 8 additions & 137 deletions src/cache/CircuitsCompileCache.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import fsExtra from "fs-extra";
import { isEqual } from "lodash";

import { CompileCacheSchema } from "./schemas";
import { CIRCUIT_COMPILE_CACHE_VERSION } from "../constants";

import { CompileCache, CompileCacheEntry } from "../types/cache";
import { BaseCache } from "@src/cache/BaseCache";
import { CompileCacheEntry } from "../types/cache";
import { CompileFlags } from "../types/core";
import { Reporter } from "../reporter";

/**
* Class that implements the caching logic for compiling circuits.
Expand All @@ -22,135 +21,7 @@ import { Reporter } from "../reporter";
* The caching mechanism enhances performance and efficiency, especially when dealing
* with large and complex circuit designs by minimizing redundant compilation efforts.
*/
class BaseCircuitsCompileCache {
/**
* Creates an instance of {@link BaseCircuitsCompileCache} with empty cache data
*
* @returns An instance of {@link BaseCircuitsCompileCache} initialized with empty cache data
*/
public static createEmpty(): BaseCircuitsCompileCache {
return new BaseCircuitsCompileCache({
_format: CIRCUIT_COMPILE_CACHE_VERSION,
files: {},
});
}

/**
* Creates an instance of {@link BaseCircuitsCompileCache} using the data read from the specified cache file
*
* @param circuitsCompileCachePath The full path to the compile cache file from which to read the data
* @returns A promise that resolves to an instance of {@link BaseCircuitsCompileCache} populated with the read data
*/
public static async readFromFile(circuitsCompileCachePath: string): Promise<BaseCircuitsCompileCache> {
let cacheRaw: CompileCache = {
_format: CIRCUIT_COMPILE_CACHE_VERSION,
files: {},
};

if (await fsExtra.pathExists(circuitsCompileCachePath)) {
cacheRaw = await fsExtra.readJson(circuitsCompileCachePath, {
reviver: (_key: string, value: any): any => {
if (value != null && typeof value === "object" && "__bigintval__" in value) {
return BigInt(value["__bigintval__"]);
}

return value;
},
});
}

// Validate the correctness of the data read from the file using the Zod schema
const result = CompileCacheSchema.safeParse(cacheRaw);

if (result.success) {
const circuitsCompileCache = new BaseCircuitsCompileCache(result.data);
await circuitsCompileCache.removeNonExistingFiles();

return circuitsCompileCache;
} else {
Reporter!.verboseLog("circuits-compile-cache", "Errors during ZOD schema parsing: %o", [result.error]);
}

return new BaseCircuitsCompileCache({
_format: CIRCUIT_COMPILE_CACHE_VERSION,
files: {},
});
}

constructor(private _compileCache: CompileCache) {}

/**
* Removes cache entries for files that no longer exist.
*
* This method helps keep the cache up-to-date by deleting references
* to non-existent files, ensuring that the cache remains valid.
*/
public async removeNonExistingFiles() {
await Promise.all(
Object.keys(this._compileCache.files).map(async (absolutePath) => {
if (!(await fsExtra.pathExists(absolutePath))) {
this.removeEntry(absolutePath);
}
}),
);
}

/**
* Writes the current cache state to the specified file
*
* @param circuitsCompileCachePath The full path to the compile cache file where the cache will be saved
*/
public async writeToFile(circuitsCompileCachePath: string) {
fsExtra.outputFileSync(
circuitsCompileCachePath,
JSON.stringify(this._compileCache, (_key, value) => {
if (typeof value === "bigint") {
return { __bigintval__: value.toString() };
}

return value;
}),
);
}

/**
* Adds a file cache entry to the cache data using the specified absolute path
*
* @param absolutePath The absolute path to the circuit file
* @param entry The cache entry to be added for the specified file path
*/
public addFile(absolutePath: string, entry: CompileCacheEntry) {
this._compileCache.files[absolutePath] = entry;
}

/**
* Returns all stored cache entries
*
* @returns An array of all stored cache entries
*/
public getEntries(): CompileCacheEntry[] {
return Object.values(this._compileCache.files);
}

/**
* Returns the cache entry for the specified file path, or undefined if no entry exists
*
* @param file The absolute path to the circuit file
* @returns The stored cache entry or undefined if no entry is found
*/
public getEntry(file: string): CompileCacheEntry | undefined {
return this._compileCache.files[file];
}

/**
* Removes the cache entry for the specified file path from the cache
*
* @param file The absolute path to the circuit file
*/
public removeEntry(file: string) {
delete this._compileCache.files[file];
}

class BaseCircuitsCompileCache extends BaseCache<CompileCacheEntry> {
/**
* Checks if the specified file has changed since the last check based on its content hash and compile flags.
*
Expand Down Expand Up @@ -205,11 +76,11 @@ export async function createCircuitsCompileCache(circuitsCompileCachePath?: stri
return;
}

if (circuitsCompileCachePath) {
CircuitsCompileCache = await BaseCircuitsCompileCache.readFromFile(circuitsCompileCachePath);
} else {
CircuitsCompileCache = BaseCircuitsCompileCache.createEmpty();
}
CircuitsCompileCache = new BaseCircuitsCompileCache(
CIRCUIT_COMPILE_CACHE_VERSION,
CompileCacheSchema,
circuitsCompileCachePath,
);
}

/**
Expand Down
Loading

0 comments on commit f412556

Please sign in to comment.