@@ -37,9 +37,18 @@ public sealed class PluginManager
3737 // All hook subscriptions
3838 private readonly IDictionary < string , IList < Plugin > > hookSubscriptions ;
3939
40+ // The current depth of hook calls
41+ private int hookCallDepth ;
42+
4043 // Stores the last time a deprecation warning was printed for a specific hook
4144 private readonly Dictionary < string , float > lastDeprecatedWarningAt = new Dictionary < string , float > ( ) ;
4245
46+ // Pending hook subscriptions
47+ private readonly Dictionary < string , IList < Plugin > > pendingHookSubscriptions = new Dictionary < string , IList < Plugin > > ( ) ;
48+
49+ // Pending hook unsubscriptions
50+ private readonly Dictionary < string , IList < Plugin > > pendingHookUnsubscriptions = new Dictionary < string , IList < Plugin > > ( ) ;
51+
4352 // Re-usable conflict list used for hook calls
4453 private readonly List < string > hookConflicts = new List < string > ( ) ;
4554
@@ -125,7 +134,25 @@ internal void SubscribeToHook(string hook, Plugin plugin)
125134 return ;
126135 }
127136
128- if ( ! hookSubscriptions . TryGetValue ( hook , out IList < Plugin > sublist ) )
137+ IList < Plugin > sublist ;
138+ // Avoids modifying the plugin list while iterating over it during a hook call
139+ if ( hookCallDepth > 0 )
140+ {
141+ if ( ! pendingHookSubscriptions . TryGetValue ( hook , out sublist ) )
142+ {
143+ sublist = new List < Plugin > ( ) ;
144+ pendingHookSubscriptions . Add ( hook , sublist ) ;
145+ }
146+
147+ if ( ! sublist . Contains ( plugin ) )
148+ {
149+ sublist . Add ( plugin ) ;
150+ }
151+
152+ return ;
153+ }
154+
155+ if ( ! hookSubscriptions . TryGetValue ( hook , out sublist ) )
129156 {
130157 sublist = new List < Plugin > ( ) ;
131158 hookSubscriptions . Add ( hook , sublist ) ;
@@ -149,7 +176,24 @@ internal void UnsubscribeToHook(string hook, Plugin plugin)
149176 return ;
150177 }
151178
152- if ( hookSubscriptions . TryGetValue ( hook , out IList < Plugin > sublist ) && sublist . Contains ( plugin ) )
179+ IList < Plugin > sublist ;
180+ // Avoids modifying the plugin list while iterating over it during a hook call
181+ if ( hookCallDepth > 0 )
182+ {
183+ if ( ! pendingHookUnsubscriptions . TryGetValue ( hook , out sublist ) )
184+ {
185+ sublist = new List < Plugin > ( ) ;
186+ pendingHookUnsubscriptions . Add ( hook , sublist ) ;
187+ }
188+
189+ if ( ! sublist . Contains ( plugin ) )
190+ {
191+ sublist . Add ( plugin ) ;
192+ }
193+
194+ return ;
195+ }
196+ if ( hookSubscriptions . TryGetValue ( hook , out sublist ) && sublist . Contains ( plugin ) )
153197 {
154198 sublist . Remove ( plugin ) ;
155199 }
@@ -180,64 +224,113 @@ public object CallHook(string hook, params object[] args)
180224 int returnCount = 0 ;
181225 object finalValue = null ;
182226 Plugin finalPlugin = null ;
183- for ( int i = 0 ; i < plugins . Count ; i ++ )
184- {
185- // Call the hook
186- object value = plugins [ i ] . CallHook ( hook , args ) ;
187- if ( value != null )
188- {
189- values [ i ] = value ;
190- finalValue = value ;
191- finalPlugin = plugins [ i ] ;
192- returnCount ++ ;
193- }
194- }
195227
196- // Is there a return value?
197- if ( returnCount == 0 )
198- {
199- ArrayPool . Free ( values ) ;
200- return null ;
201- }
228+ hookCallDepth ++ ;
202229
203- if ( returnCount > 1 && finalValue != null )
230+ try
204231 {
205- // Notify log of hook conflict
206- hookConflicts . Clear ( ) ;
207232 for ( int i = 0 ; i < plugins . Count ; i ++ )
208233 {
209- object value = values [ i ] ;
210- if ( value == null )
234+ // Call the hook
235+ object value = plugins [ i ] . CallHook ( hook , args ) ;
236+ if ( value != null )
211237 {
212- continue ;
238+ values [ i ] = value ;
239+ finalValue = value ;
240+ finalPlugin = plugins [ i ] ;
241+ returnCount ++ ;
213242 }
243+ }
244+
245+ // Is there a return value?
246+ if ( returnCount == 0 )
247+ {
248+ ArrayPool . Free ( values ) ;
249+ return null ;
250+ }
214251
215- if ( value . GetType ( ) . IsValueType )
252+ if ( returnCount > 1 && finalValue != null )
253+ {
254+ // Notify log of hook conflict
255+ hookConflicts . Clear ( ) ;
256+ for ( int i = 0 ; i < plugins . Count ; i ++ )
216257 {
217- if ( ! values [ i ] . Equals ( finalValue ) )
258+ object value = values [ i ] ;
259+ if ( value == null )
218260 {
219- hookConflicts . Add ( $ " { plugins [ i ] . Name } - { value } ( { value . GetType ( ) . Name } )" ) ;
261+ continue ;
220262 }
221- }
222- else
223- {
224- if ( values [ i ] != finalValue )
263+
264+ if ( value . GetType ( ) . IsValueType )
225265 {
226- hookConflicts . Add ( $ "{ plugins [ i ] . Name } - { value } ({ value . GetType ( ) . Name } )") ;
266+ if ( ! values [ i ] . Equals ( finalValue ) )
267+ {
268+ hookConflicts . Add ( $ "{ plugins [ i ] . Name } - { value } ({ value . GetType ( ) . Name } )") ;
269+ }
227270 }
271+ else
272+ {
273+ if ( values [ i ] != finalValue )
274+ {
275+ hookConflicts . Add ( $ "{ plugins [ i ] . Name } - { value } ({ value . GetType ( ) . Name } )") ;
276+ }
277+ }
278+ }
279+ if ( hookConflicts . Count > 0 )
280+ {
281+ hookConflicts . Add ( $ "{ finalPlugin . Name } ({ finalValue } ({ finalValue . GetType ( ) . Name } ))") ;
282+ Logger . Write ( LogType . Warning , "Calling hook {0} resulted in a conflict between the following plugins: {1}" , hook , string . Join ( ", " , hookConflicts . ToArray ( ) ) ) ;
228283 }
229284 }
230- if ( hookConflicts . Count > 0 )
285+ ArrayPool . Free ( values ) ;
286+ }
287+ finally
288+ {
289+ hookCallDepth -- ;
290+ if ( hookCallDepth == 0 )
231291 {
232- hookConflicts . Add ( $ "{ finalPlugin . Name } ({ finalValue } ({ finalValue . GetType ( ) . Name } ))") ;
233- Logger . Write ( LogType . Warning , "Calling hook {0} resulted in a conflict between the following plugins: {1}" , hook , string . Join ( ", " , hookConflicts . ToArray ( ) ) ) ;
292+ ProcessHookChanges ( ) ;
234293 }
235294 }
236- ArrayPool . Free ( values ) ;
237295
238296 return finalValue ;
239297 }
240298
299+ private void ProcessHookChanges ( )
300+ {
301+ foreach ( var pair in pendingHookSubscriptions )
302+ {
303+ if ( ! hookSubscriptions . TryGetValue ( pair . Key , out IList < Plugin > list ) )
304+ {
305+ list = new List < Plugin > ( ) ;
306+ hookSubscriptions . Add ( pair . Key , list ) ;
307+ }
308+
309+ foreach ( var plugin in pair . Value )
310+ {
311+ if ( ! list . Contains ( plugin ) )
312+ {
313+ list . Add ( plugin ) ;
314+ }
315+ }
316+ }
317+
318+ pendingHookSubscriptions . Clear ( ) ;
319+
320+ foreach ( var pair in pendingHookUnsubscriptions )
321+ {
322+ if ( hookSubscriptions . TryGetValue ( pair . Key , out IList < Plugin > list ) )
323+ {
324+ foreach ( var plugin in pair . Value )
325+ {
326+ list . Remove ( plugin ) ;
327+ }
328+ }
329+ }
330+
331+ pendingHookUnsubscriptions . Clear ( ) ;
332+ }
333+
241334 /// <summary>
242335 /// Calls a hook on all plugins of this manager and prints a deprecation warning
243336 /// </summary>
0 commit comments