• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orion-ecs / keen-eye / 20763214739

06 Jan 2026 09:50PM UTC coverage: 70.212% (-1.1%) from 71.324%
20763214739

push

github

tyevco
fix(ci): Correct placement of --max-parallel-test-modules option

The option was incorrectly placed after the -- separator (xUnit runner
options). It is a top-level dotnet test option and must be placed before
the -- separator.

This prevents test assemblies from running in parallel, which was
causing intermittent hangs due to ThreadPool contention.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

6646 of 9148 branches covered (72.65%)

Branch coverage included in aggregate %.

41510 of 59439 relevant lines covered (69.84%)

1.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.88
/src/KeenEyes.Debugging/DebugPlugin.cs
1
using KeenEyes.Capabilities;
2
using KeenEyes.Debugging.Timeline;
3

4
namespace KeenEyes.Debugging;
5

6
/// <summary>
7
/// Plugin that adds debugging and profiling capabilities to a KeenEyes world.
8
/// </summary>
9
/// <remarks>
10
/// <para>
11
/// The DebugPlugin provides comprehensive debugging tools including system profiling,
12
/// entity inspection, memory tracking, and GC allocation monitoring. These tools use
13
/// SystemHooks for automatic integration without modifying individual systems.
14
/// </para>
15
/// <para>
16
/// All debugging features are exposed through extension APIs that can be retrieved
17
/// using <see cref="IWorld.GetExtension{T}"/>. Features can be enabled or disabled
18
/// individually through the configuration options.
19
/// </para>
20
/// </remarks>
21
/// <example>
22
/// <code>
23
/// // Install with default options (all features enabled)
24
/// using var world = new World();
25
/// world.InstallPlugin(new DebugPlugin());
26
///
27
/// // Access debugging tools
28
/// var profiler = world.GetExtension&lt;Profiler&gt;();
29
/// var inspector = world.GetExtension&lt;EntityInspector&gt;();
30
/// var memoryTracker = world.GetExtension&lt;MemoryTracker&gt;();
31
/// var gcTracker = world.GetExtension&lt;GCTracker&gt;();
32
/// var queryProfiler = world.GetExtension&lt;QueryProfiler&gt;();
33
/// var timeline = world.GetExtension&lt;TimelineRecorder&gt;();
34
///
35
/// // Run your simulation
36
/// world.Update(0.016f);
37
///
38
/// // Print profiling report
39
/// foreach (var profile in profiler.GetAllSystemProfiles())
40
/// {
41
///     Console.WriteLine($"{profile.Name}: {profile.AverageTime.TotalMilliseconds:F2}ms");
42
/// }
43
/// </code>
44
/// </example>
45
/// <param name="options">Configuration options for the debug plugin.</param>
46
public sealed class DebugPlugin(DebugOptions? options = null) : IWorldPlugin
1✔
47
{
48
    private readonly DebugOptions options = options ?? new DebugOptions();
1✔
49
    private EventSubscription? profilingHook;
50
    private EventSubscription? gcTrackingHook;
51
    private EventSubscription? timelineHook;
52

53
    /// <summary>
54
    /// Creates a new debug plugin with default options.
55
    /// </summary>
56
    public DebugPlugin() : this(null)
1✔
57
    {
58
    }
1✔
59

60
    /// <summary>
61
    /// Gets the name of this plugin.
62
    /// </summary>
63
    public string Name => "Debug";
1✔
64

65
    /// <inheritdoc />
66
    public void Install(IPluginContext context)
67
    {
68
        // Install DebugController (always available, provides debug mode toggle)
69
        var debugController = new DebugController(options.InitialDebugMode);
1✔
70
        context.SetExtension(debugController);
1✔
71

72
        // Install EntityInspector if inspection capability is available (no performance overhead)
73
        if (context.TryGetCapability<IInspectionCapability>(out var inspectionCapability) && inspectionCapability is not null)
1✔
74
        {
75
            context.TryGetCapability<IHierarchyCapability>(out var hierarchyCapability);
1✔
76
            var inspector = new EntityInspector(context.World, inspectionCapability, hierarchyCapability);
1✔
77
            context.SetExtension(inspector);
1✔
78
        }
79

80
        // Get statistics capability for MemoryTracker and QueryProfiler
81
        if (context.TryGetCapability<IStatisticsCapability>(out var statsCapability) && statsCapability is not null)
1✔
82
        {
83
            var memoryTracker = new MemoryTracker(statsCapability);
1✔
84
            context.SetExtension(memoryTracker);
1✔
85

86
            // Install QueryProfiler if enabled
87
            if (options.EnableQueryProfiling)
1✔
88
            {
89
                var queryProfiler = new QueryProfiler(statsCapability);
1✔
90
                context.SetExtension(queryProfiler);
1✔
91
            }
92
        }
93

94
        // Get the system hook capability for profiling, GC tracking, and timeline
95
        ISystemHookCapability? hookCapability = null;
1✔
96
        if (options.EnableProfiling || options.EnableGCTracking || options.EnableTimeline)
1✔
97
        {
98
            if (!context.TryGetCapability<ISystemHookCapability>(out hookCapability))
1✔
99
            {
100
                throw new InvalidOperationException(
1✔
101
                    "DebugPlugin requires ISystemHookCapability for profiling, GC tracking, or timeline. " +
1✔
102
                    "Disable these options or provide a world that supports system hooks.");
1✔
103
            }
104
        }
105

106
        // Conditionally install profiling
107
        if (options.EnableProfiling && hookCapability is not null)
1✔
108
        {
109
            var profiler = new Profiler();
1✔
110
            context.SetExtension(profiler);
1✔
111

112
            profilingHook = hookCapability.AddSystemHook(
1✔
113
                beforeHook: (system, dt) => profiler.BeginSample(system.GetType().Name),
1✔
114
                afterHook: (system, dt) => profiler.EndSample(system.GetType().Name),
1✔
115
                phase: options.ProfilingPhase
1✔
116
            );
1✔
117
        }
118

119
        // Conditionally install GC tracking
120
        if (options.EnableGCTracking && hookCapability is not null)
1✔
121
        {
122
            var gcTracker = new GCTracker();
1✔
123
            context.SetExtension(gcTracker);
1✔
124

125
            gcTrackingHook = hookCapability.AddSystemHook(
1✔
126
                beforeHook: (system, dt) => gcTracker.BeginTracking(system.GetType().Name),
1✔
127
                afterHook: (system, dt) => gcTracker.EndTracking(system.GetType().Name),
1✔
128
                phase: options.GCTrackingPhase
1✔
129
            );
1✔
130
        }
131

132
        // Conditionally install timeline recording
133
        if (options.EnableTimeline && hookCapability is not null)
1✔
134
        {
135
            var timelineRecorder = new TimelineRecorder(options.TimelineMaxFrames);
1✔
136
            context.SetExtension(timelineRecorder);
1✔
137

138
            timelineHook = hookCapability.AddSystemHook(
1✔
139
                beforeHook: (system, dt) => timelineRecorder.BeginRecording(system.GetType().Name),
1✔
140
                afterHook: (system, dt) => timelineRecorder.EndRecording(system.GetType().Name, dt),
1✔
141
                phase: options.TimelinePhase
1✔
142
            );
1✔
143
        }
144
    }
1✔
145

146
    /// <inheritdoc />
147
    public void Uninstall(IPluginContext context)
148
    {
149
        // Dispose hooks
150
        profilingHook?.Dispose();
1✔
151
        gcTrackingHook?.Dispose();
1✔
152
        timelineHook?.Dispose();
1✔
153

154
        // Remove extensions
155
        context.RemoveExtension<DebugController>();
1✔
156
        context.RemoveExtension<EntityInspector>();
1✔
157
        context.RemoveExtension<MemoryTracker>();
1✔
158

159
        if (options.EnableProfiling)
1✔
160
        {
161
            context.RemoveExtension<Profiler>();
1✔
162
        }
163

164
        if (options.EnableGCTracking)
1✔
165
        {
166
            context.RemoveExtension<GCTracker>();
1✔
167
        }
168

169
        if (options.EnableQueryProfiling)
1✔
170
        {
171
            context.RemoveExtension<QueryProfiler>();
1✔
172
        }
173

174
        if (options.EnableTimeline)
1✔
175
        {
176
            context.RemoveExtension<TimelineRecorder>();
1✔
177
        }
178
    }
1✔
179
}
180

