diff --git a/Models/CpuPreset.cs b/Models/CpuPreset.cs
new file mode 100644
index 0000000..e5dc17e
--- /dev/null
+++ b/Models/CpuPreset.cs
@@ -0,0 +1,46 @@
+/*
+ * ThreadPilot - Advanced Windows Process and Power Plan Manager
+ * Copyright (C) 2025 Prime Build
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+namespace ThreadPilot.Models
+{
+ ///
+ /// Topology-aware CPU affinity preset generated from a CPU topology snapshot.
+ ///
+ public sealed record CpuPreset
+ {
+ public string PresetId { get; init; } = string.Empty;
+
+ public string Name { get; init; } = string.Empty;
+
+ public string Description { get; init; } = string.Empty;
+
+ public CpuSelection Selection { get; init; } = new();
+
+ public string Reason { get; init; } = string.Empty;
+
+ public string? SourcePresetId { get; init; }
+
+ public string? Warning { get; init; }
+
+ public CpuTopologySignature? GeneratedByTopologySignature { get; init; }
+
+ public bool IsUserEditable { get; init; } = true;
+
+ public bool IsGenerated { get; init; } = true;
+
+ public bool ReviewRequired { get; init; }
+ }
+}
diff --git a/Services/CpuPresetGenerationOptions.cs b/Services/CpuPresetGenerationOptions.cs
new file mode 100644
index 0000000..cd77711
--- /dev/null
+++ b/Services/CpuPresetGenerationOptions.cs
@@ -0,0 +1,28 @@
+/*
+ * ThreadPilot - Advanced Windows Process and Power Plan Manager
+ * Copyright (C) 2025 Prime Build
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+namespace ThreadPilot.Services
+{
+ public sealed record CpuPresetGenerationOptions
+ {
+ public bool ExcludeCpu0ForGaming { get; init; } = true;
+
+ public IReadOnlySet DeletedGeneratedPresetIds { get; init; } =
+ new HashSet(StringComparer.Ordinal);
+
+ public bool IncludeExperimentalPresets { get; init; }
+ }
+}
diff --git a/Services/CpuPresetGenerator.cs b/Services/CpuPresetGenerator.cs
new file mode 100644
index 0000000..f3f31b4
--- /dev/null
+++ b/Services/CpuPresetGenerator.cs
@@ -0,0 +1,340 @@
+/*
+ * ThreadPilot - Advanced Windows Process and Power Plan Manager
+ * Copyright (C) 2025 Prime Build
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+namespace ThreadPilot.Services
+{
+ using ThreadPilot.Models;
+
+ public sealed class CpuPresetGenerator : ICpuPresetGenerator
+ {
+ private const string GamingWarning =
+ "Suggested default. Results may vary by game and system. You can edit or delete this preset.";
+
+ public IReadOnlyList Generate(
+ CpuTopologySnapshot topology,
+ CpuPresetGenerationOptions? options = null)
+ {
+ ArgumentNullException.ThrowIfNull(topology);
+
+ var resolvedOptions = options ?? new CpuPresetGenerationOptions();
+ var presets = new List();
+
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ "all-cores",
+ "All cores",
+ "Use every logical processor reported by the current CPU topology.",
+ topology.LogicalProcessors,
+ topology,
+ "Uses all logical processors from the topology snapshot."));
+
+ var allPhysicalProcessors = HasCoreIndexForAllProcessors(topology)
+ ? SelectOneLogicalProcessorPerCore(topology.LogicalProcessors, topology)
+ : [];
+ if (allPhysicalProcessors.Count > 0)
+ {
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ "all-physical-cores",
+ "All physical cores / no SMT",
+ "Use one logical processor per physical core.",
+ allPhysicalProcessors,
+ topology,
+ "Uses CoreIndex and SMT sibling metadata to select one logical processor per core."));
+ }
+
+ var allExceptCpu0 = topology.LogicalProcessors
+ .Where(processor => processor.GlobalIndex != 0)
+ .ToList();
+ if (topology.LogicalProcessors.Count >= 2 && allExceptCpu0.Count > 0)
+ {
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ "all-except-cpu0",
+ "All except CPU0",
+ "Use every logical processor except global CPU index 0.",
+ allExceptCpu0,
+ topology,
+ "Excludes GlobalIndex 0 while keeping the remaining topology-aware processor refs."));
+ }
+
+ var efficiencyClasses = topology.LogicalProcessors
+ .Select(processor => topology.TryGetEfficiencyClass(processor, out var efficiencyClass)
+ ? (byte?)efficiencyClass
+ : null)
+ .Where(efficiencyClass => efficiencyClass.HasValue)
+ .Select(efficiencyClass => efficiencyClass!.Value)
+ .Distinct()
+ .OrderBy(efficiencyClass => efficiencyClass)
+ .ToList();
+
+ var hasDistinctEfficiencyClasses = efficiencyClasses.Count >= 2;
+ List pCoreProcessors = [];
+ if (hasDistinctEfficiencyClasses)
+ {
+ var performanceClass = efficiencyClasses.Max();
+ pCoreProcessors = topology.LogicalProcessors
+ .Where(processor =>
+ topology.TryGetEfficiencyClass(processor, out var efficiencyClass) &&
+ efficiencyClass == performanceClass)
+ .ToList();
+ var eCoreProcessors = topology.LogicalProcessors
+ .Where(processor =>
+ topology.TryGetEfficiencyClass(processor, out var efficiencyClass) &&
+ efficiencyClass < performanceClass)
+ .ToList();
+
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ "p-cores-only",
+ "P-cores only",
+ "Use logical processors in the highest EfficiencyClass.",
+ pCoreProcessors,
+ topology,
+ "Uses the highest EfficiencyClass in the topology snapshot as performance cores."));
+
+ if (HasCoreIndexForProcessors(pCoreProcessors, topology))
+ {
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ "p-cores-no-smt",
+ "P-cores only / no SMT",
+ "Use one logical processor per performance core.",
+ SelectOneLogicalProcessorPerCore(pCoreProcessors, topology),
+ topology,
+ "Uses EfficiencyClass plus CoreIndex and SMT sibling metadata to choose one logical processor per P-core."));
+ }
+
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ "e-cores-only",
+ "E-cores only",
+ "Use logical processors below the highest EfficiencyClass.",
+ eCoreProcessors,
+ topology,
+ "Uses EfficiencyClass values below the performance class as efficiency cores.",
+ "Usually not recommended for games. Useful for background tasks."));
+ }
+
+ if (topology.Signature.LastLevelCacheGroupCount > 1 && HasCoreIndexForAllProcessors(topology))
+ {
+ var l3Groups = topology.LogicalProcessors
+ .Select(processor => topology.TryGetLastLevelCacheIndex(processor, out var cacheIndex)
+ ? new { Processor = processor, CacheIndex = (int?)cacheIndex }
+ : null)
+ .Where(item => item?.CacheIndex != null)
+ .GroupBy(item => item!.CacheIndex!.Value)
+ .OrderBy(group => group.Key);
+
+ foreach (var group in l3Groups)
+ {
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ $"l3-group-{group.Key}-physical",
+ $"L3 group {group.Key} / physical cores",
+ $"Use one logical processor per core in L3/cache group {group.Key}.",
+ SelectOneLogicalProcessorPerCore(group.Select(item => item!.Processor), topology),
+ topology,
+ $"Based on LastLevelCacheIndex/L3 cache group {group.Key}, not on CPU SKU naming."));
+ }
+ }
+
+ var bestGamingSourceId = SelectBestGamingSourcePresetId(presets, resolvedOptions);
+ if (bestGamingSourceId != null)
+ {
+ var sourcePreset = presets.Single(preset => preset.PresetId == bestGamingSourceId);
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ "best-gaming",
+ "Best gaming suggestion",
+ "Suggested topology-aware starting point for games.",
+ sourcePreset.Selection.LogicalProcessors,
+ topology,
+ CreateBestGamingReason(bestGamingSourceId),
+ GamingWarning,
+ sourcePresetId: bestGamingSourceId));
+ }
+
+ AddPreset(
+ presets,
+ resolvedOptions,
+ CreatePreset(
+ "safe-compatibility",
+ "Safe compatibility",
+ "Use every logical processor for maximum compatibility.",
+ topology.LogicalProcessors,
+ topology,
+ "Maximum compatibility."));
+
+ // TODO: X3D CCD-only presets require reliable cache/topology detection.
+ // Do not generate X3D CCD-only until it can be detected with confidence.
+ return presets;
+ }
+
+ private static string? SelectBestGamingSourcePresetId(
+ IReadOnlyList presets,
+ CpuPresetGenerationOptions options)
+ {
+ var orderedCandidates = options.ExcludeCpu0ForGaming
+ ? new[]
+ {
+ "p-cores-no-smt",
+ "l3-group-0-physical",
+ "all-physical-cores",
+ "all-except-cpu0",
+ "all-cores",
+ }
+ : new[]
+ {
+ "p-cores-no-smt",
+ "l3-group-0-physical",
+ "all-physical-cores",
+ "all-cores",
+ };
+
+ return orderedCandidates.FirstOrDefault(candidate =>
+ presets.Any(preset => preset.PresetId == candidate));
+ }
+
+ private static string CreateBestGamingReason(string sourcePresetId) =>
+ sourcePresetId switch
+ {
+ "p-cores-no-smt" =>
+ "Selected P-cores without SMT because the topology exposes distinct performance and efficiency core classes.",
+ "l3-group-0-physical" =>
+ "Selected physical cores from L3/cache group 0 because the topology exposes multiple L3 groups and no P/E core classes.",
+ "all-physical-cores" =>
+ "Selected one logical processor per physical core because reliable CoreIndex metadata is available.",
+ "all-except-cpu0" =>
+ "Selected all logical processors except CPU0 as a conservative gaming-oriented fallback.",
+ "all-cores" =>
+ "Selected all logical processors as the safest fallback because no more specific topology preset was available.",
+ _ =>
+ "Selected the best available topology-aware preset for this CPU.",
+ };
+
+ private static bool HasCoreIndexForAllProcessors(CpuTopologySnapshot topology) =>
+ HasCoreIndexForProcessors(topology.LogicalProcessors, topology);
+
+ private static bool HasCoreIndexForProcessors(
+ IEnumerable processors,
+ CpuTopologySnapshot topology)
+ {
+ var processorList = processors.ToList();
+ return processorList.Count > 0 &&
+ processorList.All(processor => topology.TryGetCoreIndex(processor, out _));
+ }
+
+ private static List SelectOneLogicalProcessorPerCore(
+ IEnumerable processors,
+ CpuTopologySnapshot topology)
+ {
+ return processors
+ .Select(processor =>
+ {
+ topology.TryGetCoreIndex(processor, out var coreIndex);
+ return new
+ {
+ Processor = processor,
+ CoreIndex = coreIndex,
+ SmtSiblingCount = topology.GetSmtSiblingGlobalIndexes(processor).Count,
+ };
+ })
+ .GroupBy(item => item.CoreIndex)
+ .OrderBy(group => group.Key)
+ .Select(group => group
+ .OrderBy(item => item.Processor.GlobalIndex)
+ .ThenBy(item => item.SmtSiblingCount)
+ .First()
+ .Processor)
+ .ToList();
+ }
+
+ private static CpuPreset CreatePreset(
+ string presetId,
+ string name,
+ string description,
+ IEnumerable processors,
+ CpuTopologySnapshot topology,
+ string reason,
+ string? warning = null,
+ string? sourcePresetId = null,
+ bool reviewRequired = false)
+ {
+ var selectedProcessors = processors
+ .Distinct()
+ .OrderBy(processor => processor.GlobalIndex)
+ .ThenBy(processor => processor.Group)
+ .ThenBy(processor => processor.LogicalProcessorNumber)
+ .ToList();
+
+ return new CpuPreset
+ {
+ PresetId = presetId,
+ Name = name,
+ Description = description,
+ Selection = CpuSelection.FromProcessors(selectedProcessors, topology, reason),
+ Reason = reason,
+ SourcePresetId = sourcePresetId,
+ Warning = warning,
+ GeneratedByTopologySignature = topology.Signature,
+ IsUserEditable = true,
+ IsGenerated = true,
+ ReviewRequired = reviewRequired,
+ };
+ }
+
+ private static void AddPreset(
+ List presets,
+ CpuPresetGenerationOptions options,
+ CpuPreset preset)
+ {
+ if (options.DeletedGeneratedPresetIds.Contains(preset.PresetId) ||
+ preset.Selection.LogicalProcessors.Count == 0 ||
+ presets.Any(existing => existing.PresetId == preset.PresetId))
+ {
+ return;
+ }
+
+ var duplicateSamePurpose = presets.Any(existing =>
+ existing.Reason == preset.Reason &&
+ existing.Selection.GlobalLogicalProcessorIndexes.SequenceEqual(
+ preset.Selection.GlobalLogicalProcessorIndexes));
+ if (duplicateSamePurpose)
+ {
+ return;
+ }
+
+ presets.Add(preset);
+ }
+ }
+}
diff --git a/Services/ICpuPresetGenerator.cs b/Services/ICpuPresetGenerator.cs
new file mode 100644
index 0000000..c53763a
--- /dev/null
+++ b/Services/ICpuPresetGenerator.cs
@@ -0,0 +1,27 @@
+/*
+ * ThreadPilot - Advanced Windows Process and Power Plan Manager
+ * Copyright (C) 2025 Prime Build
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, version 3 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+namespace ThreadPilot.Services
+{
+ using ThreadPilot.Models;
+
+ public interface ICpuPresetGenerator
+ {
+ IReadOnlyList Generate(
+ CpuTopologySnapshot topology,
+ CpuPresetGenerationOptions? options = null);
+ }
+}
diff --git a/Services/ServiceConfiguration.cs b/Services/ServiceConfiguration.cs
index eb70c5e..477fe46 100644
--- a/Services/ServiceConfiguration.cs
+++ b/Services/ServiceConfiguration.cs
@@ -111,6 +111,7 @@ private static IServiceCollection ConfigureCoreSystemServices(this IServiceColle
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
// CoreMaskService needs IServiceProvider for checking profile references
services.AddSingleton(sp =>
@@ -238,4 +239,3 @@ public static void ValidateServiceConfiguration(IServiceProvider serviceProvider
}
}
}
-
diff --git a/Tests/ThreadPilot.Core.Tests/CpuPresetGeneratorTests.cs b/Tests/ThreadPilot.Core.Tests/CpuPresetGeneratorTests.cs
new file mode 100644
index 0000000..674eaf0
--- /dev/null
+++ b/Tests/ThreadPilot.Core.Tests/CpuPresetGeneratorTests.cs
@@ -0,0 +1,359 @@
+namespace ThreadPilot.Core.Tests
+{
+ using ThreadPilot.Models;
+ using ThreadPilot.Services;
+
+ public sealed class CpuPresetGeneratorTests
+ {
+ [Fact]
+ public void Generate_WithFourCoreEightThreadSmt_GeneratesSafeBasePresets()
+ {
+ var topology = CreateSmtTopology(physicalCoreCount: 4, threadsPerCore: 2);
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ AssertPresetIdsContain(
+ presets,
+ "all-cores",
+ "all-physical-cores",
+ "all-except-cpu0",
+ "best-gaming",
+ "safe-compatibility");
+ Assert.Equal(4, GetPreset(presets, "all-physical-cores").Selection.LogicalProcessors.Count);
+ AssertBestGamingSource(GetPreset(presets, "best-gaming"), "all-physical-cores");
+ AssertValidPresets(presets, topology);
+ AssertStableIdsAndOrder(generator, topology, presets);
+ }
+
+ [Fact]
+ public void Generate_WithEightCoreEightThreadSmtOff_KeepsBestGamingValid()
+ {
+ var topology = CreateSmtTopology(physicalCoreCount: 8, threadsPerCore: 1);
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ var allCores = GetPreset(presets, "all-cores");
+ var physical = presets.SingleOrDefault(preset => preset.PresetId == "all-physical-cores");
+ if (physical != null)
+ {
+ AssertSameSelection(allCores, physical);
+ Assert.NotEqual(allCores.Reason, physical.Reason);
+ }
+
+ var bestGaming = GetPreset(presets, "best-gaming");
+ Assert.NotEmpty(bestGaming.Selection.LogicalProcessors);
+ AssertBestGamingSource(bestGaming, "all-physical-cores");
+ AssertValidPresets(presets, topology);
+ }
+
+ [Fact]
+ public void Generate_WithHybridPAndECoresWithHt_GeneratesHybridPresets()
+ {
+ var topology = CreateHybridTopology(pCoreCount: 4, eCoreCount: 4, pCoreThreads: 2);
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ AssertPresetIdsContain(presets, "p-cores-only", "p-cores-no-smt", "e-cores-only");
+ Assert.Equal(8, GetPreset(presets, "p-cores-only").Selection.LogicalProcessors.Count);
+ Assert.Equal(4, GetPreset(presets, "p-cores-no-smt").Selection.LogicalProcessors.Count);
+ Assert.Equal(4, GetPreset(presets, "e-cores-only").Selection.LogicalProcessors.Count);
+ AssertBestGamingSource(GetPreset(presets, "best-gaming"), "p-cores-no-smt");
+ AssertValidPresets(presets, topology);
+ }
+
+ [Fact]
+ public void Generate_WithHybridPAndECoresWithoutHt_HandlesNoSmtDuplicate()
+ {
+ var topology = CreateHybridTopology(pCoreCount: 4, eCoreCount: 4, pCoreThreads: 1);
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ var pCoresOnly = GetPreset(presets, "p-cores-only");
+ var pCoresNoSmt = presets.SingleOrDefault(preset => preset.PresetId == "p-cores-no-smt");
+ if (pCoresNoSmt != null)
+ {
+ AssertSameSelection(pCoresOnly, pCoresNoSmt);
+ Assert.NotEqual(pCoresOnly.Reason, pCoresNoSmt.Reason);
+ }
+
+ AssertValidPresets(presets, topology);
+ }
+
+ [Fact]
+ public void Generate_WithRyzenDualCcdSixPlusSix_GeneratesL3PhysicalPresets()
+ {
+ var topology = CreateDualCcdTopology(physicalCoresPerCcd: 6);
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ AssertPresetIdsContain(presets, "l3-group-0-physical", "l3-group-1-physical");
+ Assert.Equal(6, GetPreset(presets, "l3-group-0-physical").Selection.LogicalProcessors.Count);
+ Assert.Equal(6, GetPreset(presets, "l3-group-1-physical").Selection.LogicalProcessors.Count);
+ AssertBestGamingSource(GetPreset(presets, "best-gaming"), "l3-group-0-physical");
+ Assert.Contains("L3", GetPreset(presets, "l3-group-0-physical").Reason, StringComparison.OrdinalIgnoreCase);
+ AssertValidPresets(presets, topology);
+ }
+
+ [Fact]
+ public void Generate_WithRyzenDualCcdEightPlusEight_GeneratesEightPhysicalPerL3Preset()
+ {
+ var topology = CreateDualCcdTopology(physicalCoresPerCcd: 8);
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ Assert.Equal(8, GetPreset(presets, "l3-group-0-physical").Selection.LogicalProcessors.Count);
+ Assert.Equal(8, GetPreset(presets, "l3-group-1-physical").Selection.LogicalProcessors.Count);
+ AssertBestGamingSource(GetPreset(presets, "best-gaming"), "l3-group-0-physical");
+ AssertValidPresets(presets, topology);
+ }
+
+ [Fact]
+ public void Generate_WithMoreThan64LogicalProcessors_UsesCpuSelectionWithoutCpu64Alias()
+ {
+ var topology = CreateSmtTopology(physicalCoreCount: 40, threadsPerCore: 2);
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ var allCores = GetPreset(presets, "all-cores");
+ Assert.Contains(allCores.Selection.LogicalProcessors, processor => processor.GlobalIndex == 64);
+ Assert.NotEqual(
+ allCores.Selection.LogicalProcessors.Single(processor => processor.GlobalIndex == 64),
+ allCores.Selection.LogicalProcessors.Single(processor => processor.GlobalIndex == 0));
+ Assert.Null(CpuSelection.ToLegacyAffinityMaskOrNull(allCores.Selection));
+ AssertValidPresets(presets, topology);
+ }
+
+ [Fact]
+ public void Generate_WhenGeneratedPresetWasDeleted_DoesNotRegenerateIt()
+ {
+ var topology = CreateSmtTopology(physicalCoreCount: 4, threadsPerCore: 2);
+ var generator = new CpuPresetGenerator();
+ var options = new CpuPresetGenerationOptions
+ {
+ DeletedGeneratedPresetIds = new HashSet(StringComparer.Ordinal)
+ {
+ "best-gaming",
+ },
+ };
+
+ var presets = generator.Generate(topology, options);
+
+ Assert.DoesNotContain(presets, preset => preset.PresetId == "best-gaming");
+ AssertPresetIdsContain(presets, "all-cores", "safe-compatibility");
+ }
+
+ [Fact]
+ public void Generate_WithoutCoreIndex_SkipsPhysicalPresets()
+ {
+ var topology = CpuTopologySnapshot.Create(
+ CreateProcessorRefs(8),
+ signature: CreateSignature(logicalProcessorCount: 8, physicalCoreCount: 0));
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ Assert.DoesNotContain(presets, preset => preset.PresetId == "all-physical-cores");
+ Assert.DoesNotContain(presets, preset => preset.PresetId == "p-cores-no-smt");
+ Assert.DoesNotContain(presets, preset => preset.PresetId.StartsWith("l3-group-", StringComparison.Ordinal));
+ AssertPresetIdsContain(presets, "all-cores", "all-except-cpu0", "best-gaming", "safe-compatibility");
+ AssertBestGamingSource(GetPreset(presets, "best-gaming"), "all-except-cpu0");
+ AssertValidPresets(presets, topology);
+ }
+
+ [Fact]
+ public void Generate_DoesNotReturnEmptySelections()
+ {
+ var topology = CreateHybridTopology(pCoreCount: 2, eCoreCount: 2, pCoreThreads: 2);
+ var generator = new CpuPresetGenerator();
+
+ var presets = generator.Generate(topology);
+
+ Assert.All(presets, preset => Assert.NotEmpty(preset.Selection.LogicalProcessors));
+ }
+
+ private static CpuPreset GetPreset(IReadOnlyList presets, string presetId) =>
+ presets.Single(preset => preset.PresetId == presetId);
+
+ private static void AssertPresetIdsContain(IReadOnlyList presets, params string[] presetIds)
+ {
+ foreach (var presetId in presetIds)
+ {
+ Assert.Contains(presets, preset => preset.PresetId == presetId);
+ }
+ }
+
+ private static void AssertSameSelection(CpuPreset expected, CpuPreset actual) =>
+ Assert.Equal(
+ expected.Selection.GlobalLogicalProcessorIndexes,
+ actual.Selection.GlobalLogicalProcessorIndexes);
+
+ private static void AssertBestGamingSource(CpuPreset bestGaming, string expectedSourcePresetId)
+ {
+ Assert.Equal("best-gaming", bestGaming.PresetId);
+ Assert.Equal(expectedSourcePresetId, bestGaming.SourcePresetId);
+ Assert.NotEqual(bestGaming.SourcePresetId, bestGaming.Reason);
+ Assert.False(string.IsNullOrWhiteSpace(bestGaming.Reason));
+ }
+
+ private static void AssertStableIdsAndOrder(
+ CpuPresetGenerator generator,
+ CpuTopologySnapshot topology,
+ IReadOnlyList firstRun)
+ {
+ var secondRun = generator.Generate(topology);
+ Assert.Equal(
+ firstRun.Select(preset => preset.PresetId),
+ secondRun.Select(preset => preset.PresetId));
+ }
+
+ private static void AssertValidPresets(IReadOnlyList presets, CpuTopologySnapshot topology)
+ {
+ Assert.NotEmpty(presets);
+ Assert.Equal(presets.Count, presets.Select(preset => preset.PresetId).Distinct(StringComparer.Ordinal).Count());
+
+ var topologyProcessors = topology.LogicalProcessors.ToHashSet();
+ foreach (var preset in presets)
+ {
+ Assert.False(string.IsNullOrWhiteSpace(preset.PresetId));
+ Assert.False(string.IsNullOrWhiteSpace(preset.Name));
+ Assert.False(string.IsNullOrWhiteSpace(preset.Description));
+ Assert.False(string.IsNullOrWhiteSpace(preset.Reason));
+ Assert.True(preset.IsGenerated);
+ Assert.True(preset.IsUserEditable);
+ Assert.NotEmpty(preset.Selection.LogicalProcessors);
+ Assert.Equal(topology.Signature, preset.GeneratedByTopologySignature);
+ Assert.Equal(topology.Signature, preset.Selection.Metadata.TopologySignature);
+ Assert.All(preset.Selection.LogicalProcessors, processor => Assert.Contains(processor, topologyProcessors));
+ }
+ }
+
+ private static CpuTopologySnapshot CreateSmtTopology(int physicalCoreCount, int threadsPerCore)
+ {
+ var processors = new List();
+ var coreIndexes = new Dictionary();
+ var siblings = new Dictionary>();
+
+ for (var core = 0; core < physicalCoreCount; core++)
+ {
+ var coreProcessors = new List();
+ for (var thread = 0; thread < threadsPerCore; thread++)
+ {
+ var globalIndex = (core * threadsPerCore) + thread;
+ var processor = CreateProcessorRef(globalIndex);
+ processors.Add(processor);
+ coreIndexes[processor] = core;
+ coreProcessors.Add(processor);
+ }
+
+ foreach (var processor in coreProcessors)
+ {
+ siblings[processor] = coreProcessors
+ .Where(sibling => sibling != processor)
+ .Select(sibling => sibling.GlobalIndex)
+ .ToList();
+ }
+ }
+
+ return CpuTopologySnapshot.Create(
+ processors,
+ signature: CreateSignature(processors.Count, physicalCoreCount),
+ coreIndexes: coreIndexes,
+ smtSiblingGlobalIndexes: siblings);
+ }
+
+ private static CpuTopologySnapshot CreateHybridTopology(int pCoreCount, int eCoreCount, int pCoreThreads)
+ {
+ var processors = new List();
+ var coreIndexes = new Dictionary();
+ var siblings = new Dictionary>();
+ var efficiency = new Dictionary();
+
+ for (var core = 0; core < pCoreCount; core++)
+ {
+ var coreProcessors = new List();
+ for (var thread = 0; thread < pCoreThreads; thread++)
+ {
+ var processor = CreateProcessorRef(processors.Count);
+ processors.Add(processor);
+ coreIndexes[processor] = core;
+ efficiency[processor] = 2;
+ coreProcessors.Add(processor);
+ }
+
+ foreach (var processor in coreProcessors)
+ {
+ siblings[processor] = coreProcessors
+ .Where(sibling => sibling != processor)
+ .Select(sibling => sibling.GlobalIndex)
+ .ToList();
+ }
+ }
+
+ for (var core = 0; core < eCoreCount; core++)
+ {
+ var processor = CreateProcessorRef(processors.Count);
+ processors.Add(processor);
+ coreIndexes[processor] = pCoreCount + core;
+ efficiency[processor] = 0;
+ siblings[processor] = [];
+ }
+
+ return CpuTopologySnapshot.Create(
+ processors,
+ efficiencyClasses: efficiency,
+ signature: CreateSignature(processors.Count, pCoreCount + eCoreCount),
+ coreIndexes: coreIndexes,
+ smtSiblingGlobalIndexes: siblings);
+ }
+
+ private static CpuTopologySnapshot CreateDualCcdTopology(int physicalCoresPerCcd)
+ {
+ var processorCount = physicalCoresPerCcd * 2;
+ var processors = CreateProcessorRefs(processorCount).ToList();
+ var coreIndexes = processors.ToDictionary(processor => processor, processor => processor.GlobalIndex);
+ var siblings = processors.ToDictionary(
+ processor => processor,
+ _ => (IReadOnlyList)[]);
+ var l3Indexes = processors.ToDictionary(
+ processor => processor,
+ processor => processor.GlobalIndex < physicalCoresPerCcd ? 0 : 1);
+
+ return CpuTopologySnapshot.Create(
+ processors,
+ signature: CreateSignature(
+ logicalProcessorCount: processorCount,
+ physicalCoreCount: processorCount,
+ lastLevelCacheGroupCount: 2),
+ coreIndexes: coreIndexes,
+ lastLevelCacheIndexes: l3Indexes,
+ smtSiblingGlobalIndexes: siblings);
+ }
+
+ private static IEnumerable CreateProcessorRefs(int count) =>
+ Enumerable.Range(0, count).Select(CreateProcessorRef);
+
+ private static ProcessorRef CreateProcessorRef(int globalIndex) =>
+ new((ushort)(globalIndex / 64), (byte)(globalIndex % 64), globalIndex);
+
+ private static CpuTopologySignature CreateSignature(
+ int logicalProcessorCount,
+ int physicalCoreCount,
+ int lastLevelCacheGroupCount = 0) =>
+ new()
+ {
+ CpuBrand = "Synthetic CPU",
+ LogicalProcessorCount = logicalProcessorCount,
+ PhysicalCoreCount = physicalCoreCount,
+ ProcessorGroupCount = Math.Max(1, (logicalProcessorCount + 63) / 64),
+ LastLevelCacheGroupCount = lastLevelCacheGroupCount,
+ Source = "Test",
+ };
+ }
+}