AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
SyncObject.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 #if UNITY_WINRT && !UNITY_EDITOR
5 #define USE_WINRT
6 #endif
7 
8 using System;
9 using System.Collections.Generic;
10 using System.Globalization;
11 using System.Reflection;
12 using UnityEngine;
13 using UnityEngine.Assertions;
14 using HoloToolkit.Unity;
15 
16 namespace HoloToolkit.Sharing.SyncModel
17 {
21  public class SyncObject : SyncPrimitive
22  {
23  private ObjectElementAdapter syncListener; // The sync object that we use to get sync events for this element
24  private Dictionary<long, SyncPrimitive> primitiveMap; // A dictionary of all of the primitives that are children of this object. Maps element GUID to data
25  private List<SyncPrimitive> primitives; // Raw list of child primitives
26 
27  public event Action<SyncObject> ObjectChanged; // Invoked when one of the child primitives has changed
28  public event Action<SyncObject> InitializationComplete; // Invoked when this object has been fully initialized
29 
30  private ObjectElement internalObjectElement;
34  public ObjectElement Element
35  {
36  get { return internalObjectElement; }
37  internal set
38  {
39  if (internalObjectElement == null)
40  {
41  internalObjectElement = value;
42  NetworkElement = value;
43 
44  if (internalObjectElement != null)
45  {
46  CreateSyncListener(internalObjectElement);
47  }
48  }
49  }
50  }
51 
52 #if UNITY_EDITOR
53  public override object RawValue
57  {
58  get { return null; }
59  }
60 #endif
61 
62  private User owner;
66  public User Owner
67  {
68  get { return owner; }
69  set
70  {
71  if (owner == null)
72  {
73  owner = value;
74  }
75  }
76  }
77 
81  public string ObjectType
82  {
83  get
84  {
85  return internalObjectElement != null ? internalObjectElement.GetObjectType().GetString() : null;
86  }
87  }
88 
92  public int OwnerId
93  {
94  get { return internalObjectElement != null ? internalObjectElement.GetOwnerID() : int.MaxValue; }
95  }
96 
97  public SyncObject(string field)
98  : base(field)
99  {
100  InitializePrimitives();
101  }
102 
103  public SyncObject()
104  : base(string.Empty)
105  {
106  InitializePrimitives();
107  }
108 
114  {
115  return primitives.ToArray();
116  }
117 
118  private void InitializePrimitives()
119  {
120  primitiveMap = new Dictionary<long, SyncPrimitive>();
121  primitives = new List<SyncPrimitive>();
122 
123  // Scan the type of object this is a look for the SyncDataAttribute
124  Type baseType = GetType();
125 
126 #if USE_WINRT
127  var typeFields = baseType.GetRuntimeFields();
128 #else
129  var typeFields = baseType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
130 #endif
131 
132  foreach (FieldInfo typeField in typeFields)
133  {
134  SyncDataAttribute attribute = null;
135 
136 #if USE_WINRT
137  attribute = typeField.GetCustomAttribute<SyncDataAttribute>(true);
138 #else
139  object[] customAttributes = typeField.GetCustomAttributes(typeof(SyncDataAttribute), true);
140  if (customAttributes.Length > 0)
141  {
142  attribute = customAttributes[0] as SyncDataAttribute;
143  }
144 #endif
145 
146  if (attribute != null)
147  {
148  Type fieldType = typeField.FieldType;
149  string memberName = typeField.Name;
150 
151  // Override the member name if provided
152  if (!string.IsNullOrEmpty(attribute.CustomFieldName))
153  {
154  memberName = attribute.CustomFieldName;
155  }
156 
157  // Auto instantiate the primitive if it doesn't already exist
158  SyncPrimitive dataPrimitive = typeField.GetValue(this) as SyncPrimitive;
159  if (dataPrimitive == null)
160  {
161  try
162  {
163  // Constructors are not inherited, as per Section 1.6.7.1 of the C# Language Specification.
164  // This means that if a class subclasses Object or Primitive, they must either declare a constructor
165  // that takes the "memberName" property or use the default (parameter less constructor).
166 
167  // First check if there is a constructor that takes the member name and if so call it
168  bool hasConstructor = fieldType.GetConstructor(new[] { typeof(string) }) != null;
169  if (hasConstructor)
170  {
171  dataPrimitive = (SyncPrimitive)Activator.CreateInstance(fieldType, memberName);
172  }
173  else
174  {
175  // Fallback on using the default constructor and manually assign the member name
176  dataPrimitive = (SyncPrimitive)Activator.CreateInstance(fieldType, null);
177  dataPrimitive.FieldName = memberName;
178  }
179 
180  typeField.SetValue(this, dataPrimitive);
181  }
182  catch (Exception ex)
183  {
184  Debug.LogWarningFormat("Unable to create SyncPrimitive of type {0}. Exception: {1}", memberName, ex);
185  }
186  }
187 
188  if (dataPrimitive != null)
189  {
190  // Register the child
191  AddChild(dataPrimitive);
192  }
193  }
194  }
195  }
196 
201  protected void AddChild(SyncPrimitive data)
202  {
203  if (data.HasNetworkElement)
204  {
205  long guid = data.Guid;
206  Assert.AreNotEqual(SharingClient.kInvalidXGuid, guid, "A primitive GUID should never be invalid if it is networked.");
207  primitiveMap.Add(guid, data);
208  }
209 
210  if (!primitives.Contains(data))
211  {
212  primitives.Add(data);
213  }
214  }
215 
220  protected void RemoveChild(SyncPrimitive data)
221  {
222  // Manually remove from maps
223  if (primitives.Remove(data) && data.HasNetworkElement)
224  {
225  primitiveMap.Remove(data.NetworkElement.GetGUID());
226 
227  // Object has been removed internally, notify network
228  ObjectElement parentElement = ObjectElement.Cast(data.NetworkElement.GetParent());
229  if (parentElement != null)
230  {
231  parentElement.RemoveElement(data.NetworkElement);
232  }
233  }
234  }
235 
240  protected virtual void OnElementAdded(Element element)
241  {
242  // Find the existing element.
243  bool primitiveDataChanged = false;
244  for (int i = 0; i < primitives.Count; i++)
245  {
246  if (primitives[i].XStringFieldName.IsEqual(element.GetName()))
247  {
248  // Found it, register the element so it knows where to pull data from
249  primitives[i].AddFromRemote(element);
250 
251  // Update the internal map
252  long guid = element.GetGUID();
253  primitiveMap[guid] = primitives[i];
254  primitiveDataChanged = true;
255  break;
256  }
257  }
258 
259  if (primitiveDataChanged)
260  {
261  // If there are no more primitives waiting to be added, finish the initialization
262  if (primitiveMap.Count == primitives.Count)
263  {
264  if (InitializationComplete != null)
265  {
266  InitializationComplete(this);
267  }
268  }
269  }
270  }
271 
276  protected virtual void OnElementDeleted(Element element)
277  {
278  // If the child exists, then remove it.
279  long guid = element.GetGUID();
280  if (primitiveMap.ContainsKey(guid))
281  {
282  RemoveChild(primitiveMap[guid]);
283  }
284  }
285 
291  protected virtual void OnBoolElementChanged(long elementID, bool newValue)
292  {
293  if (primitiveMap.ContainsKey(elementID))
294  {
295  SyncPrimitive primitive = primitiveMap[elementID];
296  primitive.UpdateFromRemote(newValue);
297  NotifyPrimitiveChanged(primitive);
298  }
299  else
300  {
301  LogUnknownElement(elementID.ToString(), newValue.ToString(), typeof(bool));
302  }
303  }
304 
310  protected virtual void OnIntElementChanged(long elementID, int newValue)
311  {
312  if (primitiveMap.ContainsKey(elementID))
313  {
314  SyncPrimitive primitive = primitiveMap[elementID];
315  primitive.UpdateFromRemote(newValue);
316  NotifyPrimitiveChanged(primitive);
317  }
318  else
319  {
320  LogUnknownElement(elementID.ToString(), newValue.ToString(), typeof(int));
321  }
322  }
323 
329  protected virtual void OnLongElementChanged(long elementID, long newValue)
330  {
331  if (primitiveMap.ContainsKey(elementID))
332  {
333  SyncPrimitive primitive = primitiveMap[elementID];
334  primitive.UpdateFromRemote(newValue);
335  NotifyPrimitiveChanged(primitive);
336  }
337  else
338  {
339  LogUnknownElement(elementID.ToString(), newValue.ToString(), typeof(long));
340  }
341  }
342 
348  protected virtual void OnFloatElementChanged(long elementID, float newValue)
349  {
350  if (primitiveMap.ContainsKey(elementID))
351  {
352  SyncPrimitive primitive = primitiveMap[elementID];
353  primitive.UpdateFromRemote(newValue);
354  NotifyPrimitiveChanged(primitive);
355  }
356  else
357  {
358  LogUnknownElement(elementID.ToString(), newValue.ToString(CultureInfo.InvariantCulture), typeof(float));
359  }
360  }
361 
367  protected virtual void OnDoubleElementChanged(long elementID, double newValue)
368  {
369  if (primitiveMap.ContainsKey(elementID))
370  {
371  SyncPrimitive primitive = primitiveMap[elementID];
372  primitive.UpdateFromRemote(newValue);
373  NotifyPrimitiveChanged(primitive);
374  }
375  else
376  {
377  LogUnknownElement(elementID.ToString(), newValue.ToString(CultureInfo.InvariantCulture), typeof(double));
378  }
379  }
380 
386  protected virtual void OnStringElementChanged(long elementID, XString newValue)
387  {
388  if (primitiveMap.ContainsKey(elementID))
389  {
390  SyncPrimitive primitive = primitiveMap[elementID];
391  primitive.UpdateFromRemote(newValue);
392  NotifyPrimitiveChanged(primitive);
393  }
394  else
395  {
396  LogUnknownElement(elementID.ToString(), newValue.GetString(), typeof(string));
397  }
398  }
399 
400  private void LogUnknownElement(string elementName, string elementValue, Type elementType)
401  {
402  Debug.LogWarningFormat("Error: Trying to update an unknown child element! Discarding update. Type: {0}, Value: {1}, Id: {2}", elementType, elementValue, elementName);
403  }
404 
405  protected virtual void NotifyPrimitiveChanged(SyncPrimitive primitive)
406  {
407  ObjectChanged.RaiseEvent(this);
408  }
409 
410  private void CreateSyncListener(ObjectElement element)
411  {
412  // Create a listener for this
413  syncListener = new ObjectElementAdapter();
414  syncListener.ElementAddedEvent += OnElementAdded;
415  syncListener.ElementDeletedEvent += OnElementDeleted;
416  syncListener.BoolChangedEvent += OnBoolElementChanged;
417  syncListener.IntChangedEvent += OnIntElementChanged;
418  syncListener.LongChangedEvent += OnLongElementChanged;
419  syncListener.FloatChangedEvent += OnFloatElementChanged;
420  syncListener.DoubleChangedEvent += OnDoubleElementChanged;
421  syncListener.StringChangedEvent += OnStringElementChanged;
422  element.AddListener(syncListener);
423  }
424 
425  #region From SyncPrimitive
426 
431  public override void InitializeLocal(ObjectElement parentElement)
432  {
433  // Auto create element if needed
434  if (Element == null)
435  {
436  Element = parentElement.CreateObjectElement(XStringFieldName, GetType().FullName, Owner);
437  NetworkElement = Element;
438  }
439 
440  // Initialize all primitives
441  for (int i = 0; i < primitives.Count; i++)
442  {
443  SyncPrimitive data = primitives[i];
444  data.InitializeLocal(Element);
445  primitiveMap[data.Guid] = data;
446  }
447 
448  // Complete the initialization
449  if (InitializationComplete != null)
450  {
451  InitializationComplete(this);
452  }
453  }
454 
455  public override void AddFromRemote(Element remoteElement)
456  {
457  Element = ObjectElement.Cast(remoteElement);
458  NetworkElement = remoteElement;
459  }
460 
461  #endregion //From SyncPrimitive
462  }
463 }
override void InitializeLocal(ObjectElement parentElement)
Initializes this object for local use. Doesn&#39;t wait for network initialization.
Definition: SyncObject.cs:431
System.Action< long, long > LongChangedEvent
void RemoveChild(SyncPrimitive data)
Remove a child primitive that belongs to this object.
Definition: SyncObject.cs:220
virtual void OnStringElementChanged(long elementID, XString newValue)
Handler for if a child string changes
Definition: SyncObject.cs:386
System.Action< long, XString > StringChangedEvent
static ObjectElement Cast(Element element)
virtual XString GetName()
Definition: Element.cs:53
virtual void RemoveElement(Element element)
virtual void OnElementAdded(Element element)
Handler for if a child is added.
Definition: SyncObject.cs:240
string FieldName
The field name of the primitive.
virtual Element GetParent()
Definition: Element.cs:59
bool HasNetworkElement
Indicates if the primitive has a network element. The primitive can only be modified if this returns ...
virtual Element NetworkElement
Network Element that represents the sync primitive&#39;s value on the server.
virtual void AddListener(ObjectElementListener newListener)
Base primitive used to define an element within the data model. The primitive is defined by a field a...
virtual void OnIntElementChanged(long elementID, int newValue)
Handler for if a child int changes.
Definition: SyncObject.cs:310
virtual long GetGUID()
Definition: Element.cs:48
void AddChild(SyncPrimitive data)
Register a new data model primitive as part of this object. Can be called multiple times on the same ...
Definition: SyncObject.cs:201
virtual void OnLongElementChanged(long elementID, long newValue)
Handler for if a child long changes
Definition: SyncObject.cs:329
virtual void NotifyPrimitiveChanged(SyncPrimitive primitive)
Definition: SyncObject.cs:405
override void AddFromRemote(Element remoteElement)
Called when being remotely initialized.
Definition: SyncObject.cs:455
virtual void OnElementDeleted(Element element)
Handler for if a child is deleted.
Definition: SyncObject.cs:276
abstract void InitializeLocal(ObjectElement parentElement)
Initializes this object for local use. Doesn&#39;t wait for network initialization.
virtual void OnBoolElementChanged(long elementID, bool newValue)
Handler for if a child bool changes.
Definition: SyncObject.cs:291
The SyncObject class is a container object that can hold multiple SyncPrimitives. ...
Definition: SyncObject.cs:21
Used to markup SyncPrimitives within a class.
virtual void UpdateFromRemote(XString remoteValue)
Called when the primitive value has changed from a remote action.
Action< SyncObject > InitializationComplete
Definition: SyncObject.cs:28
System.Action< long, bool > BoolChangedEvent
virtual ObjectElement CreateObjectElement(XString name, XString objectType, User owner)
System.Action< long, double > DoubleChangedEvent
Allows users of ObjectElements to register to receive event callbacks without having their classes in...
System.Action< long, float > FloatChangedEvent
virtual void OnDoubleElementChanged(long elementID, double newValue)
Handler for if a child double changes
Definition: SyncObject.cs:367
SyncPrimitive [] GetChildren()
Returns a list of all child primitives.
Definition: SyncObject.cs:113
long Guid
Unique identifier for primitive. Returns kInvalidXGuid if uninitialized.
virtual void OnFloatElementChanged(long elementID, float newValue)
Handler for if a child float changes
Definition: SyncObject.cs:348