Skip to content

Commit

Permalink
feat(store-sync): add lru cache to recs encode/decode entity (lattice…
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored May 10, 2024
1 parent 3dbf3bf commit a3f8d77
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 7 deletions.
22 changes: 22 additions & 0 deletions packages/common/src/LruMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Map with a LRU (least recently used) policy.
*
* @link https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
* @link https://github.com/wevm/viem/blob/0fa08e113a890e6672fdc64fa7a2206a840611ab/src/utils/lru.ts
*/
export class LruMap<key, value> extends Map<key, value> {
maxSize: number;

constructor(size: number) {
super();
this.maxSize = size;
}

override set(key: key, value: value): this {
super.set(key, value);
if (this.maxSize && this.size > this.maxSize) {
this.delete(this.keys().next().value);
}
return this;
}
}
1 change: 1 addition & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from "./getBurnerPrivateKey";
export * from "./getNonceManager";
export * from "./getNonceManagerId";
export * from "./hexToResource";
export * from "./LruMap";
export * from "./readHex";
export * from "./resourceToLabel";
export * from "./resourceToHex";
Expand Down
43 changes: 39 additions & 4 deletions packages/store-sync/src/recs/decodeEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@ import { Entity } from "@latticexyz/recs";
import { Hex, decodeAbiParameters } from "viem";
import { entityToHexKeyTuple } from "./entityToHexKeyTuple";
import { KeySchema, SchemaToPrimitives } from "@latticexyz/protocol-parser/internal";
import { LruMap } from "@latticexyz/common";

export function decodeEntity<TKeySchema extends KeySchema>(
keySchema: TKeySchema,
const caches = new Map<KeySchema, LruMap<Entity, SchemaToPrimitives<KeySchema>>>();

function getCache<keySchema extends KeySchema>(keySchema: keySchema): LruMap<Entity, SchemaToPrimitives<keySchema>> {
const cache = caches.get(keySchema);
if (cache != null) return cache as never;

const map = new LruMap<Entity, SchemaToPrimitives<keySchema>>(8096);
caches.set(keySchema, map);
return map;
}

export function _decodeEntity<keySchema extends KeySchema>(
keySchema: keySchema,
entity: Entity,
): SchemaToPrimitives<TKeySchema> {
): SchemaToPrimitives<keySchema> {
const hexKeyTuple = entityToHexKeyTuple(entity);
if (hexKeyTuple.length !== Object.keys(keySchema).length) {
throw new Error(
Expand All @@ -18,5 +30,28 @@ export function decodeEntity<TKeySchema extends KeySchema>(
key,
decodeAbiParameters([{ type }], hexKeyTuple[index] as Hex)[0],
]),
) as SchemaToPrimitives<TKeySchema>;
) as never;
}

// decoding can get expensive if we have thousands of entities, so we use a cache to ease this
export function decodeEntity<keySchema extends KeySchema>(
keySchema: keySchema,
entity: Entity,
): SchemaToPrimitives<keySchema> {
const cache = getCache(keySchema);

const cached = cache.get(entity);
if (cached != null) {
return cached as never;
}

const hexKeyTuple = entityToHexKeyTuple(entity);
if (hexKeyTuple.length !== Object.keys(keySchema).length) {
throw new Error(
`entity key tuple length ${hexKeyTuple.length} does not match key schema length ${Object.keys(keySchema).length}`,
);
}
const decoded = _decodeEntity(keySchema, entity);
cache.set(entity, decoded);
return decoded;
}
35 changes: 32 additions & 3 deletions packages/store-sync/src/recs/encodeEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@ import { Entity } from "@latticexyz/recs";
import { encodeAbiParameters } from "viem";
import { hexKeyTupleToEntity } from "./hexKeyTupleToEntity";
import { KeySchema, SchemaToPrimitives } from "@latticexyz/protocol-parser/internal";
import { LruMap } from "@latticexyz/common";

export function encodeEntity<TKeySchema extends KeySchema>(
keySchema: TKeySchema,
key: SchemaToPrimitives<TKeySchema>,
const caches = new Map<KeySchema, LruMap<SchemaToPrimitives<KeySchema>, Entity>>();

function getCache<keySchema extends KeySchema>(keySchema: keySchema): LruMap<SchemaToPrimitives<keySchema>, Entity> {
const cache = caches.get(keySchema);
if (cache != null) return cache as never;

const map = new LruMap<SchemaToPrimitives<keySchema>, Entity>(8096);
caches.set(keySchema, map);
return map;
}

export function _encodeEntity<keySchema extends KeySchema>(
keySchema: keySchema,
key: SchemaToPrimitives<keySchema>,
): Entity {
if (Object.keys(keySchema).length !== Object.keys(key).length) {
throw new Error(
Expand All @@ -16,3 +28,20 @@ export function encodeEntity<TKeySchema extends KeySchema>(
Object.entries(keySchema).map(([keyName, type]) => encodeAbiParameters([{ type }], [key[keyName]])),
);
}

// encoding can get expensive if we have thousands of entities, so we use a cache to ease this
export function encodeEntity<keySchema extends KeySchema>(
keySchema: keySchema,
key: SchemaToPrimitives<keySchema>,
): Entity {
const cache = getCache(keySchema);

const cached = cache.get(key);
if (cached != null) {
return cached as never;
}

const encoded = _encodeEntity(keySchema, key);
cache.set(key, encoded);
return encoded;
}

0 comments on commit a3f8d77

Please sign in to comment.