@@ -25,9 +25,9 @@ namespace ts {
2525 export type FileWatcherCallback = ( fileName : string , eventKind : FileWatcherEventKind ) => void ;
2626 export type DirectoryWatcherCallback = ( fileName : string ) => void ;
2727 export interface WatchedFile {
28- fileName : string ;
29- callback : FileWatcherCallback ;
30- mtime ? : Date ;
28+ readonly fileName : string ;
29+ readonly callback : FileWatcherCallback ;
30+ mtime : Date ;
3131 }
3232
3333 /* @internal */
@@ -37,7 +37,13 @@ namespace ts {
3737 Low
3838 }
3939
40- const pollingIntervalsForPriority = [ 250 , 1000 , 4000 ] ;
40+ function getPriorityValues ( highPriorityValue : number ) : [ number , number , number ] {
41+ const mediumPriorityValue = highPriorityValue * 2 ;
42+ const lowPriorityValue = mediumPriorityValue * 4 ;
43+ return [ highPriorityValue , mediumPriorityValue , lowPriorityValue ] ;
44+ }
45+
46+ const pollingIntervalsForPriority = getPriorityValues ( 250 ) ;
4147 function pollingInterval ( watchPriority : WatchPriority ) : number {
4248 return pollingIntervalsForPriority [ watchPriority ] ;
4349 }
@@ -47,6 +53,201 @@ namespace ts {
4753 return host . watchFile ( fileName , callback , pollingInterval ( watchPriority ) ) ;
4854 }
4955
56+ /* @internal */
57+ export interface DynamicPriorityPollingStatsSet {
58+ watchFile ( fileName : string , callback : FileWatcherCallback , defaultPriority : WatchPriority ) : FileWatcher ;
59+ }
60+
61+ /* @internal */
62+ export const missingFileModifiedTime = new Date ( 0 ) ; // Any subsequent modification will occur after this time
63+ /* @internal */
64+ export function createDynamicPriorityPollingStatsSet ( host : System ) : DynamicPriorityPollingStatsSet {
65+ if ( ! host . getModifiedTime || ! host . setTimeout ) {
66+ throw notImplemented ( ) ;
67+ }
68+
69+ interface WatchedFile extends ts . WatchedFile {
70+ isClosed ?: boolean ;
71+ unchangedPolls : number ;
72+ }
73+
74+ interface WatchPriorityQueue extends Array < WatchedFile > {
75+ watchPriority : WatchPriority ;
76+ pollIndex : number ;
77+ }
78+
79+ const chunkSizes = getPriorityValues ( 32 ) ;
80+ const unChangedThresholds = getPriorityValues ( 32 ) ;
81+ const watchedFiles : WatchedFile [ ] = [ ] ;
82+ const changedFilesInLastPoll : WatchedFile [ ] = [ ] ;
83+ const priorityQueues = [ createPriorityQueue ( WatchPriority . High ) , createPriorityQueue ( WatchPriority . Medium ) , createPriorityQueue ( WatchPriority . Low ) ] ;
84+ return {
85+ watchFile
86+ } ;
87+
88+ function watchFile ( fileName : string , callback : FileWatcherCallback , defaultPriority : WatchPriority ) : FileWatcher {
89+ const file : WatchedFile = {
90+ fileName,
91+ callback,
92+ unchangedPolls : 0 ,
93+ mtime : getModifiedTime ( fileName )
94+ } ;
95+ watchedFiles . push ( file ) ;
96+
97+ addToPriorityQueue ( file , defaultPriority ) ;
98+ return {
99+ close : ( ) => {
100+ file . isClosed = true ;
101+ // Remove from watchedFiles
102+ unorderedRemoveItem ( watchedFiles , file ) ;
103+ // Do not update priority queue since that will happen as part of polling
104+ }
105+ } ;
106+ }
107+
108+ function createPriorityQueue ( watchPriority : WatchPriority ) : WatchPriorityQueue {
109+ const queue = [ ] as WatchPriorityQueue ;
110+ queue . watchPriority = watchPriority ;
111+ queue . pollIndex = 0 ;
112+ return queue ;
113+ }
114+
115+ function pollPriorityQueue ( queue : WatchPriorityQueue ) {
116+ const priority = queue . watchPriority ;
117+ queue . pollIndex = pollQueue ( queue , priority , queue . pollIndex , chunkSizes [ priority ] ) ;
118+ // Set the next polling index and timeout
119+ if ( queue . length ) {
120+ scheduleNextPoll ( priority ) ;
121+ }
122+ else {
123+ Debug . assert ( queue . pollIndex === 0 ) ;
124+ }
125+ }
126+
127+ function pollHighPriorityQueue ( queue : WatchPriorityQueue ) {
128+ // Always poll complete list of changedFilesInLastPoll
129+ pollQueue ( changedFilesInLastPoll , WatchPriority . High , /*pollIndex*/ 0 , changedFilesInLastPoll . length ) ;
130+
131+ // Finally do the actual polling of the queue
132+ pollPriorityQueue ( queue ) ;
133+ // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue
134+ // as pollPriorityQueue wont schedule for next poll
135+ if ( ! queue . length && changedFilesInLastPoll . length ) {
136+ scheduleNextPoll ( WatchPriority . High ) ;
137+ }
138+ }
139+
140+ function pollQueue ( queue : WatchedFile [ ] , priority : WatchPriority , pollIndex : number , chunkSize : number ) {
141+ // Max visit would be all elements of the queue
142+ let needsVisit = queue . length ;
143+ let definedValueCopyToIndex = pollIndex ;
144+ const unChangedThreshold = unChangedThresholds [ priority ] ;
145+ for ( let polled = 0 ; polled < chunkSize && needsVisit > 0 ; nextPollIndex ( ) , needsVisit -- ) {
146+ const watchedFile = queue [ pollIndex ] ;
147+ if ( ! watchedFile ) {
148+ continue ;
149+ }
150+ else if ( watchedFile . isClosed ) {
151+ queue [ pollIndex ] = undefined ;
152+ continue ;
153+ }
154+
155+ polled ++ ;
156+ const fileChanged = onWatchedFileStat ( watchedFile , getModifiedTime ( watchedFile . fileName ) ) ;
157+ if ( watchedFile . isClosed ) {
158+ // Closed watcher as part of callback
159+ queue [ pollIndex ] = undefined ;
160+ }
161+ else if ( fileChanged ) {
162+ watchedFile . unchangedPolls = 0 ;
163+ // Changed files go to changedFilesInLastPoll queue
164+ if ( queue !== changedFilesInLastPoll && priority !== WatchPriority . High ) {
165+ queue [ pollIndex ] = undefined ;
166+ addChangedFileToHighPriorityQueue ( watchedFile ) ;
167+ }
168+ }
169+ else if ( watchedFile . unchangedPolls !== unChangedThreshold ) {
170+ watchedFile . unchangedPolls ++ ;
171+ }
172+ else if ( queue === changedFilesInLastPoll ) {
173+ // Restart unchangedPollCount for unchanged file and move to high priority queue
174+ watchedFile . unchangedPolls = 0 ;
175+ addToPriorityQueue ( watchedFile , WatchPriority . High ) ;
176+ }
177+ else if ( priority !== WatchPriority . Low ) {
178+ watchedFile . unchangedPolls ++ ;
179+ queue [ pollIndex ] = undefined ;
180+ addToPriorityQueue ( watchedFile , priority + 1 ) ;
181+ }
182+
183+ if ( queue [ pollIndex ] ) {
184+ // Copy this file to the non hole location
185+ if ( definedValueCopyToIndex < pollIndex ) {
186+ queue [ definedValueCopyToIndex ] = watchedFile ;
187+ queue [ pollIndex ] = undefined ;
188+ }
189+ definedValueCopyToIndex ++ ;
190+ }
191+ }
192+
193+ // Return next poll index
194+ return pollIndex ;
195+
196+ function nextPollIndex ( ) {
197+ pollIndex ++ ;
198+ if ( pollIndex === queue . length ) {
199+ if ( definedValueCopyToIndex < pollIndex ) {
200+ // There are holes from nextDefinedValueIndex to end of queue, change queue size
201+ queue . length = definedValueCopyToIndex ;
202+ }
203+ pollIndex = 0 ;
204+ definedValueCopyToIndex = 0 ;
205+ }
206+ }
207+ }
208+
209+ function addToPriorityQueue ( file : WatchedFile , priority : WatchPriority ) {
210+ if ( priorityQueues [ priority ] . push ( file ) === 1 ) {
211+ scheduleNextPoll ( priority ) ;
212+ }
213+ }
214+
215+ function addChangedFileToHighPriorityQueue ( file : WatchedFile ) {
216+ if ( changedFilesInLastPoll . push ( file ) === 1 && ! priorityQueues [ WatchPriority . High ] . length ) {
217+ scheduleNextPoll ( WatchPriority . High ) ;
218+ }
219+ }
220+
221+ function scheduleNextPoll ( priority : WatchPriority ) {
222+ host . setTimeout ( priority === WatchPriority . High ? pollHighPriorityQueue : pollPriorityQueue , pollingInterval ( priority ) , priorityQueues [ priority ] ) ;
223+ }
224+
225+ function getModifiedTime ( fileName : string ) {
226+ return host . getModifiedTime ( fileName ) || missingFileModifiedTime ;
227+ }
228+ }
229+
230+ /**
231+ * Returns true if file status changed
232+ */
233+ /*@internal */
234+ export function onWatchedFileStat ( watchedFile : WatchedFile , modifiedTime : Date ) : boolean {
235+ const oldTime = watchedFile . mtime . getTime ( ) ;
236+ const newTime = modifiedTime . getTime ( ) ;
237+ if ( oldTime !== newTime ) {
238+ watchedFile . mtime = modifiedTime ;
239+ const eventKind = oldTime === 0
240+ ? FileWatcherEventKind . Created
241+ : newTime === 0
242+ ? FileWatcherEventKind . Deleted
243+ : FileWatcherEventKind . Changed ;
244+ watchedFile . callback ( watchedFile . fileName , eventKind ) ;
245+ return true ;
246+ }
247+
248+ return false ;
249+ }
250+
50251 /**
51252 * Partial interface of the System thats needed to support the caching of directory structure
52253 */
0 commit comments