Skip to content

Commit 69b2312

Browse files
committed
feat: Add context buffer for more efficient loading
1 parent 3b38099 commit 69b2312

File tree

3 files changed

+61
-5
lines changed

3 files changed

+61
-5
lines changed

src/schema/walk.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BIDSFile, FileOpener, FileTree } from '../types/filetree.ts'
33
import type { DatasetIssues } from '../issues/datasetIssues.ts'
44
import { loadTSV } from '../files/tsv.ts'
55
import { loadJSON } from '../files/json.ts'
6+
import { queuedAsyncIterator } from '../utils/queue.ts'
67

78
function* quickWalk(dir: FileTree): Generator<BIDSFile> {
89
for (const file of dir.files) {
@@ -38,11 +39,13 @@ function pseudoFile(dir: FileTree, opaque: boolean): BIDSFile {
3839
)
3940
}
4041

42+
type CleanupFunction = () => void
43+
4144
/** Recursive algorithm for visiting each file in the dataset, creating a context */
4245
async function* _walkFileTree(
4346
fileTree: FileTree,
4447
dsContext: BIDSContextDataset,
45-
): AsyncIterable<BIDSContext> {
48+
): AsyncIterable<BIDSContext | CleanupFunction> {
4649
for (const file of fileTree.files) {
4750
yield new BIDSContext(file, dsContext)
4851
}
@@ -56,13 +59,23 @@ async function* _walkFileTree(
5659
yield* _walkFileTree(dir, dsContext)
5760
}
5861
}
59-
loadTSV.cache.delete(fileTree.path)
60-
loadJSON.cache.delete(fileTree.path)
62+
yield () => {
63+
loadTSV.cache.delete(fileTree.path)
64+
loadJSON.cache.delete(fileTree.path)
65+
}
6166
}
6267

6368
/** Walk all files in the dataset and construct a context for each one */
6469
export async function* walkFileTree(
6570
dsContext: BIDSContextDataset,
71+
bufferSize: number = 1,
6672
): AsyncIterable<BIDSContext> {
67-
yield* _walkFileTree(dsContext.tree, dsContext)
73+
for await (
74+
await using context of queuedAsyncIterator(
75+
_walkFileTree(dsContext.tree, dsContext),
76+
bufferSize,
77+
)
78+
) {
79+
yield context
80+
}
6881
}

src/utils/queue.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
export async function* bufferAsyncIterator<T>(
2+
asyncIterator: AsyncIterable<T>,
3+
bufferSize: number = 5,
4+
): AsyncGenerator<T, void, undefined> {
5+
const iterator = asyncIterator[Symbol.asyncIterator]()
6+
const promises: Promise<IteratorResult<T>>[] = []
7+
8+
// Initialize buffer
9+
for (let i = 0; i < bufferSize; i++) {
10+
promises.push(iterator.next())
11+
}
12+
13+
while (promises.length > 0) {
14+
const result = await promises.shift()!
15+
16+
if (!result.done) {
17+
yield result.value
18+
// Keep buffer full
19+
promises.push(iterator.next())
20+
}
21+
}
22+
}
23+
24+
type CleanupFunction = () => void
25+
26+
export async function* cleanupAsyncIterator<T extends object>(
27+
asyncIterator: AsyncIterable<T | CleanupFunction>,
28+
): AsyncGenerator<T, void, undefined> {
29+
for await (const item of asyncIterator) {
30+
if (typeof item === 'function') {
31+
item()
32+
} else {
33+
yield item
34+
}
35+
}
36+
}
37+
38+
export async function* queuedAsyncIterator<T extends object>(
39+
asyncIterator: AsyncIterable<T | CleanupFunction>,
40+
bufferSize: number = 5,
41+
): AsyncGenerator<T, void, undefined> {
42+
yield* cleanupAsyncIterator(bufferAsyncIterator(asyncIterator, bufferSize))
43+
}

src/validators/bids.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export async function validate(
108108
return false
109109
})
110110

111-
for await (const context of walkFileTree(dsContext)) {
111+
for await (const context of walkFileTree(dsContext, 20)) {
112112
// TODO - Skip ignored files for now (some tests may reference ignored files)
113113
if (context.file.ignored) {
114114
continue

0 commit comments

Comments
 (0)