impl friends system
This commit is contained in:
129
projects/common/src/cache.ts
Normal file
129
projects/common/src/cache.ts
Normal file
@ -0,0 +1,129 @@
|
||||
type CacheOptions = {
|
||||
/**
|
||||
* The time the cached object will be valid for
|
||||
*/
|
||||
ttl?: number;
|
||||
|
||||
/**
|
||||
* How often to check for expired objects
|
||||
*/
|
||||
checkInterval?: number;
|
||||
|
||||
/**
|
||||
* Enable debug messages
|
||||
*/
|
||||
debug?: boolean;
|
||||
};
|
||||
|
||||
type CachedObject = {
|
||||
/**
|
||||
* The cached object
|
||||
*/
|
||||
value: any;
|
||||
|
||||
/**
|
||||
* The timestamp the object was cached
|
||||
*/
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
export class SSRCache {
|
||||
/**
|
||||
* The time the cached object will be valid for
|
||||
* @private
|
||||
*/
|
||||
private readonly ttl: number | undefined;
|
||||
|
||||
/**
|
||||
* How often to check for expired objects
|
||||
* @private
|
||||
*/
|
||||
private readonly checkInterval: number | undefined;
|
||||
|
||||
/**
|
||||
* Enable debug messages
|
||||
* @private
|
||||
*/
|
||||
private readonly debug: boolean;
|
||||
|
||||
/**
|
||||
* The objects that have been cached
|
||||
* @private
|
||||
*/
|
||||
private cache = new Map<string, CachedObject>();
|
||||
|
||||
constructor({ ttl, checkInterval, debug }: CacheOptions) {
|
||||
this.ttl = ttl;
|
||||
this.checkInterval = checkInterval || this.ttl ? 1000 * 60 : undefined; // 1 minute
|
||||
this.debug = debug || false;
|
||||
|
||||
if (this.ttl !== undefined && this.checkInterval !== undefined) {
|
||||
setInterval(() => {
|
||||
for (const [key, value] of this.cache.entries()) {
|
||||
if (value.timestamp + this.ttl! > Date.now()) {
|
||||
continue;
|
||||
}
|
||||
this.remove(key);
|
||||
}
|
||||
}, this.checkInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object from the cache
|
||||
*
|
||||
* @param key the cache key for the object
|
||||
*/
|
||||
public get<T>(key: string): T | undefined {
|
||||
const cachedObject = this.cache.get(key);
|
||||
if (cachedObject === undefined) {
|
||||
if (this.debug) {
|
||||
console.log(`Cache miss for key: ${key}`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (this.debug) {
|
||||
console.log(`Retrieved ${key} from cache, value: ${JSON.stringify(cachedObject)}`);
|
||||
}
|
||||
return cachedObject.value as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an object in the cache
|
||||
*
|
||||
* @param key the cache key
|
||||
* @param value the object
|
||||
*/
|
||||
public set<T>(key: string, value: T): void {
|
||||
this.cache.set(key, {
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
if (this.debug) {
|
||||
console.log(`Inserted ${key} into cache, value: ${JSON.stringify(value)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an object is in the cache
|
||||
*
|
||||
* @param key the cache key
|
||||
*/
|
||||
public has(key: string): boolean {
|
||||
return this.cache.has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an object from the cache
|
||||
*
|
||||
* @param key the cache key
|
||||
*/
|
||||
public remove(key: string): void {
|
||||
this.cache.delete(key);
|
||||
|
||||
if (this.debug) {
|
||||
console.log(`Removed ${key} from cache`);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import ScoreSaberLeaderboardToken from "../../types/token/scoresaber/score-saber
|
||||
import ScoreSaberLeaderboardScoresPageToken from "../../types/token/scoresaber/score-saber-leaderboard-scores-page-token";
|
||||
import { clamp, lerp } from "../../utils/math-utils";
|
||||
import { CurvePoint } from "../../utils/curve-point";
|
||||
import { SSRCache } from "../../cache";
|
||||
|
||||
const API_BASE = "https://scoresaber.com/api";
|
||||
|
||||
@ -28,6 +29,10 @@ const LOOKUP_LEADERBOARD_SCORES_ENDPOINT = `${API_BASE}/leaderboard/by-id/:id/sc
|
||||
|
||||
const STAR_MULTIPLIER = 42.117208413;
|
||||
|
||||
const playerCache = new SSRCache({
|
||||
ttl: 60 * 30, // 30 minutes
|
||||
});
|
||||
|
||||
class ScoreSaberService extends Service {
|
||||
private curvePoints = [
|
||||
new CurvePoint(0, 0),
|
||||
@ -98,9 +103,13 @@ class ScoreSaberService extends Service {
|
||||
* Looks up a player by their ID.
|
||||
*
|
||||
* @param playerId the ID of the player to look up
|
||||
* @param cache whether to use the local cache
|
||||
* @returns the player that matches the ID, or undefined
|
||||
*/
|
||||
public async lookupPlayer(playerId: string): Promise<ScoreSaberPlayerToken | undefined> {
|
||||
public async lookupPlayer(playerId: string, cache: boolean = false): Promise<ScoreSaberPlayerToken | undefined> {
|
||||
if (cache && playerCache.has(playerId)) {
|
||||
return playerCache.get(playerId);
|
||||
}
|
||||
const before = performance.now();
|
||||
this.log(`Looking up player "${playerId}"...`);
|
||||
const token = await this.fetch<ScoreSaberPlayerToken>(LOOKUP_PLAYER_ENDPOINT.replace(":id", playerId));
|
||||
@ -108,6 +117,9 @@ class ScoreSaberService extends Service {
|
||||
return undefined;
|
||||
}
|
||||
this.log(`Found player "${playerId}" in ${(performance.now() - before).toFixed(0)}ms`);
|
||||
if (cache) {
|
||||
playerCache.set(playerId, token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user