Skip to content

Commit bc15ffd

Browse files
committed
Performed a large amount of refactoring to improve parsing efficiency of large sets of sample code.
Parse tree graphing is now performed in the background and the rate at which it gets rebuilt is throttled based on the graph size.
1 parent 76fccca commit bc15ffd

File tree

19 files changed

+1045
-105
lines changed

19 files changed

+1045
-105
lines changed

Code Grapher/IParseTreeGrapher.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,6 @@ namespace Org.Edgerunner.ANTLR4.Tools.Graphing
4949
/// </summary>
5050
public interface IParseTreeGrapher
5151
{
52-
/// <summary>
53-
/// Gets the subject parse tree to graph.
54-
/// </summary>
55-
/// <value>The subject tree.</value>
56-
ITree Subject { get; }
57-
58-
/// <summary>
59-
/// Gets the parser rules.
60-
/// </summary>
61-
/// <value>The parser rules.</value>
62-
IList<string> ParserRules { get; }
63-
6452
/// <summary>
6553
/// Gets or sets the color of the background.
6654
/// </summary>
@@ -82,7 +70,9 @@ public interface IParseTreeGrapher
8270
/// <summary>
8371
/// Creates the parse tree graph.
8472
/// </summary>
85-
/// <returns>A new <see cref="Graph"/>.</returns>
86-
Graph CreateGraph();
73+
/// <param name="tree">The parse tree to graph.</param>
74+
/// <param name="parserRules">The parser rules.</param>
75+
/// <returns>A new <see cref="Graph" />.</returns>
76+
Graph CreateGraph(ITree tree, IList<string> parserRules);
8777
}
8878
}

Code Grapher/ParseTreeGrapher.cs

Lines changed: 13 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636

3737
#endregion
3838

39-
using System;
4039
using System.Collections.Generic;
4140

4241
using Antlr4.Runtime;
@@ -52,66 +51,26 @@ namespace Org.Edgerunner.ANTLR4.Tools.Graphing
5251
/// <remarks>This class uses Microsoft's Automatic Graph Layout Library to do the actual dirty work.</remarks>
5352
public class ParseTreeGrapher : IParseTreeGrapher
5453
{
55-
#region Constructors And Finalizers
56-
57-
/// <summary>
58-
/// Initializes a new instance of the <see cref="ParseTreeGrapher" /> class.
59-
/// </summary>
60-
/// <param name="tree">The tree to graph.</param>
61-
/// <param name="rules">The parser rules.</param>
62-
/// <seealso cref="Antlr4.Runtime.Tree.ITree" />
63-
public ParseTreeGrapher(ITree tree, IList<string> rules)
64-
{
65-
Subject = tree;
66-
ParserRules = rules ?? new List<string>();
67-
}
68-
69-
#endregion
70-
71-
/// <summary>
72-
/// Gets the subject parse tree to graph.
73-
/// </summary>
74-
/// <value>The subject tree.</value>
75-
public ITree Subject { get; }
76-
77-
/// <summary>
78-
/// Gets the parser rules.
79-
/// </summary>
80-
/// <value>The parser rules.</value>
81-
public IList<string> ParserRules { get; }
82-
83-
/// <summary>
84-
/// Gets or sets the color of the background.
85-
/// </summary>
86-
/// <value>The color of the background.</value>
54+
/// <inheritdoc/>
8755
public Color? BackgroundColor { get; set; }
8856

89-
/// <summary>
90-
/// Gets or sets the color of the text.
91-
/// </summary>
92-
/// <value>The color of the text.</value>
57+
/// <inheritdoc/>
9358
public Color? TextColor { get; set; }
9459

95-
/// <summary>
96-
/// Gets or sets the color of the border.
97-
/// </summary>
98-
/// <value>The color of the border.</value>
60+
/// <inheritdoc/>
9961
public Color? BorderColor { get; set; }
10062

101-
/// <summary>
102-
/// Creates the parse tree graph.
103-
/// </summary>
104-
/// <returns>A new <see cref="Graph"/>.</returns>
105-
public Graph CreateGraph()
63+
/// <inheritdoc/>
64+
public Graph CreateGraph(ITree tree, IList<string> parserRules)
10665
{
10766
var graph = new Graph();
108-
if (Subject != null)
67+
if (tree != null)
10968
{
110-
if (Subject.ChildCount == 0)
111-
graph.AddNode(Subject.GetHashCode().ToString());
69+
if (tree.ChildCount == 0)
70+
graph.AddNode(tree.GetHashCode().ToString());
11271
else
113-
GraphEdges(graph, Subject);
114-
FormatNodes(graph, Subject);
72+
GraphEdges(graph, tree);
73+
FormatNodes(graph, tree, parserRules);
11574
}
11675

11776
return graph;
@@ -128,12 +87,12 @@ private void GraphEdges(Graph graph, ITree tree)
12887
}
12988
}
13089

131-
private void FormatNodes(Graph graph, ITree tree)
90+
private void FormatNodes(Graph graph, ITree tree, IList<string> parserRules)
13291
{
13392
var node = graph.FindNode(tree.GetHashCode().ToString());
13493
if (node != null)
13594
{
136-
node.LabelText = Trees.GetNodeText(tree, ParserRules);
95+
node.LabelText = Trees.GetNodeText(tree, parserRules);
13796

13897
var ruleFailedAndMatchedNothing = false;
13998

@@ -160,7 +119,7 @@ private void FormatNodes(Graph graph, ITree tree)
160119
}
161120

162121
for (int i = 0; i < tree.ChildCount; i++)
163-
FormatNodes(graph, tree.GetChild(i));
122+
FormatNodes(graph, tree.GetChild(i), parserRules);
164123
}
165124
}
166125
}

Code Grapher/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("1.0.20066.2")]
36-
[assembly: AssemblyFileVersion("1.0.20066.2")]
35+
[assembly: AssemblyVersion("1.0.20069.3")]
36+
[assembly: AssemblyFileVersion("1.0.20069.3")]

Common/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("1.0.0.0")]
36-
[assembly: AssemblyFileVersion("1.0.0.0")]
35+
[assembly: AssemblyVersion("1.0.20069.0")]
36+
[assembly: AssemblyFileVersion("1.0.20069.0")]

Grun/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("1.0.20066.8")]
36-
[assembly: AssemblyFileVersion("1.0.20066.8")]
35+
[assembly: AssemblyVersion("1.0.20069.0")]
36+
[assembly: AssemblyFileVersion("1.0.20069.0")]

GunWin/GraphWorker.cs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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

Comments
 (0)