diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index c73bbac7b..e3e25c0d5 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -1292,36 +1292,39 @@ static void RequireTypedBehaviorBodyKeys(string root) static void RequireNoDeadRuntimeProjectionCaches(string root) { - var path = Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeProjection", "ReflectionExtensions.cs"); - if (!File.Exists(path)) - return; - - var forbiddenSymbols = new[] + var runtimeProjectionDirectory = Path.Combine(root, "Assets", "Scripts", "ServerShared", "RuntimeProjection"); + if (Directory.Exists(runtimeProjectionDirectory)) { - "GetAllInterfaceClasses(", - "GetParentTypes(", - "GetAllChildClasses(", - "GetAllGenericChildClasses(", - "IsAssignableToGenericType(", - "GetFullName(", - "InterfaceClasses", - "ParentTypes", - "ChildClasses", - "GenericChildClasses", - "AppDomain.CurrentDomain.GetAssemblies()" - }; + var survivors = Directory.EnumerateFileSystemEntries(runtimeProjectionDirectory) + .Select(path => Path.GetRelativePath(root, path)) + .ToArray(); + throw new InvalidOperationException( + "Dead runtime-projection helper directory should stay deleted; move live helpers under explicit runtime owners: " + + string.Join(", ", survivors)); + } - var hits = File.ReadLines(path) - .Select((line, index) => new { Line = line, LineNumber = index + 1 }) - .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"Assets/Scripts/ServerShared/RuntimeProjection/ReflectionExtensions.cs:{line.LineNumber}: {line.Line.Trim()}") - .ToArray(); + var selectionExtensionsPath = Path.Combine(root, "Assets", "Scripts", "ServerShared", "EnumerableSelectionExtensions.cs"); + var sharedProjectPath = Path.Combine(root, "Aetheria.Shared.Unity.csproj"); + var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs"); + var selectionExtensions = File.Exists(selectionExtensionsPath) + ? File.ReadAllText(selectionExtensionsPath) + : ""; + var sharedProject = File.Exists(sharedProjectPath) + ? File.ReadAllText(sharedProjectPath) + : ""; + var tradeMenu = File.Exists(tradeMenuPath) + ? File.ReadAllText(tradeMenuPath) + : ""; - if (hits.Length > 0) + if (!selectionExtensions.Contains("public static T MaxBy", StringComparison.Ordinal) || + !selectionExtensions.Contains("public static T MinBy", StringComparison.Ordinal) || + !sharedProject.Contains(@"Assets\Scripts\ServerShared\EnumerableSelectionExtensions.cs", StringComparison.Ordinal) || + sharedProject.Contains("RuntimeProjection", StringComparison.Ordinal) || + tradeMenu.Contains(".FormatTypeName()", StringComparison.Ordinal) || + !tradeMenu.Contains("private static string FormatTypeName(string typeName)", StringComparison.Ordinal)) { throw new InvalidOperationException( - "Dead runtime-projection reflection caches should stay deleted; keep only the live string-formatting helpers: " + - string.Join("; ", hits)); + "Live RuntimeProjection remnants must move into explicit owners: enumerable selection helpers under ServerShared and trade labels inside TradeMenu."); } } diff --git a/Assets/Scripts/ServerShared/EnumerableSelectionExtensions.cs b/Assets/Scripts/ServerShared/EnumerableSelectionExtensions.cs new file mode 100644 index 000000000..27c51fc8f --- /dev/null +++ b/Assets/Scripts/ServerShared/EnumerableSelectionExtensions.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +public static class EnumerableSelectionExtensions +{ + public static T MaxBy(this IEnumerable items, Func selector) + { + if (!items.Any()) + { + throw new InvalidOperationException("Empty input sequence"); + } + + var comparer = Comparer.Default; + var maxItem = items.First(); + var maxValue = selector(maxItem); + + foreach (var item in items.Skip(1)) + { + var value = selector(item); + if (comparer.Compare(value, maxValue) > 0) + { + maxValue = value; + maxItem = item; + } + } + + return maxItem; + } + + public static T MinBy(this IEnumerable items, Func selector) + { + if (!items.Any()) + { + throw new InvalidOperationException("Empty input sequence"); + } + + var comparer = Comparer.Default; + var minItem = items.First(); + var minValue = selector(minItem); + + foreach (var item in items.Skip(1)) + { + var value = selector(item); + if (comparer.Compare(value, minValue) < 0) + { + minValue = value; + minItem = item; + } + } + + return minItem; + } +} diff --git a/Assets/Scripts/ServerShared/RuntimeProjection/CollectionExtensions.cs.meta b/Assets/Scripts/ServerShared/EnumerableSelectionExtensions.cs.meta similarity index 83% rename from Assets/Scripts/ServerShared/RuntimeProjection/CollectionExtensions.cs.meta rename to Assets/Scripts/ServerShared/EnumerableSelectionExtensions.cs.meta index 6ff22bed8..9928f3f1b 100644 --- a/Assets/Scripts/ServerShared/RuntimeProjection/CollectionExtensions.cs.meta +++ b/Assets/Scripts/ServerShared/EnumerableSelectionExtensions.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: eed05df00a9a6b84292c81be1662a66e +guid: 7b36e5ee07ee44d68e755ea4993d9dd6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/ServerShared/RuntimeProjection/CollectionExtensions.cs b/Assets/Scripts/ServerShared/RuntimeProjection/CollectionExtensions.cs deleted file mode 100644 index 18e3500fd..000000000 --- a/Assets/Scripts/ServerShared/RuntimeProjection/CollectionExtensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -public static class CollectionExtensions -{ - // https://stackoverflow.com/a/3188835 - public static T MaxBy(this IEnumerable items, Func selector) - { - if (!items.Any()) - { - throw new InvalidOperationException("Empty input sequence"); - } - - var comparer = Comparer.Default; - T maxItem = items.First(); - U maxValue = selector(maxItem); - - foreach (T item in items.Skip(1)) - { - // Get the value of the item and compare it to the current max. - U value = selector(item); - if (comparer.Compare(value, maxValue) > 0) - { - maxValue = value; - maxItem = item; - } - } - - return maxItem; - } - - public static T MinBy(this IEnumerable items, Func selector) - { - if (!items.Any()) - { - throw new InvalidOperationException("Empty input sequence"); - } - - var comparer = Comparer.Default; - T maxItem = items.First(); - U maxValue = selector(maxItem); - - foreach (T item in items.Skip(1)) - { - // Get the value of the item and compare it to the current max. - U value = selector(item); - if (comparer.Compare(value, maxValue) < 0) - { - maxValue = value; - maxItem = item; - } - } - - return maxItem; - } -} - -public static class AsyncAwaitExtensions -{ - public static async void WrapAwait(this Task task) - { - await task; - } -} diff --git a/Assets/Scripts/ServerShared/RuntimeProjection/ReflectionExtensions.cs b/Assets/Scripts/ServerShared/RuntimeProjection/ReflectionExtensions.cs deleted file mode 100644 index 5518238b1..000000000 --- a/Assets/Scripts/ServerShared/RuntimeProjection/ReflectionExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Text.RegularExpressions; - -public static class ReflectionExtensions -{ - public static string SplitCamelCase( this string str ) - { - return Regex.Replace( - Regex.Replace( - str, - @"(\P{Ll})(\P{Ll}\p{Ll})", - "$1 $2" - ), - @"(\p{Ll})(\P{Ll})", - "$1 $2" - ); - } - - public static string FormatTypeName(this string typeName) - { - return (typeName.EndsWith("Data", StringComparison.InvariantCultureIgnoreCase) - ? typeName.Substring(0, typeName.Length - 4) - : typeName).SplitCamelCase(); - } -} diff --git a/Assets/Scripts/ServerShared/RuntimeProjection/ReflectionExtensions.cs.meta b/Assets/Scripts/ServerShared/RuntimeProjection/ReflectionExtensions.cs.meta deleted file mode 100644 index 64127bd9f..000000000 --- a/Assets/Scripts/ServerShared/RuntimeProjection/ReflectionExtensions.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: fd5642f26b80465fb57f3d2610633140 -timeCreated: 1617055075 \ No newline at end of file diff --git a/Assets/Scripts/UI/Menu/TradeMenu.cs b/Assets/Scripts/UI/Menu/TradeMenu.cs index 29f5eff6c..763cf987d 100644 --- a/Assets/Scripts/UI/Menu/TradeMenu.cs +++ b/Assets/Scripts/UI/Menu/TradeMenu.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using GameCult.Aetheria.EveRuntime; using GameCult.Aetheria.State.Verse; using GameCult.Eve.Surface; @@ -840,26 +841,26 @@ public class TradeMenu : MonoBehaviour .Select(type => new AetheriaRuntimeTradeFilterOption( AetheriaRuntimeTradeFilterSelectionKind.Hardpoint, type.ToString(), - type.ToString().FormatTypeName()))); + FormatTypeName(type.ToString())))); options.AddRange(((SimpleCommodityCategory[])Enum.GetValues(typeof(SimpleCommodityCategory))) .Where(type => _commodityFilter.filter == null || type != _commodityFilter.type) .Select(type => new AetheriaRuntimeTradeFilterOption( AetheriaRuntimeTradeFilterSelectionKind.SimpleCommodity, type.ToString(), - type.ToString().FormatTypeName()))); + FormatTypeName(type.ToString())))); options.AddRange(((CompoundCommodityCategory[])Enum.GetValues(typeof(CompoundCommodityCategory))) .Where(type => _compoundCommodityFilter.filter == null || type != _compoundCommodityFilter.type) .Select(type => new AetheriaRuntimeTradeFilterOption( AetheriaRuntimeTradeFilterSelectionKind.CompoundCommodity, type.ToString(), - type.ToString().FormatTypeName()))); + FormatTypeName(type.ToString())))); options.AddRange(AetheriaRuntimeBehaviorMetadataCatalog.All .Where(option => _behaviorFilters.All(filter => filter.Kind != option.Kind)) .OrderBy(option => option.Kind, StringComparer.Ordinal) .Select(option => new AetheriaRuntimeTradeFilterOption( AetheriaRuntimeTradeFilterSelectionKind.Behavior, option.Kind, - option.Kind.FormatTypeName()))); + FormatTypeName(option.Kind)))); if (!MinimumSizeFilter.gameObject.activeSelf) { @@ -1017,7 +1018,7 @@ public class TradeMenu : MonoBehaviour } var filter = FilterPrototype.Instantiate(); - filter.Label.text = metadata.Kind.FormatTypeName(); + filter.Label.text = FormatTypeName(metadata.Kind); var behaviorFilter = new BehaviorFilter(filter, metadata); filter.OnDisable += () => { @@ -1132,7 +1133,7 @@ public class TradeMenu : MonoBehaviour activeFilters.Add($"Compound: {Enum.GetName(typeof(CompoundCommodityCategory), _compoundCommodityFilter.type)}"); } - activeFilters.AddRange(_behaviorFilters.Select(filter => $"Behavior: {filter.Kind.FormatTypeName()}")); + activeFilters.AddRange(_behaviorFilters.Select(filter => $"Behavior: {FormatTypeName(filter.Kind)}")); if (MinimumSizeFilter.gameObject.activeSelf) { @@ -1282,6 +1283,26 @@ public class TradeMenu : MonoBehaviour } } + private static string FormatTypeName(string typeName) + { + var value = typeName ?? ""; + var trimmed = value.EndsWith("Data", StringComparison.InvariantCultureIgnoreCase) + ? value.Substring(0, value.Length - 4) + : value; + return SplitCamelCase(trimmed); + } + + private static string SplitCamelCase(string value) + { + return Regex.Replace( + Regex.Replace( + value ?? "", + @"(\P{Ll})(\P{Ll}\p{Ll})", + "$1 $2"), + @"(\p{Ll})(\P{Ll})", + "$1 $2"); + } + private void OnDisable() { HideCargoSelectorSurface();