@@ -19,6 +19,11 @@ interface PendingCall {
1919 reject : ( reason : any ) => void
2020}
2121
22+ interface Shard {
23+ worker : Worker
24+ globalIndices : number [ ]
25+ }
26+
2227const DEFAULT_MAX_WORKERS = 8
2328
2429function getDefaultWorkerCount ( ) : number {
@@ -31,8 +36,9 @@ function getDefaultWorkerCount(): number {
3136export default class FuseWorker < T > {
3237 private _options : IFuseOptions < T >
3338 private _workerOptions : FuseWorkerOptions
34- private _docs : ReadonlyArray < T >
35- private _workers : Worker [ ] | null = null
39+ private _docs : T [ ]
40+ private _shards : Shard [ ] | null = null
41+ private _addCursor = 0
3642 private _initPromise : Promise < void > | null = null
3743 private _pending : Map < number , PendingCall > = new Map ( )
3844 private _nextId = 0
@@ -43,7 +49,7 @@ export default class FuseWorker<T> {
4349 options ?: IFuseOptions < T > ,
4450 workerOptions ?: FuseWorkerOptions
4551 ) {
46- this . _docs = docs
52+ this . _docs = docs . slice ( )
4753 this . _options = options || { } as IFuseOptions < T >
4854 this . _workerOptions = workerOptions || { }
4955 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -63,37 +69,48 @@ export default class FuseWorker<T> {
6369 return this . _initPromise
6470 }
6571
72+ private _spawnWorker ( ) : Worker {
73+ const worker = new Worker ( this . _workerUrl , { type : 'module' } )
74+
75+ worker . onmessage = ( e : MessageEvent ) => {
76+ const { id, result, error } = e . data
77+ const handler = this . _pending . get ( id )
78+ if ( ! handler ) return
79+ this . _pending . delete ( id )
80+ if ( error ) {
81+ handler . reject ( new Error ( error ) )
82+ } else {
83+ handler . resolve ( result )
84+ }
85+ }
86+
87+ worker . onerror = ( e : ErrorEvent ) => {
88+ for ( const [ , handler ] of this . _pending ) {
89+ handler . reject ( new Error ( e . message ) )
90+ }
91+ }
92+
93+ return worker
94+ }
95+
6696 private async _init ( ) : Promise < void > {
6797 const numWorkers = this . _getNumWorkers ( )
6898 const chunkSize = Math . ceil ( this . _docs . length / numWorkers )
69- const initPromises : Promise < void > [ ] = [ ]
7099
71- this . _workers = [ ]
100+ this . _shards = [ ]
101+ this . _addCursor = 0
72102
103+ const initPromises : Promise < void > [ ] = [ ]
73104 for ( let i = 0 ; i < numWorkers ; i ++ ) {
74- const chunk = this . _docs . slice ( i * chunkSize , ( i + 1 ) * chunkSize )
75- const worker = new Worker ( this . _workerUrl , { type : 'module' } )
76-
77- worker . onmessage = ( e : MessageEvent ) => {
78- const { id, result, error } = e . data
79- const handler = this . _pending . get ( id )
80- if ( ! handler ) return
81- this . _pending . delete ( id )
82- if ( error ) {
83- handler . reject ( new Error ( error ) )
84- } else {
85- handler . resolve ( result )
86- }
87- }
88-
89- worker . onerror = ( e : ErrorEvent ) => {
90- for ( const [ , handler ] of this . _pending ) {
91- handler . reject ( new Error ( e . message ) )
92- }
93- }
94-
95- this . _workers . push ( worker )
96- initPromises . push ( this . _call ( worker , 'init' , [ chunk , this . _options ] ) )
105+ const start = i * chunkSize
106+ const end = Math . min ( start + chunkSize , this . _docs . length )
107+ const chunk = this . _docs . slice ( start , end )
108+ const globalIndices : number [ ] = [ ]
109+ for ( let j = start ; j < end ; j += 1 ) globalIndices . push ( j )
110+
111+ const shard : Shard = { worker : this . _spawnWorker ( ) , globalIndices }
112+ this . _shards . push ( shard )
113+ initPromises . push ( this . _call ( shard . worker , 'init' , [ chunk , this . _options ] ) )
97114 }
98115
99116 await Promise . all ( initPromises )
@@ -113,14 +130,18 @@ export default class FuseWorker<T> {
113130 ) : Promise < FuseResult < T > [ ] > {
114131 await this . _ensureInit ( )
115132
116- const results = await Promise . all (
117- this . _workers ! . map ( ( worker ) => this . _call ( worker , 'search' , [ query , options ] ) )
133+ const shards = this . _shards !
134+ const results : FuseResult < T > [ ] [ ] = await Promise . all (
135+ shards . map ( ( s ) => this . _call ( s . worker , 'search' , [ query , options ] ) )
118136 )
119137
120- // Merge results from all shards
138+ // Merge results from all shards, rewriting refIndex from shard-local to global
121139 const merged : FuseResult < T > [ ] = [ ]
122- for ( const shardResults of results ) {
123- merged . push ( ...shardResults )
140+ for ( let i = 0 , len = results . length ; i < len ; i += 1 ) {
141+ const { globalIndices } = shards [ i ]
142+ for ( const r of results [ i ] ) {
143+ merged . push ( { ...r , refIndex : globalIndices [ r . refIndex ] } )
144+ }
124145 }
125146
126147 // Sort by score (lower is better)
@@ -141,35 +162,50 @@ export default class FuseWorker<T> {
141162 async add ( doc : T ) : Promise < void > {
142163 await this . _ensureInit ( )
143164
144- // Round-robin across workers
145- const idx = this . _nextId % this . _workers ! . length
146- await this . _call ( this . _workers ! [ idx ] , 'add' , [ doc ] )
165+ const shards = this . _shards !
166+ const shard = shards [ this . _addCursor % shards . length ]
167+ this . _addCursor += 1
168+
169+ const globalIdx = this . _docs . length
170+ this . _docs . push ( doc )
171+ shard . globalIndices . push ( globalIdx )
172+
173+ await this . _call ( shard . worker , 'add' , [ doc ] )
147174 }
148175
149176 async setCollection ( docs : ReadonlyArray < T > ) : Promise < void > {
150- this . _docs = docs
151-
152- if ( this . _workers ) {
153- const numWorkers = this . _workers . length
154- const chunkSize = Math . ceil ( docs . length / numWorkers )
155-
156- await Promise . all (
157- this . _workers . map ( ( worker , i ) => {
158- const chunk = docs . slice ( i * chunkSize , ( i + 1 ) * chunkSize )
159- return this . _call ( worker , 'setCollection' , [ chunk ] )
160- } )
161- )
162- } else {
177+ this . _docs = docs . slice ( )
178+
179+ if ( ! this . _shards ) {
163180 this . _initPromise = null
181+ return
182+ }
183+
184+ const shards = this . _shards
185+ const chunkSize = Math . ceil ( this . _docs . length / shards . length )
186+ this . _addCursor = 0
187+
188+ const tasks : Promise < void > [ ] = [ ]
189+ for ( let i = 0 , len = shards . length ; i < len ; i += 1 ) {
190+ const start = i * chunkSize
191+ const end = Math . min ( start + chunkSize , this . _docs . length )
192+ const chunk = this . _docs . slice ( start , end )
193+ const globalIndices : number [ ] = [ ]
194+ for ( let j = start ; j < end ; j += 1 ) globalIndices . push ( j )
195+
196+ shards [ i ] . globalIndices = globalIndices
197+ tasks . push ( this . _call ( shards [ i ] . worker , 'setCollection' , [ chunk ] ) )
164198 }
199+
200+ await Promise . all ( tasks )
165201 }
166202
167203 terminate ( ) : void {
168- if ( this . _workers ) {
169- for ( const worker of this . _workers ) {
204+ if ( this . _shards ) {
205+ for ( const { worker } of this . _shards ) {
170206 worker . terminate ( )
171207 }
172- this . _workers = null
208+ this . _shards = null
173209 }
174210 this . _initPromise = null
175211
0 commit comments