Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

UNET 32 [SyncVar] Limit Workaround

Discussion in 'UNet' started by mischa2k, May 28, 2017.

  1. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,331
    UNET only allows us to use 32 Syncvars+SyncLists per Script. That's fine in most cases, but sometimes we need more, which will result in the following errors:
    • UNetWeaver error: Script class [Name] has too many SyncVars (32). (This could include base classes)
    • Failure generating network code. UnityEditor.Scripting.Serialization.Weaver:WeaveUnetFromEditor(String, String, String, String, Boolean)
    The reason for '32' SyncVars is that the UNET Weaver uses a 32 bit dirty mask, hence 32 SyncVars. Right now there's just no way to modify this value without reverse engineering Unity DLLs. I hope one of the UNET developers can at least give us the option to use a 64 bit dirty mask for SyncVars in the future, so that we can use 64 of them too. Perhaps with a flag in the Global Config, similar to the new 'use ack long' property? 32 sounds like a lot, but I had plenty of people add dozens of attributes to uMMORPG's Player script, running into the limit over and over again. Sending around 32 more bits should be worth making a developer's life easier.
    @aabramychev @Erik-Juhl @larus

    In the meantime, there is a way around it. I implemented my own [SyncVar_] attribute that allows for abitrary amounts of SyncVars in your scripts by mapping them to SyncList entries. Here is how to use it:

    1. Create SyncVarLimitWorkaround.cs:
    Code (CSharp):
    1. // (c) ummorpg.net
    2. //
    3. // This script provides a custom [SyncVar_] attribute to work around UNET's 32
    4. // SyncVar limit.
    5. //
    6. // Usage: attach it to the Prefab; use [SyncVar_] in any component.
    7. //
    8. // This script uses reflection and is kind of hack. It works fine, but it would
    9. // be even better if UNET allows more than 32 SyncVars by default some day.
    10. using System;
    11. using System.Reflection;
    12. using System.Collections.Generic;
    13. using System.Linq;
    14. using UnityEngine;
    15. using UnityEngine.Networking;
    16.  
    17. [AttributeUsage(AttributeTargets.Field)]
    18. public class SyncVar_Attribute : Attribute{}
    19.  
    20. // UNET has no SyncListLong. Workaround via SyncListStruct
    21. public struct LongWrapper {
    22.     public long value;
    23.     public LongWrapper(long value) { this.value = value; }
    24. }
    25. public class SyncListLongWrapper : SyncListStruct<LongWrapper> { }
    26.  
    27. // we need a list of field,obj. a dictionary can't guarantee order.
    28. public class FieldInfoAndObject {
    29.     public FieldInfo field;
    30.     public object obj;
    31.     public FieldInfoAndObject(FieldInfo field, object obj) {
    32.         this.field = field;
    33.         this.obj = obj;
    34.     }
    35. }
    36.  
    37. public class SyncVarLimitWorkaround : NetworkBehaviour {
    38.     List<FieldInfoAndObject> stringFields = new List<FieldInfoAndObject>();
    39.     SyncListString strings = new SyncListString();
    40.  
    41.     List<FieldInfoAndObject> floatFields = new List<FieldInfoAndObject>();
    42.     SyncListFloat floats = new SyncListFloat();
    43.  
    44.     List<FieldInfoAndObject> intFields = new List<FieldInfoAndObject>();
    45.     SyncListInt ints = new SyncListInt();
    46.  
    47.     List<FieldInfoAndObject> uintFields = new List<FieldInfoAndObject>();
    48.     SyncListUInt uints = new SyncListUInt();
    49.  
    50.     List<FieldInfoAndObject> longFields = new List<FieldInfoAndObject>();
    51.     SyncListLongWrapper longs = new SyncListLongWrapper();
    52.  
    53.     List<FieldInfoAndObject> boolFields = new List<FieldInfoAndObject>();
    54.     SyncListBool bools = new SyncListBool();
    55.  
    56.     public static List<FieldInfo> GetFieldsWithAttribute(Type objectType, Type attributeType) {
    57.         // getfields with all binding flags still doesn't get base type's private
    58.         // fields. we have to iterate through base types to get all of them.
    59.         var result = new List<FieldInfo>();
    60.         while (true) {
    61.             var fields = objectType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(
    62.                 field => field.IsDefined(attributeType, true)
    63.             );
    64.             result.AddRange(fields);
    65.             objectType = objectType.BaseType;
    66.             if (objectType == null) break;
    67.         }
    68.         return result;
    69.     }
    70.  
    71.     // server & client: build field lists for each type. we assume that the
    72.     // order is the same on client and server.
    73.     void Awake() {
    74.         // go through each networkbehaviour component
    75.         foreach (var component in GetComponents<NetworkBehaviour>()) {
    76.             // find all the custom syncvars
    77.             foreach (var field in GetFieldsWithAttribute(component.GetType(), typeof(SyncVar_Attribute))) {
    78.                 //Debug.LogWarning(component.GetType() + " => " + field + "=>" + field.GetValue(component));
    79.                 // add them to the field lists
    80.                 if (field.FieldType == typeof(string))
    81.                     stringFields.Add(new FieldInfoAndObject(field, component));
    82.                 else if (field.FieldType == typeof(float))
    83.                     floatFields.Add(new FieldInfoAndObject(field, component));
    84.                 else if (field.FieldType == typeof(int))
    85.                     intFields.Add(new FieldInfoAndObject(field, component));
    86.                 else if (field.FieldType == typeof(uint))
    87.                     uintFields.Add(new FieldInfoAndObject(field, component));
    88.                 else if (field.FieldType == typeof(long))
    89.                     longFields.Add(new FieldInfoAndObject(field, component));
    90.                 else if (field.FieldType == typeof(bool))
    91.                     boolFields.Add(new FieldInfoAndObject(field, component));
    92.                 else Debug.LogError("Unsupported [SyncVar_] type: " + field);
    93.             }
    94.         }
    95.     }
    96.  
    97.     // server: populate the synclists with the field values
    98.     public override void OnStartServer() {
    99.         foreach (var fieldAndObject in stringFields) {
    100.             //print("add string: " + fieldAndObject.field.GetValue(fieldAndObject.obj));
    101.             strings.Add((string)fieldAndObject.field.GetValue(fieldAndObject.obj));
    102.         }
    103.  
    104.         foreach (var fieldAndObject in floatFields) {
    105.             //print("add float: " + fieldAndObject.field.GetValue(fieldAndObject.obj));
    106.             floats.Add((float)fieldAndObject.field.GetValue(fieldAndObject.obj));
    107.         }
    108.  
    109.         foreach (var fieldAndObject in intFields) {
    110.             //print("add int: " + fieldAndObject.field.GetValue(fieldAndObject.obj));
    111.             ints.Add((int)fieldAndObject.field.GetValue(fieldAndObject.obj));
    112.         }
    113.  
    114.         foreach (var fieldAndObject in uintFields) {
    115.             //print("add uint: " + fieldAndObject.field.GetValue(fieldAndObject.obj));
    116.             uints.Add((uint)fieldAndObject.field.GetValue(fieldAndObject.obj));
    117.         }
    118.  
    119.         foreach (var fieldAndObject in longFields) {
    120.             //print("add long: " + fieldAndObject.field.GetValue(fieldAndObject.obj));
    121.             longs.Add(new LongWrapper((long)fieldAndObject.field.GetValue(fieldAndObject.obj)));
    122.         }
    123.  
    124.         foreach (var fieldAndObject in boolFields) {
    125.             //print("add bool: " + fieldAndObject.field.GetValue(fieldAndObject.obj));
    126.             bools.Add((bool)fieldAndObject.field.GetValue(fieldAndObject.obj));
    127.         }
    128.     }
    129.  
    130.     // server: copy field values to synclists all the time
    131.     // (still works if obj becomes null)
    132.     [ServerCallback]
    133.     void Update() {
    134.         for (int i = 0; i < stringFields.Count; ++i) {
    135.             var fieldAndObject = stringFields[i];
    136.             string value = (string)fieldAndObject.field.GetValue(fieldAndObject.obj);
    137.             // only update if changed. don't mess with dirty flags.
    138.             if (strings[i] != value) strings[i] = value;
    139.         }
    140.  
    141.         for (int i = 0; i < floatFields.Count; ++i) {
    142.             var fieldAndObject = floatFields[i];
    143.             float value = (float)fieldAndObject.field.GetValue(fieldAndObject.obj);
    144.             // only update if changed. don't mess with dirty flags.
    145.             if (floats[i] != value) floats[i] = value;
    146.         }
    147.  
    148.         for (int i = 0; i < intFields.Count; ++i) {
    149.             var fieldAndObject = intFields[i];
    150.             int value = (int)fieldAndObject.field.GetValue(fieldAndObject.obj);
    151.             // only update if changed. don't mess with dirty flags.
    152.             if (ints[i] != value) ints[i] = value;
    153.         }
    154.  
    155.         for (int i = 0; i < uintFields.Count; ++i) {
    156.             var fieldAndObject = uintFields[i];
    157.             uint value = (uint)fieldAndObject.field.GetValue(fieldAndObject.obj);
    158.             // only update if changed. don't mess with dirty flags.
    159.             if (uints[i] != value) uints[i] = value;
    160.         }
    161.  
    162.         for (int i = 0; i < longFields.Count; ++i) {
    163.             var fieldAndObject = longFields[i];
    164.             long value = (long)fieldAndObject.field.GetValue(fieldAndObject.obj);
    165.             // only update if changed. don't mess with dirty flags.
    166.             if (longs[i].value != value) longs[i] = new LongWrapper(value);
    167.         }
    168.  
    169.         for (int i = 0; i < boolFields.Count; ++i) {
    170.             var fieldAndObject = boolFields[i];
    171.             bool value = (bool)fieldAndObject.field.GetValue(fieldAndObject.obj);
    172.             // only update if changed. don't mess with dirty flags.
    173.             if (bools[i] != value) bools[i] = value;
    174.         }
    175.     }
    176.  
    177.     // client: hook synclists and update fields when changed
    178.     // (still works if obj becomes null)
    179.     // we also call all hooks once to apply initial values
    180.     // e.g. if a syncvar was set when loading from database before starting
    181.     public override void OnStartClient() {
    182.         strings.Callback += OnStringsChanged;
    183.         for (int i = 0; i < strings.Count; ++i)
    184.             OnStringsChanged(SyncListString.Operation.OP_ADD, i);
    185.  
    186.         floats.Callback += OnFloatsChanged;
    187.         for (int i = 0; i < floats.Count; ++i)
    188.             OnFloatsChanged(SyncListFloat.Operation.OP_ADD, i);
    189.  
    190.         ints.Callback += OnIntsChanged;
    191.         for (int i = 0; i < ints.Count; ++i)
    192.             OnIntsChanged(SyncListInt.Operation.OP_ADD, i);
    193.  
    194.         uints.Callback += OnUIntsChanged;
    195.         for (int i = 0; i < uints.Count; ++i)
    196.             OnUIntsChanged(SyncListUInt.Operation.OP_ADD, i);
    197.  
    198.         longs.Callback += OnLongsChanged;
    199.         for (int i = 0; i < longs.Count; ++i)
    200.             OnLongsChanged(SyncListLongWrapper.Operation.OP_ADD, i);
    201.  
    202.         bools.Callback += OnBoolsChanged;
    203.         for (int i = 0; i < bools.Count; ++i)
    204.             OnBoolsChanged(SyncListBool.Operation.OP_ADD, i);
    205.     }
    206.  
    207.     void OnStringsChanged(SyncListString.Operation op, int index) {
    208.         var fieldAndObject = stringFields[index];
    209.         string value = strings[index];
    210.         fieldAndObject.field.SetValue(fieldAndObject.obj, value);
    211.         //print("STRING CHANGED: " + fieldAndObject.field + " => " + value);
    212.     }
    213.  
    214.     void OnFloatsChanged(SyncListFloat.Operation op, int index) {
    215.         var fieldAndObject = floatFields[index];
    216.         float value = floats[index];
    217.         fieldAndObject.field.SetValue(fieldAndObject.obj, value);
    218.         //print("FLOAT CHANGED: " + fieldAndObject.field + " => " + value);
    219.     }
    220.  
    221.     void OnIntsChanged(SyncListInt.Operation op, int index) {
    222.         var fieldAndObject = intFields[index];
    223.         int value = ints[index];
    224.         fieldAndObject.field.SetValue(fieldAndObject.obj, value);
    225.         //print("INT CHANGED: " + fieldAndObject.field + " => " + value);
    226.     }
    227.  
    228.     void OnUIntsChanged(SyncListUInt.Operation op, int index) {
    229.         var fieldAndObject = uintFields[index];
    230.         uint value = uints[index];
    231.         fieldAndObject.field.SetValue(fieldAndObject.obj, value);
    232.         //print("UINT CHANGED: " + fieldAndObject.field + " => " + value);
    233.     }
    234.  
    235.     void OnLongsChanged(SyncListLongWrapper.Operation op, int index) {
    236.         var fieldAndObject = longFields[index];
    237.         long value = longs[index].value;
    238.         fieldAndObject.field.SetValue(fieldAndObject.obj, value);
    239.         //print("LONG CHANGED: " + fieldAndObject.field + " => " + value);
    240.     }
    241.  
    242.     void OnBoolsChanged(SyncListBool.Operation op, int index) {
    243.         var fieldAndObject = boolFields[index];
    244.         bool value = bools[index];
    245.         fieldAndObject.field.SetValue(fieldAndObject.obj, value);
    246.         //print("BOOL CHANGED: " + fieldAndObject.field + " => " + value);
    247.     }
    248. }
    249.  
    2. Use [SyncVar_] instead of [SyncVar] after reaching the limit. Example:
    Code (CSharp):
    1. [SyncVar_] int health = 0;
    3. Make sure that each Prefab that uses [SyncVar_] also has the above Script attached to it.

    Enjoy!
     
    Last edited: May 28, 2017
    Artaani, cioa00, TwoTen and 2 others like this.
  2. Tiny-Tree

    Tiny-Tree

    Joined:
    Dec 26, 2012
    Posts:
    1,314
    that just works great, hope someone from unet team will see that.
     
    mischa2k likes this.
  3. veereshkumbar

    veereshkumbar

    Joined:
    Aug 24, 2017
    Posts:
    4
    @vis2k i am facing a problem with syncVar and Command both. I have used syncVar and command attributes in my project and they are working fine but when i am adding one more at that time i m getting a problem the value are not syncing over network not even from Client to server and server to client. When i go through your answer i got to know that there will be a limit for using SyncVars but my question is what about the Command. Please help me out this got stuck. :(
     
  4. mischa2k

    mischa2k

    Joined:
    Sep 4, 2015
    Posts:
    4,331
    SkyLimitStudio and iSleepzZz like this.