1
0

GasMixture.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Linq;
  4. using System.Runtime.CompilerServices;
  5. using Content.Shared.Atmos.EntitySystems;
  6. using Content.Shared.Atmos.Reactions;
  7. using Robust.Shared.Serialization;
  8. using Robust.Shared.Utility;
  9. namespace Content.Shared.Atmos
  10. {
  11. /// <summary>
  12. /// A general-purpose, variable volume gas mixture.
  13. /// </summary>
  14. [Serializable]
  15. [DataDefinition]
  16. public sealed partial class GasMixture : IEquatable<GasMixture>, ISerializationHooks, IEnumerable<(Gas gas, float moles)>
  17. {
  18. public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true};
  19. // No access, to ensure immutable mixtures are never accidentally mutated.
  20. [Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), typeof(GasEnumerator), Other = AccessPermissions.None)]
  21. [DataField]
  22. public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];
  23. public float this[int gas] => Moles[gas];
  24. [DataField("temperature")]
  25. [ViewVariables(VVAccess.ReadWrite)]
  26. private float _temperature = Atmospherics.TCMB;
  27. [DataField("immutable")]
  28. public bool Immutable { get; private set; }
  29. [ViewVariables]
  30. public readonly float[] ReactionResults =
  31. {
  32. 0f,
  33. };
  34. [ViewVariables]
  35. public float TotalMoles
  36. {
  37. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  38. get => NumericsHelpers.HorizontalAdd(Moles);
  39. }
  40. [ViewVariables]
  41. public float Pressure
  42. {
  43. get
  44. {
  45. if (Volume <= 0) return 0f;
  46. return TotalMoles * Atmospherics.R * Temperature / Volume;
  47. }
  48. }
  49. [ViewVariables]
  50. public float Temperature
  51. {
  52. get => _temperature;
  53. set
  54. {
  55. DebugTools.Assert(!float.IsNaN(value));
  56. if (!Immutable)
  57. _temperature = MathF.Min(MathF.Max(value, Atmospherics.TCMB), Atmospherics.Tmax);
  58. }
  59. }
  60. [DataField("volume")]
  61. [ViewVariables(VVAccess.ReadWrite)]
  62. public float Volume { get; set; }
  63. public GasMixture()
  64. {
  65. }
  66. public GasMixture(float volume = 0f)
  67. {
  68. if (volume < 0)
  69. volume = 0;
  70. Volume = volume;
  71. }
  72. public GasMixture(float[] moles, float temp, float volume = Atmospherics.CellVolume)
  73. {
  74. if (moles.Length != Atmospherics.AdjustedNumberOfGases)
  75. throw new InvalidOperationException($"Invalid mole array length");
  76. if (volume < 0)
  77. volume = 0;
  78. DebugTools.Assert(!float.IsNaN(temp));
  79. _temperature = temp;
  80. Moles = moles;
  81. Volume = volume;
  82. }
  83. public GasMixture(GasMixture toClone)
  84. {
  85. CopyFrom(toClone);
  86. }
  87. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  88. public void MarkImmutable()
  89. {
  90. Immutable = true;
  91. }
  92. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  93. public float GetMoles(int gasId)
  94. {
  95. return Moles[gasId];
  96. }
  97. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  98. public float GetMoles(Gas gas)
  99. {
  100. return GetMoles((int)gas);
  101. }
  102. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  103. public void SetMoles(int gasId, float quantity)
  104. {
  105. if (!float.IsFinite(quantity) || float.IsNegative(quantity))
  106. throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
  107. if (!Immutable)
  108. Moles[gasId] = quantity;
  109. }
  110. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  111. public void SetMoles(Gas gas, float quantity)
  112. {
  113. SetMoles((int)gas, quantity);
  114. }
  115. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  116. public void AdjustMoles(int gasId, float quantity)
  117. {
  118. if (Immutable)
  119. return;
  120. if (!float.IsFinite(quantity))
  121. throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
  122. // Clamping is needed because x - x can be negative with floating point numbers. If we don't
  123. // clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
  124. ref var moles = ref Moles[gasId];
  125. moles = MathF.Max(moles + quantity, 0);
  126. }
  127. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  128. public void AdjustMoles(Gas gas, float moles)
  129. {
  130. AdjustMoles((int)gas, moles);
  131. }
  132. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  133. public GasMixture Remove(float amount)
  134. {
  135. return RemoveRatio(amount / TotalMoles);
  136. }
  137. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  138. public GasMixture RemoveRatio(float ratio)
  139. {
  140. switch (ratio)
  141. {
  142. case <= 0:
  143. return new GasMixture(Volume){Temperature = Temperature};
  144. case > 1:
  145. ratio = 1;
  146. break;
  147. }
  148. var removed = new GasMixture(Volume) { Temperature = Temperature };
  149. Moles.CopyTo(removed.Moles.AsSpan());
  150. NumericsHelpers.Multiply(removed.Moles, ratio);
  151. if (!Immutable)
  152. NumericsHelpers.Sub(Moles, removed.Moles);
  153. for (var i = 0; i < Moles.Length; i++)
  154. {
  155. var moles = Moles[i];
  156. var otherMoles = removed.Moles[i];
  157. if ((moles < Atmospherics.GasMinMoles || float.IsNaN(moles)) && !Immutable)
  158. Moles[i] = 0;
  159. if (otherMoles < Atmospherics.GasMinMoles || float.IsNaN(otherMoles))
  160. removed.Moles[i] = 0;
  161. }
  162. return removed;
  163. }
  164. public GasMixture RemoveVolume(float vol)
  165. {
  166. return RemoveRatio(vol / Volume);
  167. }
  168. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  169. public void CopyFrom(GasMixture sample)
  170. {
  171. if (Immutable)
  172. return;
  173. Volume = sample.Volume;
  174. sample.Moles.CopyTo(Moles, 0);
  175. Temperature = sample.Temperature;
  176. }
  177. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  178. public void Clear()
  179. {
  180. if (Immutable) return;
  181. Array.Clear(Moles, 0, Atmospherics.TotalNumberOfGases);
  182. }
  183. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  184. public void Multiply(float multiplier)
  185. {
  186. if (Immutable) return;
  187. NumericsHelpers.Multiply(Moles, multiplier);
  188. }
  189. void ISerializationHooks.AfterDeserialization()
  190. {
  191. // ISerializationHooks is obsolete.
  192. // TODO add fixed-length-array serializer
  193. // The arrays MUST have a specific length.
  194. Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases);
  195. }
  196. public GasMixtureStringRepresentation ToPrettyString()
  197. {
  198. var molesPerGas = new Dictionary<string, float>();
  199. for (int i = 0; i < Moles.Length; i++)
  200. {
  201. if (Moles[i] == 0)
  202. continue;
  203. molesPerGas.Add(((Gas) i).ToString(), Moles[i]);
  204. }
  205. return new GasMixtureStringRepresentation(TotalMoles, Temperature, Pressure, molesPerGas);
  206. }
  207. GasEnumerator GetEnumerator()
  208. {
  209. return new GasEnumerator(this);
  210. }
  211. IEnumerator<(Gas gas, float moles)> IEnumerable<(Gas gas, float moles)>.GetEnumerator()
  212. {
  213. return GetEnumerator();
  214. }
  215. public override bool Equals(object? obj)
  216. {
  217. if (obj is GasMixture mix)
  218. return Equals(mix);
  219. return false;
  220. }
  221. public bool Equals(GasMixture? other)
  222. {
  223. if (ReferenceEquals(this, other))
  224. return true;
  225. if (ReferenceEquals(null, other))
  226. return false;
  227. return Moles.SequenceEqual(other.Moles)
  228. && _temperature.Equals(other._temperature)
  229. && ReactionResults.SequenceEqual(other.ReactionResults)
  230. && Immutable == other.Immutable
  231. && Volume.Equals(other.Volume);
  232. }
  233. [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
  234. public override int GetHashCode()
  235. {
  236. var hashCode = new HashCode();
  237. for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
  238. {
  239. var moles = Moles[i];
  240. hashCode.Add(moles);
  241. }
  242. hashCode.Add(_temperature);
  243. hashCode.Add(Immutable);
  244. hashCode.Add(Volume);
  245. return hashCode.ToHashCode();
  246. }
  247. IEnumerator IEnumerable.GetEnumerator()
  248. {
  249. return GetEnumerator();
  250. }
  251. public GasMixture Clone()
  252. {
  253. if (Immutable)
  254. return this;
  255. var newMixture = new GasMixture()
  256. {
  257. Moles = (float[])Moles.Clone(),
  258. _temperature = _temperature,
  259. Volume = Volume,
  260. };
  261. return newMixture;
  262. }
  263. public struct GasEnumerator(GasMixture mixture) : IEnumerator<(Gas gas, float moles)>
  264. {
  265. private int _idx = -1;
  266. public void Dispose()
  267. {
  268. // Nada.
  269. }
  270. public bool MoveNext()
  271. {
  272. return ++_idx < Atmospherics.TotalNumberOfGases;
  273. }
  274. public void Reset()
  275. {
  276. _idx = -1;
  277. }
  278. public (Gas gas, float moles) Current => ((Gas)_idx, mixture.Moles[_idx]);
  279. object? IEnumerator.Current => Current;
  280. }
  281. }
  282. }