-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Expand file tree
/
Copy pathDecycler.cs
More file actions
146 lines (129 loc) · 4.29 KB
/
Copy pathDecycler.cs
File metadata and controls
146 lines (129 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using System;
using System.Collections.Generic;
namespace Avalonia.Media.Fonts.Tables
{
/// <summary>
/// Errors that can occur during graph traversal with cycle detection.
/// </summary>
internal enum DecyclerError
{
/// <summary>
/// A cycle was detected in the graph.
/// </summary>
CycleDetected,
/// <summary>
/// The maximum depth limit was exceeded.
/// </summary>
DepthLimitExceeded
}
/// <summary>
/// Exception thrown when a decycler error occurs.
/// </summary>
internal class DecyclerException : Exception
{
public DecyclerError Error { get; }
public DecyclerException(DecyclerError error, string message) : base(message)
{
Error = error;
}
}
/// <summary>
/// A guard that tracks entry into a node and ensures proper cleanup.
/// </summary>
/// <typeparam name="T">The type of the node identifier.</typeparam>
internal ref struct CycleGuard<T> where T : struct
{
private readonly Decycler<T> _decycler;
private readonly T _id;
private bool _exited;
internal CycleGuard(Decycler<T> decycler, T id)
{
_decycler = decycler;
_id = id;
_exited = false;
}
/// <summary>
/// Exits the guard, removing the node ID from the visited set.
/// </summary>
public void Dispose()
{
if (!_exited)
{
_decycler.Exit(_id);
_exited = true;
}
}
}
/// <summary>
/// Tracks visited nodes to detect cycles in a graph (composite glyphs, paint graphs, etc.).
/// Uses a depth limit to prevent stack overflow during recursive traversal.
/// </summary>
/// <typeparam name="T">The type of the node identifier.</typeparam>
internal class Decycler<T> where T : struct
{
private readonly HashSet<T> _visited;
private readonly int _maxDepth;
private int _currentDepth;
/// <summary>
/// Creates a new Decycler with the specified maximum depth.
/// </summary>
/// <param name="maxDepth">Maximum traversal depth before returning an error.</param>
public Decycler(int maxDepth)
{
_visited = new HashSet<T>();
_maxDepth = maxDepth;
_currentDepth = 0;
}
/// <summary>
/// Attempts to enter a node with the given ID.
/// Returns a guard that will automatically exit when disposed.
/// </summary>
/// <param name="id">The node identifier to enter.</param>
/// <returns>A guard that will clean up on disposal.</returns>
/// <exception cref="DecyclerException">Thrown if a cycle is detected or depth limit exceeded.</exception>
public CycleGuard<T> Enter(T id)
{
if (_currentDepth >= _maxDepth)
{
throw new DecyclerException(
DecyclerError.DepthLimitExceeded,
$"Graph depth limit of {_maxDepth} exceeded");
}
if (_visited.Contains(id))
{
throw new DecyclerException(
DecyclerError.CycleDetected,
"Cycle detected in graph");
}
_visited.Add(id);
_currentDepth++;
return new CycleGuard<T>(this, id);
}
/// <summary>
/// Exits a node, removing it from the visited set.
/// Called automatically by CycleGuard.Dispose().
/// </summary>
/// <param name="id">The node identifier to exit.</param>
internal void Exit(T id)
{
_visited.Remove(id);
_currentDepth--;
}
/// <summary>
/// Returns the current traversal depth.
/// </summary>
public int CurrentDepth => _currentDepth;
/// <summary>
/// Returns the maximum allowed traversal depth.
/// </summary>
public int MaxDepth => _maxDepth;
/// <summary>
/// Resets the decycler to its initial state, clearing all visited nodes.
/// </summary>
public void Reset()
{
_visited.Clear();
_currentDepth = 0;
}
}
}