11#region BSD 3-Clause License
2+
23// <copyright file="GraphWorker.cs" company="Edgerunner.org">
3- // Copyright 2020 Thaddeus Ryker
4+ // Copyright 2020
45// </copyright>
56//
67// BSD 3-Clause License
78//
8- // Copyright (c) 2020, Thaddeus Ryker
9+ // Copyright (c) 2020,
910// All rights reserved.
1011//
1112// Redistribution and use in source and binary forms, with or without
3233// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3334// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3435// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36+
3537#endregion
3638
3739using System ;
5052namespace Org . Edgerunner . ANTLR4 . Tools . Testing . GrunWin
5153{
5254 /// <summary>
53- /// Class that handles the work of text in the background.
54- /// Implements the <see cref="Org.Edgerunner.ANTLR4.Tools.Testing.GrunWin.IGraphWorker" />
55+ /// Class that handles the work of text in the background.
56+ /// Implements the <see cref="Org.Edgerunner.ANTLR4.Tools.Testing.GrunWin.IGraphWorker" />
5557 /// </summary>
5658 /// <remarks>This class is thread safe.</remarks>
5759 /// <seealso cref="Org.Edgerunner.ANTLR4.Tools.Testing.GrunWin.IGraphWorker" />
5860 public class GraphWorker : IGraphWorker
5961 {
62+ private readonly object _Padlock ;
63+
64+ private readonly EditorSettings _Settings ;
65+
6066 private readonly SynchronizationContext _SynchronizationContext ;
6167
62- private readonly object _Padlock ;
68+ private DateTime _LastQueuedTime ;
6369
6470 private int _PreviousNodeQty ;
6571
66- private DateTime _LastQueuedTime ;
72+ #region Constructors And Finalizers
6773
6874 /// <summary>
69- /// Initializes a new instance of the <see cref="GraphWorker" /> class.
75+ /// Initializes a new instance of the <see cref="GraphWorker" /> class.
7076 /// </summary>
7177 /// <param name="synchronizationContext">The synchronization context.</param>
72- public GraphWorker ( [ NotNull ] SynchronizationContext synchronizationContext )
78+ /// <param name="settings">The settings.</param>
79+ /// <exception cref="ArgumentNullException">
80+ /// synchronizationContext
81+ /// or
82+ /// settings are null
83+ /// </exception>
84+ public GraphWorker ( [ NotNull ] SynchronizationContext synchronizationContext , [ NotNull ] EditorSettings settings )
7385 {
7486 _SynchronizationContext = synchronizationContext ?? throw new ArgumentNullException ( nameof ( synchronizationContext ) ) ;
87+ _Settings = settings ?? throw new ArgumentNullException ( nameof ( settings ) ) ;
7588 _PreviousNodeQty = 0 ;
7689 _Padlock = new object ( ) ;
7790 QueuedWork = new Queue < GraphingWorkItem > ( ) ;
7891 GraphingTask = new Task ( GraphingWorkLoop ) ;
7992 }
8093
81- /// <inheritdoc/>
82- public event EventHandler < GraphingResult > GraphingFinished ;
94+ #endregion
8395
8496 /// <summary>
85- /// Gets or sets the graphing task.
97+ /// Gets or sets the graphing task.
8698 /// </summary>
8799 /// <value>The graphing task.</value>
88100 private Task GraphingTask { get ; set ; }
89101
90102 private Queue < GraphingWorkItem > QueuedWork { get ; }
91103
92- private void PostGraphingFinishedEvent ( object state )
104+ #region IGraphWorker Members
105+
106+ /// <inheritdoc />
107+ public bool CancelWork ( )
93108 {
94- GraphingFinished ? . Invoke ( this , ( GraphingResult ) state ) ;
109+ lock ( _Padlock )
110+ {
111+ var hadWork = QueuedWork . Count != 0 ;
112+ QueuedWork . Clear ( ) ;
113+ return hadWork ;
114+ }
95115 }
96116
97- private void OnGraphingFinished ( GraphingResult result )
117+ /// <inheritdoc />
118+ public void Graph ( IParseTreeGrapher grapher , ITree tree , IList < string > parserRules )
98119 {
99- _SynchronizationContext . Post ( PostGraphingFinishedEvent , result ) ;
120+ lock ( _Padlock )
121+ {
122+ var nextRun =
123+ CalculateNextRunTime (
124+ _PreviousNodeQty ,
125+ _Settings . NodeThresholdCountForThrottling ,
126+ _Settings . MillisecondsToDelayPerNodeWhenThrottling ,
127+ _Settings . MaximumRenderShortDelay ) ;
128+ var work = new GraphingWorkItem ( tree , parserRules , grapher , nextRun ) ;
129+ QueuedWork . Enqueue ( work ) ;
130+ _LastQueuedTime = DateTime . Now ;
131+ if ( GraphingTask . IsCompleted )
132+ GraphingTask = new Task ( GraphingWorkLoop ) ;
133+ if ( GraphingTask . Status != TaskStatus . Running )
134+ GraphingTask . Start ( ) ;
135+ }
136+ }
137+
138+ /// <inheritdoc />
139+ public event EventHandler < GraphingResult > GraphingFinished ;
140+
141+ #endregion
142+
143+ private DateTime CalculateNextRunTime (
144+ int previousNodes ,
145+ int minimumNodeThresholdToDelay ,
146+ int millisecondsPerNodeToDelay ,
147+ int maximumDelay )
148+ {
149+ // We use reactive throttling to avoid overwhelming the client with repeated parsing of large samples
150+ if ( previousNodes < minimumNodeThresholdToDelay )
151+ return DateTime . Now ;
152+
153+ var delay = Math . Min ( maximumDelay , previousNodes * millisecondsPerNodeToDelay ) ;
154+ return DateTime . Now + TimeSpan . FromMilliseconds ( delay ) ;
155+ }
156+
157+ private int CalculateTotalTreeNodes ( ITree tree )
158+ {
159+ if ( tree == null )
160+ return 0 ;
161+
162+ var result = 1 ;
163+ for ( var i = 0 ; i < tree . ChildCount ; i ++ )
164+ result += CalculateTotalTreeNodes ( tree . GetChild ( i ) ) ;
165+
166+ return result ;
100167 }
101168
102169 private void GraphingWorkLoop ( )
@@ -114,7 +181,7 @@ private void GraphingWorkLoop()
114181 lastQueued = _LastQueuedTime ;
115182 }
116183
117- if ( workCount > 4 && ( DateTime . Now - lastQueued ) < TimeSpan . FromMilliseconds ( 1000 ) )
184+ if ( workCount > _Settings . MinimumRenderCountToTriggerLongDelay && ( DateTime . Now - lastQueued ) < TimeSpan . FromMilliseconds ( 500 ) )
118185 {
119186 Thread . Sleep ( 500 ) ;
120187 continue ;
@@ -126,14 +193,15 @@ private void GraphingWorkLoop()
126193 if ( QueuedWork . Count == 0 )
127194 return ;
128195
129- var work = new GraphingWorkItem ( ) ;
130-
131196 // We really only care about the last work item, so that's all we are keeping
197+ // However, we use the "ParseWhen" target date/time for the first entry for our check
198+ var work = QueuedWork . Dequeue ( ) ;
199+ var graphWhen = work . GraphWhen ;
132200 while ( QueuedWork . Count != 0 )
133201 work = QueuedWork . Dequeue ( ) ;
134202
135203 // If we should delay longer, we enqueue the last item again for later evaluation
136- if ( DateTime . Now < work . GraphWhen )
204+ if ( DateTime . Now < graphWhen )
137205 {
138206 QueuedWork . Enqueue ( work ) ;
139207 continue ;
@@ -146,33 +214,11 @@ private void GraphingWorkLoop()
146214 }
147215 }
148216
149- private int CalculateTotalTreeNodes ( ITree tree )
150- {
151- if ( tree == null )
152- return 0 ;
153-
154- var result = 1 ;
155- for ( int i = 0 ; i < tree . ChildCount ; i ++ )
156- result += CalculateTotalTreeNodes ( tree . GetChild ( i ) ) ;
157-
158- return result ;
159- }
160-
161- private DateTime CalculateNextRunTime ( int previousNodes , int minimumNodeThresholdToDelay , int millisecondsPerNodeToDelay , int maximumDelay )
162- {
163- // We use reactive throttling to avoid overwhelming the client with repeated parsing of large samples
164- if ( previousNodes < minimumNodeThresholdToDelay )
165- return DateTime . Now ;
166-
167- var delay = Math . Min ( maximumDelay , previousNodes * millisecondsPerNodeToDelay ) ;
168- return DateTime . Now + TimeSpan . FromMilliseconds ( delay ) ;
169- }
170-
171217 /// <summary>
172- /// Handles the actual graphing.
218+ /// Handles the actual graphing.
173219 /// </summary>
174220 /// <param name="work">The work item to graph.</param>
175- /// <returns>A new <see cref="Graph"/>.</returns>
221+ /// <returns>A new <see cref="Graph" />.</returns>
176222 private GraphingResult HandleGraphing ( GraphingWorkItem work )
177223 {
178224 if ( work . TreeGrapher == null || work . ParseTree == null )
@@ -184,31 +230,14 @@ private GraphingResult HandleGraphing(GraphingWorkItem work)
184230 return new GraphingResult ( graph , work . ParseTree ) ;
185231 }
186232
187- /// <inheritdoc/>
188- public void Graph ( IParseTreeGrapher grapher , ITree tree , IList < string > parserRules )
233+ private void OnGraphingFinished ( GraphingResult result )
189234 {
190- lock ( _Padlock )
191- {
192- var nextRun = CalculateNextRunTime ( _PreviousNodeQty , 50 , 5 , 2000 ) ;
193- var work = new GraphingWorkItem ( tree , parserRules , grapher , nextRun ) ;
194- QueuedWork . Enqueue ( work ) ;
195- _LastQueuedTime = DateTime . Now ;
196- if ( GraphingTask . IsCompleted )
197- GraphingTask = new Task ( GraphingWorkLoop ) ;
198- if ( GraphingTask . Status != TaskStatus . Running )
199- GraphingTask . Start ( ) ;
200- }
235+ _SynchronizationContext . Post ( PostGraphingFinishedEvent , result ) ;
201236 }
202237
203- /// <inheritdoc/>
204- public bool CancelWork ( )
238+ private void PostGraphingFinishedEvent ( object state )
205239 {
206- lock ( _Padlock )
207- {
208- var hadWork = QueuedWork . Count != 0 ;
209- QueuedWork . Clear ( ) ;
210- return hadWork ;
211- }
240+ GraphingFinished ? . Invoke ( this , ( GraphingResult ) state ) ;
212241 }
213242 }
214243}
0 commit comments