-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGameDataValues.cs
More file actions
309 lines (249 loc) · 10.8 KB
/
Copy pathGameDataValues.cs
File metadata and controls
309 lines (249 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace antunity.GameData
{
/// <summary>A struct pairing game data with a value, similar to key-value-pairs in dictionaries. Used as the building block in GameDataValues.</summary>
/// <typeparam name="TGameData">the game data type</typeparam>
/// <typeparam name="TValue">the value type</typeparam>
[Serializable]
[GameDataDrawer(GameDataLayout.Vertical)]
public struct DataValuePair<TGameData, TValue> : IUseGameDataDrawer where TGameData : IGameDataBase
{
[Tooltip("An indexed data entry associated with a value.")]
[SerializeField] private TGameData data;
[Tooltip("A value associated with this data entry.")]
[SerializeField] private TValue value;
/// <summary>Use to access the data.</summary>
public TGameData Data
{
get => data;
set => data = value;
}
/// <summary>Use to access the value.</summary>
public TValue Value
{
get => value;
set => this.value = value;
}
public DataValuePair(TGameData data, TValue value = default)
{
this.data = data;
this.value = value;
}
}
/// <summary>A serializable dictionary-style list of data-value-pairs. This structure is Unity inspector-friendly.</summary>
/// <typeparam name="TGameData">the type of the serialized game data</typeparam>
/// <typeparam name="TValue">the type of the value associated with the game data</typeparam>
[Serializable]
public class GameDataValues<TGameData, TValue> : IEnumerable<DataValuePair<TGameData, TValue>>, ICopyable<GameDataValues<TGameData, TValue>> where TGameData : IGameDataBase
{
#region IEnumerable
public IEnumerator<DataValuePair<TGameData, TValue>> GetEnumerator()
{
EnsureInitialised();
return dataValuePairs.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion IEnumerable
#region ICopyable
public GameDataValues<TGameData, TValue> Copy()
{
var copy = new GameDataValues<TGameData, TValue>();
copy.dataValuePairs = new List<DataValuePair<TGameData, TValue>>(dataValuePairs);
copy.EnsureInitialised();
return copy;
}
#endregion ICopyable
[NonSerialized] private bool isInitialised = false;
[SerializeField] protected List<DataValuePair<TGameData, TValue>> dataValuePairs = new();
protected readonly Dictionary<object, int> itemsIndex = new();
/// <summary>Can be used to get or set values in the list by index.</summary>
/// <param name="index">the index</param>
/// <returns>the associated value</returns>
/// <exception cref="Exception">thrown if the index is of invalid type or when the index is not found in the registry</exception>
public TValue this[object index]
{
get
{
if (!ContainsIndex(index))
throw new Exception($"Index `{index}` not found in list `{typeof(TGameData)}`");
if (itemsIndex[index] >= dataValuePairs.Count)
throw new Exception($"Index `{index}` is out of range in list `{typeof(TGameData)}`");
return dataValuePairs[itemsIndex[index]].Value;
}
set
{
if (!ContainsIndex(index))
throw new Exception($"Index `{index}` not found in list `{typeof(TGameData)}`");
var data = GetData(index);
AddOrUpdate(data, value);
}
}
/// <summary>Can be used to get or set values in the list.</summary>
/// <param name="asset">the game data</param>
/// <returns>the associated value</returns>
public TValue this[TGameData asset]
{
get => this[asset.GetIndex()];
set => AddOrUpdate(asset, value);
}
/// <summary>The number of items in the list.</summary>
public int Count => dataValuePairs.Count;
/// <summary>A list of game data in the list.</summary>
public IReadOnlyList<TGameData> Data => dataValuePairs.ConvertAll(item => item.Data).AsReadOnly();
/// <summary>A list of the unique keys in the list.</summary>
public IReadOnlyList<object> Keys => dataValuePairs.ConvertAll(item => item.Data.GetIndex()).AsReadOnly();
/// <summary>A list of the values in the list.</summary>
public IReadOnlyList<TValue> Values => dataValuePairs.ConvertAll(item => item.Value).AsReadOnly();
/// <summary>Adds an entry to the list.</summary>
/// <param name="item">the game data</param>
/// <param name="value">the value associated with the game data</param>
/// <exception cref="Exception">thrown when a duplicate index is found in the list</exception>
public void Add(TGameData item, TValue value)
{
var pair = new DataValuePair<TGameData, TValue>(item, value);
if (!TryAdd(pair))
throw new Exception($"Index `{item.GetIndex()}` already exists in list `{typeof(TGameData)}`");
}
/// <summary>Clears all entries from the list.</summary>
public void Clear()
{
dataValuePairs.Clear();
itemsIndex.Clear();
}
/// <summary>Checks whether the provided data is found in the list.</summary>
/// <param name="data">the game data</param>
/// <returns>true if the data is found</returns>
public bool ContainsData(TGameData data) => data != null && ContainsIndex(data.GetIndex());
/// <summary>Checks if a specific index key is found in the list.</summary>
/// <param name="index">the index</param>
/// <returns>true if the index is found</returns>
public bool ContainsIndex(object index)
{
EnsureInitialised();
if (index == null)
return false;
if (index is TGameData data)
throw new Exception($"Use {nameof(ContainsData)} to check for data items in list `{typeof(TGameData)}`");
return itemsIndex.ContainsKey(index);
}
/// <summary>Attempts to get game data by the specified index.</summary>
/// <param name="index">the index</param>
/// <param name="data">the returned data</param>
/// <returns>true of the data is found</returns>
public bool TryGetData(object index, out TGameData data)
{
if (ContainsIndex(index))
{
data = GetData(index);
return true;
}
data = default;
return false;
}
/// <summary>Attempts to get a value by the specified index.</summary>
/// <param name="index">the index</param>
/// <param name="value">the returned value</param>
/// <returns>true if the index was found</returns>
public bool TryGetValue(object index, out TValue value)
{
if (ContainsIndex(index))
{
value = this[index];
return true;
}
value = default;
return false;
}
/// <summary>Attempts to get a value by the specified game data entry.</summary>
/// <param name="item">the game data</param>
/// <param name="value">the returned value</param>
/// <returns>true if the game data was found</returns>
public bool TryGetValue(TGameData item, out TValue value)
{
if (item != null)
return TryGetValue(item.GetIndex(), out value);
value = default;
return false;
}
/// <summary>Removes the value entry associated with the specified index.</summary>
/// <param name="index">the index</param>
public void Remove(object index)
{
if (!ContainsIndex(index))
return;
int i = itemsIndex[index];
dataValuePairs.RemoveAt(i);
itemsIndex.Remove(index);
Validate(i);
}
/// <summary>Removes the value entry associated with the specified game data.</summary>
/// <param name="item">the game data</param>
public void Remove(TGameData item)
{
if (item != null)
Remove(item.GetIndex());
}
protected void AddOrUpdate(TGameData data, TValue value)
{
var pair = new DataValuePair<TGameData, TValue>(data, value);
if (!TryAdd(pair))
dataValuePairs[itemsIndex[data.GetIndex()]] = pair;
}
private TGameData GetData(object index)
{
if (!ContainsIndex(index))
throw new Exception($"Index `{index}` not found in list `{typeof(TGameData)}`");
if (itemsIndex[index] >= dataValuePairs.Count)
throw new Exception($"Index `{index}` is out of range in list `{typeof(TGameData)}`");
return dataValuePairs[itemsIndex[index]].Data;
}
private bool TryAdd(DataValuePair<TGameData, TValue> pair)
{
EnsureInitialised();
object index = pair.Data.GetIndex();
if (itemsIndex.ContainsKey(index))
return false;
dataValuePairs.Add(pair);
itemsIndex.Add(index, dataValuePairs.Count - 1);
return true;
}
protected void EnsureInitialised()
{
if (isInitialised)
return;
isInitialised = true;
Validate();
}
private void Validate(int start = 0)
{
if (dataValuePairs.Count == 0)
{
itemsIndex.Clear();
return;
}
if (start == 0)
itemsIndex.Clear();
for (int i = start; i < dataValuePairs.Count; i++)
{
var data = dataValuePairs[i].Data;
if (data == null)
continue;
object index = dataValuePairs[i].Data.GetIndex();
if (index == null || index.Equals(default))
continue;
if (start != 0)
itemsIndex.Remove(index);
if (!itemsIndex.ContainsKey(index))
itemsIndex.Add(index, i);
else
{
dataValuePairs[i] = new DataValuePair<TGameData, TValue>();
Debug.LogWarning($"Discarded item with duplicate index [{index}] at position {i} in {nameof(GameDataValues<TGameData, TValue>)}");
}
}
}
}
}