From 5bd5a384b7b41cad1aaab81e7b1840c8607a4e29 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sun, 18 Mar 2018 18:45:16 +0000 Subject: [PATCH] Use a BitSet for representing target types. - Rename Bits to BitSet. - Implement set based helpers for BitSet. - When representing TargetTypes of ITargetable in various traits, use a BitSet instead of HashSet for better performance & reduced memory usage. - Fix FieldLoader to trim input values when generating a BitSet. - Require T in BitSet and BitSetAllocator to be a class since it's just a marker value. This allows the JIT to instantiate generic code for these classes once, as they don't benefit from specialized code for T. (Typically JITs will generate shared code for all reference types, and unique code for every value type encountered). --- Makefile | 2 +- OpenRA.Game/Actor.cs | 14 +- OpenRA.Game/FieldLoader.cs | 7 +- OpenRA.Game/FieldSaver.cs | 6 + OpenRA.Game/GameRules/ActorInfo.cs | 9 ++ OpenRA.Game/GameRules/WeaponInfo.cs | 7 +- OpenRA.Game/Map/TileSet.cs | 3 +- OpenRA.Game/OpenRA.Game.csproj | 3 +- OpenRA.Game/Primitives/BitSet.cs | 151 ++++++++++++++++++ OpenRA.Game/Primitives/Bits.cs | 69 -------- OpenRA.Game/Traits/Player/FrozenActorLayer.cs | 8 +- OpenRA.Game/Traits/TraitsInterfaces.cs | 9 +- OpenRA.Mods.Cnc/Traits/Disguise.cs | 5 +- .../Traits/Infiltration/InfiltrateForCash.cs | 5 +- .../Infiltration/InfiltrateForDecoration.cs | 5 +- .../Infiltration/InfiltrateForExploration.cs | 5 +- .../Infiltration/InfiltrateForPowerOutage.cs | 5 +- .../Infiltration/InfiltrateForSupportPower.cs | 4 +- .../Traits/Infiltration/Infiltrates.cs | 9 +- OpenRA.Mods.Cnc/Traits/MadTank.cs | 2 +- OpenRA.Mods.Common/AI/States/AirStates.cs | 3 +- OpenRA.Mods.Common/AI/States/GroundStates.cs | 2 +- OpenRA.Mods.Common/AI/States/NavyStates.cs | 2 +- OpenRA.Mods.Common/AI/States/StateBase.cs | 2 +- OpenRA.Mods.Common/AI/SupportPowerDecision.cs | 3 +- OpenRA.Mods.Common/Lint/CheckDeathTypes.cs | 4 +- .../Orders/UnitOrderTargeter.cs | 5 +- .../Scripting/ScriptTriggers.cs | 3 +- .../Traits/Attack/AttackSuicides.cs | 3 +- OpenRA.Mods.Common/Traits/AutoTarget.cs | 2 +- .../Traits/AutoTargetPriority.cs | 6 +- .../Traits/Crates/DuplicateUnitCrateAction.cs | 2 +- OpenRA.Mods.Common/Traits/Targetable.cs | 7 +- OpenRA.Mods.Common/TraitsInterfaces.cs | 2 +- .../Warheads/CreateEffectWarhead.cs | 3 +- OpenRA.Mods.Common/Warheads/Warhead.cs | 7 +- 36 files changed, 250 insertions(+), 134 deletions(-) create mode 100644 OpenRA.Game/Primitives/BitSet.cs delete mode 100644 OpenRA.Game/Primitives/Bits.cs diff --git a/Makefile b/Makefile index a1b2913b3c..b34ad1e63c 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ SDK ?= CSC = mcs $(SDK) CSFLAGS = -nologo -warn:4 -codepage:utf8 -langversion:5 -unsafe -warnaserror DEFINE = TRACE -COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Xml.dll thirdparty/download/ICSharpCode.SharpZipLib.dll thirdparty/download/FuzzyLogicLibrary.dll thirdparty/download/MaxMind.Db.dll thirdparty/download/Eluant.dll thirdparty/download/rix0rrr.BeaconLib.dll +COMMON_LIBS = System.dll System.Core.dll System.Data.dll System.Data.DataSetExtensions.dll System.Drawing.dll System.Numerics.dll System.Xml.dll thirdparty/download/ICSharpCode.SharpZipLib.dll thirdparty/download/FuzzyLogicLibrary.dll thirdparty/download/MaxMind.Db.dll thirdparty/download/Eluant.dll thirdparty/download/rix0rrr.BeaconLib.dll NUNIT_LIBS_PATH := NUNIT_LIBS := $(NUNIT_LIBS_PATH)nunit.framework.dll diff --git a/OpenRA.Game/Actor.cs b/OpenRA.Game/Actor.cs index b0a8d64414..23436144f5 100644 --- a/OpenRA.Game/Actor.cs +++ b/OpenRA.Game/Actor.cs @@ -339,21 +339,23 @@ namespace OpenRA return defaultVisibility.IsVisible(this, player); } - public IEnumerable GetAllTargetTypes() + public BitSet GetAllTargetTypes() { // PERF: Avoid LINQ. + var targetTypes = new BitSet(); foreach (var targetable in Targetables) - foreach (var targetType in targetable.TargetTypes) - yield return targetType; + targetTypes = targetTypes.Union(targetable.TargetTypes); + return targetTypes; } - public IEnumerable GetEnabledTargetTypes() + public BitSet GetEnabledTargetTypes() { // PERF: Avoid LINQ. + var targetTypes = new BitSet(); foreach (var targetable in Targetables) if (targetable.IsTraitEnabled()) - foreach (var targetType in targetable.TargetTypes) - yield return targetType; + targetTypes = targetTypes.Union(targetable.TargetTypes); + return targetTypes; } public bool IsTargetableBy(Actor byActor) diff --git a/OpenRA.Game/FieldLoader.cs b/OpenRA.Game/FieldLoader.cs index f09fd1f25d..4b8fedb96a 100644 --- a/OpenRA.Game/FieldLoader.cs +++ b/OpenRA.Game/FieldLoader.cs @@ -607,14 +607,13 @@ namespace OpenRA return InvalidValueAction(value, fieldType, fieldName); } - else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Bits<>)) + else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(BitSet<>)) { if (value != null) { var parts = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var argTypes = new Type[] { typeof(string[]) }; - var argValues = new object[] { parts }; - return fieldType.GetConstructor(argTypes).Invoke(argValues); + var ctor = fieldType.GetConstructor(new[] { typeof(string[]) }); + return ctor.Invoke(new object[] { parts.Select(p => p.Trim()).ToArray() }); } return InvalidValueAction(value, fieldType, fieldName); diff --git a/OpenRA.Game/FieldSaver.cs b/OpenRA.Game/FieldSaver.cs index 8e4caf3a77..8b1780c7f4 100644 --- a/OpenRA.Game/FieldSaver.cs +++ b/OpenRA.Game/FieldSaver.cs @@ -18,6 +18,7 @@ using System.Globalization; using System.Linq; using System.Reflection; using OpenRA.Graphics; +using OpenRA.Primitives; namespace OpenRA { @@ -98,6 +99,11 @@ namespace OpenRA return "{0},{1},{2},{3}".F(r.X, r.Y, r.Width, r.Height); } + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(BitSet<>)) + { + return ((IEnumerable)v).Select(FormatValue).JoinWith(", "); + } + if (t.IsArray && t.GetArrayRank() == 1) { return ((Array)v).Cast().Select(FormatValue).JoinWith(", "); diff --git a/OpenRA.Game/GameRules/ActorInfo.cs b/OpenRA.Game/GameRules/ActorInfo.cs index 8476b89ecf..bead7a4a63 100644 --- a/OpenRA.Game/GameRules/ActorInfo.cs +++ b/OpenRA.Game/GameRules/ActorInfo.cs @@ -162,5 +162,14 @@ namespace OpenRA public T TraitInfo() where T : ITraitInfoInterface { return traits.Get(); } public T TraitInfoOrDefault() where T : ITraitInfoInterface { return traits.GetOrDefault(); } public IEnumerable TraitInfos() where T : ITraitInfoInterface { return traits.WithInterface(); } + + public BitSet GetAllTargetTypes() + { + // PERF: Avoid LINQ. + var targetTypes = new BitSet(); + foreach (var targetable in TraitInfos()) + targetTypes = targetTypes.Union(targetable.GetTargetTypes()); + return targetTypes; + } } } diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index 2b88556bdc..55ab9d5e8d 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Effects; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.GameRules @@ -64,10 +65,10 @@ namespace OpenRA.GameRules public readonly int Burst = 1; [Desc("What types of targets are affected.")] - public readonly HashSet ValidTargets = new HashSet { "Ground", "Water" }; + public readonly BitSet ValidTargets = new BitSet("Ground", "Water"); [Desc("What types of targets are unaffected.", "Overrules ValidTargets.")] - public readonly HashSet InvalidTargets = new HashSet(); + public readonly BitSet InvalidTargets; [Desc("Delay in ticks between firing shots from the same ammo magazine. If one entry, it will be used for all bursts.", "If multiple entries, their number needs to match Burst - 1.")] @@ -116,7 +117,7 @@ namespace OpenRA.GameRules return retList; } - public bool IsValidTarget(IEnumerable targetTypes) + public bool IsValidTarget(BitSet targetTypes) { return ValidTargets.Overlaps(targetTypes) && !InvalidTargets.Overlaps(targetTypes); } diff --git a/OpenRA.Game/Map/TileSet.cs b/OpenRA.Game/Map/TileSet.cs index 3629f5bc98..0d2dae30e6 100644 --- a/OpenRA.Game/Map/TileSet.cs +++ b/OpenRA.Game/Map/TileSet.cs @@ -15,6 +15,7 @@ using System.IO; using System.Linq; using OpenRA.FileSystem; using OpenRA.Primitives; +using OpenRA.Traits; namespace OpenRA { @@ -60,7 +61,7 @@ namespace OpenRA static readonly TerrainTypeInfo Default = new TerrainTypeInfo(); public readonly string Type; - public readonly HashSet TargetTypes = new HashSet(); + public readonly BitSet TargetTypes; public readonly HashSet AcceptsSmudgeType = new HashSet(); public readonly Color Color; public readonly bool RestrictPlayerColor = false; diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index d618498159..94cc61b8e9 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -98,6 +98,7 @@ ..\thirdparty\download\MaxMind.Db.dll False + @@ -272,7 +273,7 @@ - + diff --git a/OpenRA.Game/Primitives/BitSet.cs b/OpenRA.Game/Primitives/BitSet.cs new file mode 100644 index 0000000000..ee24dc1332 --- /dev/null +++ b/OpenRA.Game/Primitives/BitSet.cs @@ -0,0 +1,151 @@ +#region Copyright & License Information +/* + * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using BitSetIndex = System.Numerics.BigInteger; + +namespace OpenRA.Primitives +{ + static class BitSetAllocator where T : class + { + static readonly Cache Bits = new Cache(Allocate); + static BitSetIndex nextBits = 1; + + static BitSetIndex Allocate(string value) + { + lock (Bits) + { + var bits = nextBits; + nextBits <<= 1; + return bits; + } + } + + public static BitSetIndex GetBits(string[] values) + { + BitSetIndex bits = 0; + lock (Bits) + foreach (var value in values) + bits |= Bits[value]; + + return bits; + } + + public static IEnumerable GetStrings(BitSetIndex bits) + { + var values = new List(); + lock (Bits) + foreach (var kvp in Bits) + if ((kvp.Value & bits) != 0) + values.Add(kvp.Key); + + return values; + } + + public static bool BitsContainString(BitSetIndex bits, string value) + { + BitSetIndex valueBit; + lock (Bits) + if (!Bits.TryGetValue(value, out valueBit)) + return false; + return (bits & valueBit) != 0; + } + } + + public struct BitSet : IEnumerable, IEquatable> where T : class + { + readonly BitSetIndex bits; + + public BitSet(params string[] values) : this(BitSetAllocator.GetBits(values)) { } + BitSet(BitSetIndex bits) { this.bits = bits; } + + public override string ToString() + { + return BitSetAllocator.GetStrings(bits).JoinWith(","); + } + + public static bool operator ==(BitSet me, BitSet other) { return me.bits == other.bits; } + public static bool operator !=(BitSet me, BitSet other) { return !(me == other); } + + public bool Equals(BitSet other) { return other == this; } + public override bool Equals(object obj) { return obj is BitSet && Equals((BitSet)obj); } + public override int GetHashCode() { return bits.GetHashCode(); } + + public bool IsEmpty { get { return bits == 0; } } + + public bool IsProperSubsetOf(BitSet other) + { + return IsSubsetOf(other) && !SetEquals(other); + } + + public bool IsProperSupersetOf(BitSet other) + { + return IsSupersetOf(other) && !SetEquals(other); + } + + public bool IsSubsetOf(BitSet other) + { + return (bits | other.bits) == other.bits; + } + + public bool IsSupersetOf(BitSet other) + { + return (bits | other.bits) == bits; + } + + public bool Overlaps(BitSet other) + { + return (bits & other.bits) != 0; + } + + public bool SetEquals(BitSet other) + { + return bits == other.bits; + } + + public bool Contains(string value) + { + return BitSetAllocator.BitsContainString(bits, value); + } + + public IEnumerator GetEnumerator() + { + return BitSetAllocator.GetStrings(bits).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public BitSet Except(BitSet other) + { + return new BitSet(bits & ~other.bits); + } + + public BitSet Intersect(BitSet other) + { + return new BitSet(bits & other.bits); + } + + public BitSet SymmetricExcept(BitSet other) + { + return new BitSet(bits ^ other.bits); + } + + public BitSet Union(BitSet other) + { + return new BitSet(bits | other.bits); + } + } +} diff --git a/OpenRA.Game/Primitives/Bits.cs b/OpenRA.Game/Primitives/Bits.cs deleted file mode 100644 index efe60f8b07..0000000000 --- a/OpenRA.Game/Primitives/Bits.cs +++ /dev/null @@ -1,69 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2018 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace OpenRA.Primitives -{ - static class BitAllocator where T : struct - { - static int nextVal = 1; - static Cache bits = new Cache(_ => Allocate()); - - static int Allocate() - { - if (nextVal == 0) - throw new InvalidOperationException( - "Too many values in BitAllocator<{0}>".F(typeof(T).Name)); - - var val = nextVal; - nextVal <<= 1; - return val; - } - - public static int GetValue(string[] val) - { - return val.Select(a => bits[a]).Aggregate(0, (a, b) => a | b); - } - - public static IEnumerable GetStrings(int val) - { - for (var i = 0; i < 32; i++) - { - var x = 1 << i; - if ((val & x) != 0) - yield return bits.Single(a => a.Value == x).Key; - } - } - } - - public struct Bits : IEquatable> where T : struct - { - public int Value; - - public Bits(string[] val) { Value = BitAllocator.GetValue(val); } - public Bits(Bits other) { Value = other.Value; } - - public override string ToString() - { - return BitAllocator.GetStrings(Value).JoinWith(","); - } - - public static bool operator ==(Bits me, Bits other) { return me.Value == other.Value; } - public static bool operator !=(Bits me, Bits other) { return !(me == other); } - - public bool Equals(Bits other) { return other == this; } - public override bool Equals(object obj) { return obj is Bits && Equals((Bits)obj); } - public override int GetHashCode() { return Value.GetHashCode(); } - } -} diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index 5190746cba..4724de23da 100644 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -36,7 +36,7 @@ namespace OpenRA.Traits readonly Shroud shroud; public Player Owner { get; private set; } - public HashSet TargetTypes { get; private set; } + public BitSet TargetTypes { get; private set; } public ITooltipInfo TooltipInfo { get; private set; } public Player TooltipOwner { get; private set; } @@ -84,7 +84,6 @@ namespace OpenRA.Traits footprint.Select(p => shroud.Contains(p).ToString()).JoinWith("|"))); CenterPosition = actor.CenterPosition; - TargetTypes = new HashSet(); tooltips = actor.TraitsImplementing().ToArray(); health = actor.TraitOrDefault(); @@ -101,10 +100,7 @@ namespace OpenRA.Traits public void RefreshState() { Owner = actor.Owner; - - // PERF: Reuse collection to avoid allocations. - TargetTypes.Clear(); - TargetTypes.UnionWith(actor.GetEnabledTargetTypes()); + TargetTypes = actor.GetEnabledTargetTypes(); if (health != null) { diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index ad32fe1c65..5cc6623a31 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -365,15 +365,20 @@ namespace OpenRA.Traits bool SpatiallyPartitionable { get; } } + /// + /// Indicates target types as defined on are present in a . + /// + public sealed class TargetableType { TargetableType() { } } + public interface ITargetableInfo : ITraitInfoInterface { - HashSet GetTargetTypes(); + BitSet GetTargetTypes(); } public interface ITargetable { // Check IsTraitEnabled or !IsTraitDisabled first - HashSet TargetTypes { get; } + BitSet TargetTypes { get; } bool TargetableBy(Actor self, Actor byActor); bool RequiresForceFire { get; } } diff --git a/OpenRA.Mods.Cnc/Traits/Disguise.cs b/OpenRA.Mods.Cnc/Traits/Disguise.cs index af33e413b4..53c56def73 100644 --- a/OpenRA.Mods.Cnc/Traits/Disguise.cs +++ b/OpenRA.Mods.Cnc/Traits/Disguise.cs @@ -16,6 +16,7 @@ using System.Linq; using OpenRA.Mods.Common.Orders; using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits.Render; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Cnc.Traits @@ -84,7 +85,7 @@ namespace OpenRA.Mods.Cnc.Traits public readonly Stance ValidStances = Stance.Ally | Stance.Neutral | Stance.Enemy; [Desc("Target types of actors that this actor disguise as.")] - public readonly HashSet TargetTypes = new HashSet { "Disguise" }; + public readonly BitSet TargetTypes = new BitSet("Disguise"); [Desc("Triggers which cause the actor to drop it's disguise. Possible values: None, Attack, Damaged,", "Unload, Infiltrate, Demolish, Move.")] @@ -317,7 +318,7 @@ namespace OpenRA.Mods.Cnc.Traits if (!info.ValidStances.HasStance(stance)) return false; - return info.TargetTypes.Overlaps(target.Info.TraitInfos().SelectMany(ti => ti.GetTargetTypes())); + return info.TargetTypes.Overlaps(target.Info.GetAllTargetTypes()); } } } diff --git a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForCash.cs b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForCash.cs index 3e7575bbbb..2c95ad520b 100644 --- a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForCash.cs +++ b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForCash.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using OpenRA.Mods.Common.Effects; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Cnc.Traits @@ -20,7 +21,7 @@ namespace OpenRA.Mods.Cnc.Traits [Desc("This structure can be infiltrated causing funds to be stolen.")] class InfiltrateForCashInfo : ITraitInfo { - public readonly HashSet Types = new HashSet(); + public readonly BitSet Types; [Desc("Percentage of the victim's resources that will be stolen.")] public readonly int Percentage = 100; @@ -47,7 +48,7 @@ namespace OpenRA.Mods.Cnc.Traits public InfiltrateForCash(InfiltrateForCashInfo info) { this.info = info; } - void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, HashSet types) + void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet types) { if (!info.Types.Overlaps(types)) return; diff --git a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForDecoration.cs b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForDecoration.cs index 8b0ebedc2f..0e4e23176a 100644 --- a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForDecoration.cs +++ b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForDecoration.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits.Render; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Cnc.Traits @@ -20,7 +21,7 @@ namespace OpenRA.Mods.Cnc.Traits [Desc("Reveals a decoration sprite to the indicated players when infiltrated.")] class InfiltrateForDecorationInfo : WithDecorationInfo { - public readonly HashSet Types = new HashSet(); + public readonly BitSet Types; public override object Create(ActorInitializer init) { return new InfiltrateForDecoration(init.Self, this); } } @@ -36,7 +37,7 @@ namespace OpenRA.Mods.Cnc.Traits this.info = info; } - void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, HashSet types) + void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet types) { if (!info.Types.Overlaps(types)) return; diff --git a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForExploration.cs b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForExploration.cs index 8e8d07d395..55a09bef56 100644 --- a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForExploration.cs +++ b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForExploration.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Cnc.Traits @@ -19,7 +20,7 @@ namespace OpenRA.Mods.Cnc.Traits [Desc("Steal and reset the owner's exploration.")] class InfiltrateForExplorationInfo : ITraitInfo { - public readonly HashSet Types = new HashSet(); + public readonly BitSet Types; public object Create(ActorInitializer init) { return new InfiltrateForExploration(init.Self, this); } } @@ -33,7 +34,7 @@ namespace OpenRA.Mods.Cnc.Traits this.info = info; } - void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, HashSet types) + void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet types) { if (!info.Types.Overlaps(types)) return; diff --git a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForPowerOutage.cs b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForPowerOutage.cs index 768e6c9ac9..fe36ec2d97 100644 --- a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForPowerOutage.cs +++ b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForPowerOutage.cs @@ -11,13 +11,14 @@ using System.Collections.Generic; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Cnc.Traits { class InfiltrateForPowerOutageInfo : ITraitInfo { - public readonly HashSet Types = new HashSet(); + public readonly BitSet Types; public readonly int Duration = 25 * 20; @@ -35,7 +36,7 @@ namespace OpenRA.Mods.Cnc.Traits playerPower = self.Owner.PlayerActor.Trait(); } - void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, HashSet types) + void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet types) { if (!info.Types.Overlaps(types)) return; diff --git a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForSupportPower.cs b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForSupportPower.cs index 1175b96162..7e3db3fb92 100644 --- a/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForSupportPower.cs +++ b/OpenRA.Mods.Cnc/Traits/Infiltration/InfiltrateForSupportPower.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.Cnc.Traits { [ActorReference, FieldLoader.Require] public readonly string Proxy = null; - public readonly HashSet Types = new HashSet(); + public readonly BitSet Types; public object Create(ActorInitializer init) { return new InfiltrateForSupportPower(this); } } @@ -34,7 +34,7 @@ namespace OpenRA.Mods.Cnc.Traits this.info = info; } - void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, HashSet types) + void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet types) { if (!info.Types.Overlaps(types)) return; diff --git a/OpenRA.Mods.Cnc/Traits/Infiltration/Infiltrates.cs b/OpenRA.Mods.Cnc/Traits/Infiltration/Infiltrates.cs index b0e5d6ded7..9ad770adf2 100644 --- a/OpenRA.Mods.Cnc/Traits/Infiltration/Infiltrates.cs +++ b/OpenRA.Mods.Cnc/Traits/Infiltration/Infiltrates.cs @@ -17,13 +17,14 @@ using OpenRA.Mods.Common; using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Orders; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Cnc.Traits { public class InfiltratesInfo : ConditionalTraitInfo { - public readonly HashSet Types = new HashSet(); + public readonly BitSet Types; [VoiceReference] public readonly string Voice = "Action"; @@ -72,14 +73,14 @@ namespace OpenRA.Mods.Cnc.Traits if (IsTraitDisabled) return false; - IEnumerable targetTypes = null; + var targetTypes = new BitSet(); if (order.Target.Type == TargetType.FrozenActor) targetTypes = order.Target.FrozenActor.TargetTypes; if (order.Target.Type == TargetType.Actor) targetTypes = order.TargetActor.GetEnabledTargetTypes(); - return targetTypes != null && Info.Types.Overlaps(targetTypes); + return Info.Types.Overlaps(targetTypes); } public string VoicePhraseForOrder(Actor self, Order order) @@ -133,7 +134,7 @@ namespace OpenRA.Mods.Cnc.Traits if (!info.ValidStances.HasStance(stance)) return false; - return info.Types.Overlaps(target.Info.TraitInfos().SelectMany(ti => ti.GetTargetTypes())); + return info.Types.Overlaps(target.Info.GetAllTargetTypes()); } } } diff --git a/OpenRA.Mods.Cnc/Traits/MadTank.cs b/OpenRA.Mods.Cnc/Traits/MadTank.cs index 8b534deb2e..e832f97c6a 100644 --- a/OpenRA.Mods.Cnc/Traits/MadTank.cs +++ b/OpenRA.Mods.Cnc/Traits/MadTank.cs @@ -114,7 +114,7 @@ namespace OpenRA.Mods.Cnc.Traits { get { - yield return new TargetTypeOrderTargeter(new HashSet { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; + yield return new TargetTypeOrderTargeter(new BitSet("DetonateAttack"), "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; yield return new DeployOrderTargeter("Detonate", 5); } } diff --git a/OpenRA.Mods.Common/AI/States/AirStates.cs b/OpenRA.Mods.Common/AI/States/AirStates.cs index b6b47cd6dd..7736af9781 100644 --- a/OpenRA.Mods.Common/AI/States/AirStates.cs +++ b/OpenRA.Mods.Common/AI/States/AirStates.cs @@ -13,13 +13,14 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.AI { abstract class AirStateBase : StateBase { - static readonly string[] AirTargetTypes = new[] { "Air" }; + static readonly BitSet AirTargetTypes = new BitSet("Air"); protected const int MissileUnitMultiplier = 3; diff --git a/OpenRA.Mods.Common/AI/States/GroundStates.cs b/OpenRA.Mods.Common/AI/States/GroundStates.cs index eb08773eb5..04100ec3cf 100644 --- a/OpenRA.Mods.Common/AI/States/GroundStates.cs +++ b/OpenRA.Mods.Common/AI/States/GroundStates.cs @@ -105,7 +105,7 @@ namespace OpenRA.Mods.Common.AI else { var enemies = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Bot.Info.AttackScanRadius)) - .Where(a => !a.IsDead && leader.Owner.Stances[a.Owner] == Stance.Enemy && a.GetEnabledTargetTypes().Any()); + .Where(a => !a.IsDead && leader.Owner.Stances[a.Owner] == Stance.Enemy && !a.GetEnabledTargetTypes().IsEmpty); var target = enemies.ClosestTo(leader.CenterPosition); if (target != null) { diff --git a/OpenRA.Mods.Common/AI/States/NavyStates.cs b/OpenRA.Mods.Common/AI/States/NavyStates.cs index a383200750..fa57a2cc92 100644 --- a/OpenRA.Mods.Common/AI/States/NavyStates.cs +++ b/OpenRA.Mods.Common/AI/States/NavyStates.cs @@ -132,7 +132,7 @@ namespace OpenRA.Mods.Common.AI else { var enemies = owner.World.FindActorsInCircle(leader.CenterPosition, WDist.FromCells(owner.Bot.Info.AttackScanRadius)) - .Where(a => !a.IsDead && leader.Owner.Stances[a.Owner] == Stance.Enemy && a.GetEnabledTargetTypes().Any()); + .Where(a => !a.IsDead && leader.Owner.Stances[a.Owner] == Stance.Enemy && !a.GetEnabledTargetTypes().IsEmpty); var target = enemies.ClosestTo(leader.CenterPosition); if (target != null) { diff --git a/OpenRA.Mods.Common/AI/States/StateBase.cs b/OpenRA.Mods.Common/AI/States/StateBase.cs index edb270f966..662fcc6896 100644 --- a/OpenRA.Mods.Common/AI/States/StateBase.cs +++ b/OpenRA.Mods.Common/AI/States/StateBase.cs @@ -65,7 +65,7 @@ namespace OpenRA.Mods.Common.AI return false; var targetTypes = target.GetEnabledTargetTypes(); - if (!targetTypes.Any()) + if (targetTypes.IsEmpty) return false; var arms = a.TraitsImplementing(); diff --git a/OpenRA.Mods.Common/AI/SupportPowerDecision.cs b/OpenRA.Mods.Common/AI/SupportPowerDecision.cs index cd43972b7a..84c4c03f84 100644 --- a/OpenRA.Mods.Common/AI/SupportPowerDecision.cs +++ b/OpenRA.Mods.Common/AI/SupportPowerDecision.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.AI @@ -122,7 +123,7 @@ namespace OpenRA.Mods.Common.AI public readonly Stance Against = Stance.Enemy; [Desc("What types should the desired targets of this power be?")] - public readonly HashSet Types = new HashSet { "Air", "Ground", "Water" }; + public readonly BitSet Types = new BitSet("Air", "Ground", "Water"); [Desc("How attractive are these types of targets?")] public readonly int Attractiveness = 100; diff --git a/OpenRA.Mods.Common/Lint/CheckDeathTypes.cs b/OpenRA.Mods.Common/Lint/CheckDeathTypes.cs index 693519bd72..a3c6b4604f 100644 --- a/OpenRA.Mods.Common/Lint/CheckDeathTypes.cs +++ b/OpenRA.Mods.Common/Lint/CheckDeathTypes.cs @@ -35,8 +35,8 @@ namespace OpenRA.Mods.Common.Lint if (!deathTypes.Any() || spawnActorOnAnyDeathType) continue; - var targetable = actorInfo.Value.TraitInfos().SelectMany(x => x.GetTargetTypes()).ToList(); - if (!targetable.Any()) + var targetable = actorInfo.Value.GetAllTargetTypes(); + if (targetable.IsEmpty) continue; foreach (var weaponInfo in rules.Weapons) diff --git a/OpenRA.Mods.Common/Orders/UnitOrderTargeter.cs b/OpenRA.Mods.Common/Orders/UnitOrderTargeter.cs index a20603e1c1..165b6f236e 100644 --- a/OpenRA.Mods.Common/Orders/UnitOrderTargeter.cs +++ b/OpenRA.Mods.Common/Orders/UnitOrderTargeter.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Orders @@ -67,9 +68,9 @@ namespace OpenRA.Mods.Common.Orders public class TargetTypeOrderTargeter : UnitOrderTargeter { - readonly HashSet targetTypes; + readonly BitSet targetTypes; - public TargetTypeOrderTargeter(HashSet targetTypes, string order, int priority, string cursor, bool targetEnemyUnits, bool targetAllyUnits) + public TargetTypeOrderTargeter(BitSet targetTypes, string order, int priority, string cursor, bool targetEnemyUnits, bool targetAllyUnits) : base(order, priority, cursor, targetEnemyUnits, targetAllyUnits) { this.targetTypes = targetTypes; diff --git a/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs b/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs index 2630ffc22d..70706590d1 100644 --- a/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs +++ b/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using Eluant; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Scripting; using OpenRA.Traits; @@ -302,7 +303,7 @@ namespace OpenRA.Mods.Common.Scripting OnCapturedInternal(self); } - void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, HashSet types) + void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet types) { if (world.Disposing) return; diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackSuicides.cs b/OpenRA.Mods.Common/Traits/Attack/AttackSuicides.cs index e5a09c6fef..60cdb5c060 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackSuicides.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackSuicides.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Drawing; using OpenRA.Activities; using OpenRA.Mods.Common.Orders; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -45,7 +46,7 @@ namespace OpenRA.Mods.Common.Traits if (IsTraitDisabled) yield break; - yield return new TargetTypeOrderTargeter(new HashSet { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; + yield return new TargetTypeOrderTargeter(new BitSet("DetonateAttack"), "DetonateAttack", 5, "attack", true, false) { ForceAttack = false }; yield return new DeployOrderTargeter("Detonate", 5); } } diff --git a/OpenRA.Mods.Common/Traits/AutoTarget.cs b/OpenRA.Mods.Common/Traits/AutoTarget.cs index c82193f055..0ac5d52459 100644 --- a/OpenRA.Mods.Common/Traits/AutoTarget.cs +++ b/OpenRA.Mods.Common/Traits/AutoTarget.cs @@ -284,7 +284,7 @@ namespace OpenRA.Mods.Common.Traits continue; // Check whether we can auto-target this actor - var targetTypes = actor.GetEnabledTargetTypes().ToArray(); + var targetTypes = actor.GetEnabledTargetTypes(); var validPriorities = activePriorities.Where(ati => { // Already have a higher priority target diff --git a/OpenRA.Mods.Common/Traits/AutoTargetPriority.cs b/OpenRA.Mods.Common/Traits/AutoTargetPriority.cs index 7c7301255e..159a3f2115 100644 --- a/OpenRA.Mods.Common/Traits/AutoTargetPriority.cs +++ b/OpenRA.Mods.Common/Traits/AutoTargetPriority.cs @@ -9,7 +9,7 @@ */ #endregion -using System.Collections.Generic; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -18,10 +18,10 @@ namespace OpenRA.Mods.Common.Traits public class AutoTargetPriorityInfo : ConditionalTraitInfo, Requires { [Desc("Target types that can be AutoTargeted.")] - public readonly HashSet ValidTargets = new HashSet { "Ground", "Water", "Air" }; + public readonly BitSet ValidTargets = new BitSet("Ground", "Water", "Air"); [Desc("Target types that can't be AutoTargeted.", "Overrules ValidTargets.")] - public readonly HashSet InvalidTargets = new HashSet(); + public readonly BitSet InvalidTargets; [Desc("ValidTargets with larger priorities will be AutoTargeted before lower priorities.")] public readonly int Priority = 1; diff --git a/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs b/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs index bdeae1b5b0..d9fb73c145 100644 --- a/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs +++ b/OpenRA.Mods.Common/Traits/Crates/DuplicateUnitCrateAction.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Traits public readonly int MaxRadius = 4; [Desc("The list of unit target types we are allowed to duplicate.")] - public readonly HashSet ValidTargets = new HashSet { "Ground", "Water" }; + public readonly BitSet ValidTargets = new BitSet("Ground", "Water"); [Desc("Which factions this crate action can occur for.")] public readonly HashSet ValidFactions = new HashSet(); diff --git a/OpenRA.Mods.Common/Traits/Targetable.cs b/OpenRA.Mods.Common/Traits/Targetable.cs index df39a350c8..10c48af022 100644 --- a/OpenRA.Mods.Common/Traits/Targetable.cs +++ b/OpenRA.Mods.Common/Traits/Targetable.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Linq; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -19,8 +20,8 @@ namespace OpenRA.Mods.Common.Traits public class TargetableInfo : ConditionalTraitInfo, ITargetableInfo { [Desc("Target type. Used for filtering (in)valid targets.")] - public readonly HashSet TargetTypes = new HashSet(); - public HashSet GetTargetTypes() { return TargetTypes; } + public readonly BitSet TargetTypes; + public BitSet GetTargetTypes() { return TargetTypes; } public bool RequiresForceFire = false; @@ -53,7 +54,7 @@ namespace OpenRA.Mods.Common.Traits return cloaks.All(c => c.IsTraitDisabled || c.IsVisible(self, viewer.Owner)); } - public virtual HashSet TargetTypes { get { return Info.TargetTypes; } } + public virtual BitSet TargetTypes { get { return Info.TargetTypes; } } public bool RequiresForceFire { get { return Info.RequiresForceFire; } } } diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index e3fb2ba686..744211fbfe 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -118,7 +118,7 @@ namespace OpenRA.Mods.Common.Traits public interface ISeedableResource { void Seed(Actor self); } [RequireExplicitImplementation] - public interface INotifyInfiltrated { void Infiltrated(Actor self, Actor infiltrator, HashSet types); } + public interface INotifyInfiltrated { void Infiltrated(Actor self, Actor infiltrator, BitSet types); } [RequireExplicitImplementation] public interface INotifyBlockingMove { void OnNotifyBlockingMove(Actor self, Actor blocking); } diff --git a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs index 61fe5f5ca1..f3cafcebf9 100644 --- a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs @@ -14,6 +14,7 @@ using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.Common.Effects; using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Warheads @@ -48,7 +49,7 @@ namespace OpenRA.Mods.Common.Warheads [Desc("Whether to consider actors in determining whether the explosion should happen. If false, only terrain will be considered.")] public readonly bool ImpactActors = true; - static readonly string[] TargetTypeAir = new string[] { "Air" }; + static readonly BitSet TargetTypeAir = new BitSet("Air"); public ImpactType GetImpactType(World world, CPos cell, WPos pos, Actor firedBy) { diff --git a/OpenRA.Mods.Common/Warheads/Warhead.cs b/OpenRA.Mods.Common/Warheads/Warhead.cs index 4f507fe469..69dd3be345 100644 --- a/OpenRA.Mods.Common/Warheads/Warhead.cs +++ b/OpenRA.Mods.Common/Warheads/Warhead.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Warheads @@ -35,10 +36,10 @@ namespace OpenRA.Mods.Common.Warheads public abstract class Warhead : IWarhead { [Desc("What types of targets are affected.")] - public readonly HashSet ValidTargets = new HashSet { "Ground", "Water" }; + public readonly BitSet ValidTargets = new BitSet("Ground", "Water"); [Desc("What types of targets are unaffected.", "Overrules ValidTargets.")] - public readonly HashSet InvalidTargets = new HashSet(); + public readonly BitSet InvalidTargets; [Desc("What diplomatic stances are affected.")] public readonly Stance ValidStances = Stance.Ally | Stance.Neutral | Stance.Enemy; @@ -54,7 +55,7 @@ namespace OpenRA.Mods.Common.Warheads [Desc("The color used for this warhead's visualization in the world's `WarheadDebugOverlay` trait.")] public readonly Color DebugOverlayColor = Color.Red; - public bool IsValidTarget(IEnumerable targetTypes) + public bool IsValidTarget(BitSet targetTypes) { return ValidTargets.Overlaps(targetTypes) && !InvalidTargets.Overlaps(targetTypes); }