Merge branch 'master' into DocUpdate919

This commit is contained in:
Chris Langsenkamp 2019-08-26 22:59:08 -04:00
commit 49b3307477
6 changed files with 207 additions and 41 deletions

View File

@ -170,18 +170,25 @@ public override void OnInspectorGUI()
}
}
// only show SyncInterval if we have an OnSerialize function.
// No need to show it if the class only has Cmds/Rpcs and no sync.
// does it sync anything? then show extra properties
// (no need to show it if the class only has Cmds/Rpcs and no sync)
if (syncsAnything)
{
NetworkBehaviour networkBehaviour = target as NetworkBehaviour;
if (networkBehaviour != null)
{
// syncMode
serializedObject.FindProperty("syncMode").enumValueIndex = (int)(SyncMode)
EditorGUILayout.EnumPopup("Network Sync Mode", networkBehaviour.syncMode);
// syncInterval
// [0,2] should be enough. anything >2s is too laggy anyway.
serializedObject.FindProperty("syncInterval").floatValue = EditorGUILayout.Slider(
new GUIContent("Network Sync Interval",
"Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)"),
networkBehaviour.syncInterval, 0, 2);
// apply
serializedObject.ApplyModifiedProperties();
}
}

View File

@ -5,6 +5,11 @@
namespace Mirror
{
/// <summary>
/// Sync to everyone, or only to owner.
/// </summary>
public enum SyncMode { Observers, Owner }
/// <summary>
/// Base class which should be inherited by scripts which contain networking functionality.
/// </summary>
@ -19,6 +24,12 @@ public class NetworkBehaviour : MonoBehaviour
{
float lastSyncTime;
// hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
/// <summary>
/// sync mode for OnSerialize
/// </summary>
[HideInInspector] public SyncMode syncMode = SyncMode.Observers;
// hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
/// <summary>
/// sync interval for OnSerialize (in seconds)
@ -321,7 +332,7 @@ protected void SendEventInternal(Type invokeClass, string eventName, NetworkWrit
payload = writer.ToArraySegment() // segment to avoid reader allocations
};
NetworkServer.SendToReady(netIdentity,message, channelId);
NetworkServer.SendToReady(netIdentity, message, channelId);
}
/// <summary>

View File

@ -676,20 +676,38 @@ bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, bool initial
}
// serialize all components (or only dirty ones if not initial state)
// -> returns true if something was written
internal bool OnSerializeAllSafely(bool initialState, NetworkWriter writer)
// -> check ownerWritten/observersWritten to know if anything was written
internal void OnSerializeAllSafely(bool initialState, NetworkWriter ownerWriter, out int ownerWritten, NetworkWriter observersWriter, out int observersWritten)
{
// clear 'written' variables
ownerWritten = observersWritten = 0;
if (NetworkBehaviours.Length > 64)
{
Debug.LogError("Only 64 NetworkBehaviour components are allowed for NetworkIdentity: " + name + " because of the dirtyComponentMask");
return false;
return;
}
ulong dirtyComponentsMask = GetDirtyMask(initialState);
if (dirtyComponentsMask == 0L)
return false;
return;
writer.WritePackedUInt64(dirtyComponentsMask); // WritePacked64 so we don't write full 8 bytes if we don't have to
// calculate syncMode mask at runtime. this allows users to change
// component.syncMode while the game is running, which can be a huge
// advantage over syncvar-based sync modes. e.g. if a player decides
// to share or not share his inventory, or to go invisible, etc.
//
// (this also lets the TestSynchronizingObjects test pass because
// otherwise if we were to cache it in Awake, then we would call
// GetComponents<NetworkBehaviour> before all the test behaviours
// were added)
ulong syncModeObserversMask = GetSyncModeObserversMask();
// write regular dirty mask for owner,
// writer 'dirty mask & syncMode==Everyone' for everyone else
// (WritePacked64 so we don't write full 8 bytes if we don't have to)
ownerWriter.WritePackedUInt64(dirtyComponentsMask);
observersWriter.WritePackedUInt64(dirtyComponentsMask & syncModeObserversMask);
foreach (NetworkBehaviour comp in NetworkBehaviours)
{
@ -698,13 +716,32 @@ internal bool OnSerializeAllSafely(bool initialState, NetworkWriter writer)
// -> note: IsDirty() is false if the component isn't dirty or sendInterval isn't elapsed yet
if (initialState || comp.IsDirty())
{
// serialize the data
if (LogFilter.Debug) Debug.Log("OnSerializeAllSafely: " + name + " -> " + comp.GetType() + " initial=" + initialState);
OnSerializeSafely(comp, writer, initialState);
// serialize into ownerWriter first
// (owner always gets everything!)
int startPosition = ownerWriter.Position;
OnSerializeSafely(comp, ownerWriter, initialState);
++ownerWritten;
// copy into observersWriter too if SyncMode.Observers
// -> we copy instead of calling OnSerialize again because
// we don't know what magic the user does in OnSerialize.
// -> it's not guaranteed that calling it twice gets the
// same result
// -> it's not guaranteed that calling it twice doesn't mess
// with the user's OnSerialize timing code etc.
// => so we just copy the result without touching
// OnSerialize again
if (comp.syncMode == SyncMode.Observers)
{
ArraySegment<byte> segment = ownerWriter.ToArraySegment();
int length = ownerWriter.Position - startPosition;
observersWriter.WriteBytes(segment.Array, startPosition, length);
++observersWritten;
}
}
}
return true;
}
internal ulong GetDirtyMask(bool initialState)
@ -724,6 +761,24 @@ internal ulong GetDirtyMask(bool initialState)
return dirtyComponentsMask;
}
// a mask that contains all the components with SyncMode.Observers
internal ulong GetSyncModeObserversMask()
{
// loop through all components
ulong mask = 0UL;
NetworkBehaviour[] components = NetworkBehaviours;
for (int i = 0; i < NetworkBehaviours.Length; ++i)
{
NetworkBehaviour comp = components[i];
if (comp.syncMode == SyncMode.Observers)
{
mask |= 1UL << i;
}
}
return mask;
}
void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState)
{
// read header as 4 bytes and calculate this chunk's start+end
@ -1141,21 +1196,42 @@ internal void MirrorUpdate()
{
if (observers != null && observers.Count > 0)
{
NetworkWriter writer = NetworkWriterPool.GetWriter();
// one writer for owner, one for observers
NetworkWriter ownerWriter = NetworkWriterPool.GetWriter();
NetworkWriter observersWriter = NetworkWriterPool.GetWriter();
// serialize all the dirty components and send (if any were dirty)
if (OnSerializeAllSafely(false, writer))
OnSerializeAllSafely(false, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
if (ownerWritten > 0 || observersWritten > 0)
{
// populate cached UpdateVarsMessage and send
varsMessage.netId = netId;
// segment to avoid reader allocations.
// (never null because of our above check)
varsMessage.payload = writer.ToArraySegment();
NetworkServer.SendToReady(this, varsMessage);
// send ownerWriter to owner
// (only if we serialized anything for owner)
// (only if there is a connection (e.g. if not a monster),
// and if connection is ready because we use SendToReady
// below too)
if (ownerWritten > 0)
{
varsMessage.payload = ownerWriter.ToArraySegment();
if (connectionToClient != null && connectionToClient.isReady)
NetworkServer.SendToClientOfPlayer(this, varsMessage);
}
// send observersWriter to everyone but owner
// (only if we serialized anything for observers)
if (observersWritten > 0)
{
varsMessage.payload = observersWriter.ToArraySegment();
NetworkServer.SendToReady(this, varsMessage, false);
}
// only clear bits if we sent something
ClearDirtyBits();
}
NetworkWriterPool.Recycle(writer);
NetworkWriterPool.Recycle(ownerWriter);
NetworkWriterPool.Recycle(observersWriter);
}
else
{

View File

@ -333,9 +333,10 @@ public static bool SendToReady(NetworkIdentity identity, short msgType, MessageB
/// <typeparam name="T">Message type.</typeparam>
/// <param name="identity"></param>
/// <param name="msg">Message structure.</param>
/// <param name="includeSelf">Send to observers including self..</param>
/// <param name="channelId">Transport channel to use</param>
/// <returns></returns>
public static bool SendToReady<T>(NetworkIdentity identity,T msg, int channelId = Channels.DefaultReliable) where T : IMessageBase
public static bool SendToReady<T>(NetworkIdentity identity, T msg, bool includeSelf = true, int channelId = Channels.DefaultReliable) where T : IMessageBase
{
if (LogFilter.Debug) Debug.Log("Server.SendToReady msgType:" + typeof(T));
@ -347,7 +348,9 @@ public static bool SendToReady<T>(NetworkIdentity identity,T msg, int channelId
bool result = true;
foreach (KeyValuePair<int, NetworkConnection> kvp in identity.observers)
{
if (kvp.Value.isReady)
bool isSelf = kvp.Value == identity.connectionToClient;
if ((!isSelf || includeSelf) &&
kvp.Value.isReady)
{
result &= kvp.Value.SendBytes(bytes, channelId);
}
@ -357,6 +360,20 @@ public static bool SendToReady<T>(NetworkIdentity identity,T msg, int channelId
return false;
}
/// <summary>
/// Send a message structure with the given type number to only clients which are ready.
/// <para>See Networking.NetworkClient.Ready.</para>
/// </summary>
/// <typeparam name="T">Message type.</typeparam>
/// <param name="identity"></param>
/// <param name="msg">Message structure.</param>
/// <param name="channelId">Transport channel to use</param>
/// <returns></returns>
public static bool SendToReady<T>(NetworkIdentity identity, T msg, int channelId = Channels.DefaultReliable) where T : IMessageBase
{
return SendToReady(identity, msg, true, channelId);
}
/// <summary>
/// Disconnect all currently connected clients, including the local connection.
/// <para>This can only be called on the server. Clients will receive the Disconnect message.</para>
@ -1009,18 +1026,19 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio
if (LogFilter.Debug) Debug.Log("Server SendSpawnMessage: name=" + identity.name + " sceneId=" + identity.sceneId.ToString("X") + " netid=" + identity.netId); // for easier debugging
NetworkWriter writer = NetworkWriterPool.GetWriter();
// one writer for owner, one for observers
NetworkWriter ownerWriter = NetworkWriterPool.GetWriter();
NetworkWriter observersWriter = NetworkWriterPool.GetWriter();
// convert to ArraySegment to avoid reader allocations
// (need to handle null case too)
ArraySegment<byte> segment = default;
// serialize all components with initialState = true
// (can be null if has none)
if (identity.OnSerializeAllSafely(true, writer))
{
segment = writer.ToArraySegment();
}
identity.OnSerializeAllSafely(true, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
// convert to ArraySegment to avoid reader allocations
// (need to handle null case too)
ArraySegment<byte> ownerSegment = ownerWritten > 0 ? ownerWriter.ToArraySegment() : default;
ArraySegment<byte> observersSegment = observersWritten > 0 ? observersWriter.ToArraySegment() : default;
// 'identity' is a prefab that should be spawned
if (identity.sceneId == 0)
@ -1033,19 +1051,35 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio
// use local values for VR support
position = identity.transform.localPosition,
rotation = identity.transform.localRotation,
scale = identity.transform.localScale,
payload = segment
scale = identity.transform.localScale
};
// conn is != null when spawning it for a client
if (conn != null)
{
// use owner segment if 'conn' owns this identity, otherwise
// use observers segment
bool isOwner = identity.connectionToClient == conn;
msg.payload = isOwner ? ownerSegment : observersSegment;
conn.Send(msg);
}
// conn is == null when spawning it for the local player
else
{
SendToReady(identity, msg);
// send ownerWriter to owner
// (spawn no matter what, even if no components were
// serialized because the spawn message contains more data.
// components might still be updated later on.)
msg.payload = ownerSegment;
SendToClientOfPlayer(identity, msg);
// send observersWriter to everyone but owner
// (spawn no matter what, even if no components were
// serialized because the spawn message contains more data.
// components might still be updated later on.)
msg.payload = observersSegment;
SendToReady(identity, msg, false);
}
}
// 'identity' is a scene object that should be spawned again
@ -1059,23 +1093,40 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio
// use local values for VR support
position = identity.transform.localPosition,
rotation = identity.transform.localRotation,
scale = identity.transform.localScale,
payload = segment
scale = identity.transform.localScale
};
// conn is != null when spawning it for a client
if (conn != null)
{
// use owner segment if 'conn' owns this identity, otherwise
// use observers segment
bool isOwner = identity.connectionToClient == conn;
msg.payload = isOwner ? ownerSegment : observersSegment;
conn.Send(msg);
}
// conn is == null when spawning it for the local player
else
{
SendToReady(identity, msg);
// send ownerWriter to owner
// (spawn no matter what, even if no components were
// serialized because the spawn message contains more data.
// components might still be updated later on.)
msg.payload = ownerSegment;
SendToClientOfPlayer(identity, msg);
// send observersWriter to everyone but owner
// (spawn no matter what, even if no components were
// serialized because the spawn message contains more data.
// components might still be updated later on.)
msg.payload = observersSegment;
SendToReady(identity, msg, false);
}
}
NetworkWriterPool.Recycle(writer);
NetworkWriterPool.Recycle(ownerWriter);
NetworkWriterPool.Recycle(observersWriter);
}
/// <summary>

View File

@ -65,8 +65,9 @@ public void TestSynchronizingObjects()
player1.guild = myGuild;
// serialize all the data as we would for the network
NetworkWriter writer = new NetworkWriter();
identity1.OnSerializeAllSafely(true, writer);
NetworkWriter ownerWriter = new NetworkWriter();
NetworkWriter observersWriter = new NetworkWriter(); // not really used in this Test
identity1.OnSerializeAllSafely(true, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
// set up a "client" object
GameObject gameObject2 = new GameObject();
@ -74,11 +75,31 @@ public void TestSynchronizingObjects()
MockPlayer player2 = gameObject2.AddComponent<MockPlayer>();
// apply all the data from the server object
NetworkReader reader = new NetworkReader(writer.ToArray());
NetworkReader reader = new NetworkReader(ownerWriter.ToArray());
identity2.OnDeserializeAllSafely(reader, true);
// check that the syncvars got updated
Assert.That(player2.guild.name, Is.EqualTo("Back street boys"), "Data should be synchronized");
}
[Test]
public void TestSyncModeObserversMask()
{
GameObject gameObject1 = new GameObject();
NetworkIdentity identity = gameObject1.AddComponent<NetworkIdentity>();
MockPlayer player1 = gameObject1.AddComponent<MockPlayer>();
player1.syncInterval = 0;
MockPlayer player2 = gameObject1.AddComponent<MockPlayer>();
player2.syncInterval = 0;
MockPlayer player3 = gameObject1.AddComponent<MockPlayer>();
player3.syncInterval = 0;
// sync mode
player1.syncMode = SyncMode.Observers;
player2.syncMode = SyncMode.Owner;
player3.syncMode = SyncMode.Observers;
Assert.That(identity.GetSyncModeObserversMask(), Is.EqualTo(0b101));
}
}
}

View File

@ -24,9 +24,9 @@ What previously required **10.000** lines of code, now takes **1.000** lines of
_Note: Mirror is based on Unity's abandoned UNET Networking system. We fixed it up and pushed it to MMO Scale._
## Documentation
Check out our [Documentation](https://vis2k.github.io/Mirror/).
Check out our [Documentation](https://mirror-networking.com/xmldocs/).
If you are migrating from UNET, then please check out our [Migration Guide](https://vis2k.github.io/Mirror/General/Migration). Don't panic, it's very easy and won't take more than 5 minutes.
If you are migrating from UNET, then please check out our [Migration Guide](https://mirror-networking.com/xmldocs/articles/General/Migration.html). Don't panic, it's very easy and won't take more than 5 minutes.
## Installation
We **recommend** to download the most **stable Mirror version** from the [Asset Store](https://www.assetstore.unity3d.com/#!/content/129321)!