[SyncVar] GameObjects are now 100% based on their underlying netId field. Fixes #149 (#292)

* fix

* Test file

* Syntax

* NetworkBehaviour.Set/GetSyncVarNetworkIdentity functions added

* add definitions to weaver

* Use them

* add NetworkIdentity cases everywhere else too

* ProcessInstructionSetter/GetterField replaces for OnDeserialize too, so that [SyncVar]GameObjects/NetworkIdentities work with custom On(De)Serialize as well

* Custom on(De)serialize test

* Weaver.ProcessSiteMethod doesn't ignore OnDeserialize anymore either

* [SyncVar] GameObject/NetworkIdentity hooks work again

* testscene

* public gameobject field test

* pass gameobject field to getsyncvargameobject etc.

* GetSyncVarGameObject/NetworkIdentity always returns the field if server, looks up netId otherwise; functions aren't static anymore.

* Use original OnSerialize generation again, so it simply calls writer.Write(go) on server, which sends the netId internally anyway.

* fixed hooks not using 'this.' because getter function isn't static anymore.

* add scene referencing test

* remove tests
This commit is contained in:
vis2k 2019-01-14 23:39:29 +01:00 committed by GitHub
parent 3c0ff33c94
commit 43baeff444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 231 additions and 103 deletions

View File

@ -276,39 +276,99 @@ internal bool InvokeHandlerDelegate(int cmdHash, UNetInvokeType invokeType, Netw
// ----------------------------- Helpers --------------------------------
// helper function for [SyncVar] GameObjects.
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gameObjectField, ulong dirtyBit, ref uint netIdField)
{
if (m_SyncVarGuard)
return;
uint newGameObjectNetId = 0;
uint newNetId = 0;
if (newGameObject != null)
{
NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
if (identity != null)
{
newGameObjectNetId = identity.netId;
if (newGameObjectNetId == 0)
newNetId = identity.netId;
if (newNetId == 0)
{
Debug.LogWarning("SetSyncVarGameObject GameObject " + newGameObject + " has a zero netId. Maybe it is not spawned yet?");
}
}
}
uint oldGameObjectNetId = 0;
if (gameObjectField != null)
// netId changed?
if (newNetId != netIdField)
{
oldGameObjectNetId = gameObjectField.GetComponent<NetworkIdentity>().netId;
if (LogFilter.Debug) { Debug.Log("SetSyncVar GameObject " + GetType().Name + " bit [" + dirtyBit + "] netfieldId:" + netIdField + "->" + newNetId); }
SetDirtyBit(dirtyBit);
gameObjectField = newGameObject; // assign new one on the server, and in case we ever need it on client too
netIdField = newNetId;
}
}
if (newGameObjectNetId != oldGameObjectNetId)
// helper function for [SyncVar] GameObjects.
// -> ref GameObject as second argument makes OnDeserialize processing easier
[EditorBrowsable(EditorBrowsableState.Never)]
protected GameObject GetSyncVarGameObject(uint netId, ref GameObject gameObjectField)
{
if (LogFilter.Debug) { Debug.Log("SetSyncVar GameObject " + GetType().Name + " bit [" + dirtyBit + "] netfieldId:" + oldGameObjectNetId + "->" + newGameObjectNetId); }
SetDirtyBit(dirtyBit);
gameObjectField = newGameObject;
netIdField = newGameObjectNetId;
// server always uses the field
if (isServer)
{
return gameObjectField;
}
// client always looks up based on netId because objects might get in and out of range
// over and over again, which shouldn't null them forever
NetworkIdentity identity;
if (NetworkIdentity.spawned.TryGetValue(netId, out identity) && identity != null)
return identity.gameObject;
return null;
}
// helper function for [SyncVar] NetworkIdentities.
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SetSyncVarNetworkIdentity(NetworkIdentity newIdentity, ref NetworkIdentity identityField, ulong dirtyBit, ref uint netIdField)
{
if (m_SyncVarGuard)
return;
uint newNetId = 0;
if (newIdentity != null)
{
newNetId = newIdentity.netId;
if (newNetId == 0)
{
Debug.LogWarning("SetSyncVarNetworkIdentity NetworkIdentity " + newIdentity + " has a zero netId. Maybe it is not spawned yet?");
}
}
// netId changed?
if (newNetId != netIdField)
{
if (LogFilter.Debug) { Debug.Log("SetSyncVarNetworkIdentity NetworkIdentity " + GetType().Name + " bit [" + dirtyBit + "] netIdField:" + netIdField + "->" + newNetId); }
SetDirtyBit(dirtyBit);
netIdField = newNetId;
identityField = newIdentity; // assign new one on the server, and in case we ever need it on client too
}
}
// helper function for [SyncVar] NetworkIdentities.
// -> ref GameObject as second argument makes OnDeserialize processing easier
[EditorBrowsable(EditorBrowsableState.Never)]
protected NetworkIdentity GetSyncVarNetworkIdentity(uint netId, ref NetworkIdentity identityField)
{
// server always uses the field
if (isServer)
{
return identityField;
}
// client always looks up based on netId because objects might get in and out of range
// over and over again, which shouldn't null them forever
NetworkIdentity identity;
NetworkIdentity.spawned.TryGetValue(netId, out identity);
return identity;
}
[EditorBrowsable(EditorBrowsableState.Never)]
@ -440,7 +500,6 @@ private void DeSerializeObjectsDelta(NetworkReader reader)
}
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual void PreStartClient() {}
public virtual void OnNetworkDestroy() {}
public virtual void OnStartServer() {}
public virtual void OnStartClient() {}

