upm
Anton Franzluebbers 2022-01-30 17:09:21 -05:00
commit 997c2d5738
12 changed files with 826 additions and 385 deletions

48
Editor/EditorUtils.cs Normal file
View File

@ -0,0 +1,48 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace VelNet.Editor
{
public class EditorUtils : MonoBehaviour
{
[MenuItem("VelNet/Check For Duplicate NetworkIds", false, 10)]
private static void CheckDuplicateNetworkIds()
{
NetworkObject[] objs = FindObjectsOfType<NetworkObject>();
Dictionary<int, NetworkObject> ids = new Dictionary<int, NetworkObject>();
foreach (NetworkObject o in objs)
{
if (!o.isSceneObject) continue;
if (ids.ContainsKey(o.sceneNetworkId) || o.sceneNetworkId < 100)
{
if (ids.ContainsKey(o.sceneNetworkId))
{
Debug.Log($"Found duplicated id: {o.name} {ids[o.sceneNetworkId].name}", o);
}
else
{
Debug.Log($"Found duplicated id: {o.name} {o.sceneNetworkId}", o);
}
o.sceneNetworkId = 100;
while (ids.ContainsKey(o.sceneNetworkId))
{
o.sceneNetworkId += 1;
}
PrefabUtility.RecordPrefabInstancePropertyModifications(o);
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
}
ids.Add(o.sceneNetworkId, o);
}
}
}
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8f2f5f489d44f614c96bcf8f493c787d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
{
"name": "VelNet.Editor",
"rootNamespace": "VelNet.Editor",
"references": [
"GUID:1e55e2c4387020247a1ae212bbcbd381"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ae0703a992a8fe347978b1cd2dd2d7a9
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -62,7 +62,7 @@ namespace VelNet
} }
else else
{ {
owner.SendMessage(this, index.ToString(), message, reliable); VelNetPlayer.SendMessage(this, (byte)index, message, reliable);
} }
} }
@ -76,15 +76,15 @@ namespace VelNet
// send the message and an identifier for which component it belongs to // send the message and an identifier for which component it belongs to
int index = syncedComponents.IndexOf(component); int index = syncedComponents.IndexOf(component);
owner.SendGroupMessage(this, group, index.ToString(), message, reliable); VelNetPlayer.SendGroupMessage(this, group, (byte)index, message, reliable);
} }
public void ReceiveBytes(string identifier, byte[] message) public void ReceiveBytes(byte componentIdx, byte[] message)
{ {
// send the message to the right component // send the message to the right component
try try
{ {
syncedComponents[int.Parse(identifier)].ReceiveBytes(message); syncedComponents[componentIdx].ReceiveBytes(message);
} }
catch (Exception e) catch (Exception e)
{ {
@ -128,6 +128,7 @@ namespace VelNet
foreach (NetworkComponent c in comps) foreach (NetworkComponent c in comps)
{ {
c.networkObject = t; c.networkObject = t;
PrefabUtility.RecordPrefabInstancePropertyModifications(c);
} }
PrefabUtility.RecordPrefabInstancePropertyModifications(t); PrefabUtility.RecordPrefabInstancePropertyModifications(t);
} }

View File

@ -8,6 +8,8 @@ namespace VelNet
{ {
public static class BinaryWriterExtensions public static class BinaryWriterExtensions
{ {
#region Writers
public static void Write(this BinaryWriter writer, Vector3 v) public static void Write(this BinaryWriter writer, Vector3 v)
{ {
writer.Write(v.x); writer.Write(v.x);
@ -23,6 +25,18 @@ namespace VelNet
writer.Write(q.w); writer.Write(q.w);
} }
public static void Write(this BinaryWriter writer, Color c)
{
writer.Write(c.r);
writer.Write(c.g);
writer.Write(c.b);
writer.Write(c.a);
}
#endregion
#region Readers
public static Vector3 ReadVector3(this BinaryReader reader) public static Vector3 ReadVector3(this BinaryReader reader)
{ {
return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
@ -38,6 +52,35 @@ namespace VelNet
); );
} }
public static Color ReadColor(this BinaryReader reader)
{
return new Color(
reader.ReadSingle(),
reader.ReadSingle(),
reader.ReadSingle(),
reader.ReadSingle()
);
}
#endregion
public static bool BytesSame(byte[] b1, byte[] b2)
{
if (b1 == null && b2 != null) return false; // only one null
if (b1 != null && b2 == null) return false; // only one null
if (b1 == null) return true; // both null
// length doesn't match
if (b1.Length != b2.Length)
{
return false;
}
// check if any bytes are different
return !b1.Where((t, i) => t != b2[i]).Any();
}
/// <summary> /// <summary>
/// Compresses the list of bools into bytes using a bitmask /// Compresses the list of bools into bytes using a bitmask
/// </summary> /// </summary>
@ -88,6 +131,5 @@ namespace VelNet
{ {
return (b & (1 << index)) != 0; return (b & (1 << index)) != 0;
} }
} }
} }

