@@ -27,6 +27,7 @@ public static class MirrorMethods
2727 private static Dictionary < string , int > rpcHashes = new ( ) ;
2828 private static Dictionary < string , ulong > dirtyBits = new ( ) ;
2929 private static Dictionary < Type , MethodInfo > writers = new ( ) ;
30+ private static Dictionary < Type , Dictionary < ulong , PropertyInfo > > propertiesByBit = new ( ) ;
3031
3132 private static Action < NetworkIdentity , NetworkConnection > sendSpawnMessage ;
3233
@@ -40,7 +41,7 @@ public static class MirrorMethods
4041 /// <remarks>Keys are formatted as the declaring type of the property and then the name of the property
4142 /// (MyType.MyProperty)</remarks>
4243 /// </summary>
43- public static Dictionary < string , string > RpcNames
44+ public static IReadOnlyDictionary < string , string > RpcNames
4445 {
4546 get
4647 {
@@ -56,7 +57,7 @@ public static Dictionary<string, string> RpcNames
5657 /// <remarks>Keys are formatted as the declaring type of the property and then the name of the property
5758 /// (MyType.MyProperty)</remarks>
5859 /// </summary>
59- public static Dictionary < string , int > RpcHashes
60+ public static IReadOnlyDictionary < string , int > RpcHashes
6061 {
6162 get
6263 {
@@ -72,7 +73,7 @@ public static Dictionary<string, int> RpcHashes
7273 /// <remarks>Keys are formatted as the declaring type of the property and then the name of the property
7374 /// (MyType.MyProperty)</remarks>
7475 /// </summary>
75- public static Dictionary < string , ulong > DirtyBits
76+ public static IReadOnlyDictionary < string , ulong > DirtyBits
7677 {
7778 get
7879 {
@@ -83,10 +84,28 @@ public static Dictionary<string, ulong> DirtyBits
8384 }
8485 }
8586
87+ /// <summary>
88+ /// Gets a mapping of types to dictionaries that associate bit values with their corresponding property names.
89+ /// </summary>
90+ /// <remarks>This property provides access to a dictionary where each key is a type, and each value is
91+ /// another dictionary mapping bitwise flag values to property names for that type. This can be used to look up the
92+ /// name of a property based on its bit representation for supported types. The property is initialized on first
93+ /// access and is thread-safe for reading.</remarks>
94+ public static IReadOnlyDictionary < Type , Dictionary < ulong , PropertyInfo > > PropertiesByBit
95+ {
96+ get
97+ {
98+ if ( ! hasInitialized )
99+ Init ( ) ;
100+
101+ return propertiesByBit ;
102+ }
103+ }
104+
86105 /// <summary>
87106 /// Gets Mirror-generated network writer extensions.
88107 /// </summary>
89- public static Dictionary < Type , MethodInfo > Writers
108+ public static IReadOnlyDictionary < Type , MethodInfo > Writers
90109 {
91110 get
92111 {
@@ -146,12 +165,12 @@ public static bool TryGetBehaviour<T>(uint identityId, out T behaviour) where T
146165 /// <param name="writer">The found delegate</param>
147166 /// <returns>true if the delegate was found</returns>
148167 /// <exception cref="ArgumentNullException"></exception>
149- public static bool TryGetWriter ( this Type type , out Func < object , object [ ] , object > writer )
168+ public static bool TryGetWriter ( this Type type , out MethodInfo writer )
150169 {
151170 if ( type is null )
152171 throw new ArgumentNullException ( nameof ( type ) ) ;
153172
154- return Writers . TryGetValue ( type , out writer ) ;
173+ return writers . TryGetValue ( type , out writer ) ;
155174 }
156175
157176 /// <summary>
@@ -312,6 +331,52 @@ public static bool TryGetDirtyBit(this Type behaviourType, string propertyName,
312331
313332 return DirtyBits . TryGetValue ( string . Concat ( behaviourType . Name , propertyName ) , out dirtyBit ) ;
314333 }
334+
335+ /// <summary>
336+ /// Attempts to retrieve the property name associated with the specified dirty bit for a given behavior type.
337+ /// </summary>
338+ /// <param name="behaviourType">The type of the behavior whose property name mapping is to be queried. Cannot be null.</param>
339+ /// <param name="dirtyBit">The dirty bit value for which to look up the corresponding property name.</param>
340+ /// <param name="property">When this method returns, contains the property associated with the specified dirty bit, if found;
341+ /// otherwise, an empty string. This parameter is passed uninitialized.</param>
342+ /// <returns>true if a property name corresponding to the specified dirty bit is found for the given behavior type;
343+ /// otherwise, false.</returns>
344+ /// <exception cref="ArgumentNullException">Thrown if behaviourType is null.</exception>
345+ public static bool TryGetPropertyName ( Type behaviourType , ulong dirtyBit , out PropertyInfo property )
346+ {
347+ if ( behaviourType is null )
348+ throw new ArgumentNullException ( nameof ( behaviourType ) ) ;
349+
350+ if ( ! propertiesByBit . TryGetValue ( behaviourType , out var dict ) )
351+ {
352+ property = null ! ;
353+ return false ;
354+ }
355+
356+ return dict . TryGetValue ( dirtyBit , out property ) ;
357+ }
358+
359+ /// <summary>
360+ /// Attempts to retrieve the property name associated with the specified dirty bit for the given network behaviour
361+ /// type.
362+ /// </summary>
363+ /// <remarks>Use this method to map a dirty bit to its corresponding property name for a specific
364+ /// NetworkBehaviour type, typically when handling network synchronization or debugging.</remarks>
365+ /// <typeparam name="T">The type of NetworkBehaviour for which to look up the property name.</typeparam>
366+ /// <param name="dirtyBit">The dirty bit value representing a property to look up.</param>
367+ /// <param name="property">When this method returns, contains the property associated with the specified dirty bit, if found;
368+ /// otherwise, an empty string. This parameter is passed uninitialized.</param>
369+ /// <returns>true if a property name was found for the specified dirty bit and type; otherwise, false.</returns>
370+ public static bool TryGetPropertyName < T > ( ulong dirtyBit , out PropertyInfo property ) where T : NetworkBehaviour
371+ {
372+ if ( ! propertiesByBit . TryGetValue ( typeof ( T ) , out var dict ) )
373+ {
374+ property = null ! ;
375+ return false ;
376+ }
377+
378+ return dict . TryGetValue ( dirtyBit , out property ) ;
379+ }
315380
316381 /// <summary>
317382 /// Sets a network property as dirty.
@@ -1129,8 +1194,6 @@ private static void InitIdentityServerSide(NetworkIdentity identity, NetworkConn
11291194 }
11301195 }
11311196
1132- // TODO: There's a weird bug that causes the Mono runtime to randomly crash at random points in this method.
1133- // Seems to be caused by the Mono runtimn as no exceptions are thrown (and it doesn't happen on Windows).
11341197 private static void Init ( )
11351198 {
11361199 try
@@ -1172,16 +1235,16 @@ private static void Init()
11721235 if ( serializedType is null )
11731236 continue ;
11741237
1175- if ( Writers . ContainsKey ( serializedType ) )
1238+ if ( writers . ContainsKey ( serializedType ) )
11761239 continue ;
11771240
1178- Writers . Add ( serializedType , method ) ;
1241+ writers . Add ( serializedType , method ) ;
11791242 }
11801243 else if ( method . HasAttribute < ClientRpcAttribute > ( ) || method . HasAttribute < TargetRpcAttribute > ( ) )
11811244 {
11821245 var name = $ "{ method . ReflectedType . Name } .{ method . Name } ";
11831246
1184- if ( RpcNames . ContainsKey ( name ) )
1247+ if ( rpcNames . ContainsKey ( name ) )
11851248 continue ;
11861249
11871250 var body = method . GetMethodBody ( ) ;
@@ -1194,14 +1257,14 @@ private static void Init()
11941257 if ( codes ? . Length < 1 )
11951258 continue ;
11961259
1197- var full = method . Module . ResolveString ( BitConverter . ToInt32 ( codes ,
1198- codes . IndexOf ( ( byte ) OpCodes . Ldstr . Value ) + 1 ) ) ;
1260+ var full = method . Module . ResolveString ( BitConverter . ToInt32 ( codes , codes . IndexOf ( ( byte ) OpCodes . Ldstr . Value ) + 1 ) ) ;
1261+
11991262 var hashIndex = codes . IndexOf ( ( byte ) OpCodes . Ldc_I4 . Value ) + 1 ;
12001263 var hash = codes [ hashIndex ] | ( codes [ hashIndex + 1 ] << 8 ) | ( codes [ hashIndex + 2 ] << 16 ) |
12011264 ( codes [ hashIndex + 3 ] << 24 ) ;
12021265
1203- RpcNames . Add ( name , full ) ;
1204- RpcHashes . Add ( name , hash ) ;
1266+ rpcNames . Add ( name , full ) ;
1267+ rpcHashes . Add ( name , hash ) ;
12051268 }
12061269 }
12071270
@@ -1214,9 +1277,12 @@ private static void Init()
12141277
12151278 var name = $ "{ prop . ReflectedType . Name } .{ prop . Name } ";
12161279
1217- if ( DirtyBits . ContainsKey ( name ) )
1280+ if ( dirtyBits . ContainsKey ( name ) )
12181281 continue ;
12191282
1283+ if ( ! propertiesByBit . TryGetValue ( type , out var typeDict ) )
1284+ propertiesByBit [ type ] = typeDict = new ( ) ;
1285+
12201286 var setter = prop . GetSetMethod ( true ) ;
12211287
12221288 if ( setter is null )
@@ -1234,7 +1300,8 @@ private static void Init()
12341300
12351301 var bit = il [ il . LastIndexOf ( ( byte ) OpCodes . Ldc_I8 . Value ) + 1 ] ;
12361302
1237- DirtyBits . Add ( name , bit ) ;
1303+ dirtyBits . Add ( name , bit ) ;
1304+ typeDict . Add ( bit , prop ) ;
12381305 }
12391306 }
12401307 catch ( Exception ex )
@@ -1267,7 +1334,7 @@ private static void Init()
12671334 if ( type is null )
12681335 continue ;
12691336
1270- Writers . Add ( type , method ) ;
1337+ writers . Add ( type , method ) ;
12711338 }
12721339 catch ( Exception ex )
12731340 {
@@ -1300,10 +1367,10 @@ private static void Init()
13001367 if ( type is null )
13011368 continue ;
13021369
1303- if ( Writers . ContainsKey ( type ) )
1370+ if ( writers . ContainsKey ( type ) )
13041371 continue ;
13051372
1306- Writers . Add ( type , method ) ;
1373+ writers . Add ( type , method ) ;
13071374 }
13081375 catch ( Exception ex )
13091376 {
0 commit comments