Assuming a situation like this:
public class Base
{
public int test;
}
public class Derived : Base
{ }
If we try to register a sync field through the API:
MP.RegisterSyncField(typeof(Derived), nameof(Derived.test));
we'll end up registering the sync field for Base class, rather than Derived. Which means that if we try watching changes:
public class SomeClass
{
public Derived derived = new Derived();
public ISyncField derivedSync = MP.RegisterSyncField(typeof(Derived), nameof(Derived.test));
public void DoStuff()
{
// Start watching
MP.WatchBehin();
derivedSync.Watch();
// Change watched value
derived.test++;
// Stop watching
MP.WatchEnd();
}
}
this will try syncing the changes as Base, rather than Derived. This happens due to the way registering happens in MP.
First, RegisterSyncField calls Sync.RegisterSyncField:
|
public ISyncField RegisterSyncField(Type targetType, string memberPath) |
|
{ |
|
return Sync.RegisterSyncField(targetType, memberPath); |
|
} |
which ends up calling its overload taking FieldInfo:
|
public static ISyncField RegisterSyncField(Type targetType, string fieldName) |
|
{ |
|
return RegisterSyncField(AccessTools.Field(targetType, fieldName)); |
|
} |
and the overload is grabbing the type using FieldInfo.ReflectedType property:
|
public static ISyncField RegisterSyncField(FieldInfo field) |
|
{ |
|
string memberPath = field.ReflectedType + "/" + field.Name; |
|
SyncField sf; |
|
if (field.IsStatic) { |
|
sf = Field(null, null, memberPath); |
|
} else { |
|
sf = Field(field.ReflectedType, null, field.Name); |
|
} |
|
|
|
registeredSyncFields.Add(memberPath, sf); |
|
|
|
return sf; |
|
} |
The issue here is that, as far as I can tell from my testing, FieldInfo.ReflectedType will always return the type that this field is declared in, not the type that we used to get the FieldInfo from. Which means that, in the situations above, registering the sync field for Derived.test field will read FieldInfo.ReflectedType as Base, not Derived.
And, because of this, the targetType will be Base, this when syncing the field MP will attempt to sync the Derived class as Base class:
|
if (targetType != null) |
|
{ |
|
SyncSerialization.WriteSyncObject(writer, target, targetType); |
|
if (context.map != null) |
|
mapId = context.map.uniqueID; |
|
} |
Assuming a situation like this:
If we try to register a sync field through the API:
we'll end up registering the sync field for
Baseclass, rather thanDerived. Which means that if we try watching changes:this will try syncing the changes as
Base, rather thanDerived. This happens due to the way registering happens in MP.First,
RegisterSyncFieldcallsSync.RegisterSyncField:Multiplayer/Source/Client/MultiplayerAPIBridge.cs
Lines 84 to 87 in 1bfc18a
which ends up calling its overload taking
FieldInfo:Multiplayer/Source/Client/Syncing/Sync.cs
Lines 58 to 61 in 1bfc18a
and the overload is grabbing the type using
FieldInfo.ReflectedTypeproperty:Multiplayer/Source/Client/Syncing/Sync.cs
Lines 63 to 76 in 1bfc18a
The issue here is that, as far as I can tell from my testing,
FieldInfo.ReflectedTypewill always return the type that this field is declared in, not the type that we used to get the FieldInfo from. Which means that, in the situations above, registering the sync field forDerived.testfield will readFieldInfo.ReflectedTypeasBase, notDerived.And, because of this, the
targetTypewill beBase, this when syncing the field MP will attempt to sync theDerivedclass asBaseclass:Multiplayer/Source/Client/Syncing/Handler/SyncField.cs
Lines 47 to 52 in 1bfc18a