Dev changes #5

Merged
omegaleo merged 45 commits from dev into main 2025-12-23 16:37:30 +00:00
21 changed files with 458 additions and 51 deletions
+9
View File
@@ -3,3 +3,12 @@ obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
/.vs
<<<<<<< Updated upstream
/OmegaLeo.HelperLib.Web
=======
/OmegaLeo.HelperLib.Web/.config
/OmegaLeo.HelperLib.Web/Properties/PublishProfiles
/OmegaLeo.HelperLib.Web/publish
>>>>>>> Stashed changes
/OmegaLeo.HelperLib.Web/OmegaLeo.HelperLib.Web.csproj.user
@@ -13,9 +13,13 @@
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\"/>
<None Include="CHANGELOG.md" Pack="true" PackagePath="\"/>
<None Include="LICENSE.md" Pack="true" PackagePath="\"/>
<None Include="README.md" Pack="true" PackagePath="\" />
<None Include="CHANGELOG.md" Pack="true" PackagePath="\" />
<None Include="LICENSE.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.3" />
</ItemGroup>
<ItemGroup>
@@ -9,7 +9,12 @@
<ItemGroup>
<ProjectReference Include="..\OmegaLeo.HelperLib.Changelog\OmegaLeo.HelperLib.Changelog.csproj" />
<ProjectReference Include="..\OmegaLeo.HelperLib.Game\OmegaLeo.HelperLib.Game.csproj" />
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.3" />
</ItemGroup>
</Project>
@@ -14,6 +14,7 @@ var changelogs = new Dictionary<string, Assembly>()
{
{Path.Join(root, "OmegaLeo.HelperLib.Changelog", "CHANGELOG.md"), typeof(Changelog).Assembly},
{Path.Join(root, "OmegaLeo.HelperLib", "CHANGELOG.md"), typeof(BenchmarkUtility).Assembly},
{Path.Join(root, "OmegaLeo.HelperLib.Game", "CHANGELOG.md"), typeof(OmegaLeo.HelperLib.Game.Models.ConsoleCommand).Assembly},
};
foreach (var changelog in changelogs)
@@ -13,4 +13,8 @@
<ProjectReference Include="..\OmegaLeo.HelperLib\OmegaLeo.HelperLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.3" />
</ItemGroup>
</Project>
@@ -1,10 +1,21 @@
using System.Collections.Generic;
using NetFlow.DocumentationHelper.Library.Attributes;
using OmegaLeo.HelperLib.Game.Models;
namespace OmegaLeo.HelperLib.Game.Extensions
{
public static class ListExtensions
{
[Documentation(nameof(Shuffle),
"Shuffles the elements of the list in place using the provided RandomNumberGenerator.",
null,
@"```csharp
var list = new List<int> { 1, 2, 3, 4};
var rng = new RandomNumberGenerator();
// List before: [1, 2, 3, 4]
list.Shuffle(rng);
// List after: [3, 1, 4, 2] (example output)
```")]
public static void Shuffle<T>(this IList<T> list, RandomNumberGenerator rng)
{
for (int i = list.Count - 1; i > 0; i--)
@@ -1,7 +1,12 @@
using NetFlow.DocumentationHelper.Library.Attributes;
using OmegaLeo.HelperLib.Game.Models;
namespace OmegaLeo.HelperLib.Game.Handlers
{
[Documentation(nameof(DelayedActionHandler), "Handler for managing delayed actions, allowing cancellation and status checking.", null, @"```csharp
var action = new DelayedAction(...);
var handler = new DelayedActionHandler(action);
```")]
public class DelayedActionHandler
{
private readonly DelayedAction _action;
@@ -11,6 +16,9 @@ namespace OmegaLeo.HelperLib.Game.Handlers
_action = action;
}
[Documentation(nameof(Cancel), "Cancels the delayed action if it has not yet executed.", null, @"```csharp
handler.Cancel();
```")]
public void Cancel()
{
if (_action != null)
@@ -16,6 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="LibGit2Sharp" Version="0.30.0"/>
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.2"/>
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.3" />
</ItemGroup>
<ItemGroup>
@@ -1,9 +1,12 @@
// File: `OmegaLeo.HelperLib.Web/Controllers/HomeController.cs`
using System.Diagnostics;
using System.Reflection;
using System.Runtime.ExceptionServices;
using GameDevLibrary.Helpers;
using Microsoft.AspNetCore.Mvc;
using NetFlow.DocumentationHelper.Library.Helpers;
using NetFlow.DocumentationHelper.Library.Models;
using OmegaLeo.HelperLib.Changelog.Tools;
using OmegaLeo.HelperLib.Web.Models;
@@ -25,31 +28,34 @@ public class HomeController : Controller
if (_libraryCache != null) return;
var root = GetSolutionRoot();
_logger.LogInformation("Initializing libraries from base directory: {Root}", root);
_libraryCache = new Dictionary<string, LibraryDocumentationViewModel>
{
["core"] = GenerateLibraryDocs(
typeof(BenchmarkUtility).Assembly,
Path.Join(root, "OmegaLeo.HelperLib", "CHANGELOG.md"),
Path.Combine(root, "changelogs", "OmegaLeo.HelperLib.CHANGELOG.md"),
"Core Library",
"Core utilities and helper functions for general C# development",
"OmegaLeo.HelperLib"
),
["changelog"] = GenerateLibraryDocs(
typeof(Changelog.Models.Changelog).Assembly,
Path.Join(root, "OmegaLeo.HelperLib.Changelog", "CHANGELOG.md"),
Path.Combine(root, "changelogs", "OmegaLeo.HelperLib.Changelog.CHANGELOG.md"),
"Changelog Library",
"Tools for managing and generating changelogs",
copilot-pull-request-reviewer[bot] commented 2025-12-23 16:32:10 +00:00 (Migrated from github.com)
Review

Write to static field from instance method, property, or constructor.

Write to static field from instance method, property, or constructor.
omegaleo commented 2025-12-23 16:33:13 +00:00 (Migrated from github.com)
Review

@copilot open a new pull request to apply changes based on this feedback

@copilot open a new pull request to apply changes based on [this feedback](https://github.com/omegaleo/HelperLib/pull/5#discussion_r2643726666)
"OmegaLeo.HelperLib.Changelog"
),
["game"] = GenerateLibraryDocs(
typeof(OmegaLeo.HelperLib.Game.Models.ConsoleCommand).Assembly,
Path.Join(root, "OmegaLeo.HelperLib.Game", "CHANGELOG.md"),
Path.Combine(root, "changelogs", "OmegaLeo.HelperLib.Game.CHANGELOG.md"),
"Game Development Library",
"Game development utilities for Unity and Godot",
"OmegaLeo.HelperLib.Game"
)
};
_logger.LogInformation("Initialized {Count} libraries", _libraryCache.Count);
}
public IActionResult Index()
@@ -62,7 +68,18 @@ public class HomeController : Controller
if (string.IsNullOrEmpty(id) || !_libraryCache.ContainsKey(id))
return NotFound();
return View(_libraryCache[id]);
var cache = _libraryCache[id];
if (cache.Documentation.Any())
{
_logger.LogInformation($"Found cache for {id}: {cache.Documentation.Count} entries");
}
else
{
_logger.LogError($"Cache for {id} is empty!");
}
return View(cache);
}
public IActionResult Privacy()
@@ -76,55 +93,258 @@ public class HomeController : Controller
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
copilot-pull-request-reviewer[bot] commented 2025-12-23 16:32:10 +00:00 (Migrated from github.com)
Review

This assignment to allDocumentation is useless, since its value is never read.

        IEnumerable<DocumentationStructure> allDocumentation;
This assignment to [allDocumentation](1) is useless, since its value is never read. ```suggestion IEnumerable<DocumentationStructure> allDocumentation; ```
copilot-pull-request-reviewer[bot] commented 2025-12-23 16:32:10 +00:00 (Migrated from github.com)
Review

This assignment to changelog is useless, since its value is never read.

        var changelogMarkdown = string.Empty;
        if (System.IO.File.Exists(changelogPath))
        {
            try
            {
                ChangelogTool.ExtractChangelog(assembly);
                changelogMarkdown = ChangelogTool.GetMarkdown(assembly);
                _logger.LogInformation("Parsed changelog for {Assembly}", assembly.GetName().Name);
This assignment to [changelog](1) is useless, since its value is never read. ```suggestion var changelogMarkdown = string.Empty; if (System.IO.File.Exists(changelogPath)) { try { ChangelogTool.ExtractChangelog(assembly); changelogMarkdown = ChangelogTool.GetMarkdown(assembly); _logger.LogInformation("Parsed changelog for {Assembly}", assembly.GetName().Name); ```
}
private LibraryDocumentationViewModel GenerateLibraryDocs(Assembly assembly, string changelogPath, string displayName, string description, string nugetPackageId)
private void LogDocumentationHelperInfo()
{
var allDocumentation = DocumentationHelperTool.GenerateDocumentation();
var assemblyName = assembly.GetName().Name;
try
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var target = assemblies.FirstOrDefault(a =>
a.GetName().Name.Equals("NetFlow.DocumentationHelper.Library", StringComparison.OrdinalIgnoreCase));
var filteredDocumentation = allDocumentation
.Where(d => d.AssemblyName.Equals(assemblyName, StringComparison.OrdinalIgnoreCase))
.OrderBy(d => d.ClassName)
if (target == null)
{
_logger.LogWarning("NetFlow.DocumentationHelper.Library not loaded. Loaded assemblies ({Count}):",
assemblies.Length);
foreach (var a in assemblies.OrderBy(a => a.GetName().Name))
{
_logger.LogInformation(" {Name} {Version} => {Location}", a.GetName().Name, a.GetName().Version,
SafeLocation(a));
}
return;
}
_logger.LogInformation("Found {Name} {Version} at {Location}", target.GetName().Name,
target.GetName().Version, SafeLocation(target));
copilot-pull-request-reviewer[bot] commented 2025-12-23 16:32:10 +00:00 (Migrated from github.com)
Review

Inefficient use of 'ContainsKey' and indexer.

Inefficient use of 'ContainsKey' and [indexer](1).
Type attrType = null;
try
{
attrType = target.GetType("NetFlow.DocumentationHelper.Library.Attributes.DocumentationAttribute",
false, true);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to GetType for DocumentationAttribute in {Assembly}",
target.GetName().Name);
}
if (attrType == null)
{
_logger.LogWarning("Type 'DocumentationAttribute' not found in {Assembly}.", target.GetName().Name);
}
else
{
var ctors = attrType.GetConstructors(BindingFlags.Public | BindingFlags.Instance |
BindingFlags.NonPublic);
if (!ctors.Any())
_logger.LogWarning("No constructors found on DocumentationAttribute in {Assembly}.",
target.GetName().Name);
foreach (var ctor in ctors)
{
var sig = string.Join(", ", ctor.GetParameters().Select(p => p.ParameterType.Name + " " + p.Name));
_logger.LogInformation("DocumentationAttribute ctor: ({Signature})", sig);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to log DocumentationHelper assembly info.");
}
static string SafeLocation(Assembly a)
{
try
{
return string.IsNullOrEmpty(a.Location) ? "<in-memory>" : a.Location;
}
catch
{
return "<unknown>";
}
}
}
private void InspectDocumentationHelperMethod()
{
try
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
Type helperType = null;
foreach (var asm in assemblies)
{
try
{
var t = asm.GetTypes().FirstOrDefault(tt =>
tt.Name.Equals("DocumentationHelperTool", StringComparison.OrdinalIgnoreCase)
|| tt.FullName?.EndsWith(".DocumentationHelperTool") == true);
if (t != null)
{
helperType = t;
break;
}
}
catch (ReflectionTypeLoadException rtlex)
{
_logger.LogDebug(rtlex, "Could not enumerate types in {Assembly}", asm.GetName().Name);
foreach (var loaderEx in rtlex.LoaderExceptions ?? Array.Empty<Exception>())
_logger.LogDebug(loaderEx, "Loader exception for {Assembly}", asm.GetName().Name);
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed enumerating types in {Assembly}", asm.GetName().Name);
}
}
if (helperType == null)
{
_logger.LogWarning("DocumentationHelperTool type not found in loaded assemblies.");
return;
}
_logger.LogInformation("DocumentationHelperTool found: {Type} in {Assembly} ({Location})",
helperType.FullName, helperType.Assembly.GetName().Name, SafeLocation(helperType.Assembly));
var method = helperType.GetMethod("GenerateDocumentation",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
if (method == null)
{
_logger.LogWarning("GenerateDocumentation method not found on {Type}.", helperType.FullName);
}
else
{
var parameters = string.Join(", ",
method.GetParameters().Select(p => p.ParameterType.Name + " " + p.Name));
_logger.LogInformation("Method: {Name} ({Parameters}) -> {ReturnType}", method.Name, parameters,
method.ReturnType.FullName);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to inspect DocumentationHelperTool type/method.");
}
static string SafeLocation(Assembly a)
{
try
{
return string.IsNullOrEmpty(a.Location) ? "<in-memory>" : a.Location;
}
catch
{
return "<unknown>";
}
}
}
private LibraryDocumentationViewModel GenerateLibraryDocs(Assembly assembly, string changelogPath,
string displayName, string description, string nugetPackageId)
{
_logger.LogInformation("=== Starting GenerateLibraryDocs for {Assembly} ===", assembly.GetName().Name);
_logger.LogInformation("Assembly location: {Location}", assembly.Location ?? "<in-memory>");
_logger.LogInformation("Changelog path: {Path}", changelogPath);
_logger.LogInformation("Changelog exists: {Exists}", System.IO.File.Exists(changelogPath));
// Log types in assembly and their attributes
try
{
var types = assembly.GetTypes();
_logger.LogInformation("Assembly {Name} contains {Count} types", assembly.GetName().Name, types.Length);
var typesWithDocAttr = types.Where(t =>
t.GetCustomAttributes().Any(a =>
a.GetType().Name.Contains("Documentation", StringComparison.OrdinalIgnoreCase)))
.ToList();
string changelogMarkdown = "";
_logger.LogInformation("Found {Count} types with DocumentationAttribute-like attributes",
typesWithDocAttr.Count);
if (typesWithDocAttr.Any())
{
foreach (var type in typesWithDocAttr.Take(5))
{
_logger.LogInformation(" Type with doc attr: {TypeName}", type.FullName);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to inspect assembly types");
}
LogDocumentationHelperInfo();
IEnumerable<DocumentationStructure> allDocumentation = Enumerable.Empty<DocumentationStructure>();
try
{
// Pass the specific assembly to only scan that assembly
var result = DocumentationHelperTool.GenerateDocumentation();
allDocumentation = result?
.Where(d => d.AssemblyName?.Equals(assembly.GetName().Name ,StringComparison.OrdinalIgnoreCase) == true)
.ToList() ?? Enumerable.Empty<DocumentationStructure>();
_logger.LogInformation("GenerateDocumentation returned {Count} items for {Assembly}",
allDocumentation.Count(), assembly.GetName().Name);
if (!allDocumentation.Any())
{
_logger.LogWarning(
"No documentation items generated for {Assembly}. Check if types have [Documentation] attribute applied.",
assembly.GetName().Name);
}
}
catch (MissingMethodException mmex)
{
_logger.LogError(mmex,
"Missing method when calling DocumentationHelperTool.GenerateDocumentation(). Possible assembly mismatch.");
InspectDocumentationHelperMethod();
ExceptionDispatchInfo.Capture(mmex).Throw();
}
catch (Exception ex)
{
_logger.LogError(ex, "DocumentationHelperTool.GenerateDocumentation threw an exception.");
InspectDocumentationHelperMethod();
throw;
}
var changelogMarkdown = string.Empty;
if (System.IO.File.Exists(changelogPath))
{
changelogMarkdown = ChangelogTool.GetMarkdown(assembly);
}
string version = assembly.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>()?.InformationalVersion
?? assembly.GetName().Version?.ToString()
?? "1.0.0";
if (version.Contains('+'))
try
{
version = version.Substring(0, version.IndexOf('+'));
ChangelogTool.ExtractChangelog(assembly);
changelogMarkdown = ChangelogTool.GetMarkdown(assembly);
_logger.LogInformation("Parsed changelog for {Assembly}", assembly.GetName().Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to parse changelog at {Path}", changelogPath);
}
}
else
{
_logger.LogWarning("Changelog file not found at {Path}", changelogPath);
}
return new LibraryDocumentationViewModel
var viewModel = new LibraryDocumentationViewModel
{
LibraryName = displayName,
Description = description,
Version = version,
NuGetPackageId = nugetPackageId,
Documentation = filteredDocumentation,
ChangelogMarkdown = changelogMarkdown
ChangelogMarkdown = changelogMarkdown,
Documentation = allDocumentation.ToList()
};
_logger.LogInformation(
"=== Completed GenerateLibraryDocs for {Assembly}. Total documentation items: {Count} ===",
assembly.GetName().Name, viewModel.Documentation.Count);
return viewModel;
}
static string GetSolutionRoot()
{
string dir = AppContext.BaseDirectory;
while (!string.IsNullOrEmpty(dir))
{
if (Directory.GetFiles(dir, "*.sln").Length > 0)
return dir;
dir = Directory.GetParent(dir)?.FullName;
}
throw new Exception("Solution root not found.");
return AppContext.BaseDirectory;
}
}
@@ -15,7 +15,11 @@
<ItemGroup>
<PackageReference Include="Markdig" Version="0.40.0" />
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.2" />
<PackageReference Include="MarkdigExtensions.SyntaxHighlighting" Version="1.0.3" />
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.3" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
</ItemGroup>
</Project>
+15
View File
@@ -1,8 +1,23 @@
using Serilog;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
// Configure logging
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console()
.WriteTo.File(
path: Path.Combine(AppContext.BaseDirectory, "logs", "documentation-.txt"),
rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
builder.Host.UseSerilog();
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -1,8 +1,16 @@
@* File: `OmegaLeo.HelperLib.Web/Views/Home/Library.cshtml` *@
@using ColorCode.Styling
@using GameDevLibrary.Extensions
@using Markdig
@model OmegaLeo.HelperLib.Web.Models.LibraryDocumentationViewModel
@{
ViewData["Title"] = Model.LibraryName;
var groupedByNamespace = Model.Documentation.GroupBy(d => d.AssemblyName).OrderBy(g => g.Key);
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.UseSyntaxHighlighting(StyleDictionary.DefaultDark)
.Build();
}
<div class="container-fluid mt-4">
@@ -75,7 +83,14 @@
{
<tr>
<td><code>@desc.Title</code></td>
<td>@Html.Raw(desc.Description)</td>
<td>@Html.Raw(desc.Description)<br/>
@if (desc.CodeExample.IsNotNullOrEmpty())
{
<strong>Example:</strong><br/>
<div class="example-code mb-2">
@Html.Raw(Markdig.Markdown.ToHtml(desc.CodeExample, pipeline))
</div>
}</td>
</tr>
}
</tbody>
@@ -34,6 +34,14 @@
<a class="nav-link" asp-area="" asp-controller="Home" asp-action="Library" asp-route-id="game">Game Dev</a>
</li>
</ul>
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="https://github.com/omegaleo/HelperLib" target="_blank" rel="noopener noreferrer" aria-label="GitHub repository">
<i class="bi bi-github" style="font-size:1.25rem;"></i> View on Github
</a>
</li>
</ul>
</div>
</div>
</nav>
@@ -42,12 +50,6 @@
@RenderBody()
</main>
<footer class="border-top footer text-muted">
<div class="container">
&copy; @DateTime.Now.Year - OmegaLeo Helper Libraries
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
+2 -1
View File
@@ -5,5 +5,6 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"https_port": 443
}
@@ -20,3 +20,21 @@ html {
body {
margin-bottom: 60px;
}
pre {
overflow: auto;
padding: 1rem;
background-color: #495057;
position: relative;
color: #f2f2f2;
}
code {
background-color: transparent !important;
}
.example-code > div {
border-radius: 0.5rem !important;
background-color: #495057 !important;
}
+8
View File
@@ -14,6 +14,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Web", "O
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmegaLeo.HelperLib.Game", "OmegaLeo.HelperLib.Game\OmegaLeo.HelperLib.Game.csproj", "{488BC3EA-9B7A-4251-B802-22EB098D6494}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFiles", "SolutionFiles", "{00CF0543-37F3-41DE-B78F-8ADA6DE796ED}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
CONTRIBUTING.md = CONTRIBUTING.md
LICENSE = LICENSE
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+3
View File
@@ -1,6 +1,9 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADocumentationDescription_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe7ce68ea4d3540e4bc8ef1bd962ca30b3a00_003F46_003F1f9d3ace_003FDocumentationDescription_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADocumentationHelperTool_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F165936b93b8c4f33b6ee955456d3fd6a3c00_003F08_003F718f7e37_003FDocumentationHelperTool_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fbf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMonitor_002ECoreCLR_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F9ee76fb4aded5e9da6e66cc2dcb127fac08d3ec5ac84fc6db9d4ee1d2e38_003FMonitor_002ECoreCLR_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARuntimeHandles_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F42d5e9d0d54843a23887036cb9c33c9ce48bbc6f7c35ed3759154a95456c796_003FRuntimeHandles_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeFileHandle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc21672d0d37542808f219d7d1f059dc1b2e000_003Faf_003Fbed3a9b4_003FSafeFileHandle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc61cc76f205b4535a5b4f40700c07900b2d200_003Fe5_003F6b3ec67e_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATreeChanges_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Fomegaleo_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F7478b0ce6362feb345e793626f5c504bd9d7ffeb748226521658c85fc4e29_003FTreeChanges_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@@ -1,14 +1,22 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using NetFlow.DocumentationHelper.Library.Attributes;
namespace GameDevLibrary.Helpers
{
[Documentation(nameof(BenchmarkUtility), "Utility class for benchmarking code execution time.", null, @"```csharp
BenchmarkUtility.Start(""MyBenchmark"");
// Code to benchmark
BenchmarkUtility.Stop(""MyBenchmark"");
var results = BenchmarkUtility.GetResults(""MyBenchmark"");
```")]
public class BenchmarkUtility
{
private static Dictionary<string, List<long>> _benchmarks = new Dictionary<string, List<long>>();
private static Dictionary<string, Stopwatch> _stopwatches = new Dictionary<string, Stopwatch>();
[Documentation("GetStopwatch", "Retrieves or creates a Stopwatch instance for the given key.", null, null)]
private static Stopwatch GetStopwatch(string key)
{
Stopwatch stopwatch;
@@ -22,6 +30,14 @@ namespace GameDevLibrary.Helpers
return stopwatch;
}
[Documentation("Record", "Records the execution time of the provided action and returns the elapsed time in milliseconds.", null, @"```csharp
var time = BenchmarkUtility.Record(() =>
{
// Code to benchmark
});
Console.WriteLine($""Elapsed time: {time} ms"");
```")]
public static long Record(Action actionToRecord)
{
var stopwatch = new Stopwatch();
@@ -33,6 +49,19 @@ namespace GameDevLibrary.Helpers
return stopwatch.ElapsedMilliseconds;
}
[Documentation("RecordAndSaveToResults", "Records the execution time of the provided action, saves it under the given key, and returns the elapsed time in milliseconds.", null, @"```csharp
var time = BenchmarkUtility.RecordAndSaveToResults(""MyBenchmark"", () =>
{
// Code to benchmark
});
Console.WriteLine($""Elapsed time: {time} ms"");
var results = BenchmarkUtility.GetResults(""MyBenchmark"");
var averageTime = results.Average();
Console.WriteLine($""Average time: {averageTime} ms"");
```")]
public static long RecordAndSaveToResults(string key, Action actionToRecord)
{
var stopwatch = new Stopwatch();
@@ -52,11 +81,13 @@ namespace GameDevLibrary.Helpers
return stopwatch.ElapsedMilliseconds;
}
[Documentation("Start", "Starts or restarts the stopwatch for the given key.", null, null)]
public static void Start(string key)
{
GetStopwatch(key).Restart();
}
[Documentation("Stop", "Stops the stopwatch for the given key and records the elapsed time.", null, null)]
public static void Stop(string key)
{
if (!_benchmarks.ContainsKey(key))
@@ -68,16 +99,19 @@ namespace GameDevLibrary.Helpers
_benchmarks[key].Add(GetStopwatch(key).ElapsedMilliseconds);
}
[Documentation("GetResults", "Retrieves the list of recorded times for the given key.", null, null)]
public static List<long> GetResults(string key)
{
return _benchmarks.ContainsKey(key) ? _benchmarks[key] : new List<long>();
}
[Documentation("ClearResults", "Clears all recorded benchmark results.", null, null)]
public static void ClearResults()
{
_benchmarks.Clear();
}
[Documentation("GetAllResults", "Retrieves all recorded benchmark results.", null, null)]
public static Dictionary<string, List<long>> GetAllResults()
{
return new Dictionary<string, List<long>>(_benchmarks);
+1 -1
View File
@@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.2" />
<PackageReference Include="NetFlow.DocumentationHelper.Library" Version="1.1.3" />
</ItemGroup>
<ItemGroup>
+45 -1
View File
@@ -1,6 +1,50 @@
# HelperLib
copilot-pull-request-reviewer[bot] commented 2025-12-23 16:32:09 +00:00 (Migrated from github.com)
Review

Missing period at the end of the sentence. The sentence should end with "details." to maintain consistency with documentation standards.

This project is licensed under the AGPL v3 License. See the [LICENSE](LICENSE) file for details.
Missing period at the end of the sentence. The sentence should end with "details." to maintain consistency with documentation standards. ```suggestion This project is licensed under the AGPL v3 License. See the [LICENSE](LICENSE) file for details. ```
Library to help with C# projects and with game development using C#
Lightweight utility library for C\# projects and game development helpers.
## Table of contents
- [Overview](#overview)
- [Features](#features)
- [Install](#install)
- [Documentation](#documentation)
- [Contributing](#contributing)
- [License](#license)
- [Socials](#socials)
## Overview
`HelperLib` contains small helpers and abstractions commonly useful in application and game development (helpers for collections, math, and utilities to keep game and engine code concise).
## Features
- Small, focused utilities for C\#
- Helpers that favor clarity and testability
- Game development specific helpers in the `OmegaLeo.HelperLib.Game` package
- Changelog generation utilities in the `OmegaLeo.HelperLib.Changelog` package
- Lightweight and easy to integrate into existing projects
- Well-documented and maintained
- Open source under the AGPL v3 License
## Install
Add the library to your project via NuGet:
```bash
dotnet add package OmegaLeo.HelperLib
dotnet add package OmegaLeo.HelperLib.Game
dotnet add package OmegaLeo.HelperLib.Changelog
```
## Documentation
The documentation for this project can be found at [https://library.omegaleo.dev](https://library.omegaleo.dev).
This documentation is automatically generated from the code using the [NetFlow.DocumentationHelper.Library](https://github.com/omegaleo/Netflow.DocumentationHelper).
## Contributing
Contributions are welcome! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines.
## License
This project is licensed under the AGPL v3 License. See the [LICENSE](LICENSE) file for details.
## Socials
[![Youtube](https://img.shields.io/youtube/channel/subscribers/UCsdYKC7EK4Z_cpgWt0YiO9Q?style=plastic&label=Youtube&color=red