@@ -11,6 +11,41 @@ namespace Oxide.Core.Plugins
1111 /// </summary>
1212 public sealed class PluginManager
1313 {
14+ private enum SubscriptionChangeType : byte
15+ {
16+ Subscribe = 0 ,
17+ Unsubscribe = 1
18+ }
19+
20+ private struct SubscriptionChange
21+ {
22+ public Plugin Plugin { get ; }
23+
24+ public SubscriptionChangeType Change { get ; }
25+
26+ public SubscriptionChange ( Plugin plugin , SubscriptionChangeType type )
27+ {
28+ Plugin = plugin ;
29+ Change = type ;
30+ }
31+ }
32+
33+ private class HookSubscriptions
34+ {
35+ public IList < Plugin > Plugins { get ; }
36+
37+ public int CallDepth { get ; set ; }
38+
39+ public Queue < SubscriptionChange > PendingChanges { get ; }
40+
41+ public HookSubscriptions ( )
42+ {
43+ Plugins = new List < Plugin > ( ) ;
44+ PendingChanges = new Queue < SubscriptionChange > ( ) ;
45+ CallDepth = 0 ;
46+ }
47+ }
48+
1449 /// <summary>
1550 /// Gets the logger to which this plugin manager writes
1651 /// </summary>
@@ -35,20 +70,11 @@ public sealed class PluginManager
3570 private readonly IDictionary < string , Plugin > loadedPlugins ;
3671
3772 // All hook subscriptions
38- private readonly IDictionary < string , IList < Plugin > > hookSubscriptions ;
39-
40- // The current depth of hook calls
41- private int hookCallDepth ;
73+ private readonly IDictionary < string , HookSubscriptions > hookSubscriptions ;
4274
4375 // Stores the last time a deprecation warning was printed for a specific hook
4476 private readonly Dictionary < string , float > lastDeprecatedWarningAt = new Dictionary < string , float > ( ) ;
4577
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-
5278 // Re-usable conflict list used for hook calls
5379 private readonly List < string > hookConflicts = new List < string > ( ) ;
5480
@@ -59,7 +85,7 @@ public PluginManager(Logger logger)
5985 {
6086 // Initialize
6187 loadedPlugins = new Dictionary < string , Plugin > ( ) ;
62- hookSubscriptions = new Dictionary < string , IList < Plugin > > ( ) ;
88+ hookSubscriptions = new Dictionary < string , HookSubscriptions > ( ) ;
6389 Logger = logger ;
6490 }
6591
@@ -93,11 +119,15 @@ public bool RemovePlugin(Plugin plugin)
93119 }
94120
95121 loadedPlugins . Remove ( plugin . Name ) ;
96- foreach ( IList < Plugin > list in hookSubscriptions . Values )
122+
123+ lock ( hookSubscriptions )
97124 {
98- if ( list . Contains ( plugin ) )
125+ foreach ( HookSubscriptions sub in hookSubscriptions . Values )
99126 {
100- list . Remove ( plugin ) ;
127+ if ( sub . Plugins . Contains ( plugin ) )
128+ {
129+ sub . Plugins . Remove ( plugin ) ;
130+ }
101131 }
102132 }
103133
@@ -134,32 +164,27 @@ internal void SubscribeToHook(string hook, Plugin plugin)
134164 return ;
135165 }
136166
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- }
167+ HookSubscriptions sublist ;
146168
147- if ( ! sublist . Contains ( plugin ) )
169+ lock ( hookSubscriptions )
170+ {
171+ if ( ! hookSubscriptions . TryGetValue ( hook , out sublist ) )
148172 {
149- sublist . Add ( plugin ) ;
173+ sublist = new HookSubscriptions ( ) ;
174+ hookSubscriptions [ hook ] = sublist ;
150175 }
151-
152- return ;
153176 }
154177
155- if ( ! hookSubscriptions . TryGetValue ( hook , out sublist ) )
178+ // Avoids modifying the plugin list while iterating over it during a hook call
179+ if ( sublist . CallDepth > 0 )
156180 {
157- sublist = new List < Plugin > ( ) ;
158- hookSubscriptions . Add ( hook , sublist ) ;
181+ sublist . PendingChanges . Enqueue ( new SubscriptionChange ( plugin , SubscriptionChangeType . Subscribe ) ) ;
182+ return ;
159183 }
160- if ( ! sublist . Contains ( plugin ) )
184+
185+ if ( ! sublist . Plugins . Contains ( plugin ) )
161186 {
162- sublist . Add ( plugin ) ;
187+ sublist . Plugins . Add ( plugin ) ;
163188 }
164189 //Logger.Write(LogType.Debug, $"Plugin {plugin.Name} is subscribing to hook '{hook}'!");
165190 }
@@ -176,27 +201,24 @@ internal void UnsubscribeToHook(string hook, Plugin plugin)
176201 return ;
177202 }
178203
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- }
204+ HookSubscriptions sublist ;
188205
189- if ( ! sublist . Contains ( plugin ) )
206+ lock ( hookSubscriptions )
207+ {
208+ if ( ! hookSubscriptions . TryGetValue ( hook , out sublist ) )
190209 {
191- sublist . Add ( plugin ) ;
210+ return ;
192211 }
193-
194- return ;
195212 }
196- if ( hookSubscriptions . TryGetValue ( hook , out sublist ) && sublist . Contains ( plugin ) )
213+
214+ // Avoids modifying the plugin list while iterating over it during a hook call
215+ if ( sublist . CallDepth > 0 )
197216 {
198- sublist . Remove ( plugin ) ;
217+ sublist . PendingChanges . Enqueue ( new SubscriptionChange ( plugin , SubscriptionChangeType . Unsubscribe ) ) ;
218+ return ;
199219 }
220+
221+ sublist . Plugins . Remove ( plugin ) ;
200222 //Logger.Write(LogType.Debug, $"Plugin {plugin.Name} is unsubscribing to hook '{hook}'!");
201223 }
202224
@@ -208,36 +230,43 @@ internal void UnsubscribeToHook(string hook, Plugin plugin)
208230 /// <returns></returns>
209231 public object CallHook ( string hook , params object [ ] args )
210232 {
233+ HookSubscriptions subscriptions ;
234+
211235 // Locate the sublist
212- if ( ! hookSubscriptions . TryGetValue ( hook , out IList < Plugin > plugins ) )
236+ lock ( hookSubscriptions )
213237 {
214- return null ;
238+ if ( ! hookSubscriptions . TryGetValue ( hook , out subscriptions ) )
239+ {
240+ return null ;
241+ }
215242 }
216243
217- if ( plugins . Count == 0 )
244+
245+ if ( subscriptions . Plugins . Count == 0 )
218246 {
219247 return null ;
220248 }
221249
222250 // Loop each item
223- object [ ] values = ArrayPool . Get ( plugins . Count ) ;
251+ object [ ] values = ArrayPool . Get ( subscriptions . Plugins . Count ) ;
224252 int returnCount = 0 ;
225253 object finalValue = null ;
226254 Plugin finalPlugin = null ;
227255
228- hookCallDepth ++ ;
256+ subscriptions . CallDepth ++ ;
229257
230258 try
231259 {
232- for ( int i = 0 ; i < plugins . Count ; i ++ )
260+ for ( int i = 0 ; i < subscriptions . Plugins . Count ; i ++ )
233261 {
262+ Plugin plugin = subscriptions . Plugins [ i ] ;
234263 // Call the hook
235- object value = plugins [ i ] . CallHook ( hook , args ) ;
264+ object value = plugin . CallHook ( hook , args ) ;
236265 if ( value != null )
237266 {
238267 values [ i ] = value ;
239268 finalValue = value ;
240- finalPlugin = plugins [ i ] ;
269+ finalPlugin = plugin ;
241270 returnCount ++ ;
242271 }
243272 }
@@ -253,8 +282,9 @@ public object CallHook(string hook, params object[] args)
253282 {
254283 // Notify log of hook conflict
255284 hookConflicts . Clear ( ) ;
256- for ( int i = 0 ; i < plugins . Count ; i ++ )
285+ for ( int i = 0 ; i < subscriptions . Plugins . Count ; i ++ )
257286 {
287+ Plugin plugin = subscriptions . Plugins [ i ] ;
258288 object value = values [ i ] ;
259289 if ( value == null )
260290 {
@@ -265,14 +295,14 @@ public object CallHook(string hook, params object[] args)
265295 {
266296 if ( ! values [ i ] . Equals ( finalValue ) )
267297 {
268- hookConflicts . Add ( $ "{ plugins [ i ] . Name } - { value } ({ value . GetType ( ) . Name } )") ;
298+ hookConflicts . Add ( $ "{ plugin . Name } - { value } ({ value . GetType ( ) . Name } )") ;
269299 }
270300 }
271301 else
272302 {
273303 if ( values [ i ] != finalValue )
274304 {
275- hookConflicts . Add ( $ "{ plugins [ i ] . Name } - { value } ({ value . GetType ( ) . Name } )") ;
305+ hookConflicts . Add ( $ "{ plugin . Name } - { value } ({ value . GetType ( ) . Name } )") ;
276306 }
277307 }
278308 }
@@ -286,49 +316,34 @@ public object CallHook(string hook, params object[] args)
286316 }
287317 finally
288318 {
289- hookCallDepth -- ;
290- if ( hookCallDepth == 0 )
319+ subscriptions . CallDepth -- ;
320+ if ( subscriptions . CallDepth == 0 )
291321 {
292- ProcessHookChanges ( ) ;
322+ ProcessHookChanges ( subscriptions ) ;
293323 }
294324 }
295325
296326 return finalValue ;
297327 }
298328
299- private void ProcessHookChanges ( )
329+ private void ProcessHookChanges ( HookSubscriptions subscriptions )
300330 {
301- foreach ( var pair in pendingHookSubscriptions )
331+ while ( subscriptions . PendingChanges . Count != 0 )
302332 {
303- if ( ! hookSubscriptions . TryGetValue ( pair . Key , out IList < Plugin > list ) )
304- {
305- list = new List < Plugin > ( ) ;
306- hookSubscriptions . Add ( pair . Key , list ) ;
307- }
333+ SubscriptionChange change = subscriptions . PendingChanges . Dequeue ( ) ;
308334
309- foreach ( var plugin in pair . Value )
335+ if ( change . Change == SubscriptionChangeType . Subscribe )
310336 {
311- if ( ! list . Contains ( plugin ) )
337+ if ( ! subscriptions . Plugins . Contains ( change . Plugin ) )
312338 {
313- list . Add ( plugin ) ;
339+ subscriptions . Plugins . Add ( change . Plugin ) ;
314340 }
315341 }
316- }
317-
318- pendingHookSubscriptions . Clear ( ) ;
319-
320- foreach ( var pair in pendingHookUnsubscriptions )
321- {
322- if ( hookSubscriptions . TryGetValue ( pair . Key , out IList < Plugin > list ) )
342+ else
323343 {
324- foreach ( var plugin in pair . Value )
325- {
326- list . Remove ( plugin ) ;
327- }
344+ subscriptions . Plugins . Remove ( change . Plugin ) ;
328345 }
329346 }
330-
331- pendingHookUnsubscriptions . Clear ( ) ;
332347 }
333348
334349 /// <summary>
@@ -341,21 +356,28 @@ private void ProcessHookChanges()
341356 /// <returns></returns>
342357 public object CallDeprecatedHook ( string oldHook , string newHook , DateTime expireDate , params object [ ] args )
343358 {
344- if ( ! hookSubscriptions . TryGetValue ( oldHook , out IList < Plugin > plugins ) )
359+ HookSubscriptions subscriptions ;
360+
361+ lock ( hookSubscriptions )
345362 {
346- return null ;
363+ if ( ! hookSubscriptions . TryGetValue ( oldHook , out subscriptions ) )
364+ {
365+ return null ;
366+ }
347367 }
348368
349- if ( plugins . Count == 0 )
369+
370+ if ( subscriptions . Plugins . Count == 0 )
350371 {
351372 return null ;
352373 }
353374
354375 float now = Interface . Oxide . Now ;
355376 if ( ! lastDeprecatedWarningAt . TryGetValue ( oldHook , out float lastWarningAt ) || now - lastWarningAt > 3600f )
356377 {
378+ // TODO: Add better handling
357379 lastDeprecatedWarningAt [ oldHook ] = now ;
358- Interface . Oxide . LogWarning ( $ "'{ plugins [ 0 ] . Name } v{ plugins [ 0 ] . Version } ' is using deprecated hook '{ oldHook } ', which will stop working on { expireDate . ToString ( "D" ) } . Please ask the author to update to '{ newHook } '") ;
380+ Interface . Oxide . LogWarning ( $ "'{ subscriptions . Plugins [ 0 ] . Name } v{ subscriptions . Plugins [ 0 ] . Version } ' is using deprecated hook '{ oldHook } ', which will stop working on { expireDate . ToString ( "D" ) } . Please ask the author to update to '{ newHook } '") ;
359381 }
360382
361383 return CallHook ( oldHook , args ) ;
0 commit comments