View File

@ -363,7 +363,6 @@ internal void OnStartClient()
{
try
{
comp.PreStartClient(); // generated startup to resolve object references
comp.OnStartClient(); // user implemented startup
}
catch (Exception e)

View File

@ -63,7 +63,6 @@ public void Process()
}
GenerateDeSerialization();
GeneratePreStartClient();
Weaver.DLog(m_td, "Process Done");
}
@ -447,66 +446,9 @@ public static int GetChannelId(CustomAttribute ca)
return 0;
}
void GeneratePreStartClient()
{
int netIdFieldCounter = 0;
MethodDefinition preStartMethod = null;
ILProcessor serWorker = null;
foreach (var m in m_td.Methods)
{
if (m.Name == "PreStartClient")
return;
}
foreach (FieldDefinition syncVar in m_SyncVars)
{
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
{
if (preStartMethod == null)
{
preStartMethod = new MethodDefinition("PreStartClient", MethodAttributes.Public |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
Weaver.voidType);
serWorker = preStartMethod.Body.GetILProcessor();
}
FieldDefinition netIdField = m_SyncVarNetIds[netIdFieldCounter];
netIdFieldCounter += 1;
// Generates: if (!_crateNetId.IsEmpty()) { crate = ClientScene.FindLocalObject(_crateNetId); }
Instruction nullLabel = serWorker.Create(OpCodes.Nop);
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, netIdField));
serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_0));
serWorker.Append(serWorker.Create(OpCodes.Ceq));
serWorker.Append(serWorker.Create(OpCodes.Brtrue, nullLabel));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldfld, netIdField));
serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.FindLocalObjectReference));
// return value of FindLocalObjectReference is on stack, assign it to the syncvar
serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
// Generates: end crateNetId != 0
serWorker.Append(nullLabel);
}
}
if (preStartMethod != null)
{
serWorker.Append(serWorker.Create(OpCodes.Ret));
m_td.Methods.Add(preStartMethod);
}
}
void GenerateDeSerialization()
{
Weaver.DLog(m_td, " GenerateDeSerialization");
int netIdFieldCounter = 0;
foreach (var m in m_td.Methods)
{
@ -544,15 +486,21 @@ void GenerateDeSerialization()
serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
int netIdFieldCounter = 0;
foreach (var syncVar in m_SyncVars)
{
// assign value
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName ||
syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
// GameObject SyncVar - assign to generated netId var
// GameObject/NetworkIdentity SyncVar:
// OnSerialize sends writer.Write(go);
// OnDeserialize reads to __netId manually so we can use
// lookups in the getter (so it still works if objects
// move in and out of range repeatedly)
FieldDefinition netIdField = m_SyncVarNetIds[netIdFieldCounter];
netIdFieldCounter += 1;
@ -592,6 +540,7 @@ void GenerateDeSerialization()
serWorker.Append(serWorker.Create(OpCodes.Stloc_0));
// conditionally read each syncvar
netIdFieldCounter = 0; // reset
int dirtyBit = Weaver.GetSyncVarStart(m_td.BaseType.FullName); // start at number of syncvars in parent
foreach (FieldDefinition syncVar in m_SyncVars)
{
@ -603,6 +552,50 @@ void GenerateDeSerialization()
serWorker.Append(serWorker.Create(OpCodes.And));
serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
// check for Hook function
MethodDefinition foundMethod;
if (!SyncVarProcessor.CheckForHookFunction(m_td, syncVar, out foundMethod))
{
return;
}
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName ||
syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
// GameObject/NetworkIdentity SyncVar:
// OnSerialize sends writer.Write(go);
// OnDeserialize reads to __netId manually so we can use
// lookups in the getter (so it still works if objects
// move in and out of range repeatedly)
FieldDefinition netIdField = m_SyncVarNetIds[netIdFieldCounter];
netIdFieldCounter += 1;
if (foundMethod == null)
{
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32));
serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField));
}
else
{
// call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
// because we send/receive the netID, not the GameObject/NetworkIdentity
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this.
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.NetworkReaderReadPacked32));
serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar));
if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference));
else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference));
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
}
}
else
{
MethodReference readFunc = Weaver.GetReadFunc(syncVar.FieldType);
if (readFunc == null)
{
@ -611,13 +604,6 @@ void GenerateDeSerialization()
return;
}
// check for Hook function
MethodDefinition foundMethod;
if (!SyncVarProcessor.CheckForHookFunction(m_td, syncVar, out foundMethod))
{
return;
}
if (foundMethod == null)
{
// just assign value
@ -634,6 +620,7 @@ void GenerateDeSerialization()
serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
}
}
serWorker.Append(varLabel);
dirtyBit += 1;
}

