Skip to content

Commit 893d2e8

Browse files
authored
#172 Undid IExpressionNode and IExpressionWorkItem (#173)
1 parent 95a0d67 commit 893d2e8

13 files changed

Lines changed: 105 additions & 245 deletions

samples/SQuan.Helpers.Sample/MauiProgram.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public static MauiApp CreateMauiApp()
3232

3333
#if DEBUG
3434
builder.Logging.AddDebug();
35+
//builder.Logging.AddFilter("SQuan.Helpers.Maui", LogLevel.Trace);
3536
#endif
3637

3738
SamplesHelper.RegisterSample("Aspect Demo", nameof(AspectPage), typeof(AspectPage));

samples/SQuan.Helpers.Sample/Pages/ExpressionPage.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ public partial class ExpressionPage : ContentPage
1616
public ExpressionManager EM { get; }
1717
CancellationTokenSource? cts;
1818

19-
public ExpressionNode<double> X1 { get; }
20-
public ExpressionNode<double> X2 { get; }
21-
public ExpressionNode<double> Sum { get; }
22-
public ExpressionNode<double> Product { get; }
23-
public ExpressionNode<double> Hypotenuse { get; }
24-
public ExpressionNode<string> SerialCode { get; }
25-
public ExpressionNode<double> BadExpression { get; }
26-
public ExpressionNode<double> RandomNumber { get; }
19+
public ExpressionNode X1 { get; }
20+
public ExpressionNode X2 { get; }
21+
public ExpressionNode Sum { get; }
22+
public ExpressionNode Product { get; }
23+
public ExpressionNode Hypotenuse { get; }
24+
public ExpressionNode SerialCode { get; }
25+
public ExpressionNode BadExpression { get; }
26+
public ExpressionNode RandomNumber { get; }
2727

