public override void OnInspectorGUI() { Type scriptClass = _script.GetClass(); RealtimeModelAttribute realtimeModelAttribute = GetRealtimeModelAttribute(scriptClass); if (realtimeModelAttribute != null) { // If this is a model file. Do some cool UI for it. if (GUILayout.Button("Compile Model")) { try { string modelCode = GenerateModel(); SaveGeneratedModel(modelCode); } catch (Exception exception) { Debug.LogError("Failed to compile model."); Debug.LogException(exception); } } if (GUILayout.Button("Clear Autogenerated Model")) { try { SaveGeneratedModel(null); } catch (Exception exception) { Debug.LogException(exception); Debug.LogError("Failed to clear model."); } } } else { DrawDefaultInspectorGUI(); } }
private string GenerateModel() { // Model Type modelClass = _script.GetClass(); string classNamespace = modelClass.Namespace; string className = modelClass.Name; //string baseClassName = modelClass.BaseType.Name; bool hasMetaModel = false; RealtimeModelAttribute realtimeModelAttribute = GetRealtimeModelAttribute(modelClass); if (realtimeModelAttribute != null) { hasMetaModel = realtimeModelAttribute.createMetaModel; } // Properties Dictionary <uint, FieldInfo> allProperties; Dictionary <uint, FieldInfo> reliableProperties; Dictionary <uint, FieldInfo> unreliableProperties; Dictionary <uint, FieldInfo> modelProperties; Dictionary <uint, FieldInfo> eventProperties; GetRealtimePropertyAttributesForModel(modelClass, out allProperties, out reliableProperties, out unreliableProperties, out modelProperties, out eventProperties); // Model configuration bool hasNamespace = classNamespace != null; bool hasChangeCache = reliableProperties.Count > 0; // Model code List <string> code = new List <string>(); int level = 0; // Namespace if (hasNamespace) { code.Add(Indent(level++) + "namespace " + classNamespace + " {"); } // Class code.Add(Indent(level++) + "public partial class " + className + " : IModel {"); // Public properties if (allProperties.Count > 0) { code.Add(Indent(level) + "// Properties"); foreach (KeyValuePair <uint, FieldInfo> property in allProperties) { uint propertyID = property.Key; FieldInfo field = property.Value; if (reliableProperties.ContainsKey(propertyID)) { // Reliable code.Add(Indent(level++) + "public " + GetTypeAsString(field.FieldType) + " " + GetCleanPropertyName(field.Name, false) + " {"); code.Add(Indent(level) + "get { return _cache.LookForValueInCache(" + field.Name + ", entry => entry." + GetCleanPropertyName(field.Name, false) + "Set, entry => entry." + GetCleanPropertyName(field.Name, false) + "); }"); code.Add(Indent(level) + "set { if (value == " + GetCleanPropertyName(field.Name, false) + ") return; _cache.UpdateLocalCache(entry => { entry." + GetCleanPropertyName(field.Name, false) + "Set = true; entry." + GetCleanPropertyName(field.Name, false) + " = value; return entry; });" + (eventProperties.ContainsKey(propertyID) ? " Fire" + GetCleanPropertyName(field.Name, true) + "DidChange(value);" : "") + " }"); code.Add(Indent(--level) + "}"); } else if (unreliableProperties.ContainsKey(propertyID)) { // Unreliable code.Add(Indent(level++) + "public " + GetTypeAsString(field.FieldType) + " " + GetCleanPropertyName(field.Name, false) + " {"); code.Add(Indent(level) + "get { return " + field.Name + "; }"); code.Add(Indent(level) + "set { if (value == " + field.Name + ") return; _" + GetCleanPropertyName(field.Name, false) + "ShouldWrite = true; " + field.Name + " = value;" + (eventProperties.ContainsKey(propertyID) ? " Fire" + GetCleanPropertyName(field.Name, true) + "DidChange(value);" : "") + " }"); code.Add(Indent(--level) + "}"); } else if (modelProperties.ContainsKey(propertyID)) { // Model code.Add(Indent(level++) + "public " + GetTypeAsString(field.FieldType) + " " + GetCleanPropertyName(field.Name, false) + " {"); code.Add(Indent(level) + "get { return " + field.Name + "; }"); code.Add(Indent(--level) + "}"); } } code.Add(Indent(level)); } // Events if (eventProperties.Count > 0) { code.Add(Indent(level) + "// Events"); foreach (KeyValuePair <uint, FieldInfo> property in eventProperties) { FieldInfo field = property.Value; code.Add(Indent(level) + "public delegate void " + GetCleanPropertyName(field.Name, true) + "DidChange(" + _script.GetClass().Name + " model, " + GetTypeAsString(field.FieldType) + " value);"); code.Add(Indent(level) + "public event " + GetCleanPropertyName(field.Name, true) + "DidChange " + GetCleanPropertyName(field.Name, false) + "DidChange;"); } code.Add(Indent(level)); } // Ownership if (hasMetaModel) { code.Add(Indent(level) + "// Ownership"); code.Add(Indent(level) + "public int ownerID { get { return _metaModel.ownerID; } }"); code.Add(Indent(level)); code.Add(Indent(level++) + "public void RequestOwnership(int clientIndex) {"); code.Add(Indent(level) + "_metaModel.ownerID = clientIndex;"); code.Add(Indent(--level) + "}"); code.Add(Indent(level)); code.Add(Indent(level++) + "public void ClearOwnership() {"); code.Add(Indent(level) + "_metaModel.ownerID = -1;"); code.Add(Indent(--level) + "}"); code.Add(Indent(level)); } // Meta model if (hasMetaModel) { code.Add(Indent(level) + "// Meta model"); code.Add(Indent(level) + "private MetaModel _metaModel;"); code.Add(Indent(level)); } // Change cache if (hasChangeCache) { code.Add(Indent(level) + "// Delta updates"); code.Add(Indent(level++) + "private struct LocalCacheEntry {"); int longestTypeLength = 4; // bool foreach (KeyValuePair <uint, FieldInfo> property in reliableProperties) { FieldInfo field = property.Value; int typeLength = GetTypeAsString(field.FieldType).Length; if (typeLength > longestTypeLength) { longestTypeLength = typeLength; } } foreach (KeyValuePair <uint, FieldInfo> property in reliableProperties) { FieldInfo field = property.Value; string typeName = GetTypeAsString(field.FieldType); code.Add(Indent(level) + "public bool " + Space(longestTypeLength - 4) + GetCleanPropertyName(field.Name, false) + "Set;"); code.Add(Indent(level) + "public " + typeName + Space(longestTypeLength - typeName.Length) + " " + GetCleanPropertyName(field.Name, false) + ";"); } code.Add(Indent(--level) + "}"); code.Add(Indent(level)); code.Add(Indent(level) + "private LocalChangeCache<LocalCacheEntry> _cache;"); code.Add(Indent(level)); } // Unreliable property should write bools if (unreliableProperties.Count > 0) { foreach (KeyValuePair <uint, FieldInfo> property in unreliableProperties) { FieldInfo field = property.Value; code.Add(Indent(level) + "private bool _" + GetCleanPropertyName(field.Name, false) + "ShouldWrite;"); } code.Add(Indent(level)); } // Constructor code.Add(Indent(level++) + "public " + className + "() {"); if (hasMetaModel) { code.Add(Indent(level) + "_metaModel = new MetaModel();"); } if (hasChangeCache) { code.Add(Indent(level) + "_cache = new LocalChangeCache<LocalCacheEntry>();"); } if (hasChangeCache && modelProperties.Count > 0) { code.Add(Indent(level)); } if (modelProperties.Count > 0) { foreach (KeyValuePair <uint, FieldInfo> property in modelProperties) { FieldInfo field = property.Value; code.Add(Indent(level) + field.Name + " = new " + GetTypeAsString(field.FieldType) + "();"); } } code.Add(Indent(--level) + "}"); code.Add(Indent(level)); // Events if (eventProperties.Count > 0) { code.Add(Indent(level) + "// Events"); foreach (KeyValuePair <uint, FieldInfo> property in eventProperties) { FieldInfo field = property.Value; code.Add(Indent(level++) + "public void Fire" + GetCleanPropertyName(field.Name, true) + "DidChange(" + GetTypeAsString(field.FieldType) + " value) {"); code.Add(Indent(level++) + "try {"); code.Add(Indent(level++) + "if (" + GetCleanPropertyName(field.Name, false) + "DidChange != null)"); code.Add(Indent(level--) + GetCleanPropertyName(field.Name, false) + "DidChange(this, value);"); code.Add(Indent(level - 1) + "} catch (System.Exception exception) {"); code.Add(Indent(level) + "Debug.LogException(exception);"); code.Add(Indent(--level) + "}"); code.Add(Indent(--level) + "}"); } code.Add(Indent(level)); } //// Serialization // PropertyID enum code.Add(Indent(level) + "// Serialization"); code.Add(Indent(level++) + "enum PropertyID {"); foreach (KeyValuePair <uint, FieldInfo> property in allProperties) { uint propertyID = property.Key; FieldInfo field = property.Value; code.Add(Indent(level) + GetCleanPropertyName(field.Name, true) + " = " + propertyID + ","); } code.Add(Indent(--level) + "}"); code.Add(Indent(level)); // Write Length code.Add(Indent(level++) + "public int WriteLength(StreamContext context) {"); code.Add(Indent(level) + "int length = 0;"); code.Add(Indent(level)); code.Add(Indent(level++) + "if (context.fullModel) {"); if (hasChangeCache) { code.Add(Indent(level) + "// Mark unreliable properties as clean and flatten the in-flight cache."); code.Add(Indent(level) + "// TODO: Move this out of WriteLength() once we have a prepareToWrite method."); foreach (KeyValuePair <uint, FieldInfo> property in unreliableProperties) { FieldInfo field = property.Value; code.Add(Indent(level) + "_" + GetCleanPropertyName(field.Name, false) + "ShouldWrite = false;"); } foreach (KeyValuePair <uint, FieldInfo> property in reliableProperties) { FieldInfo field = property.Value; code.Add(Indent(level) + field.Name + " = " + GetCleanPropertyName(field.Name, false) + ";"); } code.Add(Indent(level) + "_cache.Clear();"); code.Add(Indent(level)); } if (hasMetaModel) { code.Add(Indent(level) + "// Meta model"); code.Add(Indent(level) + "length += WriteStream.WriteModelLength(0, _metaModel, context);"); code.Add(Indent(level)); } code.Add(Indent(level) + "// Write all properties"); foreach (KeyValuePair <uint, FieldInfo> property in allProperties) { uint propertyID = property.Key; FieldInfo field = property.Value; code.Add(Indent(level) + GetWriteLengthLineForProperty(field)); } code.Add(Indent(level - 1) + "} else {"); // Meta model if (hasMetaModel) { code.Add(Indent(level) + "// Meta model"); code.Add(Indent(level) + "length += WriteStream.WriteModelLength(0, _metaModel, context);"); } if (hasMetaModel && unreliableProperties.Count > 0) { code.Add(Indent(level)); } // Unreliable properties if (unreliableProperties.Count > 0) { code.Add(Indent(level) + "// Unreliable properties"); code.Add(Indent(level++) + "if (context.unreliableChannel) {"); foreach (KeyValuePair <uint, FieldInfo> property in unreliableProperties) { FieldInfo field = property.Value; code.Add(Indent(level++) + "if (_" + GetCleanPropertyName(field.Name, false) + "ShouldWrite) {"); code.Add(Indent(level) + GetWriteLengthLineForProperty(field)); code.Add(Indent(--level) + "}"); } code.Add(Indent(--level) + "}"); } if (unreliableProperties.Count > 0 && reliableProperties.Count > 0) { code.Add(Indent(level)); } // Reliable properties if (reliableProperties.Count > 0) { code.Add(Indent(level) + "// Reliable properties"); code.Add(Indent(level++) + "if (context.reliableChannel) {"); if (hasChangeCache) { code.Add(Indent(level) + "LocalCacheEntry entry = _cache.localCache;"); } foreach (KeyValuePair <uint, FieldInfo> property in reliableProperties) { FieldInfo field = property.Value; code.Add(Indent(level++) + "if (entry." + GetCleanPropertyName(field.Name, false) + "Set)"); code.Add(Indent(level--) + GetWriteLengthLineForProperty(field, true)); } code.Add(Indent(--level) + "}"); } if (reliableProperties.Count > 0 && modelProperties.Count > 0) { code.Add(Indent(level)); } // Models if (modelProperties.Count > 0) { code.Add(Indent(level) + "// Models"); foreach (KeyValuePair <uint, FieldInfo> property in modelProperties) { FieldInfo field = property.Value; code.Add(Indent(level) + GetWriteLengthLineForProperty(field, false)); } } code.Add(Indent(--level) + "}"); code.Add(Indent(level)); code.Add(Indent(level) + "return length;"); code.Add(Indent(--level) + "}"); code.Add(Indent(level)); // Write code.Add(Indent(level++) + "public void Write(WriteStream stream, StreamContext context) {"); code.Add(Indent(level++) + "if (context.fullModel) {"); if (hasMetaModel) { code.Add(Indent(level) + "// Meta model"); code.Add(Indent(level) + "stream.WriteModel(0, _metaModel, context);"); code.Add(Indent(level)); } code.Add(Indent(level) + "// Write all properties"); foreach (KeyValuePair <uint, FieldInfo> property in allProperties) { uint propertyID = property.Key; FieldInfo field = property.Value; code.Add(Indent(level) + GetWriteLineForProperty(field)); } code.Add(Indent(level - 1) + "} else {"); // Meta model if (hasMetaModel) { code.Add(Indent(level) + "// Meta model"); code.Add(Indent(level) + "stream.WriteModel(0, _metaModel, context);"); } if (hasMetaModel && unreliableProperties.Count > 0) { code.Add(Indent(level)); } // Unreliable properties if (unreliableProperties.Count > 0) { code.Add(Indent(level) + "// Unreliable properties"); code.Add(Indent(level++) + "if (context.unreliableChannel) {"); foreach (KeyValuePair <uint, FieldInfo> property in unreliableProperties) { FieldInfo field = property.Value; code.Add(Indent(level++) + "if (_" + GetCleanPropertyName(field.Name, false) + "ShouldWrite) {"); code.Add(Indent(level) + GetWriteLineForProperty(field)); code.Add(Indent(level) + "_" + GetCleanPropertyName(field.Name, false) + "ShouldWrite = false;"); code.Add(Indent(--level) + "}"); } code.Add(Indent(--level) + "}"); } if (unreliableProperties.Count > 0 && reliableProperties.Count > 0) { code.Add(Indent(level)); } // Reliable properties if (reliableProperties.Count > 0) { code.Add(Indent(level) + "// Reliable properties"); code.Add(Indent(level++) + "if (context.reliableChannel) {"); if (hasChangeCache) { code.Add(Indent(level) + "LocalCacheEntry entry = _cache.localCache;"); code.Add(Indent(level++) + GetIfPushToChangeCacheLineForProperties(reliableProperties.Values)); code.Add(Indent(level--) + "_cache.PushLocalCacheToInflight(context.updateID);"); code.Add(Indent(level)); } foreach (KeyValuePair <uint, FieldInfo> property in reliableProperties) { FieldInfo field = property.Value; code.Add(Indent(level++) + "if (entry." + GetCleanPropertyName(field.Name, false) + "Set)"); code.Add(Indent(level--) + GetWriteLineForProperty(field, true)); } code.Add(Indent(--level) + "}"); } if (reliableProperties.Count > 0 && modelProperties.Count > 0) { code.Add(Indent(level)); } // Models if (modelProperties.Count > 0) { code.Add(Indent(level) + "// Models"); foreach (KeyValuePair <uint, FieldInfo> property in modelProperties) { FieldInfo field = property.Value; code.Add(Indent(level) + GetWriteLineForProperty(field, false)); } } code.Add(Indent(--level) + "}"); code.Add(Indent(--level) + "}"); code.Add(Indent(level)); // Read code.Add(Indent(level++) + "public void Read(ReadStream stream, StreamContext context) {"); if (hasChangeCache) { IEnumerable <KeyValuePair <uint, FieldInfo> > reliableEventProperties = reliableProperties.Intersect(eventProperties); if (reliableEventProperties.Count() > 0) { foreach (KeyValuePair <uint, FieldInfo> property in reliableEventProperties) { uint propertyID = property.Key; FieldInfo field = property.Value; code.Add(Indent(level) + "bool " + GetCleanPropertyName(field.Name, false) + "ExistsInChangeCache = _cache.ValueExistsInCache(entry => entry." + GetCleanPropertyName(field.Name, false) + "Set);"); } code.Add(Indent(level)); } code.Add(Indent(level) + "// Remove from in-flight"); code.Add(Indent(level++) + "if (context.deltaUpdatesOnly && context.reliableChannel)"); code.Add(Indent(level--) + "_cache.RemoveUpdateFromInflight(context.updateID);"); code.Add(Indent(level)); } code.Add(Indent(level) + "// Loop through each property and deserialize"); code.Add(Indent(level) + "uint propertyID;"); code.Add(Indent(level++) + "while (stream.ReadNextPropertyID(out propertyID)) {"); code.Add(Indent(level++) + "switch (propertyID) {"); if (hasMetaModel) { code.Add(Indent(level++) + "case 0:"); code.Add(Indent(level) + "// Meta model"); code.Add(Indent(level) + "stream.ReadModel(_metaModel, context);"); code.Add(Indent(level--) + "break;"); } foreach (KeyValuePair <uint, FieldInfo> property in allProperties) { uint propertyID = property.Key; FieldInfo field = property.Value; bool reliableProperty = reliableProperties.ContainsKey(propertyID); bool unreliableProperty = unreliableProperties.ContainsKey(propertyID); bool eventProperty = eventProperties.ContainsKey(propertyID); code.Add(Indent(level++) + "case (uint)PropertyID." + GetCleanPropertyName(field.Name, true) + ": {"); if (eventProperty) { code.Add(Indent(level) + GetTypeAsString(field.FieldType) + " previousValue = " + field.Name + ";"); code.Add(Indent(level)); } code.Add(Indent(level) + GetReadLineForProperty(field)); if (unreliableProperty) { code.Add(Indent(level) + "_" + GetCleanPropertyName(field.Name, false) + "ShouldWrite = false;"); } if (eventProperty) { code.Add(Indent(level)); code.Add(Indent(level++) + "if (" + (reliableProperty ? "!" + GetCleanPropertyName(field.Name, false) + "ExistsInChangeCache && " : "") + field.Name + " != previousValue)"); code.Add(Indent(level--) + "Fire" + GetCleanPropertyName(field.Name, true) + "DidChange(" + field.Name + ");"); } code.Add(Indent(level) + "break;"); code.Add(Indent(--level) + "}"); } code.Add(Indent(level++) + "default:"); code.Add(Indent(level) + "stream.SkipProperty();"); code.Add(Indent(level--) + "break;"); code.Add(Indent(--level) + "}"); code.Add(Indent(--level) + "}"); code.Add(Indent(--level) + "}"); // Class code.Add(Indent(--level) + "}"); // Namespace if (hasNamespace) { code.Add(Indent(--level) + "}"); } // Convert to string and return string codeString = ""; foreach (string line in code) { codeString += line + Environment.NewLine; } return(codeString); }