View File

@ -53,7 +53,7 @@ public static bool CheckForHookFunction(TypeDefinition td, FieldDefinition syncV
return true;
}
public static MethodDefinition ProcessSyncVarGet(FieldDefinition fd, string originalName)
public static MethodDefinition ProcessSyncVarGet(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
{
//Create the get method
MethodDefinition get = new MethodDefinition(
@ -64,9 +64,37 @@ public static MethodDefinition ProcessSyncVarGet(FieldDefinition fd, string orig
ILProcessor getWorker = get.Body.GetILProcessor();
// [SyncVar] GameObject?
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
{
// return this.GetSyncVarGameObject(ref field, uint netId);
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0)); // this.
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldfld, netFieldId));
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldflda, fd));
getWorker.Append(getWorker.Create(OpCodes.Call, Weaver.getSyncVarGameObjectReference));
getWorker.Append(getWorker.Create(OpCodes.Ret));
}
// [SyncVar] NetworkIdentity?
else if (fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0)); // this.
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldfld, netFieldId));
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldflda, fd));
getWorker.Append(getWorker.Create(OpCodes.Call, Weaver.getSyncVarNetworkIdentityReference));
getWorker.Append(getWorker.Create(OpCodes.Ret));
}
// [SyncVar] int, string, etc.
else
{
getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
getWorker.Append(getWorker.Create(OpCodes.Ldfld, fd));
getWorker.Append(getWorker.Create(OpCodes.Ret));
}
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
get.Body.InitLocals = true;
@ -137,6 +165,14 @@ public static MethodDefinition ProcessSyncVarSet(TypeDefinition td, FieldDefinit
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarGameObjectReference));
}
else if (fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
// reference to netId Field to set
setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
setWorker.Append(setWorker.Create(OpCodes.Ldflda, netFieldId));
setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarNetworkIdentityReference));
}
else
{
// make generic version of SetSyncVar with field type
@ -158,12 +194,12 @@ public static MethodDefinition ProcessSyncVarSet(TypeDefinition td, FieldDefinit
public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, List<FieldDefinition> syncVarNetIds, long dirtyBit)
{
string originalName = fd.Name;
Weaver.DLog(td, "Sync Var " + fd.Name + " " + fd.FieldType + " " + Weaver.gameObjectType);
// GameObject SyncVars have a new field for netId
// GameObject/NetworkIdentity SyncVars have a new field for netId
FieldDefinition netFieldId = null;
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName ||
fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
netFieldId = new FieldDefinition("___" + fd.Name + "NetId",
FieldAttributes.Private,
@ -173,7 +209,7 @@ public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, List<Fi
Weaver.lists.netIdFields.Add(netFieldId);
}
var get = ProcessSyncVarGet(fd, originalName);
var get = ProcessSyncVarGet(fd, originalName, netFieldId);
var set = ProcessSyncVarSet(td, fd, originalName, dirtyBit, netFieldId);
//NOTE: is property even needed? Could just use a setter function?
@ -188,6 +224,16 @@ public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, List<Fi
td.Methods.Add(set);
td.Properties.Add(propertyDefinition);
Weaver.lists.replacementSetterProperties[fd] = set;
// replace getter field if GameObject/NetworkIdentity so it uses
// netId instead
// -> only for GameObjects, otherwise an int syncvar's getter would
// end up in recursion.
if (fd.FieldType.FullName == Weaver.gameObjectType.FullName ||
fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
{
Weaver.lists.replacementGetterProperties[fd] = get;
}
}
public static void ProcessSyncVars(TypeDefinition td, List<FieldDefinition> syncVars, List<FieldDefinition> syncObjects, List<FieldDefinition> syncVarNetIds)

View File

@ -15,6 +15,8 @@ class WeaverLists
{
// setter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementSetterProperties = new Dictionary<FieldDefinition, MethodDefinition>();
// getter functions that replace [SyncVar] member variable references. dict<field, replacement>
public Dictionary<FieldDefinition, MethodDefinition> replacementGetterProperties = new Dictionary<FieldDefinition, MethodDefinition>();
// GameObject SyncVar generated netId fields
public List<FieldDefinition> netIdFields = new List<FieldDefinition>();
@ -146,6 +148,9 @@ class Weaver
public static MethodReference setSyncVarHookGuard;
public static MethodReference getSyncVarHookGuard;
public static MethodReference setSyncVarGameObjectReference;
public static MethodReference getSyncVarGameObjectReference;
public static MethodReference setSyncVarNetworkIdentityReference;
public static MethodReference getSyncVarNetworkIdentityReference;
public static MethodReference registerCommandDelegateReference;
public static MethodReference registerRpcDelegateReference;
public static MethodReference registerEventDelegateReference;
@ -742,10 +747,11 @@ static void ConfirmGeneratedCodeClass(ModuleDefinition moduleDef)
}
}
// replaces syncvar write access with the NetworkXYZ.get property calls
static void ProcessInstructionSetterField(TypeDefinition td, MethodDefinition md, Instruction i, FieldDefinition opField)
{
// dont replace property call sites in constructors or deserialize
if (md.Name == ".ctor" || md.Name == "OnDeserialize")
// dont replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
@ -760,6 +766,25 @@ static void ProcessInstructionSetterField(TypeDefinition td, MethodDefinition md
}
}
// replaces syncvar read access with the NetworkXYZ.get property calls
static void ProcessInstructionGetterField(TypeDefinition td, MethodDefinition md, Instruction i, FieldDefinition opField)
{
// dont replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
MethodDefinition replacement;
if (lists.replacementGetterProperties.TryGetValue(opField, out replacement))
{
//replace with property
//DLog(td, " replacing " + md.Name + ":" + i);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//DLog(td, " replaced " + md.Name + ":" + i);
}
}
static void ProcessInstruction(ModuleDefinition moduleDef, TypeDefinition td, MethodDefinition md, Instruction i, int iCount)
{
if (i.OpCode == OpCodes.Call || i.OpCode == OpCodes.Callvirt)
@ -780,6 +805,16 @@ static void ProcessInstruction(ModuleDefinition moduleDef, TypeDefinition td, Me
ProcessInstructionSetterField(td, md, i, opField);
}
}
if (i.OpCode == OpCodes.Ldfld)
{
// this instruction gets the value of a field. cache the field reference.
FieldDefinition opField = i.Operand as FieldDefinition;
if (opField != null)
{
ProcessInstructionGetterField(td, md, i, opField);
}
}
}
// this is required to early-out from a function with "ref" or "out" parameters
@ -885,7 +920,6 @@ static void ProcessSiteMethod(ModuleDefinition moduleDef, TypeDefinition td, Met
//Weaver.DLog(td, " ProcessSiteMethod " + md);
if (md.Name == ".cctor" ||
md.Name == "OnDeserialize" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith("CallCmd") ||
md.Name.StartsWith("InvokeCmd") ||
@ -1129,6 +1163,9 @@ static void SetupTargetTypes()
getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "get_syncVarHookGuard");
setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SetSyncVarGameObject");
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "GetSyncVarGameObject");
setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SetSyncVarNetworkIdentity");
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "GetSyncVarNetworkIdentity");
registerCommandDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "RegisterCommandDelegate");
registerRpcDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "RegisterRpcDelegate");
registerEventDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "RegisterEventDelegate");