diff --git a/Content.Benchmarks/ComponentQueryBenchmark.cs b/Content.Benchmarks/ComponentQueryBenchmark.cs new file mode 100644 index 00000000000..11c7ab9d5f5 --- /dev/null +++ b/Content.Benchmarks/ComponentQueryBenchmark.cs @@ -0,0 +1,273 @@ +#nullable enable +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using Content.IntegrationTests; +using Content.IntegrationTests.Pair; +using Content.Shared.Clothing.Components; +using Content.Shared.Doors.Components; +using Content.Shared.Item; +using Robust.Server.GameObjects; +using Robust.Shared; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Random; + +namespace Content.Benchmarks; + +/// +/// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event +/// subscriptions +/// +[Virtual] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class ComponentQueryBenchmark +{ + public const string Map = "Maps/atlas.yml"; + + private TestPair _pair = default!; + private IEntityManager _entMan = default!; + private MapId _mapId = new(10); + private EntityQuery _itemQuery; + private EntityQuery _clothingQuery; + private EntityQuery _mapQuery; + private EntityUid[] _items = default!; + + [GlobalSetup] + public void Setup() + { + ProgramShared.PathOffset = "../../../../"; + PoolManager.Startup(typeof(QueryBenchSystem).Assembly); + + _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); + _entMan = _pair.Server.ResolveDependency(); + + _itemQuery = _entMan.GetEntityQuery(); + _clothingQuery = _entMan.GetEntityQuery(); + _mapQuery = _entMan.GetEntityQuery(); + + _pair.Server.ResolveDependency().SetSeed(42); + _pair.Server.WaitPost(() => + { + var success = _entMan.System().TryLoad(_mapId, Map, out _); + if (!success) + throw new Exception("Map load failed"); + _pair.Server.MapMan.DoMapInitialize(_mapId); + }).GetAwaiter().GetResult(); + + _items = new EntityUid[_entMan.Count()]; + var i = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out var uid, out _)) + { + _items[i++] = uid; + } + } + + [GlobalCleanup] + public async Task Cleanup() + { + await _pair.DisposeAsync(); + PoolManager.Shutdown(); + } + + #region TryComp + + /// + /// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing. + /// + [Benchmark(Baseline = true)] + [BenchmarkCategory("TryComp")] + public int TryComp() + { + var hashCode = 0; + foreach (var uid in _items) + { + if (_clothingQuery.TryGetComponent(uid, out var clothing)) + hashCode = HashCode.Combine(hashCode, clothing.GetHashCode()); + } + return hashCode; + } + + /// + /// Variant of that is meant to always fail to get a component. + /// + [Benchmark] + [BenchmarkCategory("TryComp")] + public int TryCompFail() + { + var hashCode = 0; + foreach (var uid in _items) + { + if (_mapQuery.TryGetComponent(uid, out var map)) + hashCode = HashCode.Combine(hashCode, map.GetHashCode()); + } + return hashCode; + } + + /// + /// Variant of that is meant to always succeed getting a component. + /// + [Benchmark] + [BenchmarkCategory("TryComp")] + public int TryCompSucceed() + { + var hashCode = 0; + foreach (var uid in _items) + { + if (_itemQuery.TryGetComponent(uid, out var item)) + hashCode = HashCode.Combine(hashCode, item.GetHashCode()); + } + return hashCode; + } + + /// + /// Variant of that uses `Resolve()` to try get the component. + /// + [Benchmark] + [BenchmarkCategory("TryComp")] + public int Resolve() + { + var hashCode = 0; + foreach (var uid in _items) + { + DoResolve(uid, ref hashCode); + } + return hashCode; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null) + { + if (_clothingQuery.Resolve(uid, ref clothing, false)) + hash = HashCode.Combine(hash, clothing.GetHashCode()); + } + + #endregion + + #region Enumeration + + [Benchmark] + [BenchmarkCategory("Item Enumerator")] + public int SingleItemEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out var item)) + { + hashCode = HashCode.Combine(hashCode, item.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Item Enumerator")] + public int DoubleItemEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out _, out var item)) + { + hashCode = HashCode.Combine(hashCode, item.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Item Enumerator")] + public int TripleItemEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out _, out _, out var xform)) + { + hashCode = HashCode.Combine(hashCode, xform.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Airlock Enumerator")] + public int SingleAirlockEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out var airlock)) + { + hashCode = HashCode.Combine(hashCode, airlock.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Airlock Enumerator")] + public int DoubleAirlockEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out _, out var door)) + { + hashCode = HashCode.Combine(hashCode, door.GetHashCode()); + } + + return hashCode; + } + + [Benchmark] + [BenchmarkCategory("Airlock Enumerator")] + public int TripleAirlockEnumerator() + { + var hashCode = 0; + var enumerator = _entMan.AllEntityQueryEnumerator(); + while (enumerator.MoveNext(out _, out _, out var xform)) + { + hashCode = HashCode.Combine(hashCode, xform.GetHashCode()); + } + + return hashCode; + } + + #endregion + + [Benchmark(Baseline = true)] + [BenchmarkCategory("Events")] + public int StructEvents() + { + var ev = new QueryBenchEvent(); + foreach (var uid in _items) + { + _entMan.EventBus.RaiseLocalEvent(uid, ref ev); + } + + return ev.HashCode; + } +} + +[ByRefEvent] +public struct QueryBenchEvent +{ + public int HashCode; +} + +public sealed class QueryBenchSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnEvent); + } + + private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args) + { + args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode()); + } +} diff --git a/Content.Benchmarks/EntityQueryBenchmark.cs b/Content.Benchmarks/EntityQueryBenchmark.cs deleted file mode 100644 index cef6a5e35c5..00000000000 --- a/Content.Benchmarks/EntityQueryBenchmark.cs +++ /dev/null @@ -1,137 +0,0 @@ -#nullable enable -using System; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; -using Content.IntegrationTests; -using Content.IntegrationTests.Pair; -using Content.Shared.Clothing.Components; -using Content.Shared.Item; -using Robust.Server.GameObjects; -using Robust.Shared; -using Robust.Shared.Analyzers; -using Robust.Shared.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Random; - -namespace Content.Benchmarks; - -[Virtual] -public class EntityQueryBenchmark -{ - public const string Map = "Maps/atlas.yml"; - - private TestPair _pair = default!; - private IEntityManager _entMan = default!; - private MapId _mapId = new MapId(10); - private EntityQuery _clothingQuery; - - [GlobalSetup] - public void Setup() - { - ProgramShared.PathOffset = "../../../../"; - PoolManager.Startup(null); - - _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); - _entMan = _pair.Server.ResolveDependency(); - - _pair.Server.ResolveDependency().SetSeed(42); - _pair.Server.WaitPost(() => - { - var success = _entMan.System().TryLoad(_mapId, Map, out _); - if (!success) - throw new Exception("Map load failed"); - _pair.Server.MapMan.DoMapInitialize(_mapId); - }).GetAwaiter().GetResult(); - - _clothingQuery = _entMan.GetEntityQuery(); - - // Apparently ~40% of entities are items, and 1 in 6 of those are clothing. - /* - var entCount = _entMan.EntityCount; - var itemCount = _entMan.Count(); - var clothingCount = _entMan.Count(); - var itemRatio = (float) itemCount / entCount; - var clothingRatio = (float) clothingCount / entCount; - Console.WriteLine($"Entities: {entCount}. Items: {itemRatio:P2}. Clothing: {clothingRatio:P2}."); - */ - } - - [GlobalCleanup] - public async Task Cleanup() - { - await _pair.DisposeAsync(); - PoolManager.Shutdown(); - } - - [Benchmark] - public int HasComponent() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var _)) - { - if (_entMan.HasComponent(uid)) - hashCode = HashCode.Combine(hashCode, uid.Id); - } - - return hashCode; - } - - [Benchmark] - public int HasComponentQuery() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var _)) - { - if (_clothingQuery.HasComponent(uid)) - hashCode = HashCode.Combine(hashCode, uid.Id); - } - - return hashCode; - } - - [Benchmark] - public int TryGetComponent() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var _)) - { - if (_entMan.TryGetComponent(uid, out ClothingComponent? clothing)) - hashCode = HashCode.Combine(hashCode, clothing.GetHashCode()); - } - - return hashCode; - } - - [Benchmark] - public int TryGetComponentQuery() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var _)) - { - if (_clothingQuery.TryGetComponent(uid, out var clothing)) - hashCode = HashCode.Combine(hashCode, clothing.GetHashCode()); - } - - return hashCode; - } - - /// - /// Enumerate all entities with both an item and clothing component. - /// - [Benchmark] - public int Enumerator() - { - var hashCode = 0; - var enumerator = _entMan.AllEntityQueryEnumerator(); - while (enumerator.MoveNext(out var _, out var clothing)) - { - hashCode = HashCode.Combine(hashCode, clothing.GetHashCode()); - } - - return hashCode; - } -} diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 261e164f175..a3319e3067f 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -26,7 +26,7 @@ public class MapLoadBenchmark public void Setup() { ProgramShared.PathOffset = "../../../../"; - PoolManager.Startup(null); + PoolManager.Startup(); _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); var server = _pair.Server; diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index c7f22bdb0cd..0b4dd907621 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -49,7 +49,7 @@ public void Setup() #if !DEBUG ProgramShared.PathOffset = "../../../../"; #endif - PoolManager.Startup(null); + PoolManager.Startup(); _pair = PoolManager.GetServerClient().GetAwaiter().GetResult(); _entMan = _pair.Server.ResolveDependency(); diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs index 8512107b69d..0638d945aa5 100644 --- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs +++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs @@ -32,7 +32,7 @@ public class SpawnEquipDeleteBenchmark public async Task SetupAsync() { ProgramShared.PathOffset = "../../../../"; - PoolManager.Startup(null); + PoolManager.Startup(); _pair = await PoolManager.GetServerClient(); var server = _pair.Server; diff --git a/Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs b/Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs index c029507b02a..88c05861630 100644 --- a/Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs +++ b/Content.Client/Corvax/TTS/HumanoidProfileEditor.TTS.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Client.Corvax.TTS; using Content.Client.Lobby; using Content.Corvax.Interfaces.Shared; using Content.Shared.Corvax.TTS; @@ -19,13 +20,14 @@ private void InitializeVoice() .OrderBy(o => Loc.GetString(o.Name)) .ToList(); - CVoiceButton.OnItemSelected += args => + VoiceButton.OnItemSelected += args => { - CVoiceButton.SelectId(args.Id); + VoiceButton.SelectId(args.Id); SetVoice(_voiceList[args.Id].ID); }; - CVoicePlayButton.OnPressed += _ => { UserInterfaceManager.GetUIController().PlayTTS(); }; + VoicePlayButton.OnPressed += _ => PlayPreviewTTS(); + IoCManager.Instance!.TryResolveType(out _sponsorsMgr); } @@ -34,7 +36,7 @@ private void UpdateTTSVoicesControls() if (Profile is null) return; - CVoiceButton.Clear(); + VoiceButton.Clear(); var firstVoiceChoiceId = 1; for (var i = 0; i < _voiceList.Count; i++) @@ -44,7 +46,7 @@ private void UpdateTTSVoicesControls() continue; var name = Loc.GetString(voice.Name); - CVoiceButton.AddItem(name, i); + VoiceButton.AddItem(name, i); if (firstVoiceChoiceId == 1) firstVoiceChoiceId = i; @@ -54,15 +56,23 @@ private void UpdateTTSVoicesControls() if (voice.SponsorOnly && _sponsorsMgr != null && !_sponsorsMgr.GetClientPrototypes().Contains(voice.ID)) { - CVoiceButton.SetItemDisabled(CVoiceButton.GetIdx(i), true); + VoiceButton.SetItemDisabled(VoiceButton.GetIdx(i), true); } } var voiceChoiceId = _voiceList.FindIndex(x => x.ID == Profile.Voice); - if (!CVoiceButton.TrySelectId(voiceChoiceId) && - CVoiceButton.TrySelectId(firstVoiceChoiceId)) + if (!VoiceButton.TrySelectId(voiceChoiceId) && + VoiceButton.TrySelectId(firstVoiceChoiceId)) { SetVoice(_voiceList[firstVoiceChoiceId].ID); } } + + private void PlayPreviewTTS() + { + if (Profile is null) + return; + + _entManager.System().RequestGlobalTTS(Shared.Backmen.TTS.VoiceRequestType.Preview,Profile.Voice); + } } diff --git a/Content.Client/Corvax/TTS/LobbyUIController.TTS.cs b/Content.Client/Corvax/TTS/LobbyUIController.TTS.cs deleted file mode 100644 index 8fcd128dd65..00000000000 --- a/Content.Client/Corvax/TTS/LobbyUIController.TTS.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Client.Corvax.TTS; -using Robust.Client.UserInterface; -using Robust.Shared.Random; - -namespace Content.Client.Lobby; - -public sealed partial class LobbyUIController -{ - [UISystemDependency] private readonly TTSSystem _tts = default!; - - public void PlayTTS() - { - // Test moment - if (EditedProfile == null || _stateManager.CurrentState is not LobbyState) - return; - - _tts.RequestGlobalTTS(Content.Shared.Backmen.TTS.VoiceRequestType.Preview, EditedProfile.Voice); - } -} diff --git a/Content.Client/FlavorText/FlavorText.xaml.cs b/Content.Client/FlavorText/FlavorText.xaml.cs index ffcf653f119..91b59046a47 100644 --- a/Content.Client/FlavorText/FlavorText.xaml.cs +++ b/Content.Client/FlavorText/FlavorText.xaml.cs @@ -17,7 +17,7 @@ public FlavorText() var loc = IoCManager.Resolve(); CFlavorTextInput.Placeholder = new Rope.Leaf(loc.GetString("flavor-text-placeholder")); - CFlavorTextInput.OnKeyBindDown += _ => FlavorTextChanged(); + CFlavorTextInput.OnTextChanged += _ => FlavorTextChanged(); } public void FlavorTextChanged() diff --git a/Content.Client/Gravity/GravitySystem.Shake.cs b/Content.Client/Gravity/GravitySystem.Shake.cs index c4356588d35..9b9918ca3e7 100644 --- a/Content.Client/Gravity/GravitySystem.Shake.cs +++ b/Content.Client/Gravity/GravitySystem.Shake.cs @@ -25,7 +25,7 @@ private void OnShakeInit(EntityUid uid, GravityShakeComponent component, Compone { var localPlayer = _playerManager.LocalEntity; - if (!TryComp(localPlayer, out var xform) || + if (!TryComp(localPlayer, out TransformComponent? xform) || xform.GridUid != uid && xform.MapUid != uid) { return; @@ -46,7 +46,7 @@ protected override void ShakeGrid(EntityUid uid, GravityComponent? gravity = nul var localPlayer = _playerManager.LocalEntity; - if (!TryComp(localPlayer, out var xform)) + if (!TryComp(localPlayer, out TransformComponent? xform)) return; if (xform.GridUid != uid || diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml index f46e319abeb..73a17e9bcc9 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml +++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml @@ -47,6 +47,17 @@ + + + + + + + + diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs index 537494933bc..87931bf8455 100644 --- a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs +++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs @@ -157,6 +157,39 @@ private void GenerateControl(ReagentPrototype reagent) } #endregion + #region PlantMetabolisms + if (_chemistryGuideData.ReagentGuideRegistry.TryGetValue(reagent.ID, out var guideEntryRegistryPlant) && + guideEntryRegistryPlant.PlantMetabolisms != null && + guideEntryRegistryPlant.PlantMetabolisms.Count > 0) + { + PlantMetabolismsDescriptionContainer.Children.Clear(); + var metabolismLabel = new RichTextLabel(); + metabolismLabel.SetMarkup(Loc.GetString("guidebook-reagent-plant-metabolisms-rate")); + var descriptionLabel = new RichTextLabel + { + Margin = new Thickness(25, 0, 10, 0) + }; + var descMsg = new FormattedMessage(); + var descriptionsCount = guideEntryRegistryPlant.PlantMetabolisms.Count; + var i = 0; + foreach (var effectString in guideEntryRegistryPlant.PlantMetabolisms) + { + descMsg.AddMarkup(effectString); + i++; + if (i < descriptionsCount) + descMsg.PushNewline(); + } + descriptionLabel.SetMessage(descMsg); + + PlantMetabolismsDescriptionContainer.AddChild(metabolismLabel); + PlantMetabolismsDescriptionContainer.AddChild(descriptionLabel); + } + else + { + PlantMetabolismsContainer.Visible = false; + } + #endregion + GenerateSources(reagent); FormattedMessage description = new(); diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index 5b886cc8443..f7c96c82e85 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -3,7 +3,6 @@ using Content.Client.Chat.Managers; using Content.Client.Clickable; using Content.Client.Corvax.TTS; -using Content.Client.Options; using Content.Client.Eui; using Content.Client.GhostKick; using Content.Client.Info; diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml index 6e1f96f6ab5..534f084ea58 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml @@ -93,8 +93,8 @@