181
/// <summary>
182
/// Configuration options for the debug plugin.
183
/// </summary>
184
/// <remarks>
185
/// These options control which debugging features are enabled and their behavior.
186
/// Features that are disabled have zero performance overhead.
187
/// </remarks>
188
public sealed record DebugOptions
×
189
{
190
    /// <summary>
191
    /// Gets or initializes the initial debug mode state.
192
    /// </summary>
193
    /// <remarks>
194
    /// <para>
195
    /// When debug mode is enabled, debugging components may perform additional
196
    /// diagnostics, capture more detailed information, or enable verbose logging.
197
    /// </para>
198
    /// <para>
199
    /// The debug mode can be toggled at runtime via <see cref="DebugController"/>.
200
    /// Default is false.
201
    /// </para>
202
    /// </remarks>
203
    public bool InitialDebugMode { get; init; } = false;
1✔
204

205
    /// <summary>
206
    /// Gets or initializes a value indicating whether system profiling is enabled.
207
    /// </summary>
208
    /// <remarks>
209
    /// When enabled, the profiler tracks execution time for all systems and provides
210
    /// detailed timing metrics. Default is true.
211
    /// </remarks>
212
    public bool EnableProfiling { get; init; } = true;
1✔
213

214
    /// <summary>
215
    /// Gets or initializes a value indicating whether GC allocation tracking is enabled.
216
    /// </summary>
217
    /// <remarks>
218
    /// When enabled, tracks memory allocations per system to identify allocation hotspots.
219
    /// This has minimal overhead but may not capture all allocations in multi-threaded scenarios.
220
    /// Default is true.
221
    /// </remarks>
222
    public bool EnableGCTracking { get; init; } = true;
1✔
223

224
    /// <summary>
225
    /// Gets or initializes the phase filter for profiling hooks.
226
    /// </summary>
227
    /// <remarks>
228
    /// If specified, profiling will only track systems in the specified phase.
229
    /// If null, all systems in all phases are profiled. Default is null (profile all phases).
230
    /// </remarks>
231
    public SystemPhase? ProfilingPhase { get; init; } = null;
1✔
232

233
    /// <summary>
234
    /// Gets or initializes the phase filter for GC tracking hooks.
235
    /// </summary>
236
    /// <remarks>
237
    /// If specified, GC tracking will only monitor systems in the specified phase.
238
    /// If null, all systems in all phases are tracked. Default is null (track all phases).
239
    /// </remarks>
240
    public SystemPhase? GCTrackingPhase { get; init; } = null;
1✔
241

242
    /// <summary>
243
    /// Gets or initializes a value indicating whether query profiling is enabled.
244
    /// </summary>
245
    /// <remarks>
246
    /// When enabled, the query profiler provides access to cache statistics and allows
247
    /// manual timing of individual queries. Unlike system profiling, query timing requires
248
    /// manual instrumentation. Default is true.
249
    /// </remarks>
250
    public bool EnableQueryProfiling { get; init; } = true;
1✔
251

252
    /// <summary>
253
    /// Gets or initializes a value indicating whether timeline recording is enabled.
254
    /// </summary>
255
    /// <remarks>
256
    /// When enabled, the timeline recorder captures detailed execution history for all
257
    /// systems, allowing frame-by-frame analysis and export for external visualization.
258
    /// Default is false (due to memory overhead of storing history).
259
    /// </remarks>
260
    public bool EnableTimeline { get; init; } = false;
1✔
261

262
    /// <summary>
263
    /// Gets or initializes the maximum number of frames to keep in timeline history.
264
    /// </summary>
265
    /// <remarks>
266
    /// Older frames are automatically discarded to manage memory usage. Default is 300
267
    /// (approximately 5 seconds at 60fps).
268
    /// </remarks>
269
    public int TimelineMaxFrames { get; init; } = 300;
1✔
270

271
    /// <summary>
272
    /// Gets or initializes the phase filter for timeline recording.
273
    /// </summary>
274
    /// <remarks>
275
    /// If specified, timeline will only record systems in the specified phase.
276
    /// If null, all systems in all phases are recorded. Default is null (record all phases).
277
    /// </remarks>
278
    public SystemPhase? TimelinePhase { get; init; } = null;
1✔
279
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc