1+ #region BSD 3-Clause License
2+ // <copyright file="GraphWorker.cs" company="Edgerunner.org">
3+ // Copyright 2020 Thaddeus Ryker
4+ // </copyright>
5+ //
6+ // BSD 3-Clause License
7+ //
8+ // Copyright (c) 2020, Thaddeus Ryker
9+ // All rights reserved.
10+ //
11+ // Redistribution and use in source and binary forms, with or without
12+ // modification, are permitted provided that the following conditions are met:
13+ //
14+ // 1. Redistributions of source code must retain the above copyright notice, this
15+ // list of conditions and the following disclaimer.
16+ //
17+ // 2. Redistributions in binary form must reproduce the above copyright notice,
18+ // this list of conditions and the following disclaimer in the documentation
19+ // and/or other materials provided with the distribution.
20+ //
21+ // 3. Neither the name of the copyright holder nor the names of its
22+ // contributors may be used to endorse or promote products derived from
23+ // this software without specific prior written permission.
24+ //
25+ // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26+ // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27+ // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28+ // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
29+ // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30+ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31+ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32+ // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33+ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34+ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35+ #endregion
36+
37+ using System ;
38+ using System . Collections . Generic ;
39+ using System . Threading ;
40+ using System . Threading . Tasks ;
41+
42+ using Antlr4 . Runtime . Tree ;
43+
44+ using JetBrains . Annotations ;
45+
46+ using Microsoft . Msagl . Layout . Layered ;
47+
48+ using Org . Edgerunner . ANTLR4 . Tools . Graphing ;
49+
50+ namespace Org . Edgerunner . ANTLR4 . Tools . Testing . GrunWin
51+ {
52+ /// <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+ /// </summary>
56+ /// <remarks>This class is thread safe.</remarks>
57+ /// <seealso cref="Org.Edgerunner.ANTLR4.Tools.Testing.GrunWin.IGraphWorker" />
58+ public class GraphWorker : IGraphWorker
59+ {
60+ private readonly SynchronizationContext _SynchronizationContext ;
61+
62+ private readonly object _Padlock ;
63+
64+ private int _PreviousNodeQty ;
65+
66+ private DateTime _LastQueuedTime ;
67+
68+ /// <summary>
69+ /// Initializes a new instance of the <see cref="GraphWorker" /> class.
70+ /// </summary>
71+ /// <param name="synchronizationContext">The synchronization context.</param>
72+ public GraphWorker ( [ NotNull ] SynchronizationContext synchronizationContext )
73+ {
74+ _SynchronizationContext = synchronizationContext ?? throw new ArgumentNullException ( nameof ( synchronizationContext ) ) ;
75+ _PreviousNodeQty = 0 ;
76+ _Padlock = new object ( ) ;
77+ QueuedWork = new Queue < GraphingWorkItem > ( ) ;
78+ GraphingTask = new Task ( GraphingWorkLoop ) ;
79+ }
80+
81+ /// <inheritdoc/>
82+ public event EventHandler < GraphingResult > GraphingFinished ;
83+
84+ /// <summary>
85+ /// Gets or sets the graphing task.
86+ /// </summary>
87+ /// <value>The graphing task.</value>
88+ private Task GraphingTask { get ; set ; }
89+
90+ private Queue < GraphingWorkItem > QueuedWork { get ; }
91+
92+ private void PostGraphingFinishedEvent ( object state )
93+ {
94+ GraphingFinished ? . Invoke ( this , ( GraphingResult ) state ) ;
95+ }
96+
97+ private void OnGraphingFinished ( GraphingResult result )
98+ {
99+ _SynchronizationContext . Post ( PostGraphingFinishedEvent , result ) ;
100+ }
101+
102+ private void GraphingWorkLoop ( )
103+ {
104+ while ( true )
105+ {
106+ int workCount ;
107+ DateTime lastQueued ;
108+ lock ( _Padlock )
109+ {
110+ workCount = QueuedWork . Count ;
111+ if ( workCount == 0 )
112+ return ;
113+
114+ lastQueued = _LastQueuedTime ;
115+ }
116+
117+ if ( workCount > 4 && ( DateTime . Now - lastQueued ) < TimeSpan . FromMilliseconds ( 1000 ) )
118+ {
119+ Thread . Sleep ( 500 ) ;
120+ continue ;
121+ }
122+
123+ lock ( _Padlock )
124+ {
125+ // Sanity check ....just in case
126+ if ( QueuedWork . Count == 0 )
127+ return ;
128+
129+ var work = new GraphingWorkItem ( ) ;
130+
131+ // We really only care about the last work item, so that's all we are keeping
132+ while ( QueuedWork . Count != 0 )
133+ work = QueuedWork . Dequeue ( ) ;
134+
135+ // If we should delay longer, we enqueue the last item again for later evaluation
136+ if ( DateTime . Now < work . GraphWhen )
137+ {
138+ QueuedWork . Enqueue ( work ) ;
139+ continue ;
140+ }
141+
142+ var result = HandleGraphing ( work ) ;
143+ _PreviousNodeQty = CalculateTotalTreeNodes ( work . ParseTree ) ;
144+ OnGraphingFinished ( result ) ;
145+ }
146+ }
147+ }
148+
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+
171+ /// <summary>
172+ /// Handles the actual graphing.
173+ /// </summary>
174+ /// <param name="work">The work item to graph.</param>
175+ /// <returns>A new <see cref="Graph"/>.</returns>
176+ private GraphingResult HandleGraphing ( GraphingWorkItem work )
177+ {
178+ if ( work . TreeGrapher == null || work . ParseTree == null )
179+ return new GraphingResult ( ) ;
180+
181+ var graph = work . TreeGrapher . CreateGraph ( work . ParseTree , work . ParserRules ) ;
182+ graph . LayoutAlgorithmSettings = new SugiyamaLayoutSettings ( ) ;
183+
184+ return new GraphingResult ( graph , work . ParseTree ) ;
185+ }
186+
187+ /// <inheritdoc/>
188+ public void Graph ( IParseTreeGrapher grapher , ITree tree , IList < string > parserRules )
189+ {
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+ }
201+ }
202+
203+ /// <inheritdoc/>
204+ public bool CancelWork ( )
205+ {
206+ lock ( _Padlock )
207+ {
208+ var hadWork = QueuedWork . Count != 0 ;
209+ QueuedWork . Clear ( ) ;
210+ return hadWork ;
211+ }
212+ }
213+ }
214+ }
0 commit comments