View File

@ -1,6 +1,6 @@
using System.Collections; using System;
using System.Collections;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
namespace VelNet namespace VelNet
{ {
@ -9,6 +9,15 @@ namespace VelNet
[Tooltip("Send rate of this object. This caps out at the framerate of the game.")] [Tooltip("Send rate of this object. This caps out at the framerate of the game.")]
public float serializationRateHz = 30; public float serializationRateHz = 30;
/// <summary>
/// If the data hasn't changed, only sends updates across the network at 1Hz
/// </summary>
public bool hybridOnChangeCompression = true;
private byte[] lastSentBytes;
private double lastSendTime;
private const double slowSendInterval = 2;
protected virtual void Awake() protected virtual void Awake()
{ {
StartCoroutine(SendMessageUpdate()); StartCoroutine(SendMessageUpdate());
@ -18,9 +27,31 @@ namespace VelNet
{ {
while (true) while (true)
{ {
if (IsMine) try
{ {
SendBytes(SendState()); if (IsMine && enabled)
{
byte[] newBytes = SendState();
if (hybridOnChangeCompression)
{
if (Time.timeAsDouble - lastSendTime > slowSendInterval || !BinaryWriterExtensions.BytesSame(lastSentBytes, newBytes))
{
SendBytes(newBytes);
lastSendTime = Time.timeAsDouble;
}
}
else
{
SendBytes(newBytes);
lastSendTime = Time.timeAsDouble;
}
lastSentBytes = newBytes;
}
}
catch (Exception e)
{
Debug.LogError(e);
} }
yield return new WaitForSeconds(1f / serializationRateHz); yield return new WaitForSeconds(1f / serializationRateHz);

View File

@ -2,7 +2,6 @@
using System.Collections; using System.Collections;
using System.IO; using System.IO;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
namespace VelNet namespace VelNet
{ {
@ -11,6 +10,16 @@ namespace VelNet
[Tooltip("Send rate of this object. This caps out at the framerate of the game.")] [Tooltip("Send rate of this object. This caps out at the framerate of the game.")]
public float serializationRateHz = 30; public float serializationRateHz = 30;
/// <summary>
/// If the data hasn't changed, only sends updates across the network at 1Hz
/// </summary>
public bool hybridOnChangeCompression = true;
private byte[] lastSentBytes;
private double lastSendTime;
private const double slowSendInterval = 2;
protected virtual void Awake() protected virtual void Awake()
{ {
StartCoroutine(SendMessageUpdate()); StartCoroutine(SendMessageUpdate());
@ -22,12 +31,28 @@ namespace VelNet
{ {
try try
{ {
if (IsMine) if (IsMine && enabled)
{ {
using MemoryStream mem = new MemoryStream(); using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem); using BinaryWriter writer = new BinaryWriter(mem);
SendState(writer); SendState(writer);
SendBytes(mem.ToArray());
byte[] newBytes = mem.ToArray();
if (hybridOnChangeCompression)
{
if (Time.timeAsDouble - lastSendTime > slowSendInterval || !BinaryWriterExtensions.BytesSame(lastSentBytes, newBytes))
{
SendBytes(newBytes);
lastSendTime = Time.timeAsDouble;
}
}
else
{
SendBytes(newBytes);
lastSendTime = Time.timeAsDouble;
}
lastSentBytes = newBytes;
} }
} }
catch (Exception e) catch (Exception e)

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 03a4d4e1a7fd74c7ab2eccca4ce168db guid: 233344de094f11341bdb834d564708dc
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@ -1,5 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using System.IO;
using System.Text;
using UnityEngine;
namespace VelNet namespace VelNet
{ {
@ -39,7 +42,12 @@ namespace VelNet
{ {
if (kvp.Value.owner == this && kvp.Value.prefabName != "") if (kvp.Value.owner == this && kvp.Value.prefabName != "")
{ {
VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "7," + kvp.Value.networkId + "," + kvp.Value.prefabName); using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem);
writer.Write((byte)VelNetManager.MessageType.Instantiate);
writer.Write(kvp.Value.networkId);
writer.Write(kvp.Value.prefabName);
VelNetManager.SendToRoom(mem.ToArray(), false, true);
} }
} }
@ -53,77 +61,87 @@ namespace VelNet
/// <summary> /// <summary>
/// These are generally things that come from the "owner" and should be enacted locally, where appropriate /// These are generally things that come from the "owner" and should be enacted locally, where appropriate
///
/// Message encoding:
/// byte: message type
/// byte[]: message
///
/// The length of the byte[] for message is fixed according to the message type
/// </summary> /// </summary>
public void HandleMessage(VelNetManager.Message m) public void HandleMessage(VelNetManager.DataMessage m)
{ {
//we need to parse the message using MemoryStream mem = new MemoryStream(m.data);
using BinaryReader reader = new BinaryReader(mem);
//types of messages //individual message parameters separated by comma
string[] messages = m.text.Split(';'); //messages are split by ; VelNetManager.MessageType messageType = (VelNetManager.MessageType)reader.ReadByte();
foreach (string s in messages)
switch (messageType)
{ {
//individual message parameters separated by comma case VelNetManager.MessageType.ObjectSync: // sync update for an object I may own
string[] sections = s.Split(',');
switch (sections[0])
{ {
case "5": // sync update for an object I may own string objectKey = reader.ReadString();
byte componentIdx = reader.ReadByte();
int messageLength = reader.ReadInt32();
byte[] syncMessage = reader.ReadBytes(messageLength);
if (manager.objects.ContainsKey(objectKey))
{ {
string objectKey = sections[1]; if (manager.objects[objectKey].owner == this)
string identifier = sections[2];
string syncMessage = sections[3];
byte[] messageBytes = Convert.FromBase64String(syncMessage);
if (manager.objects.ContainsKey(objectKey))
{ {
if (manager.objects[objectKey].owner == this) manager.objects[objectKey].ReceiveBytes(componentIdx, syncMessage);
{
manager.objects[objectKey].ReceiveBytes(identifier, messageBytes);
}
} }
break;
} }
case "6": // I'm trying to take ownership of an object
{
string networkId = sections[1];
if (manager.objects.ContainsKey(networkId)) break;
{
manager.objects[networkId].owner = this;
}
break;
}
case "7": // I'm trying to instantiate an object
{
string networkId = sections[1];
string prefabName = sections[2];
if (manager.objects.ContainsKey(networkId))
{
break; //we already have this one, ignore
}
VelNetManager.SomebodyInstantiatedNetworkObject(networkId, prefabName, this);
break;
}
case "8": // I'm trying to destroy a gameobject I own
{
string networkId = sections[1];
VelNetManager.NetworkDestroy(networkId);
break;
}
case "9": //deleted scene objects
{
for (int k = 1; k < sections.Length; k++)
{
VelNetManager.NetworkDestroy(sections[k]);
}
break;
}
} }
case VelNetManager.MessageType.TakeOwnership: // I'm trying to take ownership of an object
{
string networkId = reader.ReadString();
if (manager.objects.ContainsKey(networkId))
{
manager.objects[networkId].owner = this;
}
break;
}
case VelNetManager.MessageType.Instantiate: // I'm trying to instantiate an object
{
string networkId = reader.ReadString();
string prefabName = reader.ReadString();
if (manager.objects.ContainsKey(networkId))
{
break; //we already have this one, ignore
}
VelNetManager.SomebodyInstantiatedNetworkObject(networkId, prefabName, this);
break;
}
case VelNetManager.MessageType.Destroy: // I'm trying to destroy a gameobject I own
{
string networkId = reader.ReadString();
VelNetManager.NetworkDestroy(networkId);
break;
}
case VelNetManager.MessageType.DeleteSceneObjects: //deleted scene objects
{
int len = reader.ReadInt32();
for (int k = 1; k < len; k++)
{
VelNetManager.NetworkDestroy(reader.ReadString());
}
break;
}
case VelNetManager.MessageType.Custom: // custom packets
{
int len = reader.ReadInt32();
VelNetManager.CustomMessageReceived?.Invoke(reader.ReadBytes(len));
break;
}
default:
throw new ArgumentOutOfRangeException();
} }
} }
@ -134,19 +152,42 @@ namespace VelNet
//FindObjectsOfType<NetworkObject>(); //FindObjectsOfType<NetworkObject>();
} }
public void SendGroupMessage(NetworkObject obj, string group, string identifier, byte[] data, bool reliable = true) public static void SendGroupMessage(NetworkObject obj, string group, byte componentIdx, byte[] data, bool reliable = true)
{ {
VelNetManager.SendToGroup(group, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem);
writer.Write((byte)VelNetManager.MessageType.ObjectSync);
writer.Write(obj.networkId);
writer.Write(componentIdx);
writer.Write(data.Length);
writer.Write(data);
VelNetManager.SendToGroup(group, mem.ToArray(), reliable);
} }
public void SendMessage(NetworkObject obj, string identifier, byte[] data, bool reliable = true) public static void SendMessage(NetworkObject obj, byte componentIdx, byte[] data, bool reliable = true)
{ {
VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem);
writer.Write((byte)VelNetManager.MessageType.ObjectSync);
writer.Write(obj.networkId);
writer.Write(componentIdx);
writer.Write(data.Length);
writer.Write(data);
VelNetManager.SendToRoom(mem.ToArray(), false, reliable);
} }
public void SendSceneUpdate() public void SendSceneUpdate()
{ {
VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects)); using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem);
writer.Write((byte)VelNetManager.MessageType.DeleteSceneObjects);
writer.Write(manager.deletedSceneObjects.Count);
foreach (string o in manager.deletedSceneObjects)
{
writer.Write(o);
}
VelNetManager.SendToRoom(mem.ToArray());
} }
[Obsolete("Use VelNetManager.NetworkDestroy() instead.")] [Obsolete("Use VelNetManager.NetworkDestroy() instead.")]
@ -156,7 +197,12 @@ namespace VelNet
if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return; if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return;
// send to all, which will make me delete as well // send to all, which will make me delete as well
VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "8," + networkId);
using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem);
writer.Write((byte)VelNetManager.MessageType.Destroy);
writer.Write(networkId);
VelNetManager.SendToRoom(mem.ToArray(), true, true);
} }
/// <returns>True if successful, False if failed to transfer ownership</returns> /// <returns>True if successful, False if failed to transfer ownership</returns>
@ -172,8 +218,13 @@ namespace VelNet
// immediately successful // immediately successful
manager.objects[networkId].owner = this; manager.objects[networkId].owner = this;
// must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome. // must be ordered, so that ownership transfers are not confused.
VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "6," + networkId); // Also sent to all players, so that multiple simultaneous requests will result in the same outcome.
using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem);
writer.Write((byte)VelNetManager.MessageType.TakeOwnership);
writer.Write(networkId);
VelNetManager.SendToRoom(mem.ToArray(), true, true, ordered: true);
return true; return true;
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "edu.uga.engr.vel.velnet", "name": "edu.uga.engr.vel.velnet",
"displayName": "VelNet", "displayName": "VelNet",
"version": "1.0.7", "version": "1.0.8",
"unity": "2019.1", "unity": "2019.1",
"description": "A custom networking library for Unity.", "description": "A custom networking library for Unity.",
"keywords": [ "keywords": [