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

orion-ecs / keen-eye / 20770148177

07 Jan 2026 04:00AM UTC coverage: 87.384% (-0.5%) from 87.861%
20770148177

push

github

tyevco
fix: Add missing using directive in EcsLoggingPluginTests

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

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

9081 of 12143 branches covered (74.78%)

Branch coverage included in aggregate %.

157554 of 178550 relevant lines covered (88.24%)

1.01 hits per line

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

98.86
/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
    /// Gets the name of this plugin.
55
    /// </summary>
56
    public string Name => "Debug";
1✔
57

58
    /// <inheritdoc />
59
    public void Install(IPluginContext context)
60
    {
61
        // Install DebugController (always available, provides debug mode toggle)
62
        var debugController = new DebugController(options.InitialDebugMode);
1✔
63
        context.SetExtension(debugController);
1✔
64

65
        // Wire up optional callback for debug mode changes (enables logging integration)
66
        if (options.OnDebugModeChanged is not null)
1✔
67
        {
68
            debugController.DebugModeChanged += (_, isDebug) => options.OnDebugModeChanged(isDebug);
×
69
        }
70

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

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

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

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

102
        // Conditionally install profiling
103
        if (options.EnableProfiling && hookCapability is not null)
1✔
104
        {
105
            var profiler = new Profiler();
1✔
106
            context.SetExtension(profiler);
1✔
107

108
            profilingHook = hookCapability.AddSystemHook(
1✔
109
                beforeHook: (system, dt) => profiler.BeginSample(system.GetType().Name),
1✔
110
                afterHook: (system, dt) => profiler.EndSample(system.GetType().Name),
1✔
111
                phase: options.ProfilingPhase
1✔
112
            );
1✔
113
        }
114

115
        // Conditionally install GC tracking
116
        if (options.EnableGCTracking && hookCapability is not null)
1✔
117
        {
118
            var gcTracker = new GCTracker();
1✔
119
            context.SetExtension(gcTracker);
1✔
120

121
            gcTrackingHook = hookCapability.AddSystemHook(
1✔
122
                beforeHook: (system, dt) => gcTracker.BeginTracking(system.GetType().Name),
1✔
123
                afterHook: (system, dt) => gcTracker.EndTracking(system.GetType().Name),
1✔
124
                phase: options.GCTrackingPhase
1✔
125
            );
1✔
126
        }
127

128
        // Conditionally install timeline recording
129
        if (options.EnableTimeline && hookCapability is not null)
1✔
130
        {
131
            var timelineRecorder = new TimelineRecorder(options.TimelineMaxFrames);
1✔
132
            context.SetExtension(timelineRecorder);
1✔
133

134
            timelineHook = hookCapability.AddSystemHook(
1✔
135
                beforeHook: (system, dt) => timelineRecorder.BeginRecording(system.GetType().Name),
1✔
136
                afterHook: (system, dt) => timelineRecorder.EndRecording(system.GetType().Name, dt),
1✔
137
                phase: options.TimelinePhase
1✔
138
            );
1✔
139
        }
140
    }
1✔
141

142
    /// <inheritdoc />
143
    public void Uninstall(IPluginContext context)
144
    {
145
        // Dispose hooks
146
        profilingHook?.Dispose();
1✔
147
        gcTrackingHook?.Dispose();
1✔
148
        timelineHook?.Dispose();
1✔
149

150
        // Remove extensions
151
        context.RemoveExtension<DebugController>();
1✔
152
        context.RemoveExtension<EntityInspector>();
1✔
153
        context.RemoveExtension<MemoryTracker>();
1✔
154

155
        if (options.EnableProfiling)
1✔
156
        {
157
            context.RemoveExtension<Profiler>();
1✔
158
        }
159

160
        if (options.EnableGCTracking)
1✔
161
        {
162
            context.RemoveExtension<GCTracker>();
1✔
163
        }
164

165
        if (options.EnableQueryProfiling)
1✔
166
        {
167
            context.RemoveExtension<QueryProfiler>();
1✔
168
        }
169

170
        if (options.EnableTimeline)
1✔
171
        {
172
            context.RemoveExtension<TimelineRecorder>();
1✔
173
        }
174
    }
1✔
175
}
176

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

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

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

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

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

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

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

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

267
    /// <summary>
268
    /// Gets or initializes the phase filter for timeline recording.
269
    /// </summary>
270
    /// <remarks>
271
    /// If specified, timeline will only record systems in the specified phase.
272
    /// If null, all systems in all phases are recorded. Default is null (record all phases).
273
    /// </remarks>
274
    public SystemPhase? TimelinePhase { get; init; } = null;
1✔
275

276
    /// <summary>
277
    /// Gets or initializes a callback invoked when debug mode changes.
278
    /// </summary>
279
    /// <remarks>
280
    /// <para>
281
    /// Use this callback to integrate with logging systems. The callback receives
282
    /// the new debug mode state (true = debug mode enabled, false = disabled).
283
    /// </para>
284
    /// <para>
285
    /// Example usage with KeenEyes.Logging:
286
    /// <code>
287
    /// var options = new DebugOptions
288
    /// {
289
    ///     OnDebugModeChanged = (isDebug) =>
290
    ///     {
291
    ///         logManager.MinimumLevel = isDebug ? LogLevel.Debug : LogLevel.Info;
292
    ///     }
293
    /// };
294
    /// world.InstallPlugin(new DebugPlugin(options));
295
    /// </code>
296
    /// </para>
297
    /// <para>
298
    /// Default is null (no callback).
299
    /// </para>
300
    /// </remarks>
301
    public Action<bool>? OnDebugModeChanged { get; init; } = null;
1✔
302
}
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