This repository has been archived by the owner on Jan 18, 2022. It is now read-only.
/
EntityTemplate.cs
372 lines (328 loc) · 15.2 KB
/
EntityTemplate.cs
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
using System;
using System.Collections.Generic;
using Improbable.Worker.CInterop;
namespace Improbable.Gdk.Core
{
/// <summary>
/// Utility class to help build SpatialOS entities. An <see cref="EntityTemplate" /> can be mutated be used
/// multiple times.
/// </summary>
public class EntityTemplate
{
private const uint EntityAclComponentId = 50;
private const uint PositionComponentId = 54;
private readonly Dictionary<uint, ISpatialComponentSnapshot> entityData =
new Dictionary<uint, ISpatialComponentSnapshot>();
private readonly Acl acl = new Acl();
/// <summary>
/// Constructs a worker access attribute, given a worker ID.
/// </summary>
/// <param name="workerId">An ID of a worker.</param>
/// <returns>A string representing the worker access attribute.</returns>
public static string GetWorkerAccessAttribute(string workerId)
{
return $"workerId:{workerId}";
}
/// <summary>
/// Adds a SpatialOS component to the Entity Template.
/// </summary>
/// <param name="snapshot">The component snapshot to add.</param>
/// <exception cref="InvalidOperationException">
/// Thrown if the EntityTemplate already contains a component snapshot with the same component ID.
/// </exception>
/// <remarks>
/// EntityACL is handled automatically by the EntityTemplate, so a EntityACL snapshot will be ignored.
/// </remarks>
public void AddComponent(ISpatialComponentSnapshot snapshot)
{
if (snapshot.ComponentId == EntityAclComponentId)
{
// ACL handled automatically.
return;
}
if (entityData.ContainsKey(snapshot.ComponentId))
{
throw new InvalidOperationException(
"Cannot add multiple components of the same type to the same entity. " +
$"Attempted to add componentId: {snapshot.ComponentId} more than once.");
}
entityData.Add(snapshot.ComponentId, snapshot);
}
/// <summary>
/// Adds a SpatialOS component to the EntityTemplate with write permissions specified.
/// </summary>
/// <param name="snapshot">The component snapshot to add.</param>
/// <param name="writeAccess">
/// The worker attribute that should be granted write access over the given component.
/// </param>
/// <exception cref="InvalidOperationException">
/// Thrown if the EntityTemplate already contains a component snapshot with the same component ID.
/// </exception>
/// <remarks>
/// EntityACL is handled automatically by the EntityTemplate, so a EntityACL snapshot will be ignored.
/// </remarks>
public void AddComponent(ISpatialComponentSnapshot snapshot, string writeAccess)
{
AddComponent(snapshot);
if (snapshot.ComponentId != EntityAclComponentId)
{
acl.SetComponentWriteAccess(snapshot.ComponentId, writeAccess);
}
}
/// <summary>
/// Attempts to get a component snapshot stored in the EntityTemplate.
/// </summary>
/// <typeparam name="TSnapshot">The type of the component snapshot.</typeparam>
/// <returns>The component snapshot, if the component snapshot exists, null otherwise.</returns>
public TSnapshot? GetComponent<TSnapshot>() where TSnapshot : struct, ISpatialComponentSnapshot
{
if (entityData.TryGetValue(ComponentDatabase.GetSnapshotComponentId<TSnapshot>(), out var snapshot))
{
return (TSnapshot) snapshot;
}
return null;
}
/// <summary>
/// Attempts to get a component snapshot stored in the EntityTemplate.
/// </summary>
/// <param name="componentId">The ID of the component to fetch.</param>
/// <returns>The component snapshot, if the component snapshot exists, null otherwise.</returns>
public ISpatialComponentSnapshot GetComponent(uint componentId)
{
entityData.TryGetValue(componentId, out var snapshot);
return snapshot;
}
/// <summary>Gets the component of the associated type.</summary>
/// <param name="component">
/// When this method returns, contains the component, if the component is found; otherwise, the default value <see cref="TSnapshot"/>. This parameter is passed uninitialized.
/// </param>
/// <typeparam name="TSnapshot">The type of the component snapshot.</typeparam>
/// <returns>
/// True if this contains a component of type <see cref="TSnapshot"/>; otherwise, false.
/// </returns>
public bool TryGetComponent<TSnapshot>(out TSnapshot component)
where TSnapshot : struct, ISpatialComponentSnapshot
{
if (entityData.TryGetValue(ComponentDatabase.GetSnapshotComponentId<TSnapshot>(), out var boxedComponent))
{
component = (TSnapshot) boxedComponent;
return true;
}
component = default;
return false;
}
/// <summary>Gets the component with the associated component ID.</summary>
/// <param name="componentId">The ID of the component to get.</param>
/// <param name="component">
/// When this method returns, contains the component, if the component is found; otherwise null. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// True if this contains a component with the associated component ID; otherwise, false.
/// </returns>
public bool TryGetComponent(uint componentId, out ISpatialComponentSnapshot component)
{
return entityData.TryGetValue(componentId, out component);
}
/// <summary>
/// Checks if a component snapshot is stored in the EntityTemplate.
/// </summary>
/// <param name="componentId">The component id to check.</param>
/// <returns>True, if the component snapshot exists, false otherwise.</returns>
public bool HasComponent(uint componentId)
{
return entityData.ContainsKey(componentId);
}
/// <summary>
/// Checks if a component snapshot is stored in the EntityTemplate.
/// </summary>
/// <typeparam name="TSnapshot">The type of the component snapshot.</typeparam>
/// <returns>True, if the component snapshot exists, false otherwise.</returns>
public bool HasComponent<TSnapshot>() where TSnapshot : struct, ISpatialComponentSnapshot
{
return HasComponent(ComponentDatabase.GetSnapshotComponentId<TSnapshot>());
}
/// <summary>
/// Sets a component snapshot in the EntityTemplate.
/// </summary>
/// <param name="snapshot">The component snapshot that will be inserted into the EntityTemplate.</param>
/// <remarks>
/// This will override the component snapshot in the EntityTemplate if one already exists.
/// </remarks>
public void SetComponent(ISpatialComponentSnapshot snapshot)
{
entityData[snapshot.ComponentId] = snapshot;
}
/// <summary>
/// Removes a component snapshot from the EntityTemplate, if it exists.
/// </summary>
/// <typeparam name="TSnapshot">The type of the component snapshot.</typeparam>
public void RemoveComponent<TSnapshot>() where TSnapshot : struct, ISpatialComponentSnapshot
{
var id = ComponentDatabase.GetSnapshotComponentId<TSnapshot>();
entityData.Remove(id);
acl.RemoveComponentWriteAccess(id);
}
/// <summary>
/// Removes a component snapshot from the EntityTemplate, if it exists.
/// </summary>
/// <param name="componentId">The component that will be removed from the EntityTemplate.</param>
public void RemoveComponent(uint componentId)
{
entityData.Remove(componentId);
acl.RemoveComponentWriteAccess(componentId);
}
/// <summary>
/// Retrieves the write access worker attribute for a given component.
/// </summary>
/// <param name="componentId">The component id for that component.</param>
/// <returns>The write access worker attribute, if it exists, null otherwise.</returns>
public string GetComponentWriteAccess(uint componentId)
{
return acl.GetComponentStringAccess(componentId);
}
/// <summary>
/// Retrieves the write access worker attribute for a given component.
/// </summary>
/// <typeparam name="TSnapshot">The type of the component.</typeparam>
/// <returns>The write access worker attribute, if it exists, null otherwise.</returns>
public string GetComponentWriteAccess<TSnapshot>() where TSnapshot : struct, ISpatialComponentSnapshot
{
return GetComponentWriteAccess(ComponentDatabase.GetSnapshotComponentId<TSnapshot>());
}
/// <summary>
/// Sets the write access worker attribute for a given component.
/// </summary>
/// <param name="componentId">The component id for that component.</param>
/// <param name="writeAccess">The write access worker attribute.</param>
public void SetComponentWriteAccess(uint componentId, string writeAccess)
{
acl.SetComponentWriteAccess(componentId, writeAccess);
}
/// <summary>
/// Sets the write access worker attribute for a given component.
/// </summary>
/// <param name="writeAccess">The write access worker attribute.</param>
/// <typeparam name="TSnapshot">The type of the component.</typeparam>
public void SetComponentWriteAccess<TSnapshot>(string writeAccess)
where TSnapshot : struct, ISpatialComponentSnapshot
{
SetComponentWriteAccess(ComponentDatabase.GetSnapshotComponentId<TSnapshot>(), writeAccess);
}
/// <summary>
/// Sets the worker attributes which should have read access over this entity.
/// </summary>
/// <param name="attributes">The worker attributes which should have read access.</param>
public void SetReadAccess(params string[] attributes)
{
foreach (var attr in attributes)
{
acl.AddReadAccess(attr);
}
}
/// <summary>
/// Creates an <see cref="Entity" /> instance from this template.
/// </summary>
/// <remarks>
/// This function allocates native memory. The <see cref="Entity" /> returned from this function should
/// be handed to a GDK API, which will take ownership, or otherwise must be disposed of manually.
/// </remarks>
/// <returns>The Entity object.</returns>
public Entity GetEntity()
{
ValidateEntity();
var handler = new EntityTemplateDynamicHandler(entityData);
Dynamic.ForEachComponent(handler);
var entity = handler.Entity;
entity.Add(acl.Build());
return entity;
}
/// <summary>
/// Creates an <see cref="EntitySnapshot"/> from this template.
/// </summary>
/// <returns>The EntitySnapshot object.</returns>
public EntitySnapshot GetEntitySnapshot()
{
var entity = GetEntity();
var snapshot = new EntitySnapshot(entity);
foreach (var id in entity.GetComponentIds())
{
entity.Get(id).Value.SchemaData.Value.Destroy();
}
return snapshot;
}
private void ValidateEntity()
{
if (!entityData.ContainsKey(PositionComponentId))
{
throw new InvalidOperationException("Entity is invalid. No Position component was found");
}
}
private class Acl
{
private readonly Dictionary<uint, string> writePermissions = new Dictionary<uint, string>();
private readonly List<string> readPermissions = new List<string>();
public string GetComponentStringAccess(uint componentId)
{
writePermissions.TryGetValue(componentId, out var writeAccess);
return writeAccess;
}
public void SetComponentWriteAccess(uint componentId, string attribute)
{
writePermissions[componentId] = attribute;
}
public void AddReadAccess(string attribute)
{
readPermissions.Add(attribute);
}
public void RemoveComponentWriteAccess(uint componentId)
{
writePermissions.Remove(componentId);
}
public ComponentData Build()
{
var schemaComponentData = SchemaComponentData.Create();
var fields = schemaComponentData.GetFields();
// Write the read acl
var workerRequirementSet = fields.AddObject(1);
foreach (var attr in readPermissions)
{
// Add another WorkerAttributeSet to the list
var set = workerRequirementSet.AddObject(1);
set.AddString(1, attr);
}
// Write the component write acl
foreach (var writePermission in writePermissions)
{
var keyValuePair = fields.AddObject(2);
keyValuePair.AddUint32(1, writePermission.Key);
var containedRequirementSet = keyValuePair.AddObject(2);
var containedAttributeSet = containedRequirementSet.AddObject(1);
containedAttributeSet.AddString(1, writePermission.Value);
}
return new ComponentData(EntityAclComponentId, schemaComponentData);
}
}
private class EntityTemplateDynamicHandler : Dynamic.IHandler
{
public Entity Entity;
private readonly Dictionary<uint, ISpatialComponentSnapshot> data;
public EntityTemplateDynamicHandler(Dictionary<uint, ISpatialComponentSnapshot> data)
{
this.data = data;
Entity = new Entity();
}
public void Accept<TUpdate, TSnapshot>(uint componentId, Dynamic.VTable<TUpdate, TSnapshot> vtable)
where TUpdate : struct, ISpatialComponentUpdate
where TSnapshot : struct, ISpatialComponentSnapshot
{
if (!data.ContainsKey(componentId))
{
return;
}
var componentData = new ComponentData(componentId, SchemaComponentData.Create());
vtable.SerializeSnapshot((TSnapshot) data[componentId], componentData);
Entity.Add(componentData);
}
}
}
}