Skip to content

Commit

Permalink
Enable cache eviction on specific document state (#1659)
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew authored Oct 2, 2024
1 parent d71b47b commit dc18342
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 12 deletions.
60 changes: 49 additions & 11 deletions packages/langium/src/utils/caching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import type { Disposable } from './disposable.js';
import type { URI } from './uri-utils.js';
import type { LangiumSharedCoreServices } from '../services.js';
import type { DocumentState } from '../workspace/documents.js';

export abstract class DisposableCache implements Disposable {

Expand Down Expand Up @@ -141,14 +142,35 @@ export class ContextCache<Context, Key, Value, ContextKey = Context> extends Dis
* If this document is changed or deleted, all associated key/value pairs are deleted.
*/
export class DocumentCache<K, V> extends ContextCache<URI | string, K, V, string> {
constructor(sharedServices: LangiumSharedCoreServices) {

/**
* Creates a new document cache.
*
* @param sharedServices Service container instance to hook into document lifecycle events.
* @param state Optional document state on which the cache should evict.
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
* Note that only *changed* documents are considered in this case.
*
* Providing a state here will use `DocumentBuilder#onDocumentPhase` instead,
* which triggers on all documents that have been affected by this change, assuming that the
* state is `DocumentState.Linked` or a later state.
*/
constructor(sharedServices: LangiumSharedCoreServices, state?: DocumentState) {
super(uri => uri.toString());
this.onDispose(sharedServices.workspace.DocumentBuilder.onUpdate((changed, deleted) => {
const allUris = changed.concat(deleted);
for (const uri of allUris) {
this.clear(uri);
}
}));
let disposable: Disposable;
if (state) {
disposable = sharedServices.workspace.DocumentBuilder.onDocumentPhase(state, document => {
this.clear(document.uri.toString());
});
} else {
disposable = sharedServices.workspace.DocumentBuilder.onUpdate((changed, deleted) => {
const allUris = changed.concat(deleted);
for (const uri of allUris) {
this.clear(uri);
}
});
}
this.toDispose.push(disposable);
}
}

Expand All @@ -157,10 +179,26 @@ export class DocumentCache<K, V> extends ContextCache<URI | string, K, V, string
* If any document in the workspace changes, the whole cache is evicted.
*/
export class WorkspaceCache<K, V> extends SimpleCache<K, V> {
constructor(sharedServices: LangiumSharedCoreServices) {

/**
* Creates a new workspace cache.
*
* @param sharedServices Service container instance to hook into document lifecycle events.
* @param state Optional document state on which the cache should evict.
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
*/
constructor(sharedServices: LangiumSharedCoreServices, state?: DocumentState) {
super();
this.onDispose(sharedServices.workspace.DocumentBuilder.onUpdate(() => {
this.clear();
}));
let disposable: Disposable;
if (state) {
disposable = sharedServices.workspace.DocumentBuilder.onBuildPhase(state, () => {
this.clear();
});
} else {
disposable = sharedServices.workspace.DocumentBuilder.onUpdate(() => {
this.clear();
});
}
this.toDispose.push(disposable);
}
}
26 changes: 25 additions & 1 deletion packages/langium/test/utils/caching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { beforeEach, describe, expect, test } from 'vitest';
import type { DefaultDocumentBuilder} from 'langium';
import { DocumentCache, EmptyFileSystem, URI, WorkspaceCache } from 'langium';
import { DocumentCache, DocumentState, EmptyFileSystem, URI, WorkspaceCache } from 'langium';
import { createLangiumGrammarServices } from 'langium/grammar';

const services = createLangiumGrammarServices(EmptyFileSystem);
Expand Down Expand Up @@ -83,6 +83,18 @@ describe('Document Cache', () => {
expect(() => cache.clear()).toThrow();
});

test('Document cache can trigger on document state', async () => {
const cache = new DocumentCache<string, string>(services.shared, DocumentState.Linked);
cache.set(document1.uri, 'key', 'value');
const documentPhase = workspace.DocumentBuilder.onDocumentPhase(DocumentState.ComputedScopes, () => {
expect(cache.get(document1.uri, 'key')).toBe('value');
});
await workspace.DocumentBuilder.update([document1.uri], []);
expect(cache.has(document1.uri, 'key')).toBe(false);
documentPhase.dispose();
cache.dispose();
});

});

describe('Workspace Cache', () => {
Expand Down Expand Up @@ -129,4 +141,16 @@ describe('Workspace Cache', () => {
expect(() => cache.delete('key')).toThrow();
expect(() => cache.clear()).toThrow();
});

test('Workspace cache can trigger on document state', async () => {
const cache = new WorkspaceCache<string, string>(services.shared, DocumentState.Linked);
cache.set('key', 'value');
const documentPhase = workspace.DocumentBuilder.onBuildPhase(DocumentState.ComputedScopes, () => {
expect(cache.get('key')).toBe('value');
});
await workspace.DocumentBuilder.update([document1.uri], []);
expect(cache.has('key')).toBe(false);
documentPhase.dispose();
cache.dispose();
});
});

0 comments on commit dc18342

Please sign in to comment.