2828
public ExpressionPage()
2929
{

src/SQuan.Helpers.Maui/ExpressionExtensionMethods.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace SQuan.Helpers.Maui;
99
/// <summary>
1010
/// Helper extension methods used by the expression engine for:
1111
/// type coercion of evaluated values, adapting strongly-typed delegates to an object[] call-site,
12-
/// and initializing/evaluating <see cref="ExpressionNode{T}"/> instances.
12+
/// and initializing/evaluating <see cref="ExpressionNode"/> instances.
1313
/// </summary>
1414
public static class ExpressionExtensionMethods
1515
{

src/SQuan.Helpers.Maui/ExpressionManager.cs

Lines changed: 69 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
namespace SQuan.Helpers.Maui;
88

99
/// <summary>
10-
/// Manages a graph of <see cref="ExpressionNode{T}"/> instances and evaluates expressions
10+
/// Manages a graph of <see cref="ExpressionNode"/> instances and evaluates expressions
1111
/// asynchronously using a background calculation loop.
1212
/// The manager maintains dependency relationships between nodes, propagates value changes,
1313
/// and ensures calculations are performed in the correct order.
@@ -45,9 +45,9 @@ public static ILogger? Logger
4545
/// </summary>
4646
public readonly ExpressionParserPlugin ParserPlugin = new();
4747

48-
readonly ConcurrentDictionary<string, IExpressionNode> expressions = new();
49-
readonly BlockingCollection<IExpressionWorkItem> queuedWork = new();
50-
readonly ConcurrentDictionary<IExpressionNode, byte> queuedNodes = new();
48+
readonly ConcurrentDictionary<string, ExpressionNode> expressions = new();
49+
readonly BlockingCollection<ExpressionWorkItem> queuedWork = new();
50+
readonly ConcurrentDictionary<ExpressionNode, byte> queuedNodes = new();
5151
readonly WeakEventManager propertyChangedEventManager = new();
5252

5353
volatile bool isRunning = false;
@@ -115,24 +115,17 @@ public object? this[string nodeRef]
115115
{
116116
get
117117
{
118-
if (expressions.TryGetValue(nodeRef, out var node) && node is IExpressionNode _node)
118+
if (expressions.TryGetValue(nodeRef, out var node) && node is ExpressionNode _node)
119119
{
120120
return _node.GetInternalValue();
121121
}
122122
return null;
123123
}
124124
set
125125
{
126-
if (expressions.TryGetValue(nodeRef, out var node) && node is IExpressionNode _node)
126+
if (expressions.TryGetValue(nodeRef, out var node) && node is ExpressionNode _node)
127127
{
128-
IExpressionNode? _ = _node switch
129-
{
130-
ExpressionNode<int> nodeI => SetValue(nodeI, value, ExpressionValueKind.UserInput),
131-
ExpressionNode<long> nodeL => SetValue(nodeL, value, ExpressionValueKind.UserInput),
132-
ExpressionNode<double> nodeD => SetValue(nodeD, value, ExpressionValueKind.UserInput),
133-
ExpressionNode<string> nodeS => SetValue(nodeS, value, ExpressionValueKind.UserInput),
134-
_ => null
135-
};
128+
SetValue(_node, value, ExpressionValueKind.UserInput);
136129
}
137130
}
138131
}
@@ -155,26 +148,26 @@ public object? this[string nodeRef]
155148
/// <returns>The current value, or null if the node does not exist.</returns>
156149
public object? GetValue(string nodeRef)
157150
{
158-
if (expressions.TryGetValue(nodeRef, out var node) && node is IExpressionNode _node)
151+
if (expressions.TryGetValue(nodeRef, out var node) && node is ExpressionNode _node)
159152
{
160153
return _node.GetInternalValue();
161154
}
162155
return null;
163156
}
164157

165158
/// <summary>
166-
/// Creates a new instance of the <see cref="ExpressionNode{T}"/> class associated with the specified node reference.
159+
/// Creates a new instance of the <see cref="ExpressionNode"/> class associated with the specified node reference.
167160
/// </summary>
168161
/// <param name="nodeRef">The unique identifier for the node to create. Cannot be null.</param>
169-
/// <returns>A new <see cref="ExpressionNode{T}"/> instance associated with the specified node reference.</returns>
170-
public ExpressionNode<T> CreateNode<T>(string nodeRef)
162+
/// <returns>A new <see cref="ExpressionNode"/> instance associated with the specified node reference.</returns>
163+
public ExpressionNode CreateNode<T>(string nodeRef)
171164
{
172-
if (expressions.TryGetValue(nodeRef, out var _node) && _node is ExpressionNode<T> __node)
165+
if (expressions.TryGetValue(nodeRef, out var _node) && _node is ExpressionNode __node)
173166
{
174167
__node.Clear();
175168
}
176169

177-
var node = new ExpressionNode<T>
170+
var node = new ExpressionNode
178171
{
179172
Owner = this,
180173
NodeRef = nodeRef,
@@ -184,9 +177,9 @@ public ExpressionNode<T> CreateNode<T>(string nodeRef)
184177
return node;
185178
}
186179

187-
ExpressionNode<T> GetOrCreateNode<T>(string nodeRef)
180+
ExpressionNode GetOrCreateNode<T>(string nodeRef)
188181
{
189-
if (expressions.TryGetValue(nodeRef, out var _node) && _node is ExpressionNode<T> __node)
182+
if (expressions.TryGetValue(nodeRef, out var _node) && _node is ExpressionNode __node)
190183
{
191184
return __node;
192185
}
@@ -202,18 +195,17 @@ ExpressionNode<T> GetOrCreateNode<T>(string nodeRef)
202195
/// <param name="value">The value to assign.</param>
203196
/// <param name="kind">The reason for the value change.</param>
204197
/// <returns>The affected node.</returns>
205-
public ExpressionNode<T> SetValue<T>(string nodeRef, object? value, ExpressionValueKind kind = ExpressionValueKind.Default)
206-
=> SetValue<T>(GetOrCreateNode<T>(nodeRef), value, kind);
198+
public ExpressionNode SetValue<T>(string nodeRef, object? value, ExpressionValueKind kind = ExpressionValueKind.Default)
199+
=> SetValue(GetOrCreateNode<T>(nodeRef), value, kind);
207200

208201
/// <summary>
209202
/// Sets the value of a node with an explicit value type.
210203
/// </summary>
211-
/// <typeparam name="T">The expected value type.</typeparam>
212204
/// <param name="node">The node to update.</param>
213205
/// <param name="value">The value to assign.</param>
214206
/// <param name="kind">The reason for the value change.</param>
215207
/// <returns>The updated node.</returns>
216-
public ExpressionNode<T> SetValue<T>(ExpressionNode<T> node, object? value, ExpressionValueKind kind = ExpressionValueKind.Default)
208+
public ExpressionNode SetValue(ExpressionNode node, object? value, ExpressionValueKind kind = ExpressionValueKind.Default)
217209
{
218210
if (node.ValueKind == ExpressionValueKind.PendingParsing)
219211
{
@@ -232,7 +224,7 @@ public ExpressionNode<T> SetValue<T>(ExpressionNode<T> node, object? value, Expr
232224

233225
foreach (var outputKey in node.OutputNodeRefs.Keys)
234226
{
235-
if (expressions.TryGetValue(outputKey, out var outputNode) && outputNode is IExpressionNode _outputNode)
227+
if (expressions.TryGetValue(outputKey, out var outputNode) && outputNode is ExpressionNode _outputNode)
236228
{
237229
//Logger?.LogTrace("Notifying output node {OutputNodeRef} of change in {NodeRef}", outputKey, node.NodeRef);
238230
Recalculate(_outputNode);
@@ -249,17 +241,17 @@ public ExpressionNode<T> SetValue<T>(ExpressionNode<T> node, object? value, Expr
249241
/// <param name="nodeRef">The node reference.</param>
250242
/// <param name="expression">The expression text.</param>
251243
/// <returns>The affected node.</returns>
252-
public ExpressionNode<T> SetExpression<T>(string nodeRef, string expression)
244+
public ExpressionNode SetExpression<T>(string nodeRef, string expression)
253245
=> SetExpression<T>(GetOrCreateNode<T>(nodeRef), expression);
254246

255-
ExpressionNode<T> SetExpression<T>(ExpressionNode<T> node, string expression)
247+
ExpressionNode SetExpression<T>(ExpressionNode node, string expression)
256248
{
257249
Logger?.LogTrace("SetExpression {NodeRef} to {Expression} (type={ValueType})", node.NodeRef, expression, typeof(T));
258250
node.Expression = expression;
259251
node.ValueKind = ExpressionValueKind.PendingParsing;
260252
if (queuedNodes.TryAdd(node, 0))
261253
{
262-
queuedWork.Add(new ExpressionWorkItemInitialize { Node = node });
254+
queuedWork.Add(new ExpressionWorkItem { Node = node });
263255
}
264256
return node;
265257
}
@@ -271,17 +263,17 @@ ExpressionNode<T> SetExpression<T>(ExpressionNode<T> node, string expression)
271263
/// <param name="nodeRef">The node reference.</param>
272264
/// <param name="expression">The default expression text.</param>
273265
/// <returns>The affected node.</returns>
274-
public ExpressionNode<T> SetDefault<T>(string nodeRef, string expression)
266+
public ExpressionNode SetDefault<T>(string nodeRef, string expression)
275267
=> SetDefault<T>(GetOrCreateNode<T>(nodeRef), expression);
276268

277-
ExpressionNode<T> SetDefault<T>(ExpressionNode<T> node, string expression)
269+
ExpressionNode SetDefault<T>(ExpressionNode node, string expression)
278270
{
279271
Logger?.LogTrace("SetDefault {NodeRef} to {Expression} (type={ValueType})", node.NodeRef, expression, typeof(T));
280272
node.DefaultExpression = expression;
281273
node.ValueKind = ExpressionValueKind.PendingParsing;
282274
if (queuedNodes.TryAdd(node, 0))
283275
{
284-
queuedWork.Add(new ExpressionWorkItemInitialize { Node = node });
276+
queuedWork.Add(new ExpressionWorkItem { Node = node });
285277
}
286278
return node;
287279
}
@@ -292,7 +284,7 @@ ExpressionNode<T> SetDefault<T>(ExpressionNode<T> node, string expression)
292284
/// <param name="nodeRef">The node reference.</param>
293285
public void Recalculate(string nodeRef)
294286
{
295-
if (expressions.TryGetValue(nodeRef, out var node) && node is IExpressionNode _node)
287+
if (expressions.TryGetValue(nodeRef, out var node) && node is ExpressionNode _node)
296288
{
297289
Recalculate(_node);
298290
}
@@ -302,7 +294,7 @@ public void Recalculate(string nodeRef)
302294
/// Enqueues a specific node for recalculation.
303295
/// </summary>
304296
/// <param name="node">The node to recalculate.</param>
305-
public void Recalculate(IExpressionNode node)
297+
public void Recalculate(ExpressionNode node)
306298
{
307299
switch (node.ValueKind)
308300
{
@@ -327,7 +319,7 @@ public void Recalculate(IExpressionNode node)
327319

328320
if (queuedNodes.TryAdd(node, 0))
329321
{
330-
queuedWork.Add(new ExpressionWorkItemCalculate { Node = node });
322+
queuedWork.Add(new ExpressionWorkItem { Node = node });
331323
}
332324
}
333325

@@ -337,7 +329,7 @@ public void Recalculate(IExpressionNode node)
337329
/// <param name="nodeRef">The expression node to reset. This node must not be in an error state to be reset successfully.</param>
338330
public void ResetToDefault(string nodeRef)
339331
{
340-
if (expressions.TryGetValue(nodeRef, out var node) && node is IExpressionNode _node)
332+
if (expressions.TryGetValue(nodeRef, out var node) && node is ExpressionNode _node)
341333
{
342334
ResetToDefault(_node);
343335
}
@@ -347,7 +339,7 @@ public void ResetToDefault(string nodeRef)
347339
/// Resets the specified expression node to a pending reset state if it is in a valid state.
348340
/// </summary>
349341
/// <param name="node">The expression node to reset. This node must not be in an error state to be reset successfully.</param>
350-
public void ResetToDefault(IExpressionNode node)
342+
public void ResetToDefault(ExpressionNode node)
351343
{
352344
switch (node.ValueKind)
353345
{
@@ -371,7 +363,7 @@ public void ResetToDefault(IExpressionNode node)
371363

372364
if (queuedNodes.TryAdd(node, 0))
373365
{
374-
queuedWork.Add(new ExpressionWorkItemReset { Node = node });
366+
queuedWork.Add(new ExpressionWorkItem { Node = node });
375367
}
376368
}
377369

@@ -399,28 +391,8 @@ public void StartWorkLoop(CancellationToken ct)
399391
try
400392
{
401393
var workItem = queuedWork.Take(ct);
402-
if (workItem is ExpressionWorkItemNode workItemNode)
403-
{
404-
queuedNodes.TryRemove(workItemNode.Node, out _);
405-
}
406-
407-
if (workItem is ExpressionWorkItemInitialize init)
408-
{
409-
_ = InitializeNow(init.Node, ct);
410-
continue;
411-
}
412-
413-
if (workItem is ExpressionWorkItemCalculate calculate)
414-
{
415-
_ = CalculateNow(calculate.Node, ct);
416-
continue;
417-
}
418-
419-
if (workItem is ExpressionWorkItemReset reset)
420-
{
421-
_ = ResetNow(reset.Node, ct);
422-
continue;
423-
}
394+
ExpressionNode node = workItem.Node;
395+
queuedNodes.TryRemove(node, out _);
424396

425397
if (workItem is ExpressionWorkItemQuit quit)
426398
{
@@ -436,6 +408,36 @@ public void StartWorkLoop(CancellationToken ct)
436408
isRunning = false;
437409
break;
438410
}
411+
412+
switch (node.ValueKind)
413+
{
414+
case ExpressionValueKind.Uninitialized:
415+
case ExpressionValueKind.PendingParsing:
416+
if (!InitializeNow(node, ct))
417+
{
418+
Logger?.LogError("Failed to initialize {NodeRef}'s expression {Expression}", node.NodeRef, node.Expression);
419+
continue;
420+
}
421+
CalculateNow(node, ct);
422+
continue;
423+
case ExpressionValueKind.Folder:
424+
case ExpressionValueKind.UserInput:
425+
case ExpressionValueKind.Calculated:
426+
case ExpressionValueKind.PendingCalculation:
427+
CalculateNow(node, ct);
428+
continue;
429+
case ExpressionValueKind.PendingReset:
430+
case ExpressionValueKind.Default:
431+
ResetNow(node, ct);
432+
break;
433+
case ExpressionValueKind.ResetError:
434+
case ExpressionValueKind.ParseError:
435+
case ExpressionValueKind.CalculateError:
436+
default:
437+
// Do nothing, won't be re-enqueued
438+
Logger?.LogWarning("Node {NodeRef} is in error state ({ValueKind}), skipping calculation", node.NodeRef, node.ValueKind);
439+
break;
440+
}
439441
}
440442
catch (OperationCanceledException)
441443
{
@@ -452,7 +454,7 @@ public void StartWorkLoop(CancellationToken ct)
452454
}, ct);
453455
}
454456

455-
bool InitializeNow(IExpressionNode node, CancellationToken ct)
457+
bool InitializeNow(ExpressionNode node, CancellationToken ct)
456458
{
457459
var parser = new ExpressionParser(ParserPlugin);
458460
if (!string.IsNullOrEmpty(node.DefaultExpression))
@@ -476,11 +478,10 @@ bool InitializeNow(IExpressionNode node, CancellationToken ct)
476478
node.SetTokens(parser.Tokens);
477479
UpdateDependencyGraph(node);
478480
}
479-
480-
return CalculateNow(node, ct);
481+
return true;
481482
}
482483

483-
bool CalculateNow(IExpressionNode node, CancellationToken ct)
484+
bool CalculateNow(ExpressionNode node, CancellationToken ct)
484485
{
485486
switch (node.ValueKind)
486487
{
@@ -518,7 +519,7 @@ bool CalculateNow(IExpressionNode node, CancellationToken ct)
518519
return true;
519520
}
520521

521-
bool ResetNow(IExpressionNode node, CancellationToken ct)
522+
bool ResetNow(ExpressionNode node, CancellationToken ct)
522523
{
523524
switch (node.ValueKind)
524525
{
@@ -569,12 +570,12 @@ public async Task StopCalculationLoopAsync()
569570
}
570571

571572
isRunning = false;
572-
queuedWork.Add(new ExpressionWorkItemQuit { RunId = currentRunId });
573+
queuedWork.Add(new ExpressionWorkItemQuit { RunId = currentRunId, Node = ExpressionNode.Empty });
573574
await task;
574575
runningTask = null;
575576
}
576577

577-
void UpdateDependencyGraph(IExpressionNode node)
578+
void UpdateDependencyGraph(ExpressionNode node)
578579
{
579580
foreach (var inputNodeRef in node.GetInputNodeRefs())
580581
{

0 commit comments

Comments
 (0)