Client LuaCsForBarotrauma
PrefabSelector.cs
1 #nullable enable
2 using System;
3 using System.Collections;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
7 using System.Threading;
8 using Barotrauma.Threading;
9 
10 namespace Barotrauma
11 {
12  public class PrefabSelector<T> : IEnumerable<T> where T : notnull, Prefab
13  {
14  private readonly ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
15 
16  public T? BasePrefab
17  {
18  get
19  {
20  using (new ReadLock(rwl)) { return basePrefabInternal; }
21  }
22  }
23 
24  public T? ActivePrefab
25  {
26  get
27  {
28  using (new ReadLock(rwl)) { return activePrefabInternal; }
29  }
30  }
31 
32  public void Add(T prefab, bool isOverride)
33  {
34  using (new WriteLock(rwl)) { AddInternal(prefab, isOverride); }
35  }
36 
37  public void RemoveIfContains(T prefab)
38  {
39  using (new WriteLock(rwl)) { RemoveIfContainsInternal(prefab); }
40  }
41 
42  public void Remove(T prefab)
43  {
44  using (new WriteLock(rwl)) { RemoveInternal(prefab); }
45  }
46 
47  public void RemoveByFile(ContentFile file, Action<T>? callback = null)
48  {
49  var removed = new List<T>();
50  using (new WriteLock(rwl))
51  {
52  for (int i = overrides.Count-1; i >= 0; i--)
53  {
54  var prefab = overrides[i];
55  if (prefab.ContentFile == file)
56  {
57  RemoveInternal(prefab);
58  removed.Add(prefab);
59  }
60  }
61 
62  if (basePrefabInternal is { ContentFile: var baseFile } p && baseFile == file)
63  {
64  RemoveInternal(basePrefabInternal);
65  removed.Add(p);
66  }
67  }
68  if (callback != null) { removed.ForEach(callback); }
69  }
70 
71  public void Sort()
72  {
73  using (new WriteLock(rwl)) { SortInternal(); }
74  }
75 
76  public bool IsEmpty
77  {
78  get
79  {
80  using (new ReadLock(rwl)) { return isEmptyInternal; }
81  }
82  }
83 
84  public bool Contains(T prefab)
85  {
86  using (new ReadLock(rwl)) { return ContainsInternal(prefab); }
87  }
88 
89  public bool IsOverride(T prefab)
90  {
91  using (new ReadLock(rwl)) { return IsOverrideInternal(prefab); }
92  }
93 
94 
95  #region Underlying implementations of the public methods, done separately to avoid nested locking
96  private T? basePrefabInternal;
97  private readonly List<T> overrides = new List<T>();
98 
99  private T? activePrefabInternal => overrides.Count > 0 ? overrides.First() : basePrefabInternal;
100 
101  private void AddInternal(T prefab, bool isOverride)
102  {
103  if (isOverride)
104  {
105  if (overrides.Contains(prefab)) { throw new InvalidOperationException($"Duplicate prefab in PrefabSelector ({typeof(T)}, {prefab.Identifier}, {prefab.ContentFile.ContentPackage.Name})"); }
106  overrides.Add(prefab);
107  }
108  else
109  {
110  if (basePrefabInternal != null)
111  {
112  string prefabName
113  = prefab is MapEntityPrefab mapEntityPrefab
114  ? $"\"{mapEntityPrefab.OriginalName}\", \"{prefab.Identifier}\""
115  : $"\"{prefab.Identifier}\"";
116  throw new InvalidOperationException(
117  $"Failed to add the prefab {prefabName} ({prefab.GetType()}) from \"{prefab.ContentPackage?.Name ?? "[NULL]"}\" ({prefab.ContentPackage?.Dir ?? ""}): "
118  + $"a prefab with the same identifier from \"{activePrefabInternal!.ContentPackage?.Name ?? "[NULL]"}\" ({activePrefabInternal!.ContentPackage?.Dir ?? ""}) already exists; try overriding");
119  }
120  basePrefabInternal = prefab;
121  }
122  SortInternal();
123  }
124 
125  private void RemoveIfContainsInternal(T prefab)
126  {
127  if (!ContainsInternal(prefab)) { return; }
128  RemoveInternal(prefab);
129  }
130 
131  private void RemoveInternal(T prefab)
132  {
133  if (basePrefabInternal == prefab) { basePrefabInternal = null; }
134  else if (overrides.Contains(prefab)) { overrides.Remove(prefab); }
135  else { throw new InvalidOperationException($"Can't remove prefab from PrefabSelector ({typeof(T)}, {prefab.Identifier}, {prefab.ContentFile.ContentPackage.Name})"); }
136  prefab.Dispose();
137  SortInternal();
138  }
139 
140  private void SortInternal()
141  {
142  overrides.Sort((p1, p2) => (p1.ContentPackage?.Index ?? int.MaxValue) - (p2.ContentPackage?.Index ?? int.MaxValue));
143  }
144 
145  private bool isEmptyInternal => basePrefabInternal is null && overrides.Count == 0;
146 
147  private bool ContainsInternal(T prefab) => basePrefabInternal == prefab || overrides.Contains(prefab);
148 
149  private int IndexOfInternal(T prefab) => basePrefabInternal == prefab
150  ? overrides.Count
151  : overrides.IndexOf(prefab);
152 
153  private bool IsOverrideInternal(T prefab) => IndexOfInternal(prefab) > 0;
154  #endregion
155 
156  public IEnumerator<T> GetEnumerator()
157  {
158  T? basePrefab;
159  ImmutableArray<T> overrideClone;
160  using (new ReadLock(rwl))
161  {
162  basePrefab = basePrefabInternal;
163  overrideClone = overrides.ToImmutableArray();
164  }
165  if (basePrefab != null) { yield return basePrefab; }
166  foreach (T prefab in overrideClone)
167  {
168  yield return prefab;
169  }
170  }
171 
172  IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
173  }
174 }
Base class for content file types, which are loaded from filelist.xml via reflection....
Definition: ContentFile.cs:23
IEnumerator< T > GetEnumerator()
void Add(T prefab, bool isOverride)
void RemoveByFile(ContentFile file, Action< T >? callback=null)
void RemoveIfContains(T prefab)