From 06d9495944431521ed411caf40791802a489c5ee Mon Sep 17 00:00:00 2001 From: Kyle Johnsen Date: Mon, 17 Jan 2022 18:18:23 -0500 Subject: [PATCH 01/13] added binary server tester --- Runtime/VelNetBinaryManager.cs | 775 ++++++++++++++++++++++++++++ Runtime/VelNetBinaryManager.cs.meta | 11 + 2 files changed, 786 insertions(+) create mode 100644 Runtime/VelNetBinaryManager.cs create mode 100644 Runtime/VelNetBinaryManager.cs.meta diff --git a/Runtime/VelNetBinaryManager.cs b/Runtime/VelNetBinaryManager.cs new file mode 100644 index 0000000..ec4c4b4 --- /dev/null +++ b/Runtime/VelNetBinaryManager.cs @@ -0,0 +1,775 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using UnityEngine; +using System.Net; +using UnityEngine.SceneManagement; +using System.Runtime.Serialization.Formatters.Binary; +using System.Runtime.Serialization; +using System.IO; + +namespace VelNet +{ + [AddComponentMenu("VelNet/VelNet Manager")] + public class VelNetBinaryManager : MonoBehaviour + { + public enum MessageType + { + OTHERS = 0, + ALL = 1, + OTHERS_ORDERED = 2, + ALL_ORDERED = 3 + }; + + public string host; + public int port; + + public static VelNetBinaryManager instance; + + private TcpClient socketConnection; + private Socket udpSocket; + public bool udpConnected; + private IPEndPoint RemoteEndPoint; + private Thread clientReceiveThread; + private Thread clientReceiveThreadUDP; + public int userid = -1; + public string room; + private int messagesReceived = 0; + + public readonly Dictionary players = new Dictionary(); + + /// + /// We just joined a room + /// string - the room name + /// + public static Action OnJoinedRoom; + + /// + /// We just left a room + /// string - the room name we left + /// + public static Action OnLeftRoom; + + /// + /// Somebody else just joined our room + /// + public static Action OnPlayerJoined; + + /// + /// Somebody else just left our room + /// + public static Action OnPlayerLeft; + + public static Action OnConnectedToServer; + public static Action MessageReceived; + public static Action LoggedIn; + public static Action RoomsReceived; + + public bool connected; + + public List prefabs = new List(); + public NetworkObject[] sceneObjects; + public List deletedSceneObjects = new List(); + + /// + /// Maintains a list of all known objects on the server (ones that have ids) + /// + public readonly Dictionary objects = new Dictionary(); + + /// + /// Maintains a list of all known groups on the server + /// + public readonly Dictionary> groups = new Dictionary>(); + + private VelNetPlayer masterPlayer; + public static VelNetPlayer LocalPlayer => instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault(); + public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; + + + // Use this for initialization + public class Message + { + public int type; + public string text; + public int sender; + } + + public readonly List receivedMessages = new List(); + + private void Awake() + { + if (instance != null) + { + Debug.LogError("Multiple NetworkManagers detected! Bad!", this); + } + + instance = this; + + SceneManager.sceneLoaded += (scene, mode) => + { + // add all local network objects + sceneObjects = FindObjectsOfType().Where(o => o.isSceneObject).ToArray(); + }; + } + + private IEnumerator Start() + { + ConnectToTcpServer(); + yield return null; + + try + { + OnConnectedToServer?.Invoke(); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } + + + private void AddMessage(Message m) + { + lock (receivedMessages) + { + //Debug.Log(messagesReceived++); + receivedMessages.Add(m); + } + } + + private void Update() + { + lock (receivedMessages) + { + ////the main thread, which can do Unity stuff + //foreach (Message m in receivedMessages) + //{ + // switch (m.type) + // { + // // when you join the server + // case 0: + // userid = m.sender; + // Debug.Log("joined server"); + + // try + // { + // LoggedIn?.Invoke(); + // } + // // prevent errors in subscribers from breaking our code + // catch (Exception e) + // { + // Debug.LogError(e); + // } + + // //start the udp thread + // clientReceiveThreadUDP = new Thread(ListenForDataUDP); + // clientReceiveThreadUDP.IsBackground = true; + // clientReceiveThreadUDP.Start(); + // break; + // // if this message is for me, that means I joined a new room... + // case 2 when userid == m.sender: + // { + // string oldRoom = LocalPlayer?.room; + + // // we clear the list, but will recreate as we get messages from people in our room + // players.Clear(); + // masterPlayer = null; + + // if (m.text != "") + // { + // VelNetPlayer player = new VelNetPlayer + // { + // isLocal = true, + // userid = m.sender, + // room = m.text + // }; + + // players.Add(userid, player); + // if (m.text != "") + // { + // try + // { + // OnJoinedRoom?.Invoke(m.text); + // } + // // prevent errors in subscribers from breaking our code + // catch (Exception e) + // { + // Debug.LogError(e); + // } + // } + // } + // // we just left a room + // else + // { + // // delete all networkobjects that aren't sceneobjects or are null now + // objects + // .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) + // .Select(o => o.Key) + // .ToList().ForEach(NetworkDestroy); + + // // then remove references to the ones that are left + // objects.Clear(); + + // // empty all the groups + // foreach (string group in instance.groups.Keys) + // { + // SetupMessageGroup(group, new List()); + // } + + // instance.groups.Clear(); + + // try + // { + // OnLeftRoom?.Invoke(oldRoom); + // } + // // prevent errors in subscribers from breaking our code + // catch (Exception e) + // { + // Debug.LogError(e); + // } + // } + + // break; + // } + // // not for me, a player is joining or leaving + // case 2: + // { + // VelNetPlayer me = players[userid]; + + // if (me.room != m.text) + // { + // // we got a left message, kill it + // // change ownership of all objects to master + // List deleteObjects = new List(); + // foreach (KeyValuePair kvp in objects) + // { + // if (kvp.Value.owner == players[m.sender]) // the owner is the player that left + // { + // // if this object has locked ownership, delete it + // if (kvp.Value.ownershipLocked) + // { + // deleteObjects.Add(kvp.Value.networkId); + // } + // // I'm the local master player, so can take ownership immediately + // else if (me.isLocal && me == masterPlayer) + // { + // TakeOwnership(kvp.Key); + // } + // // the master player left, so everyone should set the owner null (we should get a new master shortly) + // else if (players[m.sender] == masterPlayer) + // { + // kvp.Value.owner = null; + // } + // } + // } + + // // TODO this may check for ownership in the future. We don't need ownership here + // deleteObjects.ForEach(NetworkDestroy); + + // players.Remove(m.sender); + // } + // else + // { + // // we got a join message, create it + // VelNetPlayer player = new VelNetPlayer + // { + // isLocal = false, + // room = m.text, + // userid = m.sender + // }; + // players.Add(m.sender, player); + // try + // { + // OnPlayerJoined?.Invoke(player); + // } + // // prevent errors in subscribers from breaking our code + // catch (Exception e) + // { + // Debug.LogError(e); + // } + // } + + // break; + // } + // // generic message + // case 3: + // if (players.ContainsKey(m.sender)) + // { + // players[m.sender]?.HandleMessage(m); + // } + // else + // { + // Debug.LogError("Received message from player that doesn't exist: " + m.text); + // } + + // break; + // // change master player (this should only happen when the first player joins or if the master player leaves) + // case 4: + // { + // if (masterPlayer == null) + // { + // masterPlayer = players[m.sender]; + + // // no master player yet, add the scene objects + + // for (int i = 0; i < sceneObjects.Length; i++) + // { + // sceneObjects[i].networkId = -1 + "-" + i; + // sceneObjects[i].owner = masterPlayer; + // sceneObjects[i].isSceneObject = true; // needed for special handling when deleted + // objects.Add(sceneObjects[i].networkId, sceneObjects[i]); + // } + // } + // else + // { + // masterPlayer = players[m.sender]; + // } + + // masterPlayer.SetAsMasterPlayer(); + + // // master player should take over any objects that do not have an owner + // foreach (KeyValuePair kvp in objects) + // { + // kvp.Value.owner ??= masterPlayer; + // } + + // break; + // } + // } + + // MessageReceived?.Invoke(m); + //} + + //receivedMessages.Clear(); + } + } + + private void OnApplicationQuit() + { + socketConnection.Close(); + } + + /// + /// Setup socket connection. + /// + private void ConnectToTcpServer() + { + try + { + clientReceiveThread = new Thread(ListenForData); + clientReceiveThread.IsBackground = true; + clientReceiveThread.Start(); + } + catch (Exception e) + { + Debug.Log("On client connect exception " + e); + } + } + + private void HandleMessage(string s) // this parses messages from the server, and adds them to a queue to be processed on the main thread + { + // Debug.Log("Received: " + s); + Message m = new Message(); + string[] sections = s.Split(':'); + if (sections.Length <= 0) return; + + int type = int.Parse(sections[0]); + + switch (type) + { + case 0: // logged in message + { + if (sections.Length > 1) + { + m.type = type; + m.sender = int.Parse(sections[1]); + m.text = ""; + AddMessage(m); + } + + break; + } + case 1: // room info message + { + break; + } + case 2: // joined room message + { + if (sections.Length > 2) + { + m.type = 2; + int user_id = int.Parse(sections[1]); + m.sender = user_id; + string new_room = sections[2]; + m.text = new_room; + + AddMessage(m); + } + + break; + } + case 3: // text message + { + if (sections.Length > 2) + { + m.type = 3; + m.sender = int.Parse(sections[1]); + m.text = sections[2]; + AddMessage(m); + } + + break; + } + case 4: // change master client + { + if (sections.Length > 1) + { + m.type = 4; + m.sender = int.Parse(sections[1]); + AddMessage(m); + } + + break; + } + } + } + + /// + /// Runs in background clientReceiveThread; Listens for incomming data. + /// + private void ListenForData() + { + connected = true; + try + { + socketConnection = new TcpClient(host, port); + socketConnection.NoDelay = true; + byte[] bytes = new byte[1024]; + string partialMessage = ""; + Login("Kyle", "Johnsen"); + while (true) + { + + // Get a stream object for reading + using NetworkStream stream = socketConnection.GetStream(); + int length; + // Read incomming stream into byte arrary. + while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) + { + Debug.Log("read " + length + " bytes!"); + //byte[] incommingData = new byte[length]; + //Array.Copy(bytes, 0, incommingData, 0, length); + //// Convert byte array to string message. + //string serverMessage = Encoding.ASCII.GetString(incommingData); + //string[] sections = serverMessage.Split('\n'); + //if (sections.Length > 1) + //{ + // lock (receivedMessages) + // { + // for (int i = 0; i < sections.Length - 1; i++) + // { + // if (i == 0) + // { + // HandleMessage(partialMessage + sections[0]); + // partialMessage = ""; + // } + // else + // { + // HandleMessage(sections[i]); + // } + // } + // } + //} + + //partialMessage = partialMessage + sections[sections.Length - 1]; + } + } + } + + + catch (Exception socketException) + { + Debug.Log("Socket exception: " + socketException); + } + + connected = false; + } + + private void ListenForDataUDP() + { + //I don't yet have a UDP connection + try + { + IPAddress[] addresses = Dns.GetHostAddresses(host); + Debug.Assert(addresses.Length > 0); + RemoteEndPoint = new IPEndPoint(addresses[0], port); + + + udpSocket = new Socket(AddressFamily.InterNetwork, + SocketType.Dgram, ProtocolType.Udp); + + + udpConnected = false; + byte[] buffer = new byte[1024]; + while (true) + { + string welcome = userid + ":0:Hello"; + byte[] data = Encoding.ASCII.GetBytes(welcome); + udpSocket.SendTo(data, data.Length, SocketFlags.None, RemoteEndPoint); + + if (udpSocket.Available == 0) + { + Thread.Sleep(100); + Debug.Log("Waiting for UDP response"); + } + else + { + break; + } + } + + udpConnected = true; + while (true) + { + int numReceived = udpSocket.Receive(buffer); + + string message = Encoding.UTF8.GetString(buffer, 0, numReceived); + + string[] sections = message.Split(':'); + if (sections[0] == "0") + { + Debug.Log("UDP connected"); + } + + if (sections[0] == "3") + { + HandleMessage(message); + } + } + } + catch (Exception socketException) + { + Debug.Log("Socket exception: " + socketException); + } + } + + private static void SendUdpMessage(string message) + { + if (instance.udpSocket == null || !instance.udpConnected) + { + return; + } + + byte[] data = Encoding.UTF8.GetBytes(message); + //Debug.Log("Attempting to send: " + message); + instance.udpSocket.SendTo(data, data.Length, SocketFlags.None, instance.RemoteEndPoint); + } + + /// + /// Send message to server using socket connection. + /// + private static void SendNetworkMessage(byte[] message) + { + // Debug.Log("Sent: " + clientMessage); + if (instance.socketConnection == null) + { + return; + } + + try + { + // Get a stream object for writing. + NetworkStream stream = instance.socketConnection.GetStream(); + if (stream.CanWrite) + { + + stream.Write(message,0,message.Length); + } + } + catch (SocketException socketException) + { + Debug.Log("Socket exception: " + socketException); + } + } + + /// + /// Connects to the server with a username + /// + /// + public static byte[] get_be_bytes(int n) + { + return BitConverter.GetBytes(n).Reverse().ToArray(); + } + public static void Login(string username, string password) + { + + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + + byte[] uB = Encoding.UTF8.GetBytes(username); + byte[] uP = Encoding.UTF8.GetBytes(password); + writer.Write((byte)0); + writer.Write(get_be_bytes(uB.Length)); + writer.Write(uB); + writer.Write(get_be_bytes(uP.Length)); + writer.Write(uP); + + SendNetworkMessage(stream.ToArray()); + Join("MyRoom"); + } + + /// + /// Joins a room by name + /// + /// The name of the room to join + public static void Join(string roomname) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + + byte[] R = Encoding.UTF8.GetBytes(roomname); + writer.Write((byte)2); + writer.Write(get_be_bytes(R.Length)); + writer.Write(R); + SendNetworkMessage(stream.ToArray()); + } + + /// + /// Leaves a room if we're in one + /// + public static void Leave() + { + //if (InRoom) SendNetworkMessage("2:-1"); + } + + public static void SendTo(MessageType type, string message, bool reliable = true) + { + if (reliable) + { + //SendNetworkMessage("3:" + (int)type + ":" + message); + } + else + { + SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); + } + } + + public static void SendToGroup(string group, string message, bool reliable = true) + { + if (reliable) + { + //SendNetworkMessage("4:" + group + ":" + message); + } + else + { + SendUdpMessage(instance.userid + ":4:" + group + ":" + message); + } + } + + /// + /// changes the designated group that sendto(4) will go to + /// + public static void SetupMessageGroup(string groupName, List userIds) + { + if (userIds.Count > 0) + { + instance.groups[groupName] = userIds.ToList(); + } + + //SendNetworkMessage($"5:{groupName}:{string.Join(":", userIds)}"); + } + + + public static NetworkObject InstantiateNetworkObject(string prefabName) + { + VelNetPlayer localPlayer = LocalPlayer; + NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); + if (prefab == null) + { + Debug.LogError("Couldn't find a prefab with that name: " + prefabName); + return null; + } + + string networkId = localPlayer.userid + "-" + localPlayer.lastObjectId++; + if (instance.objects.ContainsKey(networkId)) + { + Debug.LogError("Can't instantiate object. Obj with that network ID was already instantiated.", instance.objects[networkId]); + return null; + } + NetworkObject newObject = Instantiate(prefab); + newObject.networkId = networkId; + newObject.prefabName = prefabName; + newObject.owner = localPlayer; + instance.objects.Add(newObject.networkId, newObject); + + // only sent to others, as I already instantiated this. Nice that it happens immediately. + SendTo(MessageType.OTHERS, "7," + newObject.networkId + "," + prefabName); + + return newObject; + } + + public static void SomebodyInstantiatedNetworkObject(string networkId, string prefabName, VelNetPlayer owner) + { + NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); + if (prefab == null) return; + NetworkObject newObject = Instantiate(prefab); + newObject.networkId = networkId; + newObject.prefabName = prefabName; + newObject.owner = owner; + instance.objects.Add(newObject.networkId, newObject); + } + + public static void NetworkDestroy(NetworkObject obj) + { + NetworkDestroy(obj.networkId); + } + + public static void NetworkDestroy(string networkId) + { + if (!instance.objects.ContainsKey(networkId)) return; + NetworkObject obj = instance.objects[networkId]; + if (obj == null) + { + instance.objects.Remove(networkId); + return; + } + if (obj.isSceneObject) + { + instance.deletedSceneObjects.Add(networkId); + } + + Destroy(obj.gameObject); + instance.objects.Remove(networkId); + } + + /// + /// Takes local ownership of an object by id. + /// + /// Network ID of the object to transfer + /// True if successfully transferred, False if transfer message not sent + public static bool TakeOwnership(string networkId) + { + // local player must exist + if (LocalPlayer == null) return false; + + // obj must exist + if (!instance.objects.ContainsKey(networkId)) return false; + + // if the ownership is locked, fail + if (instance.objects[networkId].ownershipLocked) return false; + + // immediately successful + instance.objects[networkId].owner = LocalPlayer; + + // 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. + SendTo(MessageType.ALL_ORDERED, "6," + networkId); + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/VelNetBinaryManager.cs.meta b/Runtime/VelNetBinaryManager.cs.meta new file mode 100644 index 0000000..014ff23 --- /dev/null +++ b/Runtime/VelNetBinaryManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 233344de094f11341bdb834d564708dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From b24ec489945d14a6d5456f35622f14ec638265fe Mon Sep 17 00:00:00 2001 From: Kyle Johnsen Date: Mon, 17 Jan 2022 22:32:06 -0500 Subject: [PATCH 02/13] make small string sends --- Runtime/VelNetBinaryManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Runtime/VelNetBinaryManager.cs b/Runtime/VelNetBinaryManager.cs index ec4c4b4..43f2de1 100644 --- a/Runtime/VelNetBinaryManager.cs +++ b/Runtime/VelNetBinaryManager.cs @@ -612,12 +612,12 @@ namespace VelNet BinaryWriter writer = new BinaryWriter(stream); byte[] uB = Encoding.UTF8.GetBytes(username); - byte[] uP = Encoding.UTF8.GetBytes(password); + byte[] pB = Encoding.UTF8.GetBytes(password); writer.Write((byte)0); - writer.Write(get_be_bytes(uB.Length)); + writer.Write((byte)uB.Length); writer.Write(uB); - writer.Write(get_be_bytes(uP.Length)); - writer.Write(uP); + writer.Write((byte)pB.Length); + writer.Write(pB); SendNetworkMessage(stream.ToArray()); Join("MyRoom"); @@ -634,7 +634,7 @@ namespace VelNet byte[] R = Encoding.UTF8.GetBytes(roomname); writer.Write((byte)2); - writer.Write(get_be_bytes(R.Length)); + writer.Write((byte)R.Length); writer.Write(R); SendNetworkMessage(stream.ToArray()); } From 2e2e82754306f2cbcf4112ac590fbfeb1f52dacc Mon Sep 17 00:00:00 2001 From: Kyle Johnsen Date: Tue, 18 Jan 2022 02:11:16 -0500 Subject: [PATCH 03/13] test binary server with sending tcp messages --- Runtime/VelNetBinaryManager.cs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Runtime/VelNetBinaryManager.cs b/Runtime/VelNetBinaryManager.cs index 43f2de1..c1c2e55 100644 --- a/Runtime/VelNetBinaryManager.cs +++ b/Runtime/VelNetBinaryManager.cs @@ -462,6 +462,12 @@ namespace VelNet while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) { Debug.Log("read " + length + " bytes!"); + string t = ""; + for(int i = 0; i < length; i++) + { + t = t + "," + bytes[i]; + } + Debug.Log(t); //byte[] incommingData = new byte[length]; //Array.Copy(bytes, 0, incommingData, 0, length); //// Convert byte array to string message. @@ -621,6 +627,7 @@ namespace VelNet SendNetworkMessage(stream.ToArray()); Join("MyRoom"); + } /// @@ -637,6 +644,8 @@ namespace VelNet writer.Write((byte)R.Length); writer.Write(R); SendNetworkMessage(stream.ToArray()); + + SendTo(MessageType.OTHERS, Encoding.UTF8.GetBytes("Hello")); } /// @@ -647,6 +656,27 @@ namespace VelNet //if (InRoom) SendNetworkMessage("2:-1"); } + public static void SendTo(MessageType type, byte[] message, bool reliable = true) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + + writer.Write((byte)3); + writer.Write(get_be_bytes(message.Length)); + writer.Write(message); + + if (reliable) + { + SendNetworkMessage(stream.ToArray()); + + //SendNetworkMessage("3:" + (int)type + ":" + message); + } + else + { + SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); + } + } + public static void SendTo(MessageType type, string message, bool reliable = true) { if (reliable) From d296045a50bbd105c90af28bbea0630ecbd24791 Mon Sep 17 00:00:00 2001 From: Kyle Johnsen Date: Tue, 18 Jan 2022 19:02:07 -0500 Subject: [PATCH 04/13] progress updating binary manager --- Runtime/VelNetBinaryManager.cs | 538 ++++++++++++++++++++------------- 1 file changed, 330 insertions(+), 208 deletions(-) diff --git a/Runtime/VelNetBinaryManager.cs b/Runtime/VelNetBinaryManager.cs index c1c2e55..4671de8 100644 --- a/Runtime/VelNetBinaryManager.cs +++ b/Runtime/VelNetBinaryManager.cs @@ -17,12 +17,22 @@ namespace VelNet [AddComponentMenu("VelNet/VelNet Manager")] public class VelNetBinaryManager : MonoBehaviour { - public enum MessageType + public enum MessageSendType { - OTHERS = 0, - ALL = 1, - OTHERS_ORDERED = 2, - ALL_ORDERED = 3 + LOGIN = 0, + GET_ROOMS = 1, + JOIN_ROOM = 2, + MESSAGE_OTHERS = 3, + MESSAGE_ALL = 4, + MESSAGE_GROUP = 5, + FORM_GROUP = 6 + }; + public enum MessageReceiveType + { + LOGGED_IN = 0, + ROOM_LIST = 1, + CLIENT_JOINED = 2, + MESSAGE = 3 }; public string host; @@ -93,11 +103,14 @@ namespace VelNet // Use this for initialization public class Message { - public int type; - public string text; + public MessageReceiveType type; + //public string text; + public byte[] data; public int sender; } + + public readonly List receivedMessages = new List(); private void Awake() @@ -146,206 +159,208 @@ namespace VelNet { lock (receivedMessages) { - ////the main thread, which can do Unity stuff - //foreach (Message m in receivedMessages) - //{ - // switch (m.type) - // { - // // when you join the server - // case 0: - // userid = m.sender; - // Debug.Log("joined server"); + //the main thread, which can do Unity stuff + foreach (Message m in receivedMessages) + { + switch (m.type) + { + // when you join the server + case 0: + userid = m.sender; + Debug.Log("joined server " + userid); - // try - // { - // LoggedIn?.Invoke(); - // } - // // prevent errors in subscribers from breaking our code - // catch (Exception e) - // { - // Debug.LogError(e); - // } + try + { + LoggedIn?.Invoke(); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } - // //start the udp thread - // clientReceiveThreadUDP = new Thread(ListenForDataUDP); - // clientReceiveThreadUDP.IsBackground = true; - // clientReceiveThreadUDP.Start(); - // break; - // // if this message is for me, that means I joined a new room... - // case 2 when userid == m.sender: - // { - // string oldRoom = LocalPlayer?.room; + //start the udp thread + clientReceiveThreadUDP = new Thread(ListenForDataUDP); + clientReceiveThreadUDP.IsBackground = true; + clientReceiveThreadUDP.Start(); + break; + /* + // if this message is for me, that means I joined a new room... + case 2 when userid == m.sender: + { + string oldRoom = LocalPlayer?.room; - // // we clear the list, but will recreate as we get messages from people in our room - // players.Clear(); - // masterPlayer = null; + // we clear the list, but will recreate as we get messages from people in our room + players.Clear(); + masterPlayer = null; - // if (m.text != "") - // { - // VelNetPlayer player = new VelNetPlayer - // { - // isLocal = true, - // userid = m.sender, - // room = m.text - // }; + if (m.text != "") + { + VelNetPlayer player = new VelNetPlayer + { + isLocal = true, + userid = m.sender, + room = m.text + }; - // players.Add(userid, player); - // if (m.text != "") - // { - // try - // { - // OnJoinedRoom?.Invoke(m.text); - // } - // // prevent errors in subscribers from breaking our code - // catch (Exception e) - // { - // Debug.LogError(e); - // } - // } - // } - // // we just left a room - // else - // { - // // delete all networkobjects that aren't sceneobjects or are null now - // objects - // .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) - // .Select(o => o.Key) - // .ToList().ForEach(NetworkDestroy); + players.Add(userid, player); + if (m.text != "") + { + try + { + OnJoinedRoom?.Invoke(m.text); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } + } + // we just left a room + else + { + // delete all networkobjects that aren't sceneobjects or are null now + objects + .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) + .Select(o => o.Key) + .ToList().ForEach(NetworkDestroy); - // // then remove references to the ones that are left - // objects.Clear(); + // then remove references to the ones that are left + objects.Clear(); - // // empty all the groups - // foreach (string group in instance.groups.Keys) - // { - // SetupMessageGroup(group, new List()); - // } + // empty all the groups + foreach (string group in instance.groups.Keys) + { + SetupMessageGroup(group, new List()); + } - // instance.groups.Clear(); + instance.groups.Clear(); - // try - // { - // OnLeftRoom?.Invoke(oldRoom); - // } - // // prevent errors in subscribers from breaking our code - // catch (Exception e) - // { - // Debug.LogError(e); - // } - // } + try + { + OnLeftRoom?.Invoke(oldRoom); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } - // break; - // } - // // not for me, a player is joining or leaving - // case 2: - // { - // VelNetPlayer me = players[userid]; + break; + } + // not for me, a player is joining or leaving + case 2: + { + VelNetPlayer me = players[userid]; - // if (me.room != m.text) - // { - // // we got a left message, kill it - // // change ownership of all objects to master - // List deleteObjects = new List(); - // foreach (KeyValuePair kvp in objects) - // { - // if (kvp.Value.owner == players[m.sender]) // the owner is the player that left - // { - // // if this object has locked ownership, delete it - // if (kvp.Value.ownershipLocked) - // { - // deleteObjects.Add(kvp.Value.networkId); - // } - // // I'm the local master player, so can take ownership immediately - // else if (me.isLocal && me == masterPlayer) - // { - // TakeOwnership(kvp.Key); - // } - // // the master player left, so everyone should set the owner null (we should get a new master shortly) - // else if (players[m.sender] == masterPlayer) - // { - // kvp.Value.owner = null; - // } - // } - // } + if (me.room != m.text) + { + // we got a left message, kill it + // change ownership of all objects to master + List deleteObjects = new List(); + foreach (KeyValuePair kvp in objects) + { + if (kvp.Value.owner == players[m.sender]) // the owner is the player that left + { + // if this object has locked ownership, delete it + if (kvp.Value.ownershipLocked) + { + deleteObjects.Add(kvp.Value.networkId); + } + // I'm the local master player, so can take ownership immediately + else if (me.isLocal && me == masterPlayer) + { + TakeOwnership(kvp.Key); + } + // the master player left, so everyone should set the owner null (we should get a new master shortly) + else if (players[m.sender] == masterPlayer) + { + kvp.Value.owner = null; + } + } + } - // // TODO this may check for ownership in the future. We don't need ownership here - // deleteObjects.ForEach(NetworkDestroy); + // TODO this may check for ownership in the future. We don't need ownership here + deleteObjects.ForEach(NetworkDestroy); - // players.Remove(m.sender); - // } - // else - // { - // // we got a join message, create it - // VelNetPlayer player = new VelNetPlayer - // { - // isLocal = false, - // room = m.text, - // userid = m.sender - // }; - // players.Add(m.sender, player); - // try - // { - // OnPlayerJoined?.Invoke(player); - // } - // // prevent errors in subscribers from breaking our code - // catch (Exception e) - // { - // Debug.LogError(e); - // } - // } + players.Remove(m.sender); + } + else + { + // we got a join message, create it + VelNetPlayer player = new VelNetPlayer + { + isLocal = false, + room = m.text, + userid = m.sender + }; + players.Add(m.sender, player); + try + { + OnPlayerJoined?.Invoke(player); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } - // break; - // } - // // generic message - // case 3: - // if (players.ContainsKey(m.sender)) - // { - // players[m.sender]?.HandleMessage(m); - // } - // else - // { - // Debug.LogError("Received message from player that doesn't exist: " + m.text); - // } + break; + } + // generic message + case 3: + if (players.ContainsKey(m.sender)) + { + players[m.sender]?.HandleMessage(m); + } + else + { + Debug.LogError("Received message from player that doesn't exist: " + m.text); + } - // break; - // // change master player (this should only happen when the first player joins or if the master player leaves) - // case 4: - // { - // if (masterPlayer == null) - // { - // masterPlayer = players[m.sender]; + break; + // change master player (this should only happen when the first player joins or if the master player leaves) + case 4: + { + if (masterPlayer == null) + { + masterPlayer = players[m.sender]; - // // no master player yet, add the scene objects + // no master player yet, add the scene objects - // for (int i = 0; i < sceneObjects.Length; i++) - // { - // sceneObjects[i].networkId = -1 + "-" + i; - // sceneObjects[i].owner = masterPlayer; - // sceneObjects[i].isSceneObject = true; // needed for special handling when deleted - // objects.Add(sceneObjects[i].networkId, sceneObjects[i]); - // } - // } - // else - // { - // masterPlayer = players[m.sender]; - // } + for (int i = 0; i < sceneObjects.Length; i++) + { + sceneObjects[i].networkId = -1 + "-" + i; + sceneObjects[i].owner = masterPlayer; + sceneObjects[i].isSceneObject = true; // needed for special handling when deleted + objects.Add(sceneObjects[i].networkId, sceneObjects[i]); + } + } + else + { + masterPlayer = players[m.sender]; + } - // masterPlayer.SetAsMasterPlayer(); + masterPlayer.SetAsMasterPlayer(); - // // master player should take over any objects that do not have an owner - // foreach (KeyValuePair kvp in objects) - // { - // kvp.Value.owner ??= masterPlayer; - // } + // master player should take over any objects that do not have an owner + foreach (KeyValuePair kvp in objects) + { + kvp.Value.owner ??= masterPlayer; + } - // break; - // } - // } + break; + } + */ + } - // MessageReceived?.Invoke(m); - //} + MessageReceived?.Invoke(m); + } - //receivedMessages.Clear(); + receivedMessages.Clear(); } } @@ -373,6 +388,7 @@ namespace VelNet private void HandleMessage(string s) // this parses messages from the server, and adds them to a queue to be processed on the main thread { + /* // Debug.Log("Received: " + s); Message m = new Message(); string[] sections = s.Split(':'); @@ -436,12 +452,38 @@ namespace VelNet break; } - } + }*/ } /// /// Runs in background clientReceiveThread; Listens for incomming data. /// + /// + private byte[] ReadExact(NetworkStream stream, int N) + { + byte[] toReturn = new byte[N]; + + int numRead = 0; + int numLeft = N; + while (numLeft > 0) + { + numRead += stream.Read(toReturn, numRead, numLeft); + numLeft = N - numRead; + } + return toReturn; + } + + private uint GetUintFromBytes(byte[] bytes) + { + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToUInt32(bytes.Reverse().ToArray(),0); + } + else + { + return BitConverter.ToUInt32(bytes, 0); + } + } private void ListenForData() { connected = true; @@ -450,14 +492,44 @@ namespace VelNet socketConnection = new TcpClient(host, port); socketConnection.NoDelay = true; byte[] bytes = new byte[1024]; - string partialMessage = ""; + Login("Kyle", "Johnsen"); + //Join("MyRoom"); + //SendTo(MessageSendType.MESSAGE_OTHERS, Encoding.UTF8.GetBytes("Hello")); + //FormGroup("close", new List { 1 }); + //SendToGroup("close", Encoding.UTF8.GetBytes("HelloGroup")); while (true) { // Get a stream object for reading using NetworkStream stream = socketConnection.GetStream(); int length; + + //read a byte + byte type = (byte)stream.ReadByte(); + + if(type == 0) + { + //read the id (this is my network id) + + Message m = new Message(); + m.sender = (int)GetUintFromBytes(ReadExact(stream, 4)); + m.data = new byte[0]; //no data + m.type = 0; + AddMessage(m); + + } + else if(type == 1) + { + //rooms message + }else if(type == 2) + { + //join message + }else if(type == 3) + { + //message from client over tcp + } + // Read incomming stream into byte arrary. while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) { @@ -524,9 +596,9 @@ namespace VelNet byte[] buffer = new byte[1024]; while (true) { - string welcome = userid + ":0:Hello"; - byte[] data = Encoding.ASCII.GetBytes(welcome); - udpSocket.SendTo(data, data.Length, SocketFlags.None, RemoteEndPoint); + buffer[0] = 0; + Array.Copy(get_be_bytes((uint)userid), 0, buffer, 1, 4); + udpSocket.SendTo(buffer, 5, SocketFlags.None, RemoteEndPoint); if (udpSocket.Available == 0) { @@ -544,18 +616,27 @@ namespace VelNet { int numReceived = udpSocket.Receive(buffer); - string message = Encoding.UTF8.GetString(buffer, 0, numReceived); - - string[] sections = message.Split(':'); - if (sections[0] == "0") + + if (buffer[0] == 0) { Debug.Log("UDP connected"); + }else if (buffer[0] == 3) + { + //we should get the sender address + byte[] senderBytes = new byte[4]; + Array.Copy(buffer, 1, senderBytes, 0, 4); + uint senderId = GetUintFromBytes(senderBytes); + byte[] messageBytes = new byte[numReceived - 5]; + Array.Copy(buffer, 5, messageBytes, 0, messageBytes.Length); + Message m = new Message(); + m.sender = (int)senderId; + m.data = messageBytes; + m.type = MessageReceiveType.MESSAGE; } - if (sections[0] == "3") - { - HandleMessage(message); - } + + + } } catch (Exception socketException) @@ -607,7 +688,7 @@ namespace VelNet /// Connects to the server with a username /// /// - public static byte[] get_be_bytes(int n) + public static byte[] get_be_bytes(uint n) { return BitConverter.GetBytes(n).Reverse().ToArray(); } @@ -619,15 +700,15 @@ namespace VelNet byte[] uB = Encoding.UTF8.GetBytes(username); byte[] pB = Encoding.UTF8.GetBytes(password); - writer.Write((byte)0); + writer.Write((byte)MessageSendType.LOGIN); writer.Write((byte)uB.Length); writer.Write(uB); writer.Write((byte)pB.Length); writer.Write(pB); SendNetworkMessage(stream.ToArray()); - Join("MyRoom"); + } /// @@ -640,12 +721,30 @@ namespace VelNet BinaryWriter writer = new BinaryWriter(stream); byte[] R = Encoding.UTF8.GetBytes(roomname); - writer.Write((byte)2); + writer.Write((byte)MessageSendType.JOIN_ROOM); writer.Write((byte)R.Length); writer.Write(R); SendNetworkMessage(stream.ToArray()); - SendTo(MessageType.OTHERS, Encoding.UTF8.GetBytes("Hello")); + + + + } + + public static void FormGroup(string groupname, List client_ids) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + byte[] R = Encoding.UTF8.GetBytes(groupname); + writer.Write((byte)MessageSendType.FORM_GROUP); + writer.Write((byte)R.Length); + writer.Write(R); + writer.Write(get_be_bytes((uint)client_ids.Count*4)); + for(int i = 0; i < client_ids.Count; i++) + { + writer.Write(get_be_bytes(client_ids[i])); + } + SendNetworkMessage(stream.ToArray()); } /// @@ -656,13 +755,13 @@ namespace VelNet //if (InRoom) SendNetworkMessage("2:-1"); } - public static void SendTo(MessageType type, byte[] message, bool reliable = true) + public static void SendTo(MessageSendType type, byte[] message, bool reliable = true) { MemoryStream stream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(stream); writer.Write((byte)3); - writer.Write(get_be_bytes(message.Length)); + writer.Write(get_be_bytes((uint)message.Length)); writer.Write(message); if (reliable) @@ -677,7 +776,7 @@ namespace VelNet } } - public static void SendTo(MessageType type, string message, bool reliable = true) + public static void SendTo(MessageSendType type, string message, bool reliable = true) { if (reliable) { @@ -689,6 +788,29 @@ namespace VelNet } } + public static void SendToGroup(string group, byte[] message, bool reliable = true) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + + writer.Write((byte)MessageSendType.MESSAGE_GROUP); + writer.Write(get_be_bytes((uint)message.Length)); + writer.Write(message); + writer.Write((byte)group.Length); + writer.Write(Encoding.UTF8.GetBytes(group)); + + if (reliable) + { + SendNetworkMessage(stream.ToArray()); + + //SendNetworkMessage("3:" + (int)type + ":" + message); + } + else + { + //SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); + } + } + public static void SendToGroup(string group, string message, bool reliable = true) { if (reliable) @@ -738,7 +860,7 @@ namespace VelNet instance.objects.Add(newObject.networkId, newObject); // only sent to others, as I already instantiated this. Nice that it happens immediately. - SendTo(MessageType.OTHERS, "7," + newObject.networkId + "," + prefabName); + SendTo(MessageSendType.MESSAGE_OTHERS, "7," + newObject.networkId + "," + prefabName); return newObject; } @@ -797,7 +919,7 @@ namespace VelNet instance.objects[networkId].owner = LocalPlayer; // 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. - SendTo(MessageType.ALL_ORDERED, "6," + networkId); + SendTo(MessageSendType.MESSAGE_ALL, "6," + networkId); return true; } From a0f3d9925923a78f3bbcecf0962163fae2a2b077 Mon Sep 17 00:00:00 2001 From: Kyle Johnsen Date: Wed, 19 Jan 2022 00:30:37 -0500 Subject: [PATCH 05/13] Mostly working, except for dissonance speech --- Runtime/VelNetBinaryManager.cs | 927 ---------------------------- Runtime/VelNetBinaryManager.cs.meta | 11 - Runtime/VelNetManager.cs | 752 ++++++++++++---------- Runtime/VelNetManager.cs.meta | 2 +- Runtime/VelNetPlayer.cs | 24 +- 5 files changed, 437 insertions(+), 1279 deletions(-) delete mode 100644 Runtime/VelNetBinaryManager.cs delete mode 100644 Runtime/VelNetBinaryManager.cs.meta diff --git a/Runtime/VelNetBinaryManager.cs b/Runtime/VelNetBinaryManager.cs deleted file mode 100644 index 4671de8..0000000 --- a/Runtime/VelNetBinaryManager.cs +++ /dev/null @@ -1,927 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using UnityEngine; -using System.Net; -using UnityEngine.SceneManagement; -using System.Runtime.Serialization.Formatters.Binary; -using System.Runtime.Serialization; -using System.IO; - -namespace VelNet -{ - [AddComponentMenu("VelNet/VelNet Manager")] - public class VelNetBinaryManager : MonoBehaviour - { - public enum MessageSendType - { - LOGIN = 0, - GET_ROOMS = 1, - JOIN_ROOM = 2, - MESSAGE_OTHERS = 3, - MESSAGE_ALL = 4, - MESSAGE_GROUP = 5, - FORM_GROUP = 6 - }; - public enum MessageReceiveType - { - LOGGED_IN = 0, - ROOM_LIST = 1, - CLIENT_JOINED = 2, - MESSAGE = 3 - }; - - public string host; - public int port; - - public static VelNetBinaryManager instance; - - private TcpClient socketConnection; - private Socket udpSocket; - public bool udpConnected; - private IPEndPoint RemoteEndPoint; - private Thread clientReceiveThread; - private Thread clientReceiveThreadUDP; - public int userid = -1; - public string room; - private int messagesReceived = 0; - - public readonly Dictionary players = new Dictionary(); - - /// - /// We just joined a room - /// string - the room name - /// - public static Action OnJoinedRoom; - - /// - /// We just left a room - /// string - the room name we left - /// - public static Action OnLeftRoom; - - /// - /// Somebody else just joined our room - /// - public static Action OnPlayerJoined; - - /// - /// Somebody else just left our room - /// - public static Action OnPlayerLeft; - - public static Action OnConnectedToServer; - public static Action MessageReceived; - public static Action LoggedIn; - public static Action RoomsReceived; - - public bool connected; - - public List prefabs = new List(); - public NetworkObject[] sceneObjects; - public List deletedSceneObjects = new List(); - - /// - /// Maintains a list of all known objects on the server (ones that have ids) - /// - public readonly Dictionary objects = new Dictionary(); - - /// - /// Maintains a list of all known groups on the server - /// - public readonly Dictionary> groups = new Dictionary>(); - - private VelNetPlayer masterPlayer; - public static VelNetPlayer LocalPlayer => instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault(); - public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; - - - // Use this for initialization - public class Message - { - public MessageReceiveType type; - //public string text; - public byte[] data; - public int sender; - } - - - - public readonly List receivedMessages = new List(); - - private void Awake() - { - if (instance != null) - { - Debug.LogError("Multiple NetworkManagers detected! Bad!", this); - } - - instance = this; - - SceneManager.sceneLoaded += (scene, mode) => - { - // add all local network objects - sceneObjects = FindObjectsOfType().Where(o => o.isSceneObject).ToArray(); - }; - } - - private IEnumerator Start() - { - ConnectToTcpServer(); - yield return null; - - try - { - OnConnectedToServer?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - - - private void AddMessage(Message m) - { - lock (receivedMessages) - { - //Debug.Log(messagesReceived++); - receivedMessages.Add(m); - } - } - - private void Update() - { - lock (receivedMessages) - { - //the main thread, which can do Unity stuff - foreach (Message m in receivedMessages) - { - switch (m.type) - { - // when you join the server - case 0: - userid = m.sender; - Debug.Log("joined server " + userid); - - try - { - LoggedIn?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - - //start the udp thread - clientReceiveThreadUDP = new Thread(ListenForDataUDP); - clientReceiveThreadUDP.IsBackground = true; - clientReceiveThreadUDP.Start(); - break; - /* - // if this message is for me, that means I joined a new room... - case 2 when userid == m.sender: - { - string oldRoom = LocalPlayer?.room; - - // we clear the list, but will recreate as we get messages from people in our room - players.Clear(); - masterPlayer = null; - - if (m.text != "") - { - VelNetPlayer player = new VelNetPlayer - { - isLocal = true, - userid = m.sender, - room = m.text - }; - - players.Add(userid, player); - if (m.text != "") - { - try - { - OnJoinedRoom?.Invoke(m.text); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - } - // we just left a room - else - { - // delete all networkobjects that aren't sceneobjects or are null now - objects - .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) - .Select(o => o.Key) - .ToList().ForEach(NetworkDestroy); - - // then remove references to the ones that are left - objects.Clear(); - - // empty all the groups - foreach (string group in instance.groups.Keys) - { - SetupMessageGroup(group, new List()); - } - - instance.groups.Clear(); - - try - { - OnLeftRoom?.Invoke(oldRoom); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - - break; - } - // not for me, a player is joining or leaving - case 2: - { - VelNetPlayer me = players[userid]; - - if (me.room != m.text) - { - // we got a left message, kill it - // change ownership of all objects to master - List deleteObjects = new List(); - foreach (KeyValuePair kvp in objects) - { - if (kvp.Value.owner == players[m.sender]) // the owner is the player that left - { - // if this object has locked ownership, delete it - if (kvp.Value.ownershipLocked) - { - deleteObjects.Add(kvp.Value.networkId); - } - // I'm the local master player, so can take ownership immediately - else if (me.isLocal && me == masterPlayer) - { - TakeOwnership(kvp.Key); - } - // the master player left, so everyone should set the owner null (we should get a new master shortly) - else if (players[m.sender] == masterPlayer) - { - kvp.Value.owner = null; - } - } - } - - // TODO this may check for ownership in the future. We don't need ownership here - deleteObjects.ForEach(NetworkDestroy); - - players.Remove(m.sender); - } - else - { - // we got a join message, create it - VelNetPlayer player = new VelNetPlayer - { - isLocal = false, - room = m.text, - userid = m.sender - }; - players.Add(m.sender, player); - try - { - OnPlayerJoined?.Invoke(player); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - - break; - } - // generic message - case 3: - if (players.ContainsKey(m.sender)) - { - players[m.sender]?.HandleMessage(m); - } - else - { - Debug.LogError("Received message from player that doesn't exist: " + m.text); - } - - break; - // change master player (this should only happen when the first player joins or if the master player leaves) - case 4: - { - if (masterPlayer == null) - { - masterPlayer = players[m.sender]; - - // no master player yet, add the scene objects - - for (int i = 0; i < sceneObjects.Length; i++) - { - sceneObjects[i].networkId = -1 + "-" + i; - sceneObjects[i].owner = masterPlayer; - sceneObjects[i].isSceneObject = true; // needed for special handling when deleted - objects.Add(sceneObjects[i].networkId, sceneObjects[i]); - } - } - else - { - masterPlayer = players[m.sender]; - } - - masterPlayer.SetAsMasterPlayer(); - - // master player should take over any objects that do not have an owner - foreach (KeyValuePair kvp in objects) - { - kvp.Value.owner ??= masterPlayer; - } - - break; - } - */ - } - - MessageReceived?.Invoke(m); - } - - receivedMessages.Clear(); - } - } - - private void OnApplicationQuit() - { - socketConnection.Close(); - } - - /// - /// Setup socket connection. - /// - private void ConnectToTcpServer() - { - try - { - clientReceiveThread = new Thread(ListenForData); - clientReceiveThread.IsBackground = true; - clientReceiveThread.Start(); - } - catch (Exception e) - { - Debug.Log("On client connect exception " + e); - } - } - - private void HandleMessage(string s) // this parses messages from the server, and adds them to a queue to be processed on the main thread - { - /* - // Debug.Log("Received: " + s); - Message m = new Message(); - string[] sections = s.Split(':'); - if (sections.Length <= 0) return; - - int type = int.Parse(sections[0]); - - switch (type) - { - case 0: // logged in message - { - if (sections.Length > 1) - { - m.type = type; - m.sender = int.Parse(sections[1]); - m.text = ""; - AddMessage(m); - } - - break; - } - case 1: // room info message - { - break; - } - case 2: // joined room message - { - if (sections.Length > 2) - { - m.type = 2; - int user_id = int.Parse(sections[1]); - m.sender = user_id; - string new_room = sections[2]; - m.text = new_room; - - AddMessage(m); - } - - break; - } - case 3: // text message - { - if (sections.Length > 2) - { - m.type = 3; - m.sender = int.Parse(sections[1]); - m.text = sections[2]; - AddMessage(m); - } - - break; - } - case 4: // change master client - { - if (sections.Length > 1) - { - m.type = 4; - m.sender = int.Parse(sections[1]); - AddMessage(m); - } - - break; - } - }*/ - } - - /// - /// Runs in background clientReceiveThread; Listens for incomming data. - /// - /// - private byte[] ReadExact(NetworkStream stream, int N) - { - byte[] toReturn = new byte[N]; - - int numRead = 0; - int numLeft = N; - while (numLeft > 0) - { - numRead += stream.Read(toReturn, numRead, numLeft); - numLeft = N - numRead; - } - return toReturn; - } - - private uint GetUintFromBytes(byte[] bytes) - { - if (BitConverter.IsLittleEndian) - { - return BitConverter.ToUInt32(bytes.Reverse().ToArray(),0); - } - else - { - return BitConverter.ToUInt32(bytes, 0); - } - } - private void ListenForData() - { - connected = true; - try - { - socketConnection = new TcpClient(host, port); - socketConnection.NoDelay = true; - byte[] bytes = new byte[1024]; - - Login("Kyle", "Johnsen"); - //Join("MyRoom"); - //SendTo(MessageSendType.MESSAGE_OTHERS, Encoding.UTF8.GetBytes("Hello")); - //FormGroup("close", new List { 1 }); - //SendToGroup("close", Encoding.UTF8.GetBytes("HelloGroup")); - while (true) - { - - // Get a stream object for reading - using NetworkStream stream = socketConnection.GetStream(); - int length; - - //read a byte - byte type = (byte)stream.ReadByte(); - - if(type == 0) - { - //read the id (this is my network id) - - Message m = new Message(); - m.sender = (int)GetUintFromBytes(ReadExact(stream, 4)); - m.data = new byte[0]; //no data - m.type = 0; - AddMessage(m); - - } - else if(type == 1) - { - //rooms message - }else if(type == 2) - { - //join message - }else if(type == 3) - { - //message from client over tcp - } - - // Read incomming stream into byte arrary. - while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) - { - Debug.Log("read " + length + " bytes!"); - string t = ""; - for(int i = 0; i < length; i++) - { - t = t + "," + bytes[i]; - } - Debug.Log(t); - //byte[] incommingData = new byte[length]; - //Array.Copy(bytes, 0, incommingData, 0, length); - //// Convert byte array to string message. - //string serverMessage = Encoding.ASCII.GetString(incommingData); - //string[] sections = serverMessage.Split('\n'); - //if (sections.Length > 1) - //{ - // lock (receivedMessages) - // { - // for (int i = 0; i < sections.Length - 1; i++) - // { - // if (i == 0) - // { - // HandleMessage(partialMessage + sections[0]); - // partialMessage = ""; - // } - // else - // { - // HandleMessage(sections[i]); - // } - // } - // } - //} - - //partialMessage = partialMessage + sections[sections.Length - 1]; - } - } - } - - - catch (Exception socketException) - { - Debug.Log("Socket exception: " + socketException); - } - - connected = false; - } - - private void ListenForDataUDP() - { - //I don't yet have a UDP connection - try - { - IPAddress[] addresses = Dns.GetHostAddresses(host); - Debug.Assert(addresses.Length > 0); - RemoteEndPoint = new IPEndPoint(addresses[0], port); - - - udpSocket = new Socket(AddressFamily.InterNetwork, - SocketType.Dgram, ProtocolType.Udp); - - - udpConnected = false; - byte[] buffer = new byte[1024]; - while (true) - { - buffer[0] = 0; - Array.Copy(get_be_bytes((uint)userid), 0, buffer, 1, 4); - udpSocket.SendTo(buffer, 5, SocketFlags.None, RemoteEndPoint); - - if (udpSocket.Available == 0) - { - Thread.Sleep(100); - Debug.Log("Waiting for UDP response"); - } - else - { - break; - } - } - - udpConnected = true; - while (true) - { - int numReceived = udpSocket.Receive(buffer); - - - if (buffer[0] == 0) - { - Debug.Log("UDP connected"); - }else if (buffer[0] == 3) - { - //we should get the sender address - byte[] senderBytes = new byte[4]; - Array.Copy(buffer, 1, senderBytes, 0, 4); - uint senderId = GetUintFromBytes(senderBytes); - byte[] messageBytes = new byte[numReceived - 5]; - Array.Copy(buffer, 5, messageBytes, 0, messageBytes.Length); - Message m = new Message(); - m.sender = (int)senderId; - m.data = messageBytes; - m.type = MessageReceiveType.MESSAGE; - } - - - - - } - } - catch (Exception socketException) - { - Debug.Log("Socket exception: " + socketException); - } - } - - private static void SendUdpMessage(string message) - { - if (instance.udpSocket == null || !instance.udpConnected) - { - return; - } - - byte[] data = Encoding.UTF8.GetBytes(message); - //Debug.Log("Attempting to send: " + message); - instance.udpSocket.SendTo(data, data.Length, SocketFlags.None, instance.RemoteEndPoint); - } - - /// - /// Send message to server using socket connection. - /// - private static void SendNetworkMessage(byte[] message) - { - // Debug.Log("Sent: " + clientMessage); - if (instance.socketConnection == null) - { - return; - } - - try - { - // Get a stream object for writing. - NetworkStream stream = instance.socketConnection.GetStream(); - if (stream.CanWrite) - { - - stream.Write(message,0,message.Length); - } - } - catch (SocketException socketException) - { - Debug.Log("Socket exception: " + socketException); - } - } - - /// - /// Connects to the server with a username - /// - /// - public static byte[] get_be_bytes(uint n) - { - return BitConverter.GetBytes(n).Reverse().ToArray(); - } - public static void Login(string username, string password) - { - - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - - byte[] uB = Encoding.UTF8.GetBytes(username); - byte[] pB = Encoding.UTF8.GetBytes(password); - writer.Write((byte)MessageSendType.LOGIN); - writer.Write((byte)uB.Length); - writer.Write(uB); - writer.Write((byte)pB.Length); - writer.Write(pB); - - SendNetworkMessage(stream.ToArray()); - - - } - - /// - /// Joins a room by name - /// - /// The name of the room to join - public static void Join(string roomname) - { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - - byte[] R = Encoding.UTF8.GetBytes(roomname); - writer.Write((byte)MessageSendType.JOIN_ROOM); - writer.Write((byte)R.Length); - writer.Write(R); - SendNetworkMessage(stream.ToArray()); - - - - - } - - public static void FormGroup(string groupname, List client_ids) - { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - byte[] R = Encoding.UTF8.GetBytes(groupname); - writer.Write((byte)MessageSendType.FORM_GROUP); - writer.Write((byte)R.Length); - writer.Write(R); - writer.Write(get_be_bytes((uint)client_ids.Count*4)); - for(int i = 0; i < client_ids.Count; i++) - { - writer.Write(get_be_bytes(client_ids[i])); - } - SendNetworkMessage(stream.ToArray()); - } - - /// - /// Leaves a room if we're in one - /// - public static void Leave() - { - //if (InRoom) SendNetworkMessage("2:-1"); - } - - public static void SendTo(MessageSendType type, byte[] message, bool reliable = true) - { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - - writer.Write((byte)3); - writer.Write(get_be_bytes((uint)message.Length)); - writer.Write(message); - - if (reliable) - { - SendNetworkMessage(stream.ToArray()); - - //SendNetworkMessage("3:" + (int)type + ":" + message); - } - else - { - SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); - } - } - - public static void SendTo(MessageSendType type, string message, bool reliable = true) - { - if (reliable) - { - //SendNetworkMessage("3:" + (int)type + ":" + message); - } - else - { - SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); - } - } - - public static void SendToGroup(string group, byte[] message, bool reliable = true) - { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); - - writer.Write((byte)MessageSendType.MESSAGE_GROUP); - writer.Write(get_be_bytes((uint)message.Length)); - writer.Write(message); - writer.Write((byte)group.Length); - writer.Write(Encoding.UTF8.GetBytes(group)); - - if (reliable) - { - SendNetworkMessage(stream.ToArray()); - - //SendNetworkMessage("3:" + (int)type + ":" + message); - } - else - { - //SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); - } - } - - public static void SendToGroup(string group, string message, bool reliable = true) - { - if (reliable) - { - //SendNetworkMessage("4:" + group + ":" + message); - } - else - { - SendUdpMessage(instance.userid + ":4:" + group + ":" + message); - } - } - - /// - /// changes the designated group that sendto(4) will go to - /// - public static void SetupMessageGroup(string groupName, List userIds) - { - if (userIds.Count > 0) - { - instance.groups[groupName] = userIds.ToList(); - } - - //SendNetworkMessage($"5:{groupName}:{string.Join(":", userIds)}"); - } - - - public static NetworkObject InstantiateNetworkObject(string prefabName) - { - VelNetPlayer localPlayer = LocalPlayer; - NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); - if (prefab == null) - { - Debug.LogError("Couldn't find a prefab with that name: " + prefabName); - return null; - } - - string networkId = localPlayer.userid + "-" + localPlayer.lastObjectId++; - if (instance.objects.ContainsKey(networkId)) - { - Debug.LogError("Can't instantiate object. Obj with that network ID was already instantiated.", instance.objects[networkId]); - return null; - } - NetworkObject newObject = Instantiate(prefab); - newObject.networkId = networkId; - newObject.prefabName = prefabName; - newObject.owner = localPlayer; - instance.objects.Add(newObject.networkId, newObject); - - // only sent to others, as I already instantiated this. Nice that it happens immediately. - SendTo(MessageSendType.MESSAGE_OTHERS, "7," + newObject.networkId + "," + prefabName); - - return newObject; - } - - public static void SomebodyInstantiatedNetworkObject(string networkId, string prefabName, VelNetPlayer owner) - { - NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); - if (prefab == null) return; - NetworkObject newObject = Instantiate(prefab); - newObject.networkId = networkId; - newObject.prefabName = prefabName; - newObject.owner = owner; - instance.objects.Add(newObject.networkId, newObject); - } - - public static void NetworkDestroy(NetworkObject obj) - { - NetworkDestroy(obj.networkId); - } - - public static void NetworkDestroy(string networkId) - { - if (!instance.objects.ContainsKey(networkId)) return; - NetworkObject obj = instance.objects[networkId]; - if (obj == null) - { - instance.objects.Remove(networkId); - return; - } - if (obj.isSceneObject) - { - instance.deletedSceneObjects.Add(networkId); - } - - Destroy(obj.gameObject); - instance.objects.Remove(networkId); - } - - /// - /// Takes local ownership of an object by id. - /// - /// Network ID of the object to transfer - /// True if successfully transferred, False if transfer message not sent - public static bool TakeOwnership(string networkId) - { - // local player must exist - if (LocalPlayer == null) return false; - - // obj must exist - if (!instance.objects.ContainsKey(networkId)) return false; - - // if the ownership is locked, fail - if (instance.objects[networkId].ownershipLocked) return false; - - // immediately successful - instance.objects[networkId].owner = LocalPlayer; - - // 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. - SendTo(MessageSendType.MESSAGE_ALL, "6," + networkId); - - return true; - } - } -} \ No newline at end of file diff --git a/Runtime/VelNetBinaryManager.cs.meta b/Runtime/VelNetBinaryManager.cs.meta deleted file mode 100644 index 014ff23..0000000 --- a/Runtime/VelNetBinaryManager.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 233344de094f11341bdb834d564708dc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index 71ea086..546fe36 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -8,18 +8,24 @@ using System.Threading; using UnityEngine; using System.Net; using UnityEngine.SceneManagement; +using System.Runtime.Serialization.Formatters.Binary; +using System.Runtime.Serialization; +using System.IO; namespace VelNet { [AddComponentMenu("VelNet/VelNet Manager")] public class VelNetManager : MonoBehaviour { - public enum MessageType + public enum MessageSendType { - OTHERS = 0, - ALL = 1, - OTHERS_ORDERED = 2, - ALL_ORDERED = 3 + MESSAGE_LOGIN = 0, + MESSAGE_GETROOMS = 1, + MESSAGE_JOINROOM = 2, + MESSAGE_OTHERS = 3, + MESSAGE_ALL = 4, + MESSAGE_GROUP = 5, + MESSAGE_SETGROUP = 6 }; public string host; @@ -62,7 +68,6 @@ namespace VelNet public static Action OnPlayerLeft; public static Action OnConnectedToServer; - public static Action MessageReceived; public static Action LoggedIn; public static Action RoomsReceived; @@ -87,12 +92,40 @@ namespace VelNet public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; + //this is for sending udp packets + static byte[] toSend = new byte[1024]; + // Use this for initialization - public class Message + public abstract class Message { - public int type; - public string text; - public int sender; + + } + public class ListedRoom + { + public string name; + public int numUsers; + } + public class LoginMessage: Message + { + public int userId; + } + public class RoomsMessage: Message + { + public List rooms; + } + public class JoinMessage: Message + { + public int userId; + public string room; + } + public class DataMessage: Message + { + public int senderId; + public byte[] data; + } + public class ChangeMasterMessage: Message + { + public int masterId; } public readonly List receivedMessages = new List(); @@ -109,7 +142,7 @@ namespace VelNet SceneManager.sceneLoaded += (scene, mode) => { // add all local network objects - sceneObjects = FindObjectsOfType().Where(o=>o.isSceneObject).ToArray(); + sceneObjects = FindObjectsOfType().Where(o => o.isSceneObject).ToArray(); }; } @@ -146,200 +179,206 @@ namespace VelNet //the main thread, which can do Unity stuff foreach (Message m in receivedMessages) { - switch (m.type) + switch (m) { - // when you join the server - case 0: - userid = m.sender; - Debug.Log("joined server"); - - try + case LoginMessage lm: { - LoggedIn?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - - //start the udp thread - clientReceiveThreadUDP = new Thread(ListenForDataUDP); - clientReceiveThreadUDP.IsBackground = true; - clientReceiveThreadUDP.Start(); - break; - // if this message is for me, that means I joined a new room... - case 2 when userid == m.sender: - { - string oldRoom = LocalPlayer?.room; - - // we clear the list, but will recreate as we get messages from people in our room - players.Clear(); - masterPlayer = null; - - if (m.text != "") - { - VelNetPlayer player = new VelNetPlayer - { - isLocal = true, - userid = m.sender, - room = m.text - }; - - players.Add(userid, player); - if (m.text != "") - { - try - { - OnJoinedRoom?.Invoke(m.text); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - } - // we just left a room - else - { - // delete all networkobjects that aren't sceneobjects or are null now - objects - .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) - .Select(o => o.Key) - .ToList().ForEach(NetworkDestroy); - - // then remove references to the ones that are left - objects.Clear(); - - // empty all the groups - foreach (string group in instance.groups.Keys) - { - SetupMessageGroup(group, new List()); - } - - instance.groups.Clear(); + userid = lm.userId; + Debug.Log("joined server " + userid); try { - OnLeftRoom?.Invoke(oldRoom); + LoggedIn?.Invoke(); } // prevent errors in subscribers from breaking our code catch (Exception e) { Debug.LogError(e); } + + //start the udp thread + clientReceiveThreadUDP = new Thread(ListenForDataUDP); + clientReceiveThreadUDP.IsBackground = true; + clientReceiveThreadUDP.Start(); + + break; } + case RoomsMessage rm: { + Debug.Log("Got Rooms Message"); + + break; + } + case JoinMessage jm: { + if(userid == jm.userId) //this is us + { + string oldRoom = LocalPlayer?.room; - break; - } - // not for me, a player is joining or leaving - case 2: - { - VelNetPlayer me = players[userid]; + // we clear the list, but will recreate as we get messages from people in our room + players.Clear(); + masterPlayer = null; - if (me.room != m.text) - { - // we got a left message, kill it - // change ownership of all objects to master - List deleteObjects = new List(); + if (jm.room != "") + { + VelNetPlayer player = new VelNetPlayer + { + isLocal = true, + userid = jm.userId, + room = jm.room + }; + + players.Add(userid, player); + + try + { + OnJoinedRoom?.Invoke(jm.room); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + + } + // we just left a room + else + { + // delete all networkobjects that aren't sceneobjects or are null now + objects + .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) + .Select(o => o.Key) + .ToList().ForEach(NetworkDestroy); + + // then remove references to the ones that are left + objects.Clear(); + + // empty all the groups + foreach (string group in instance.groups.Keys) + { + SetupMessageGroup(group, new List()); + } + + instance.groups.Clear(); + + try + { + OnLeftRoom?.Invoke(oldRoom); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } + } + else + { + VelNetPlayer me = players[userid]; + + if (me.room != jm.room) + { + // we got a left message, kill it + // change ownership of all objects to master + List deleteObjects = new List(); + foreach (KeyValuePair kvp in objects) + { + if (kvp.Value.owner == players[jm.userId]) // the owner is the player that left + { + // if this object has locked ownership, delete it + if (kvp.Value.ownershipLocked) + { + deleteObjects.Add(kvp.Value.networkId); + } + // I'm the local master player, so can take ownership immediately + else if (me.isLocal && me == masterPlayer) + { + TakeOwnership(kvp.Key); + } + // the master player left, so everyone should set the owner null (we should get a new master shortly) + else if (players[jm.userId] == masterPlayer) + { + kvp.Value.owner = null; + } + } + } + + // TODO this may check for ownership in the future. We don't need ownership here + deleteObjects.ForEach(NetworkDestroy); + + players.Remove(jm.userId); + } + else + { + // we got a join message, create it + VelNetPlayer player = new VelNetPlayer + { + isLocal = false, + room = jm.room, + userid = jm.userId + }; + players.Add(jm.userId, player); + try + { + OnPlayerJoined?.Invoke(player); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } + } + break; + + } + case DataMessage dm: { + if (players.ContainsKey(dm.senderId)) + { + players[dm.senderId]?.HandleMessage(dm); //todo + } + else + { + Debug.LogError("Received message from player that doesn't exist "); + } + + break; + + + } + case ChangeMasterMessage cm: { + + if (masterPlayer == null) + { + masterPlayer = players[cm.masterId]; + + // no master player yet, add the scene objects + + for (int i = 0; i < sceneObjects.Length; i++) + { + sceneObjects[i].networkId = -1 + "-" + i; + sceneObjects[i].owner = masterPlayer; + sceneObjects[i].isSceneObject = true; // needed for special handling when deleted + objects.Add(sceneObjects[i].networkId, sceneObjects[i]); + } + } + else + { + masterPlayer = players[cm.masterId]; + } + + masterPlayer.SetAsMasterPlayer(); + + // master player should take over any objects that do not have an owner foreach (KeyValuePair kvp in objects) { - if (kvp.Value.owner == players[m.sender]) // the owner is the player that left - { - // if this object has locked ownership, delete it - if (kvp.Value.ownershipLocked) - { - deleteObjects.Add(kvp.Value.networkId); - } - // I'm the local master player, so can take ownership immediately - else if (me.isLocal && me == masterPlayer) - { - TakeOwnership(kvp.Key); - } - // the master player left, so everyone should set the owner null (we should get a new master shortly) - else if (players[m.sender] == masterPlayer) - { - kvp.Value.owner = null; - } - } + kvp.Value.owner ??= masterPlayer; } - // TODO this may check for ownership in the future. We don't need ownership here - deleteObjects.ForEach(NetworkDestroy); - - players.Remove(m.sender); + break; + } - else - { - // we got a join message, create it - VelNetPlayer player = new VelNetPlayer - { - isLocal = false, - room = m.text, - userid = m.sender - }; - players.Add(m.sender, player); - try - { - OnPlayerJoined?.Invoke(player); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - } - - break; - } - // generic message - case 3: - if (players.ContainsKey(m.sender)) - { - players[m.sender]?.HandleMessage(m); - } - else - { - Debug.LogError("Received message from player that doesn't exist: " + m.text); - } - - break; - // change master player (this should only happen when the first player joins or if the master player leaves) - case 4: - { - if (masterPlayer == null) - { - masterPlayer = players[m.sender]; - - // no master player yet, add the scene objects - - for (int i = 0; i < sceneObjects.Length; i++) - { - sceneObjects[i].networkId = -1 + "-" + i; - sceneObjects[i].owner = masterPlayer; - sceneObjects[i].isSceneObject = true; // needed for special handling when deleted - objects.Add(sceneObjects[i].networkId, sceneObjects[i]); - } - } - else - { - masterPlayer = players[m.sender]; - } - - masterPlayer.SetAsMasterPlayer(); - - // master player should take over any objects that do not have an owner - foreach (KeyValuePair kvp in objects) - { - kvp.Value.owner ??= masterPlayer; - } - - break; - } } - - MessageReceived?.Invoke(m); + + //MessageReceived?.Invoke(m); } receivedMessages.Clear(); @@ -368,77 +407,36 @@ namespace VelNet } } - private void HandleMessage(string s) // this parses messages from the server, and adds them to a queue to be processed on the main thread - { - // Debug.Log("Received: " + s); - Message m = new Message(); - string[] sections = s.Split(':'); - if (sections.Length <= 0) return; - - int type = int.Parse(sections[0]); - - switch (type) - { - case 0: // logged in message - { - if (sections.Length > 1) - { - m.type = type; - m.sender = int.Parse(sections[1]); - m.text = ""; - AddMessage(m); - } - - break; - } - case 1: // room info message - { - break; - } - case 2: // joined room message - { - if (sections.Length > 2) - { - m.type = 2; - int user_id = int.Parse(sections[1]); - m.sender = user_id; - string new_room = sections[2]; - m.text = new_room; - - AddMessage(m); - } - - break; - } - case 3: // text message - { - if (sections.Length > 2) - { - m.type = 3; - m.sender = int.Parse(sections[1]); - m.text = sections[2]; - AddMessage(m); - } - - break; - } - case 4: // change master client - { - if (sections.Length > 1) - { - m.type = 4; - m.sender = int.Parse(sections[1]); - AddMessage(m); - } - - break; - } - } - } /// /// Runs in background clientReceiveThread; Listens for incomming data. /// + /// + private byte[] ReadExact(NetworkStream stream, int N) + { + byte[] toReturn = new byte[N]; + + int numRead = 0; + int numLeft = N; + while (numLeft > 0) + { + numRead += stream.Read(toReturn, numRead, numLeft); + numLeft = N - numRead; + } + return toReturn; + } + + private int GetIntFromBytes(byte[] bytes) + { + if (BitConverter.IsLittleEndian) + { + return BitConverter.ToInt32(bytes.Reverse().ToArray(),0); + } + else + { + return BitConverter.ToInt32(bytes, 0); + } + } private void ListenForData() { connected = true; @@ -446,41 +444,65 @@ namespace VelNet { socketConnection = new TcpClient(host, port); socketConnection.NoDelay = true; - byte[] bytes = new byte[1024]; - string partialMessage = ""; + NetworkStream stream = socketConnection.GetStream(); + //Join("MyRoom"); + //SendTo(MessageSendType.MESSAGE_OTHERS, Encoding.UTF8.GetBytes("Hello")); + //FormGroup("close", new List { 1 }); + //SendToGroup("close", Encoding.UTF8.GetBytes("HelloGroup")); while (true) { + // Get a stream object for reading - using NetworkStream stream = socketConnection.GetStream(); - int length; - // Read incomming stream into byte arrary. - while ((length = stream.Read(bytes, 0, bytes.Length)) != 0) - { - byte[] incommingData = new byte[length]; - Array.Copy(bytes, 0, incommingData, 0, length); - // Convert byte array to string message. - string serverMessage = Encoding.ASCII.GetString(incommingData); - string[] sections = serverMessage.Split('\n'); - if (sections.Length > 1) - { - lock (receivedMessages) - { - for (int i = 0; i < sections.Length - 1; i++) - { - if (i == 0) - { - HandleMessage(partialMessage + sections[0]); - partialMessage = ""; - } - else - { - HandleMessage(sections[i]); - } - } - } - } + - partialMessage = partialMessage + sections[sections.Length - 1]; + //read a byte + byte type = (byte)stream.ReadByte(); + + if (type == 0) //login + { + LoginMessage m = new LoginMessage(); + m.userId = GetIntFromBytes(ReadExact(stream, 4)); //not really the sender... + AddMessage(m); + } + else if(type == 1) //rooms + { + RoomsMessage m = new RoomsMessage(); + m.rooms = new List(); + int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload + byte[] utf8data = ReadExact(stream, N); + string roomMessage = Encoding.UTF8.GetString(utf8data); + string[] sections = roomMessage.Split(','); + foreach(string s in sections) + { + string[] pieces = s.Split(':'); + ListedRoom lr = new ListedRoom(); + lr.name = pieces[0]; + lr.numUsers = int.Parse(pieces[1]); + m.rooms.Add(lr); + } + AddMessage(m); + } + else if(type == 2) //joined + { + JoinMessage m = new JoinMessage(); + m.userId = GetIntFromBytes(ReadExact(stream, 4)); + int N = stream.ReadByte(); + byte[] utf8data = ReadExact(stream, N); //the room name, encoded as utf-8 + m.room = Encoding.UTF8.GetString(utf8data); + AddMessage(m); + }else if(type == 3) //data + { + DataMessage m = new DataMessage(); + m.senderId = GetIntFromBytes(ReadExact(stream, 4)); + int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload + m.data = ReadExact(stream, N); //the message + AddMessage(m); + } + else if(type == 4) //new master + { + ChangeMasterMessage m = new ChangeMasterMessage(); + m.masterId = (int)GetIntFromBytes(ReadExact(stream, 4)); //sender is the new master + AddMessage(m); } } } @@ -512,9 +534,9 @@ namespace VelNet byte[] buffer = new byte[1024]; while (true) { - string welcome = userid + ":0:Hello"; - byte[] data = Encoding.ASCII.GetBytes(welcome); - udpSocket.SendTo(data, data.Length, SocketFlags.None, RemoteEndPoint); + buffer[0] = 0; + Array.Copy(get_be_bytes(userid), 0, buffer, 1, 4); + udpSocket.SendTo(buffer, 5, SocketFlags.None, RemoteEndPoint); if (udpSocket.Available == 0) { @@ -531,18 +553,20 @@ namespace VelNet while (true) { int numReceived = udpSocket.Receive(buffer); - - string message = Encoding.UTF8.GetString(buffer, 0, numReceived); - - string[] sections = message.Split(':'); - if (sections[0] == "0") + if (buffer[0] == 0) { Debug.Log("UDP connected"); - } - - if (sections[0] == "3") + }else if (buffer[0] == 3) { - HandleMessage(message); + DataMessage m = new DataMessage(); + //we should get the sender address + byte[] senderBytes = new byte[4]; + Array.Copy(buffer, 1, senderBytes, 0, 4); + m.senderId = GetIntFromBytes(senderBytes); + byte[] messageBytes = new byte[numReceived - 5]; + Array.Copy(buffer, 5, messageBytes, 0, messageBytes.Length); + m.data = messageBytes; + AddMessage(m); } } } @@ -552,22 +576,20 @@ namespace VelNet } } - private static void SendUdpMessage(string message) + private static void SendUdpMessage(byte[] message, int N) { if (instance.udpSocket == null || !instance.udpConnected) { return; } - byte[] data = Encoding.UTF8.GetBytes(message); - //Debug.Log("Attempting to send: " + message); - instance.udpSocket.SendTo(data, data.Length, SocketFlags.None, instance.RemoteEndPoint); + instance.udpSocket.SendTo(message, N, SocketFlags.None, instance.RemoteEndPoint); } /// /// Send message to server using socket connection. /// - private static void SendNetworkMessage(string clientMessage) + private static void SendTcpMessage(byte[] message) //we can assume that this message is already formatted, so we just send it { // Debug.Log("Sent: " + clientMessage); if (instance.socketConnection == null) @@ -581,11 +603,8 @@ namespace VelNet NetworkStream stream = instance.socketConnection.GetStream(); if (stream.CanWrite) { - // Convert string message to byte array. - clientMessage += "\n"; // append a new line to delineate the message - byte[] clientMessageAsByteArray = Encoding.ASCII.GetBytes(clientMessage); - // Write byte array to socketConnection stream. - stream.Write(clientMessageAsByteArray, 0, clientMessageAsByteArray.Length); + + stream.Write(message,0,message.Length); } } catch (SocketException socketException) @@ -597,9 +616,28 @@ namespace VelNet /// /// Connects to the server with a username /// + /// + public static byte[] get_be_bytes(int n) + { + return BitConverter.GetBytes(n).Reverse().ToArray(); + } public static void Login(string username, string password) { - SendNetworkMessage("0:" + username + ":" + password); + + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + + byte[] uB = Encoding.UTF8.GetBytes(username); + byte[] pB = Encoding.UTF8.GetBytes(password); + writer.Write((byte)0); + writer.Write((byte)uB.Length); + writer.Write(uB); + writer.Write((byte)pB.Length); + writer.Write(pB); + + SendTcpMessage(stream.ToArray()); + + } /// @@ -608,52 +646,104 @@ namespace VelNet /// The name of the room to join public static void Join(string roomname) { - SendNetworkMessage("2:" + roomname); + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + + byte[] R = Encoding.UTF8.GetBytes(roomname); + writer.Write((byte)2); + writer.Write((byte)R.Length); + writer.Write(R); + SendTcpMessage(stream.ToArray()); + + + + } + + /// /// Leaves a room if we're in one /// public static void Leave() { - if (InRoom) SendNetworkMessage("2:-1"); - } - - public static void SendTo(MessageType type, string message, bool reliable = true) - { - if (reliable) + if (InRoom) { - SendNetworkMessage("3:" + (int)type + ":" + message); - } - else - { - SendUdpMessage(instance.userid + ":3:" + (int)type + ":" + message); + Join(""); //super secret way to leave } } - public static void SendToGroup(string group, string message, bool reliable = true) + public static void SendToRoom(byte[] message, bool include_self = false, bool reliable = true) { + byte sendType = (byte)(include_self ? MessageSendType.MESSAGE_ALL : MessageSendType.MESSAGE_OTHERS); if (reliable) { - SendNetworkMessage("4:" + group + ":" + message); + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + writer.Write(sendType); + writer.Write(get_be_bytes(message.Length)); + writer.Write(message); + SendTcpMessage(stream.ToArray()); } else { - SendUdpMessage(instance.userid + ":4:" + group + ":" + message); + //udp message needs the type + toSend[0] = sendType; //we don't + Array.Copy(get_be_bytes(instance.userid), 0, toSend, 1, 4); + Array.Copy(message, 0, toSend, 5, message.Length); + SendUdpMessage(toSend,message.Length+5); //shouldn't be over 1024... + } + } + + + public static void SendToGroup(string group, byte[] message, bool reliable = true) + { + byte[] utf8bytes = Encoding.UTF8.GetBytes(group); + if (reliable) + { + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + writer.Write((byte)MessageSendType.MESSAGE_GROUP); + writer.Write(get_be_bytes(message.Length)); + writer.Write(message); + writer.Write((byte)utf8bytes.Length); + writer.Write(utf8bytes); + SendTcpMessage(stream.ToArray()); + } + else + { + toSend[0] = (byte)MessageSendType.MESSAGE_GROUP; + Array.Copy(get_be_bytes(instance.userid), 0, toSend, 1, 4); + //also need to send the group + toSend[5] = (byte)utf8bytes.Length; + Array.Copy(utf8bytes, 0, toSend, 6, utf8bytes.Length); + Array.Copy(message, 0, toSend, 6+utf8bytes.Length, message.Length); + SendUdpMessage(toSend, 6 + utf8bytes.Length + message.Length); } } /// /// changes the designated group that sendto(4) will go to /// - public static void SetupMessageGroup(string groupName, List userIds) + public static void SetupMessageGroup(string groupname, List client_ids) { - if (userIds.Count > 0) + if (client_ids.Count > 0) { - instance.groups[groupName] = userIds.ToList(); + instance.groups[groupname] = client_ids.ToList(); } - SendNetworkMessage($"5:{groupName}:{string.Join(":", userIds)}"); + MemoryStream stream = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream); + byte[] R = Encoding.UTF8.GetBytes(groupname); + writer.Write((byte)6); + writer.Write((byte)R.Length); + writer.Write(R); + writer.Write(get_be_bytes(client_ids.Count * 4)); + for (int i = 0; i < client_ids.Count; i++) + { + writer.Write(get_be_bytes(client_ids[i])); + } + SendTcpMessage(stream.ToArray()); } @@ -680,7 +770,7 @@ namespace VelNet instance.objects.Add(newObject.networkId, newObject); // only sent to others, as I already instantiated this. Nice that it happens immediately. - SendTo(MessageType.OTHERS, "7," + newObject.networkId + "," + prefabName); + SendToRoom(Encoding.UTF8.GetBytes("7," + newObject.networkId + "," + prefabName),false,true); return newObject; } @@ -718,7 +808,7 @@ namespace VelNet Destroy(obj.gameObject); instance.objects.Remove(networkId); } - + /// /// Takes local ownership of an object by id. /// @@ -728,19 +818,19 @@ namespace VelNet { // local player must exist if (LocalPlayer == null) return false; - + // obj must exist if (!instance.objects.ContainsKey(networkId)) return false; // if the ownership is locked, fail if (instance.objects[networkId].ownershipLocked) return false; - + // immediately successful instance.objects[networkId].owner = LocalPlayer; // 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. - SendTo(MessageType.ALL_ORDERED, "6," + networkId); - + SendToRoom(Encoding.UTF8.GetBytes("6," + networkId)); + return true; } } diff --git a/Runtime/VelNetManager.cs.meta b/Runtime/VelNetManager.cs.meta index 56f31aa..014ff23 100644 --- a/Runtime/VelNetManager.cs.meta +++ b/Runtime/VelNetManager.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 03a4d4e1a7fd74c7ab2eccca4ce168db +guid: 233344de094f11341bdb834d564708dc MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/VelNetPlayer.cs b/Runtime/VelNetPlayer.cs index fae5623..c31ab89 100644 --- a/Runtime/VelNetPlayer.cs +++ b/Runtime/VelNetPlayer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System; +using System.Text; namespace VelNet { @@ -41,7 +42,7 @@ namespace VelNet { if (kvp.Value.owner == this && kvp.Value.prefabName != "") { - VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "7," + kvp.Value.networkId + "," + kvp.Value.prefabName); + VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("7," + kvp.Value.networkId + "," + kvp.Value.prefabName),false,true); } } @@ -56,12 +57,14 @@ namespace VelNet /// /// These are generally things that come from the "owner" and should be enacted locally, where appropriate /// - public void HandleMessage(VelNetManager.Message m) + public void HandleMessage(VelNetManager.DataMessage m) { - //we need to parse the message + //for now, we can just convert to text...because + + string text = Encoding.UTF8.GetString(m.data); //types of messages - string[] messages = m.text.Split(';'); //messages are split by ; + string[] messages = text.Split(';'); //messages are split by ; foreach (string s in messages) { //individual message parameters separated by comma @@ -138,17 +141,20 @@ namespace VelNet public void SendGroupMessage(NetworkObject obj, string group, string identifier, byte[] data, bool reliable = true) { - VelNetManager.SendToGroup(group, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); + string message = "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data); + VelNetManager.SendToGroup(group, Encoding.UTF8.GetBytes(message), reliable); } public void SendMessage(NetworkObject obj, string identifier, byte[] data, bool reliable = true) { - VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); + string message = "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data); + VelNetManager.SendToRoom(Encoding.UTF8.GetBytes(message), false, reliable); } public void SendSceneUpdate() { - VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects)); + string message = "9," + string.Join(",", manager.deletedSceneObjects); + VelNetManager.SendToRoom( Encoding.UTF8.GetBytes(message)); } [Obsolete("Use VelNetManager.NetworkDestroy() instead.")] @@ -158,7 +164,7 @@ namespace VelNet if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return; // send to all, which will make me delete as well - VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "8," + networkId); + VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("8," + networkId), true, true); } /// True if successful, False if failed to transfer ownership @@ -175,7 +181,7 @@ namespace VelNet 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. - VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "6," + networkId); + VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("6," + networkId),true,true); return true; } From 06bc4a09fda04ffe4451cc483c7f07e5859dd94e Mon Sep 17 00:00:00 2001 From: Kyle Johnsen Date: Thu, 20 Jan 2022 00:14:30 -0500 Subject: [PATCH 06/13] added a test for the rooms message --- Runtime/VelNetManager.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index 546fe36..db95c0b 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -466,19 +466,25 @@ namespace VelNet } else if(type == 1) //rooms { + RoomsMessage m = new RoomsMessage(); m.rooms = new List(); int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload byte[] utf8data = ReadExact(stream, N); string roomMessage = Encoding.UTF8.GetString(utf8data); + + + string[] sections = roomMessage.Split(','); - foreach(string s in sections) + foreach (string s in sections) { string[] pieces = s.Split(':'); - ListedRoom lr = new ListedRoom(); - lr.name = pieces[0]; - lr.numUsers = int.Parse(pieces[1]); - m.rooms.Add(lr); + if (pieces.Length == 2) { + ListedRoom lr = new ListedRoom(); + lr.name = pieces[0]; + lr.numUsers = int.Parse(pieces[1]); + m.rooms.Add(lr); + } } AddMessage(m); } @@ -640,6 +646,12 @@ namespace VelNet } + public static void GetRooms() + { + + SendTcpMessage(new byte[1] { 1 }); //very simple message + } + /// /// Joins a room by name /// From 9819ccfbf75a6d3dc13c44126fb4d9ba22418e02 Mon Sep 17 00:00:00 2001 From: Kyle Johnsen Date: Fri, 21 Jan 2022 08:59:02 -0500 Subject: [PATCH 07/13] fixed connected event, added ordered tcp messages (not group), automatically running login/connect --- Runtime/VelNetManager.cs | 46 +++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index db95c0b..0e9ae32 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -19,6 +19,8 @@ namespace VelNet { public enum MessageSendType { + MESSAGE_OTHERS_ORDERED = 7, + MESSAGE_ALL_ORDERED = 8, MESSAGE_LOGIN = 0, MESSAGE_GETROOMS = 1, MESSAGE_JOINROOM = 2, @@ -128,6 +130,11 @@ namespace VelNet public int masterId; } + public class ConnectedMessage: Message + { + + } + public readonly List receivedMessages = new List(); private void Awake() @@ -146,20 +153,9 @@ namespace VelNet }; } - private IEnumerator Start() + private void Start() { ConnectToTcpServer(); - yield return null; - - try - { - OnConnectedToServer?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } } @@ -181,6 +177,19 @@ namespace VelNet { switch (m) { + case ConnectedMessage connected: + { + try + { + OnConnectedToServer?.Invoke(); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + break; + } case LoginMessage lm: { userid = lm.userId; @@ -445,6 +454,8 @@ namespace VelNet socketConnection = new TcpClient(host, port); socketConnection.NoDelay = true; NetworkStream stream = socketConnection.GetStream(); + //now we are connected, so add a message to the queue + AddMessage(new ConnectedMessage()); //Join("MyRoom"); //SendTo(MessageSendType.MESSAGE_OTHERS, Encoding.UTF8.GetBytes("Hello")); //FormGroup("close", new List { 1 }); @@ -632,7 +643,7 @@ namespace VelNet MemoryStream stream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(stream); - + byte[] uB = Encoding.UTF8.GetBytes(username); byte[] pB = Encoding.UTF8.GetBytes(password); writer.Write((byte)0); @@ -685,9 +696,14 @@ namespace VelNet } } - public static void SendToRoom(byte[] message, bool include_self = false, bool reliable = true) + public static void SendToRoom(byte[] message, bool include_self = false, bool reliable = true, bool ordered = false) { - byte sendType = (byte)(include_self ? MessageSendType.MESSAGE_ALL : MessageSendType.MESSAGE_OTHERS); + byte sendType = (byte) MessageSendType.MESSAGE_OTHERS; + if (include_self && ordered) sendType = (byte)MessageSendType.MESSAGE_ALL_ORDERED; + if (include_self && !ordered) sendType = (byte)MessageSendType.MESSAGE_ALL; + if (!include_self && ordered) sendType = (byte)MessageSendType.MESSAGE_OTHERS_ORDERED; + + if (reliable) { MemoryStream stream = new MemoryStream(); From dc61c63563a7e0009eb2efad138e8ed28d8889af Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Mon, 24 Jan 2022 19:18:38 -0500 Subject: [PATCH 08/13] pulled changes from main branch --- Runtime/NetworkObject.cs | 39 ++++- Runtime/Util/BinaryWriterExtensions.cs | 57 ++++++- Runtime/Util/NetworkSerializedObjectStream.cs | 20 ++- Runtime/Util/SyncRigidbody.cs | 143 ++++++++++++++++++ Runtime/Util/SyncRigidbody.cs.meta | 3 + Runtime/Util/SyncTransform.cs | 37 +++-- Runtime/VelNetManager.cs | 44 ++++-- Runtime/VelNetPlayer.cs | 6 +- package.json | 4 +- 9 files changed, 314 insertions(+), 39 deletions(-) create mode 100644 Runtime/Util/SyncRigidbody.cs create mode 100644 Runtime/Util/SyncRigidbody.cs.meta diff --git a/Runtime/NetworkObject.cs b/Runtime/NetworkObject.cs index b08e041..5c605ac 100644 --- a/Runtime/NetworkObject.cs +++ b/Runtime/NetworkObject.cs @@ -18,13 +18,19 @@ namespace VelNet [Tooltip("Whether this object's ownership is transferrable. Should be true for player objects.")] public bool ownershipLocked; - public bool IsMine => owner != null && owner.isLocal; - + public bool IsMine => owner?.isLocal ?? false; + /// /// This is forged from the combination of the creator's id (-1 in the case of a scene object) and an object id, so it's always unique for a room /// public string networkId; + /// + /// This is generated at editor time and used to generated the network id at runtime. + /// This is needed because finding all objects of type at runtime doesn't have a guaranteed order. + /// + public int sceneNetworkId; + /// /// This may be empty if it's not a prefab (scene object) /// @@ -50,7 +56,14 @@ namespace VelNet } int index = syncedComponents.IndexOf(component); - owner.SendMessage(this, index.ToString(), message, reliable); + if (index < 0) + { + Debug.LogError("WAAAAAAAH. NetworkObject doesn't have a reference to this component.", component); + } + else + { + owner.SendMessage(this, index.ToString(), message, reliable); + } } public void SendBytesToGroup(NetworkComponent component, string group, byte[] message, bool reliable = true) @@ -116,6 +129,26 @@ namespace VelNet { c.networkObject = t; } + PrefabUtility.RecordPrefabInstancePropertyModifications(t); + } + + // make the sceneNetworkId a new unique value + if (Application.isEditor && !Application.isPlaying && t.isSceneObject && t.sceneNetworkId == 0) + { + // find the first unused value + int[] used = FindObjectsOfType().Select(o => o.sceneNetworkId).ToArray(); + int available = -1; + for (int i = 1; i <= used.Max()+1; i++) + { + if (!used.Contains(i)) + { + available = i; + break; + } + } + + t.sceneNetworkId = available; + PrefabUtility.RecordPrefabInstancePropertyModifications(t); } EditorGUILayout.Space(); diff --git a/Runtime/Util/BinaryWriterExtensions.cs b/Runtime/Util/BinaryWriterExtensions.cs index f122745..f43524d 100644 --- a/Runtime/Util/BinaryWriterExtensions.cs +++ b/Runtime/Util/BinaryWriterExtensions.cs @@ -1,4 +1,7 @@ -using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; using UnityEngine; namespace VelNet @@ -34,5 +37,57 @@ namespace VelNet reader.ReadSingle() ); } + + /// + /// Compresses the list of bools into bytes using a bitmask + /// + public static byte[] GetBitmasks(this IEnumerable bools) + { + List values = bools.ToList(); + List bytes = new List(); + for (int b = 0; b < Mathf.Ceil(values.Count / 8f); b++) + { + byte currentByte = 0; + for (int bit = 0; bit < 8; bit++) + { + if (values.Count > b * 8 + bit) + { + currentByte |= (byte)((values[b * 8 + bit] ? 1 : 0) << bit); + } + } + + bytes.Add(currentByte); + } + + return bytes.ToArray(); + } + + public static List GetBitmaskValues(this IEnumerable bytes) + { + List l = new List(); + foreach (byte b in bytes) + { + l.AddRange(b.GetBitmaskValues()); + } + + return l; + } + + public static List GetBitmaskValues(this byte b) + { + List l = new List(); + for (int i = 0; i < 8; i++) + { + l.Add(b.GetBitmaskValue(i)); + } + + return l; + } + + public static bool GetBitmaskValue(this byte b, int index) + { + return (b & (1 << index)) != 0; + } + } } \ No newline at end of file diff --git a/Runtime/Util/NetworkSerializedObjectStream.cs b/Runtime/Util/NetworkSerializedObjectStream.cs index a6929f8..161791f 100644 --- a/Runtime/Util/NetworkSerializedObjectStream.cs +++ b/Runtime/Util/NetworkSerializedObjectStream.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Serialization; @@ -19,12 +20,19 @@ namespace VelNet { while (true) { - if (IsMine) + try { - using MemoryStream mem = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(mem); - SendState(writer); - SendBytes(mem.ToArray()); + if (IsMine) + { + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + SendState(writer); + SendBytes(mem.ToArray()); + } + } + catch (Exception e) + { + Debug.LogError(e); } yield return new WaitForSeconds(1f / serializationRateHz); diff --git a/Runtime/Util/SyncRigidbody.cs b/Runtime/Util/SyncRigidbody.cs new file mode 100644 index 0000000..5937ff4 --- /dev/null +++ b/Runtime/Util/SyncRigidbody.cs @@ -0,0 +1,143 @@ +using System.IO; +using UnityEngine; + +namespace VelNet +{ + /// + /// A simple class that will sync the position and rotation of a network object with a rigidbody + /// + [AddComponentMenu("VelNet/VelNet Sync Rigidbody")] + [RequireComponent(typeof(Rigidbody))] + public class SyncRigidbody : NetworkSerializedObjectStream + { + public bool useLocalTransform; + [Tooltip("0 to disable.")] + public float teleportDistance; + [Tooltip("0 to disable.")] + public float teleportAngle; + + public bool syncKinematic = true; + public bool syncGravity = true; + public bool syncVelocity = true; + public bool syncAngularVelocity = true; + + private Vector3 targetPosition; + private Quaternion targetRotation; + private float distanceAtReceiveTime; + private float angleAtReceiveTime; + private Rigidbody rb; + + private void Start() + { + rb = GetComponent(); + if (useLocalTransform) + { + targetPosition = transform.localPosition; + targetRotation = transform.localRotation; + } + else + { + targetPosition = transform.position; + targetRotation = transform.rotation; + } + } + + /// + /// This gets called at serializationRateHz when the object is locally owned + /// + protected override void SendState(BinaryWriter writer) + { + if (useLocalTransform) + { + writer.Write(transform.localPosition); + writer.Write(transform.localRotation); + } + else + { + writer.Write(transform.position); + writer.Write(transform.rotation); + } + + // writer.Write((new bool[] {rb.isKinematic, rb.useGravity}).GetBitmasks()); + if (syncKinematic) writer.Write(rb.isKinematic); + if (syncGravity) writer.Write(rb.useGravity); + if (syncVelocity) writer.Write(rb.velocity); + if (syncAngularVelocity) writer.Write(rb.angularVelocity); + } + + /// + /// This gets called whenever a message about the state of this object is received. + /// Usually at serializationRateHz. + /// + protected override void ReceiveState(BinaryReader reader) + { + targetPosition = reader.ReadVector3(); + targetRotation = reader.ReadQuaternion(); + + if (syncKinematic) rb.isKinematic = reader.ReadBoolean(); + if (syncGravity) rb.useGravity = reader.ReadBoolean(); + if (syncVelocity) rb.velocity = reader.ReadVector3(); + if (syncAngularVelocity) rb.angularVelocity = reader.ReadVector3(); + + // record the distance from the target for interpolation + if (useLocalTransform) + { + distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.localPosition); + angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.localRotation); + if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime) + { + transform.localPosition = targetPosition; + } + if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime) + { + transform.localRotation = targetRotation; + } + } + else + { + distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.position); + angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.rotation); + if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime) + { + transform.position = targetPosition; + } + if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime) + { + transform.rotation = targetRotation; + } + } + } + + private void Update() + { + if (IsMine) return; + + if (useLocalTransform) + { + transform.localPosition = Vector3.MoveTowards( + transform.localPosition, + targetPosition, + Time.deltaTime * distanceAtReceiveTime * serializationRateHz + ); + transform.localRotation = Quaternion.RotateTowards( + transform.localRotation, + targetRotation, + Time.deltaTime * angleAtReceiveTime * serializationRateHz + ); + } + else + { + transform.position = Vector3.MoveTowards( + transform.position, + targetPosition, + Time.deltaTime * distanceAtReceiveTime * serializationRateHz + ); + transform.rotation = Quaternion.RotateTowards( + transform.rotation, + targetRotation, + Time.deltaTime * angleAtReceiveTime * serializationRateHz + ); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Util/SyncRigidbody.cs.meta b/Runtime/Util/SyncRigidbody.cs.meta new file mode 100644 index 0000000..e516561 --- /dev/null +++ b/Runtime/Util/SyncRigidbody.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 951f5c5e2245481d8f969b94f998c78b +timeCreated: 1642738174 \ No newline at end of file diff --git a/Runtime/Util/SyncTransform.cs b/Runtime/Util/SyncTransform.cs index 162a9c9..dddd19f 100644 --- a/Runtime/Util/SyncTransform.cs +++ b/Runtime/Util/SyncTransform.cs @@ -1,16 +1,19 @@ using System.IO; using UnityEngine; - namespace VelNet { /// /// A simple class that will sync the position and rotation of a network object /// [AddComponentMenu("VelNet/VelNet Sync Transform")] - public class SyncTransform : NetworkSerializedObject + public class SyncTransform : NetworkSerializedObjectStream { public bool useLocalTransform; + [Tooltip("0 to disable.")] + public float teleportDistance; + [Tooltip("0 to disable.")] + public float teleportAngle; private Vector3 targetPosition; private Quaternion targetRotation; @@ -34,28 +37,18 @@ namespace VelNet /// /// This gets called at serializationRateHz when the object is locally owned /// - /// The state of this object to send across the network - protected override byte[] SendState() + protected override void SendState(BinaryWriter writer) { - using MemoryStream mem = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(mem); - writer.Write(transform.localPosition); writer.Write(transform.localRotation); - - return mem.ToArray(); } /// /// This gets called whenever a message about the state of this object is received. /// Usually at serializationRateHz. /// - /// The network state of this object - protected override void ReceiveState(byte[] message) + protected override void ReceiveState(BinaryReader reader) { - using MemoryStream mem = new MemoryStream(message); - using BinaryReader reader = new BinaryReader(mem); - targetPosition = reader.ReadVector3(); targetRotation = reader.ReadQuaternion(); @@ -64,11 +57,27 @@ namespace VelNet { distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.localPosition); angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.localRotation); + if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime) + { + transform.localPosition = targetPosition; + } + if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime) + { + transform.localRotation = targetRotation; + } } else { distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.position); angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.rotation); + if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime) + { + transform.position = targetPosition; + } + if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime) + { + transform.rotation = targetRotation; + } } } diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index 0e9ae32..31714f7 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -42,7 +42,6 @@ namespace VelNet private Thread clientReceiveThread; private Thread clientReceiveThreadUDP; public int userid = -1; - public string room; private int messagesReceived = 0; public readonly Dictionary players = new Dictionary(); @@ -90,8 +89,23 @@ namespace VelNet public readonly Dictionary> groups = new Dictionary>(); private VelNetPlayer masterPlayer; - public static VelNetPlayer LocalPlayer => instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault(); + public static VelNetPlayer LocalPlayer => instance != null ? instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault() : null; public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; + public static string Room => LocalPlayer?.room; + + /// + /// The player count in this room. + /// -1 if not in a room. + /// + public static int PlayerCount => instance.players.Count; + + /// + /// The player count in all rooms. + /// Will include players connected to the server but not in a room? + /// + public static int PlayerCountInAllRooms => PlayerCount; // TODO hook up to actual player count + + public static bool IsConnected => instance != null && instance.connected && instance.udpConnected; //this is for sending udp packets @@ -396,7 +410,7 @@ namespace VelNet private void OnApplicationQuit() { - socketConnection.Close(); + socketConnection?.Close(); } /// @@ -845,14 +859,26 @@ namespace VelNet public static bool TakeOwnership(string networkId) { // local player must exist - if (LocalPlayer == null) return false; - + if (LocalPlayer == null) + { + Debug.LogError("Can't take ownership. No local player."); + return false; + } + // obj must exist - if (!instance.objects.ContainsKey(networkId)) return false; + if (!instance.objects.ContainsKey(networkId)) + { + Debug.LogError("Can't take ownership. Object with that network id doesn't exist."); + return false; + } // if the ownership is locked, fail - if (instance.objects[networkId].ownershipLocked) return false; - + if (instance.objects[networkId].ownershipLocked) + { + Debug.LogError("Can't take ownership. Ownership for this object is locked."); + return false; + } + // immediately successful instance.objects[networkId].owner = LocalPlayer; @@ -862,4 +888,4 @@ namespace VelNet return true; } } -} \ No newline at end of file +} diff --git a/Runtime/VelNetPlayer.cs b/Runtime/VelNetPlayer.cs index c31ab89..56bb8ff 100644 --- a/Runtime/VelNetPlayer.cs +++ b/Runtime/VelNetPlayer.cs @@ -10,8 +10,6 @@ namespace VelNet public class VelNetPlayer { public int userid; - public string username; - public string room; public bool isLocal; @@ -72,7 +70,7 @@ namespace VelNet switch (sections[0]) { - case "5": //sync update for an object I may own + case "5": // sync update for an object I may own { string objectKey = sections[1]; string identifier = sections[2]; @@ -88,7 +86,7 @@ namespace VelNet break; } - case "6": //I'm trying to take ownership of an object + case "6": // I'm trying to take ownership of an object { string networkId = sections[1]; diff --git a/package.json b/package.json index b846be2..956001f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "edu.uga.engr.vel.velnet", "displayName": "VelNet", - "version": "1.0.6", + "version": "1.0.7", "unity": "2019.1", "description": "A custom networking library for Unity.", "keywords": [ @@ -23,4 +23,4 @@ "dependencies": { } } - \ No newline at end of file + From 682c470d46cd523412033d039ad27681849644eb Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Mon, 24 Jan 2022 21:12:13 -0500 Subject: [PATCH 09/13] fixes for mouse dragger, visualization for audio range, added some more callbacks to velnetmanager, added color syncing extension, take ownership of synced textbox, fix for deleting all scene objects --- Runtime/Util/BinaryWriterExtensions.cs | 42 +- Runtime/VelNetManager.cs | 543 +++++++++++++------------ package.json | 2 +- 3 files changed, 324 insertions(+), 263 deletions(-) diff --git a/Runtime/Util/BinaryWriterExtensions.cs b/Runtime/Util/BinaryWriterExtensions.cs index f43524d..2b39a98 100644 --- a/Runtime/Util/BinaryWriterExtensions.cs +++ b/Runtime/Util/BinaryWriterExtensions.cs @@ -8,6 +8,8 @@ namespace VelNet { public static class BinaryWriterExtensions { + #region Writers + public static void Write(this BinaryWriter writer, Vector3 v) { writer.Write(v.x); @@ -23,6 +25,18 @@ namespace VelNet 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) { return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); @@ -31,19 +45,32 @@ namespace VelNet public static Quaternion ReadQuaternion(this BinaryReader reader) { return new Quaternion( - reader.ReadSingle(), - reader.ReadSingle(), - reader.ReadSingle(), + reader.ReadSingle(), + reader.ReadSingle(), + reader.ReadSingle(), reader.ReadSingle() ); } + public static Color ReadColor(this BinaryReader reader) + { + return new Color( + reader.ReadSingle(), + reader.ReadSingle(), + reader.ReadSingle(), + reader.ReadSingle() + ); + } + + #endregion + + /// /// Compresses the list of bools into bytes using a bitmask /// public static byte[] GetBitmasks(this IEnumerable bools) { - List values = bools.ToList(); + List values = bools.ToList(); List bytes = new List(); for (int b = 0; b < Mathf.Ceil(values.Count / 8f); b++) { @@ -61,7 +88,7 @@ namespace VelNet return bytes.ToArray(); } - + public static List GetBitmaskValues(this IEnumerable bytes) { List l = new List(); @@ -72,7 +99,7 @@ namespace VelNet return l; } - + public static List GetBitmaskValues(this byte b) { List l = new List(); @@ -83,11 +110,10 @@ namespace VelNet return l; } - + public static bool GetBitmaskValue(this byte b, int index) { return (b & (1 << index)) != 0; } - } } \ No newline at end of file diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index 31714f7..c166404 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; @@ -8,8 +7,6 @@ using System.Threading; using UnityEngine; using System.Net; using UnityEngine.SceneManagement; -using System.Runtime.Serialization.Formatters.Binary; -using System.Runtime.Serialization; using System.IO; namespace VelNet @@ -42,10 +39,11 @@ namespace VelNet private Thread clientReceiveThread; private Thread clientReceiveThreadUDP; public int userid = -1; - private int messagesReceived = 0; public readonly Dictionary players = new Dictionary(); + #region Callbacks + /// /// We just joined a room /// string - the room name @@ -69,8 +67,12 @@ namespace VelNet public static Action OnPlayerLeft; public static Action OnConnectedToServer; - public static Action LoggedIn; - public static Action RoomsReceived; + public static Action OnLoggedIn; + public static Action RoomsReceived; + + public static Action MessageReceived; + + #endregion public bool connected; @@ -92,7 +94,7 @@ namespace VelNet public static VelNetPlayer LocalPlayer => instance != null ? instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault() : null; public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; public static string Room => LocalPlayer?.room; - + /// /// The player count in this room. /// -1 if not in a room. @@ -107,46 +109,59 @@ namespace VelNet public static bool IsConnected => instance != null && instance.connected && instance.udpConnected; - //this is for sending udp packets - static byte[] toSend = new byte[1024]; + private static readonly byte[] toSend = new byte[1024]; // Use this for initialization public abstract class Message { - } + public class ListedRoom { public string name; public int numUsers; + + public override string ToString() + { + return "Room Name: " + name + "\tUsers: " + numUsers; + } } - public class LoginMessage: Message + + public class LoginMessage : Message { public int userId; } - public class RoomsMessage: Message + + public class RoomsMessage : Message { public List rooms; + + public override string ToString() + { + return string.Join("\n", rooms); + } } - public class JoinMessage: Message + + public class JoinMessage : Message { public int userId; public string room; } - public class DataMessage: Message + + public class DataMessage : Message { public int senderId; public byte[] data; } - public class ChangeMasterMessage: Message + + public class ChangeMasterMessage : Message { public int masterId; } - public class ConnectedMessage: Message + public class ConnectedMessage : Message { - } public readonly List receivedMessages = new List(); @@ -180,6 +195,15 @@ namespace VelNet //Debug.Log(messagesReceived++); receivedMessages.Add(m); } + + try + { + MessageReceived?.Invoke(m); + } + catch (Exception e) + { + Debug.LogError(e); + } } private void Update() @@ -191,216 +215,236 @@ namespace VelNet { switch (m) { - case ConnectedMessage connected: + case ConnectedMessage msg: + { + try { - try - { - OnConnectedToServer?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - break; + OnConnectedToServer?.Invoke(); } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + + break; + } case LoginMessage lm: + { + userid = lm.userId; + Debug.Log("Joined server " + userid); + + try { - userid = lm.userId; - Debug.Log("joined server " + userid); - - try - { - LoggedIn?.Invoke(); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - - //start the udp thread - clientReceiveThreadUDP = new Thread(ListenForDataUDP); - clientReceiveThreadUDP.IsBackground = true; - clientReceiveThreadUDP.Start(); - - break; + OnLoggedIn?.Invoke(); } - case RoomsMessage rm: { - Debug.Log("Got Rooms Message"); - - break; + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); } - case JoinMessage jm: { - if(userid == jm.userId) //this is us + + //start the udp thread + clientReceiveThreadUDP = new Thread(ListenForDataUDP); + clientReceiveThreadUDP.Start(); + + break; + } + case RoomsMessage rm: + { + Debug.Log("Got Rooms Message:\n" + rm); + + try + { + RoomsReceived?.Invoke(rm); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + + break; + } + case JoinMessage jm: + { + if (userid == jm.userId) //this is us + { + string oldRoom = LocalPlayer?.room; + + // we clear the list, but will recreate as we get messages from people in our room + players.Clear(); + masterPlayer = null; + + if (jm.room != "") { - string oldRoom = LocalPlayer?.room; - - // we clear the list, but will recreate as we get messages from people in our room - players.Clear(); - masterPlayer = null; - - if (jm.room != "") + VelNetPlayer player = new VelNetPlayer { - VelNetPlayer player = new VelNetPlayer - { - isLocal = true, - userid = jm.userId, - room = jm.room - }; + isLocal = true, + userid = jm.userId, + room = jm.room + }; - players.Add(userid, player); - - try - { - OnJoinedRoom?.Invoke(jm.room); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } - + players.Add(userid, player); + + try + { + OnJoinedRoom?.Invoke(jm.room); } - // we just left a room - else + // prevent errors in subscribers from breaking our code + catch (Exception e) { - // delete all networkobjects that aren't sceneobjects or are null now - objects - .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) - .Select(o => o.Key) - .ToList().ForEach(NetworkDestroy); - - // then remove references to the ones that are left - objects.Clear(); - - // empty all the groups - foreach (string group in instance.groups.Keys) - { - SetupMessageGroup(group, new List()); - } - - instance.groups.Clear(); - - try - { - OnLeftRoom?.Invoke(oldRoom); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } + Debug.LogError(e); } } + // we just left a room else { - VelNetPlayer me = players[userid]; + // delete all networkobjects that aren't sceneobjects or are null now + objects + .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) + .Select(o => o.Key) + .ToList().ForEach(NetworkDestroy); - if (me.room != jm.room) + // then remove references to the ones that are left + objects.Clear(); + + // empty all the groups + foreach (string group in instance.groups.Keys) { - // we got a left message, kill it - // change ownership of all objects to master - List deleteObjects = new List(); - foreach (KeyValuePair kvp in objects) + SetupMessageGroup(group, new List()); + } + + instance.groups.Clear(); + + try + { + OnLeftRoom?.Invoke(oldRoom); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } + } + } + else + { + VelNetPlayer me = players[userid]; + + if (me.room != jm.room) + { + // we got a left message, kill it + // change ownership of all objects to master + List deleteObjects = new List(); + foreach (KeyValuePair kvp in objects) + { + if (kvp.Value.owner == players[jm.userId]) // the owner is the player that left { - if (kvp.Value.owner == players[jm.userId]) // the owner is the player that left + // if this object has locked ownership, delete it + if (kvp.Value.ownershipLocked) { - // if this object has locked ownership, delete it - if (kvp.Value.ownershipLocked) - { - deleteObjects.Add(kvp.Value.networkId); - } - // I'm the local master player, so can take ownership immediately - else if (me.isLocal && me == masterPlayer) - { - TakeOwnership(kvp.Key); - } - // the master player left, so everyone should set the owner null (we should get a new master shortly) - else if (players[jm.userId] == masterPlayer) - { - kvp.Value.owner = null; - } + deleteObjects.Add(kvp.Value.networkId); + } + // I'm the local master player, so can take ownership immediately + else if (me.isLocal && me == masterPlayer) + { + TakeOwnership(kvp.Key); + } + // the master player left, so everyone should set the owner null (we should get a new master shortly) + else if (players[jm.userId] == masterPlayer) + { + kvp.Value.owner = null; } } - - // TODO this may check for ownership in the future. We don't need ownership here - deleteObjects.ForEach(NetworkDestroy); - - players.Remove(jm.userId); } - else + + // TODO this may check for ownership in the future. We don't need ownership here + deleteObjects.ForEach(NetworkDestroy); + + VelNetPlayer leftPlayer = players[jm.userId]; + players.Remove(jm.userId); + + try { - // we got a join message, create it - VelNetPlayer player = new VelNetPlayer - { - isLocal = false, - room = jm.room, - userid = jm.userId - }; - players.Add(jm.userId, player); - try - { - OnPlayerJoined?.Invoke(player); - } - // prevent errors in subscribers from breaking our code - catch (Exception e) - { - Debug.LogError(e); - } + OnPlayerLeft?.Invoke(leftPlayer); } - } - break; - - } - case DataMessage dm: { - if (players.ContainsKey(dm.senderId)) - { - players[dm.senderId]?.HandleMessage(dm); //todo - } - else - { - Debug.LogError("Received message from player that doesn't exist "); - } - - break; - - - } - case ChangeMasterMessage cm: { - - if (masterPlayer == null) - { - masterPlayer = players[cm.masterId]; - - // no master player yet, add the scene objects - - for (int i = 0; i < sceneObjects.Length; i++) + // prevent errors in subscribers from breaking our code + catch (Exception e) { - sceneObjects[i].networkId = -1 + "-" + i; - sceneObjects[i].owner = masterPlayer; - sceneObjects[i].isSceneObject = true; // needed for special handling when deleted - objects.Add(sceneObjects[i].networkId, sceneObjects[i]); + Debug.LogError(e); } } else { - masterPlayer = players[cm.masterId]; + // we got a join message, create it + VelNetPlayer player = new VelNetPlayer + { + isLocal = false, + room = jm.room, + userid = jm.userId + }; + players.Add(jm.userId, player); + try + { + OnPlayerJoined?.Invoke(player); + } + // prevent errors in subscribers from breaking our code + catch (Exception e) + { + Debug.LogError(e); + } } - - masterPlayer.SetAsMasterPlayer(); - - // master player should take over any objects that do not have an owner - foreach (KeyValuePair kvp in objects) - { - kvp.Value.owner ??= masterPlayer; - } - - break; - } + + break; + } + case DataMessage dm: + { + if (players.ContainsKey(dm.senderId)) + { + players[dm.senderId]?.HandleMessage(dm); //todo + } + else + { + Debug.LogError("Received message from player that doesn't exist "); + } + + break; + } + case ChangeMasterMessage cm: + { + if (masterPlayer == null) + { + masterPlayer = players[cm.masterId]; + + // no master player yet, add the scene objects + + for (int i = 0; i < sceneObjects.Length; i++) + { + sceneObjects[i].networkId = -1 + "-" + i; + sceneObjects[i].owner = masterPlayer; + sceneObjects[i].isSceneObject = true; // needed for special handling when deleted + objects.Add(sceneObjects[i].networkId, sceneObjects[i]); + } + } + else + { + masterPlayer = players[cm.masterId]; + } + + masterPlayer.SetAsMasterPlayer(); + + // master player should take over any objects that do not have an owner + foreach (KeyValuePair kvp in objects) + { + kvp.Value.owner ??= masterPlayer; + } + + break; + } } - + //MessageReceived?.Invoke(m); } @@ -421,7 +465,6 @@ namespace VelNet try { clientReceiveThread = new Thread(ListenForData); - clientReceiveThread.IsBackground = true; clientReceiveThread.Start(); } catch (Exception e) @@ -432,10 +475,9 @@ namespace VelNet /// - /// Runs in background clientReceiveThread; Listens for incomming data. - /// - /// - private byte[] ReadExact(NetworkStream stream, int N) + /// Runs in background clientReceiveThread; Listens for incoming data. + /// + private static byte[] ReadExact(Stream stream, int N) { byte[] toReturn = new byte[N]; @@ -446,20 +488,15 @@ namespace VelNet numRead += stream.Read(toReturn, numRead, numLeft); numLeft = N - numRead; } + return toReturn; } - private int GetIntFromBytes(byte[] bytes) + private static int GetIntFromBytes(byte[] bytes) { - if (BitConverter.IsLittleEndian) - { - return BitConverter.ToInt32(bytes.Reverse().ToArray(),0); - } - else - { - return BitConverter.ToInt32(bytes, 0); - } + return BitConverter.ToInt32(BitConverter.IsLittleEndian ? bytes.Reverse().ToArray() : bytes, 0); } + private void ListenForData() { connected = true; @@ -476,44 +513,43 @@ namespace VelNet //SendToGroup("close", Encoding.UTF8.GetBytes("HelloGroup")); while (true) { - // Get a stream object for reading - + //read a byte byte type = (byte)stream.ReadByte(); - + if (type == 0) //login { LoginMessage m = new LoginMessage(); m.userId = GetIntFromBytes(ReadExact(stream, 4)); //not really the sender... AddMessage(m); } - else if(type == 1) //rooms + else if (type == 1) //rooms { - RoomsMessage m = new RoomsMessage(); m.rooms = new List(); int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload byte[] utf8data = ReadExact(stream, N); string roomMessage = Encoding.UTF8.GetString(utf8data); - string[] sections = roomMessage.Split(','); foreach (string s in sections) { string[] pieces = s.Split(':'); - if (pieces.Length == 2) { + if (pieces.Length == 2) + { ListedRoom lr = new ListedRoom(); lr.name = pieces[0]; lr.numUsers = int.Parse(pieces[1]); m.rooms.Add(lr); } } + AddMessage(m); } - else if(type == 2) //joined + else if (type == 2) //joined { JoinMessage m = new JoinMessage(); m.userId = GetIntFromBytes(ReadExact(stream, 4)); @@ -521,7 +557,8 @@ namespace VelNet byte[] utf8data = ReadExact(stream, N); //the room name, encoded as utf-8 m.room = Encoding.UTF8.GetString(utf8data); AddMessage(m); - }else if(type == 3) //data + } + else if (type == 3) //data { DataMessage m = new DataMessage(); m.senderId = GetIntFromBytes(ReadExact(stream, 4)); @@ -529,10 +566,10 @@ namespace VelNet m.data = ReadExact(stream, N); //the message AddMessage(m); } - else if(type == 4) //new master + else if (type == 4) //new master { ChangeMasterMessage m = new ChangeMasterMessage(); - m.masterId = (int)GetIntFromBytes(ReadExact(stream, 4)); //sender is the new master + m.masterId = GetIntFromBytes(ReadExact(stream, 4)); //sender is the new master AddMessage(m); } } @@ -584,20 +621,24 @@ namespace VelNet while (true) { int numReceived = udpSocket.Receive(buffer); - if (buffer[0] == 0) + switch (buffer[0]) { - Debug.Log("UDP connected"); - }else if (buffer[0] == 3) - { - DataMessage m = new DataMessage(); - //we should get the sender address - byte[] senderBytes = new byte[4]; - Array.Copy(buffer, 1, senderBytes, 0, 4); - m.senderId = GetIntFromBytes(senderBytes); - byte[] messageBytes = new byte[numReceived - 5]; - Array.Copy(buffer, 5, messageBytes, 0, messageBytes.Length); - m.data = messageBytes; - AddMessage(m); + case 0: + Debug.Log("UDP connected"); + break; + case 3: + { + DataMessage m = new DataMessage(); + //we should get the sender address + byte[] senderBytes = new byte[4]; + Array.Copy(buffer, 1, senderBytes, 0, 4); + m.senderId = GetIntFromBytes(senderBytes); + byte[] messageBytes = new byte[numReceived - 5]; + Array.Copy(buffer, 5, messageBytes, 0, messageBytes.Length); + m.data = messageBytes; + AddMessage(m); + break; + } } } } @@ -634,8 +675,7 @@ namespace VelNet NetworkStream stream = instance.socketConnection.GetStream(); if (stream.CanWrite) { - - stream.Write(message,0,message.Length); + stream.Write(message, 0, message.Length); } } catch (SocketException socketException) @@ -652,9 +692,9 @@ namespace VelNet { return BitConverter.GetBytes(n).Reverse().ToArray(); } + public static void Login(string username, string password) { - MemoryStream stream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(stream); @@ -667,14 +707,11 @@ namespace VelNet writer.Write(pB); SendTcpMessage(stream.ToArray()); - - } public static void GetRooms() { - - SendTcpMessage(new byte[1] { 1 }); //very simple message + SendTcpMessage(new byte[] { 1 }); //very simple message } /// @@ -691,13 +728,8 @@ namespace VelNet writer.Write((byte)R.Length); writer.Write(R); SendTcpMessage(stream.ToArray()); - - - - } - /// /// Leaves a room if we're in one @@ -712,12 +744,12 @@ namespace VelNet public static void SendToRoom(byte[] message, bool include_self = false, bool reliable = true, bool ordered = false) { - byte sendType = (byte) MessageSendType.MESSAGE_OTHERS; + byte sendType = (byte)MessageSendType.MESSAGE_OTHERS; if (include_self && ordered) sendType = (byte)MessageSendType.MESSAGE_ALL_ORDERED; if (include_self && !ordered) sendType = (byte)MessageSendType.MESSAGE_ALL; if (!include_self && ordered) sendType = (byte)MessageSendType.MESSAGE_OTHERS_ORDERED; - + if (reliable) { MemoryStream stream = new MemoryStream(); @@ -730,10 +762,10 @@ namespace VelNet else { //udp message needs the type - toSend[0] = sendType; //we don't + toSend[0] = sendType; //we don't Array.Copy(get_be_bytes(instance.userid), 0, toSend, 1, 4); Array.Copy(message, 0, toSend, 5, message.Length); - SendUdpMessage(toSend,message.Length+5); //shouldn't be over 1024... + SendUdpMessage(toSend, message.Length + 5); //shouldn't be over 1024... } } @@ -759,7 +791,7 @@ namespace VelNet //also need to send the group toSend[5] = (byte)utf8bytes.Length; Array.Copy(utf8bytes, 0, toSend, 6, utf8bytes.Length); - Array.Copy(message, 0, toSend, 6+utf8bytes.Length, message.Length); + Array.Copy(message, 0, toSend, 6 + utf8bytes.Length, message.Length); SendUdpMessage(toSend, 6 + utf8bytes.Length + message.Length); } } @@ -785,6 +817,7 @@ namespace VelNet { writer.Write(get_be_bytes(client_ids[i])); } + SendTcpMessage(stream.ToArray()); } @@ -805,6 +838,7 @@ namespace VelNet Debug.LogError("Can't instantiate object. Obj with that network ID was already instantiated.", instance.objects[networkId]); return null; } + NetworkObject newObject = Instantiate(prefab); newObject.networkId = networkId; newObject.prefabName = prefabName; @@ -812,7 +846,7 @@ namespace VelNet instance.objects.Add(newObject.networkId, newObject); // only sent to others, as I already instantiated this. Nice that it happens immediately. - SendToRoom(Encoding.UTF8.GetBytes("7," + newObject.networkId + "," + prefabName),false,true); + SendToRoom(Encoding.UTF8.GetBytes("7," + newObject.networkId + "," + prefabName), false, true); return newObject; } @@ -842,6 +876,7 @@ namespace VelNet instance.objects.Remove(networkId); return; } + if (obj.isSceneObject) { instance.deletedSceneObjects.Add(networkId); @@ -864,7 +899,7 @@ namespace VelNet Debug.LogError("Can't take ownership. No local player."); return false; } - + // obj must exist if (!instance.objects.ContainsKey(networkId)) { @@ -878,7 +913,7 @@ namespace VelNet Debug.LogError("Can't take ownership. Ownership for this object is locked."); return false; } - + // immediately successful instance.objects[networkId].owner = LocalPlayer; @@ -888,4 +923,4 @@ namespace VelNet return true; } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 956001f..c278bc1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "edu.uga.engr.vel.velnet", "displayName": "VelNet", - "version": "1.0.7", + "version": "1.0.8", "unity": "2019.1", "description": "A custom networking library for Unity.", "keywords": [ From 08a1a72348d08497f3528ebf3097f27b44817909 Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Mon, 24 Jan 2022 21:32:09 -0500 Subject: [PATCH 10/13] added hybrid onchange compression to serializedobject helpers --- Runtime/Util/BinaryWriterExtensions.cs | 10 +++++ Runtime/Util/NetworkSerializedObject.cs | 38 +++++++++++++++++-- Runtime/Util/NetworkSerializedObjectStream.cs | 30 +++++++++++++-- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/Runtime/Util/BinaryWriterExtensions.cs b/Runtime/Util/BinaryWriterExtensions.cs index 2b39a98..a605837 100644 --- a/Runtime/Util/BinaryWriterExtensions.cs +++ b/Runtime/Util/BinaryWriterExtensions.cs @@ -65,6 +65,16 @@ namespace VelNet #endregion + public static bool SameAs(this byte[] bytes, byte[] otherBytes) + { + if (bytes.Length != otherBytes.Length) + { + return false; + } + + return !bytes.Where((t, i) => t != otherBytes[i]).Any(); + } + /// /// Compresses the list of bools into bytes using a bitmask /// diff --git a/Runtime/Util/NetworkSerializedObject.cs b/Runtime/Util/NetworkSerializedObject.cs index 0569eed..f6fc434 100644 --- a/Runtime/Util/NetworkSerializedObject.cs +++ b/Runtime/Util/NetworkSerializedObject.cs @@ -1,6 +1,6 @@ -using System.Collections; +using System; +using System.Collections; using UnityEngine; -using UnityEngine.Serialization; namespace VelNet { @@ -9,6 +9,15 @@ namespace VelNet [Tooltip("Send rate of this object. This caps out at the framerate of the game.")] public float serializationRateHz = 30; + /// + /// If the data hasn't changed, only sends updates across the network at 1Hz + /// + public bool hybridOnChangeCompression = true; + + private byte[] lastSentBytes; + private double lastSendTime; + private const double slowSendInterval = 2; + protected virtual void Awake() { StartCoroutine(SendMessageUpdate()); @@ -18,9 +27,30 @@ namespace VelNet { while (true) { - if (IsMine) + try { - SendBytes(SendState()); + if (IsMine) + { + byte[] newBytes = SendState(); + if (hybridOnChangeCompression) + { + if (Time.timeAsDouble - lastSendTime > slowSendInterval || !lastSentBytes.SameAs(newBytes)) + { + SendBytes(newBytes); + } + } + else + { + SendBytes(newBytes); + } + + lastSendTime = Time.timeAsDouble; + lastSentBytes = newBytes; + } + } + catch (Exception e) + { + Debug.LogError(e); } yield return new WaitForSeconds(1f / serializationRateHz); diff --git a/Runtime/Util/NetworkSerializedObjectStream.cs b/Runtime/Util/NetworkSerializedObjectStream.cs index 161791f..d3ac592 100644 --- a/Runtime/Util/NetworkSerializedObjectStream.cs +++ b/Runtime/Util/NetworkSerializedObjectStream.cs @@ -2,7 +2,6 @@ using System.Collections; using System.IO; using UnityEngine; -using UnityEngine.Serialization; namespace VelNet { @@ -11,6 +10,16 @@ namespace VelNet [Tooltip("Send rate of this object. This caps out at the framerate of the game.")] public float serializationRateHz = 30; + /// + /// If the data hasn't changed, only sends updates across the network at 1Hz + /// + public bool hybridOnChangeCompression = true; + + private byte[] lastSentBytes; + private double lastSendTime; + private const double slowSendInterval = 2; + + protected virtual void Awake() { StartCoroutine(SendMessageUpdate()); @@ -27,7 +36,22 @@ namespace VelNet using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); SendState(writer); - SendBytes(mem.ToArray()); + + byte[] newBytes = mem.ToArray(); + if (hybridOnChangeCompression) + { + if (Time.timeAsDouble - lastSendTime > slowSendInterval || !lastSentBytes.SameAs(newBytes)) + { + SendBytes(newBytes); + } + } + else + { + SendBytes(newBytes); + } + + lastSendTime = Time.timeAsDouble; + lastSentBytes = newBytes; } } catch (Exception e) @@ -44,7 +68,7 @@ namespace VelNet { using MemoryStream mem = new MemoryStream(message); using BinaryReader reader = new BinaryReader(mem); - + ReceiveState(reader); } From a61796e28de46b52f9a7a1a1be7d494ee37567fc Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Tue, 25 Jan 2022 21:28:35 -0500 Subject: [PATCH 11/13] moved message encoding to binary instead of string --- Runtime/NetworkObject.cs | 5 +- Runtime/Util/BinaryWriterExtensions.cs | 12 +- Runtime/Util/NetworkSerializedObject.cs | 2 +- Runtime/Util/NetworkSerializedObjectStream.cs | 2 +- Runtime/VelNetManager.cs | 170 +++++++++++------- Runtime/VelNetPlayer.cs | 116 ++++++++---- 6 files changed, 199 insertions(+), 108 deletions(-) diff --git a/Runtime/NetworkObject.cs b/Runtime/NetworkObject.cs index 5c605ac..9c09aef 100644 --- a/Runtime/NetworkObject.cs +++ b/Runtime/NetworkObject.cs @@ -62,7 +62,7 @@ namespace VelNet } else { - owner.SendMessage(this, index.ToString(), message, reliable); + VelNetPlayer.SendMessage(this, (byte)index, message, reliable); } } @@ -76,7 +76,7 @@ namespace VelNet // send the message and an identifier for which component it belongs to 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) @@ -128,6 +128,7 @@ namespace VelNet foreach (NetworkComponent c in comps) { c.networkObject = t; + PrefabUtility.RecordPrefabInstancePropertyModifications(c); } PrefabUtility.RecordPrefabInstancePropertyModifications(t); } diff --git a/Runtime/Util/BinaryWriterExtensions.cs b/Runtime/Util/BinaryWriterExtensions.cs index a605837..309a239 100644 --- a/Runtime/Util/BinaryWriterExtensions.cs +++ b/Runtime/Util/BinaryWriterExtensions.cs @@ -65,14 +65,20 @@ namespace VelNet #endregion - public static bool SameAs(this byte[] bytes, byte[] otherBytes) + public static bool BytesSame(byte[] b1, byte[] b2) { - if (bytes.Length != otherBytes.Length) + 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; } - return !bytes.Where((t, i) => t != otherBytes[i]).Any(); + // check if any bytes are different + return !b1.Where((t, i) => t != b2[i]).Any(); } /// diff --git a/Runtime/Util/NetworkSerializedObject.cs b/Runtime/Util/NetworkSerializedObject.cs index f6fc434..a645854 100644 --- a/Runtime/Util/NetworkSerializedObject.cs +++ b/Runtime/Util/NetworkSerializedObject.cs @@ -34,7 +34,7 @@ namespace VelNet byte[] newBytes = SendState(); if (hybridOnChangeCompression) { - if (Time.timeAsDouble - lastSendTime > slowSendInterval || !lastSentBytes.SameAs(newBytes)) + if (Time.timeAsDouble - lastSendTime > slowSendInterval || !BinaryWriterExtensions.BytesSame(lastSentBytes, newBytes)) { SendBytes(newBytes); } diff --git a/Runtime/Util/NetworkSerializedObjectStream.cs b/Runtime/Util/NetworkSerializedObjectStream.cs index d3ac592..7623122 100644 --- a/Runtime/Util/NetworkSerializedObjectStream.cs +++ b/Runtime/Util/NetworkSerializedObjectStream.cs @@ -40,7 +40,7 @@ namespace VelNet byte[] newBytes = mem.ToArray(); if (hybridOnChangeCompression) { - if (Time.timeAsDouble - lastSendTime > slowSendInterval || !lastSentBytes.SameAs(newBytes)) + if (Time.timeAsDouble - lastSendTime > slowSendInterval || !BinaryWriterExtensions.BytesSame(lastSentBytes, newBytes)) { SendBytes(newBytes); } diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index c166404..670e2c3 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -27,6 +27,15 @@ namespace VelNet MESSAGE_SETGROUP = 6 }; + public enum MessageType + { + ObjectSync, + TakeOwnership, + Instantiate, + Destroy, + DeleteSceneObjects + } + public string host; public int port; @@ -455,6 +464,8 @@ namespace VelNet private void OnApplicationQuit() { socketConnection?.Close(); + clientReceiveThreadUDP?.Abort(); + clientReceiveThread?.Abort(); } /// @@ -472,11 +483,13 @@ namespace VelNet Debug.Log("On client connect exception " + e); } } - - - /// - /// Runs in background clientReceiveThread; Listens for incoming data. + + /// + /// Reads N bytes /// + /// + /// + /// private static byte[] ReadExact(Stream stream, int N) { byte[] toReturn = new byte[N]; @@ -505,6 +518,7 @@ namespace VelNet socketConnection = new TcpClient(host, port); socketConnection.NoDelay = true; NetworkStream stream = socketConnection.GetStream(); + using BinaryReader reader = new BinaryReader(stream); //now we are connected, so add a message to the queue AddMessage(new ConnectedMessage()); //Join("MyRoom"); @@ -515,62 +529,74 @@ namespace VelNet { // Get a stream object for reading - //read a byte - byte type = (byte)stream.ReadByte(); + byte type = reader.ReadByte(); - if (type == 0) //login + switch (type) { - LoginMessage m = new LoginMessage(); - m.userId = GetIntFromBytes(ReadExact(stream, 4)); //not really the sender... - AddMessage(m); - } - else if (type == 1) //rooms - { - RoomsMessage m = new RoomsMessage(); - m.rooms = new List(); - int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload - byte[] utf8data = ReadExact(stream, N); - string roomMessage = Encoding.UTF8.GetString(utf8data); - - - string[] sections = roomMessage.Split(','); - foreach (string s in sections) + //login + case 0: { - string[] pieces = s.Split(':'); - if (pieces.Length == 2) - { - ListedRoom lr = new ListedRoom(); - lr.name = pieces[0]; - lr.numUsers = int.Parse(pieces[1]); - m.rooms.Add(lr); - } + LoginMessage m = new LoginMessage(); + m.userId = reader.ReadInt32(); //not really the sender... + AddMessage(m); + break; } + //rooms + case 1: + { + RoomsMessage m = new RoomsMessage(); + m.rooms = new List(); + int N = reader.ReadInt32(); //the size of the payload + byte[] utf8data = reader.ReadBytes(N); + string roomMessage = Encoding.UTF8.GetString(utf8data); - AddMessage(m); - } - else if (type == 2) //joined - { - JoinMessage m = new JoinMessage(); - m.userId = GetIntFromBytes(ReadExact(stream, 4)); - int N = stream.ReadByte(); - byte[] utf8data = ReadExact(stream, N); //the room name, encoded as utf-8 - m.room = Encoding.UTF8.GetString(utf8data); - AddMessage(m); - } - else if (type == 3) //data - { - DataMessage m = new DataMessage(); - m.senderId = GetIntFromBytes(ReadExact(stream, 4)); - int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload - m.data = ReadExact(stream, N); //the message - AddMessage(m); - } - else if (type == 4) //new master - { - ChangeMasterMessage m = new ChangeMasterMessage(); - m.masterId = GetIntFromBytes(ReadExact(stream, 4)); //sender is the new master - AddMessage(m); + + string[] sections = roomMessage.Split(','); + foreach (string s in sections) + { + string[] pieces = s.Split(':'); + if (pieces.Length == 2) + { + ListedRoom lr = new ListedRoom(); + lr.name = pieces[0]; + lr.numUsers = int.Parse(pieces[1]); + m.rooms.Add(lr); + } + } + + AddMessage(m); + break; + } + //joined + case 2: + { + JoinMessage m = new JoinMessage(); + m.userId = reader.ReadInt32(); + int N = reader.ReadByte(); + byte[] utf8data = reader.ReadBytes(N); //the room name, encoded as utf-8 + m.room = Encoding.UTF8.GetString(utf8data); + AddMessage(m); + break; + } + //data + case 3: + { + DataMessage m = new DataMessage(); + m.senderId = reader.ReadInt32(); + int N = reader.ReadInt32(); //the size of the payload + m.data = reader.ReadBytes(N); //the message + AddMessage(m); + break; + } + //new master + case 4: + { + ChangeMasterMessage m = new ChangeMasterMessage(); + m.masterId = reader.ReadInt32(); //sender is the new master + AddMessage(m); + break; + } } } } @@ -659,7 +685,7 @@ namespace VelNet } /// - /// Send message to server using socket connection. + /// Send message to server using socket connection. /// private static void SendTcpMessage(byte[] message) //we can assume that this message is already formatted, so we just send it { @@ -684,15 +710,14 @@ namespace VelNet } } - /// - /// Connects to the server with a username - /// - /// public static byte[] get_be_bytes(int n) { return BitConverter.GetBytes(n).Reverse().ToArray(); } + /// + /// Connects to the server with a username + /// public static void Login(string username, string password) { MemoryStream stream = new MemoryStream(); @@ -742,7 +767,7 @@ namespace VelNet } } - public static void SendToRoom(byte[] message, bool include_self = false, bool reliable = true, bool ordered = false) + internal static void SendToRoom(byte[] message, bool include_self = false, bool reliable = true, bool ordered = false) { byte sendType = (byte)MessageSendType.MESSAGE_OTHERS; if (include_self && ordered) sendType = (byte)MessageSendType.MESSAGE_ALL_ORDERED; @@ -752,12 +777,12 @@ namespace VelNet if (reliable) { - MemoryStream stream = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(stream); + MemoryStream mem = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(mem); writer.Write(sendType); writer.Write(get_be_bytes(message.Length)); writer.Write(message); - SendTcpMessage(stream.ToArray()); + SendTcpMessage(mem.ToArray()); } else { @@ -770,7 +795,7 @@ namespace VelNet } - public static void SendToGroup(string group, byte[] message, bool reliable = true) + internal static void SendToGroup(string group, byte[] message, bool reliable = true) { byte[] utf8bytes = Encoding.UTF8.GetBytes(group); if (reliable) @@ -845,8 +870,14 @@ namespace VelNet newObject.owner = localPlayer; instance.objects.Add(newObject.networkId, newObject); + // only sent to others, as I already instantiated this. Nice that it happens immediately. - SendToRoom(Encoding.UTF8.GetBytes("7," + newObject.networkId + "," + prefabName), false, true); + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + writer.Write((byte)MessageType.Instantiate); + writer.Write(newObject.networkId); + writer.Write(prefabName); + SendToRoom(mem.ToArray(), include_self:false, reliable:true); return newObject; } @@ -917,8 +948,13 @@ namespace VelNet // immediately successful instance.objects[networkId].owner = LocalPlayer; - // 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. - SendToRoom(Encoding.UTF8.GetBytes("6," + networkId)); + // 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. + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + writer.Write((byte)MessageType.TakeOwnership); + writer.Write(networkId); + SendToRoom(mem.ToArray(), false, true); return true; } diff --git a/Runtime/VelNetPlayer.cs b/Runtime/VelNetPlayer.cs index 56bb8ff..f4fb524 100644 --- a/Runtime/VelNetPlayer.cs +++ b/Runtime/VelNetPlayer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System; +using System.IO; using System.Text; namespace VelNet @@ -40,7 +41,12 @@ namespace VelNet { if (kvp.Value.owner == this && kvp.Value.prefabName != "") { - VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("7," + kvp.Value.networkId + "," + kvp.Value.prefabName),false,true); + 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); } } @@ -54,27 +60,39 @@ namespace VelNet /// /// These are generally things that come from the "owner" and should be enacted locally, where appropriate + /// + /// Overall message encoding: + /// uint16: numMessages + /// for m in numMessages + /// int32: message size (including message type) + /// byte: message type + /// byte[]: message /// public void HandleMessage(VelNetManager.DataMessage m) { - //for now, we can just convert to text...because + using MemoryStream mem = new MemoryStream(m.data); + using BinaryReader reader = new BinaryReader(mem); - string text = Encoding.UTF8.GetString(m.data); - - //types of messages - string[] messages = text.Split(';'); //messages are split by ; - foreach (string s in messages) + ushort numMessages = reader.ReadUInt16(); + + for (int i = 0; i < numMessages; i++) { //individual message parameters separated by comma - string[] sections = s.Split(','); + int messageLength = reader.ReadInt32(); + VelNetManager.MessageType messageType = (VelNetManager.MessageType)reader.ReadByte(); + byte[] message = reader.ReadBytes(messageLength-1); + + // make a separate reader to prevent malformed messages from messing us up + using MemoryStream messageMem = new MemoryStream(message); + using BinaryReader messageReader = new BinaryReader(messageMem); - switch (sections[0]) + switch (messageType) { - case "5": // sync update for an object I may own + case VelNetManager.MessageType.ObjectSync: // sync update for an object I may own { - string objectKey = sections[1]; - string identifier = sections[2]; - string syncMessage = sections[3]; + string objectKey = messageReader.ReadString(); + string identifier = messageReader.ReadString(); + string syncMessage = messageReader.ReadString(); byte[] messageBytes = Convert.FromBase64String(syncMessage); if (manager.objects.ContainsKey(objectKey)) { @@ -86,9 +104,9 @@ namespace VelNet break; } - case "6": // I'm trying to take ownership of an object + case VelNetManager.MessageType.TakeOwnership: // I'm trying to take ownership of an object { - string networkId = sections[1]; + string networkId = messageReader.ReadString(); if (manager.objects.ContainsKey(networkId)) { @@ -97,10 +115,10 @@ namespace VelNet break; } - case "7": // I'm trying to instantiate an object + case VelNetManager.MessageType.Instantiate: // I'm trying to instantiate an object { - string networkId = sections[1]; - string prefabName = sections[2]; + string networkId = messageReader.ReadString(); + string prefabName = messageReader.ReadString(); if (manager.objects.ContainsKey(networkId)) { break; //we already have this one, ignore @@ -110,22 +128,25 @@ namespace VelNet break; } - case "8": // I'm trying to destroy a gameobject I own + case VelNetManager.MessageType.Destroy: // I'm trying to destroy a gameobject I own { - string networkId = sections[1]; + string networkId = messageReader.ReadString(); VelNetManager.NetworkDestroy(networkId); break; } - case "9": //deleted scene objects + case VelNetManager.MessageType.DeleteSceneObjects: //deleted scene objects { - for (int k = 1; k < sections.Length; k++) + int len = messageReader.ReadInt32(); + for (int k = 1; k < len; k++) { - VelNetManager.NetworkDestroy(sections[k]); + VelNetManager.NetworkDestroy(messageReader.ReadString()); } break; } + default: + throw new ArgumentOutOfRangeException(); } } } @@ -137,22 +158,39 @@ namespace VelNet //FindObjectsOfType(); } - 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) { - string message = "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data); - VelNetManager.SendToGroup(group, Encoding.UTF8.GetBytes(message), 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); + 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) { - string message = "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data); - VelNetManager.SendToRoom(Encoding.UTF8.GetBytes(message), false, 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); + VelNetManager.SendToRoom(mem.ToArray(), false, reliable); } public void SendSceneUpdate() { - string message = "9," + string.Join(",", manager.deletedSceneObjects); - VelNetManager.SendToRoom( Encoding.UTF8.GetBytes(message)); + 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.")] @@ -162,7 +200,12 @@ namespace VelNet if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return; // send to all, which will make me delete as well - VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("8," + networkId), true, true); + + 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); } /// True if successful, False if failed to transfer ownership @@ -178,8 +221,13 @@ namespace VelNet // immediately successful 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. - VelNetManager.SendToRoom(Encoding.UTF8.GetBytes("6," + networkId),true,true); + // 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. + 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; } From e3b4ae659ce917ac13ba8f4e643ec2a81e235018 Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Tue, 25 Jan 2022 22:50:30 -0500 Subject: [PATCH 12/13] correct conversion of messages to binary (maybe) --- Runtime/NetworkObject.cs | 4 +- Runtime/Util/NetworkSerializedObject.cs | 5 +- Runtime/Util/NetworkSerializedObjectStream.cs | 5 +- Runtime/VelNetManager.cs | 46 +++--- Runtime/VelNetPlayer.cs | 135 ++++++++---------- 5 files changed, 93 insertions(+), 102 deletions(-) diff --git a/Runtime/NetworkObject.cs b/Runtime/NetworkObject.cs index 9c09aef..0dd136a 100644 --- a/Runtime/NetworkObject.cs +++ b/Runtime/NetworkObject.cs @@ -79,12 +79,12 @@ namespace VelNet 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 try { - syncedComponents[int.Parse(identifier)].ReceiveBytes(message); + syncedComponents[componentIdx].ReceiveBytes(message); } catch (Exception e) { diff --git a/Runtime/Util/NetworkSerializedObject.cs b/Runtime/Util/NetworkSerializedObject.cs index a645854..7836f2b 100644 --- a/Runtime/Util/NetworkSerializedObject.cs +++ b/Runtime/Util/NetworkSerializedObject.cs @@ -29,7 +29,7 @@ namespace VelNet { try { - if (IsMine) + if (IsMine && enabled) { byte[] newBytes = SendState(); if (hybridOnChangeCompression) @@ -37,14 +37,15 @@ namespace VelNet if (Time.timeAsDouble - lastSendTime > slowSendInterval || !BinaryWriterExtensions.BytesSame(lastSentBytes, newBytes)) { SendBytes(newBytes); + lastSendTime = Time.timeAsDouble; } } else { SendBytes(newBytes); + lastSendTime = Time.timeAsDouble; } - lastSendTime = Time.timeAsDouble; lastSentBytes = newBytes; } } diff --git a/Runtime/Util/NetworkSerializedObjectStream.cs b/Runtime/Util/NetworkSerializedObjectStream.cs index 7623122..5808afd 100644 --- a/Runtime/Util/NetworkSerializedObjectStream.cs +++ b/Runtime/Util/NetworkSerializedObjectStream.cs @@ -31,7 +31,7 @@ namespace VelNet { try { - if (IsMine) + if (IsMine && enabled) { using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); @@ -43,14 +43,15 @@ namespace VelNet if (Time.timeAsDouble - lastSendTime > slowSendInterval || !BinaryWriterExtensions.BytesSame(lastSentBytes, newBytes)) { SendBytes(newBytes); + lastSendTime = Time.timeAsDouble; } } else { SendBytes(newBytes); + lastSendTime = Time.timeAsDouble; } - lastSendTime = Time.timeAsDouble; lastSentBytes = newBytes; } } diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index 670e2c3..9234857 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -412,7 +412,7 @@ namespace VelNet { if (players.ContainsKey(dm.senderId)) { - players[dm.senderId]?.HandleMessage(dm); //todo + players[dm.senderId]?.HandleMessage(dm); } else { @@ -431,7 +431,11 @@ namespace VelNet for (int i = 0; i < sceneObjects.Length; i++) { - sceneObjects[i].networkId = -1 + "-" + i; + if (sceneObjects[i].sceneNetworkId == 0) + { + Debug.LogError("Scene Network ID is 0. Make sure to assign one first.", sceneObjects[i]); + } + sceneObjects[i].networkId = -1 + "-" + sceneObjects[i].sceneNetworkId; sceneObjects[i].owner = masterPlayer; sceneObjects[i].isSceneObject = true; // needed for special handling when deleted objects.Add(sceneObjects[i].networkId, sceneObjects[i]); @@ -483,13 +487,11 @@ namespace VelNet Debug.Log("On client connect exception " + e); } } - - /// - /// Reads N bytes + + + /// + /// Runs in background clientReceiveThread; Listens for incoming data. /// - /// - /// - /// private static byte[] ReadExact(Stream stream, int N) { byte[] toReturn = new byte[N]; @@ -517,20 +519,16 @@ namespace VelNet { socketConnection = new TcpClient(host, port); socketConnection.NoDelay = true; + // Get a stream object for reading NetworkStream stream = socketConnection.GetStream(); using BinaryReader reader = new BinaryReader(stream); //now we are connected, so add a message to the queue AddMessage(new ConnectedMessage()); - //Join("MyRoom"); - //SendTo(MessageSendType.MESSAGE_OTHERS, Encoding.UTF8.GetBytes("Hello")); - //FormGroup("close", new List { 1 }); - //SendToGroup("close", Encoding.UTF8.GetBytes("HelloGroup")); while (true) { - // Get a stream object for reading //read a byte - byte type = reader.ReadByte(); + byte type = (byte)stream.ReadByte(); switch (type) { @@ -538,7 +536,7 @@ namespace VelNet case 0: { LoginMessage m = new LoginMessage(); - m.userId = reader.ReadInt32(); //not really the sender... + m.userId = GetIntFromBytes(ReadExact(stream, 4)); //not really the sender... AddMessage(m); break; } @@ -547,8 +545,8 @@ namespace VelNet { RoomsMessage m = new RoomsMessage(); m.rooms = new List(); - int N = reader.ReadInt32(); //the size of the payload - byte[] utf8data = reader.ReadBytes(N); + int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload + byte[] utf8data = ReadExact(stream, N); string roomMessage = Encoding.UTF8.GetString(utf8data); @@ -572,9 +570,9 @@ namespace VelNet case 2: { JoinMessage m = new JoinMessage(); - m.userId = reader.ReadInt32(); - int N = reader.ReadByte(); - byte[] utf8data = reader.ReadBytes(N); //the room name, encoded as utf-8 + m.userId = GetIntFromBytes(ReadExact(stream, 4)); + int N = stream.ReadByte(); + byte[] utf8data = ReadExact(stream, N); //the room name, encoded as utf-8 m.room = Encoding.UTF8.GetString(utf8data); AddMessage(m); break; @@ -583,9 +581,9 @@ namespace VelNet case 3: { DataMessage m = new DataMessage(); - m.senderId = reader.ReadInt32(); - int N = reader.ReadInt32(); //the size of the payload - m.data = reader.ReadBytes(N); //the message + m.senderId = GetIntFromBytes(ReadExact(stream, 4)); + int N = GetIntFromBytes(ReadExact(stream, 4)); //the size of the payload + m.data = ReadExact(stream, N); //the message AddMessage(m); break; } @@ -593,7 +591,7 @@ namespace VelNet case 4: { ChangeMasterMessage m = new ChangeMasterMessage(); - m.masterId = reader.ReadInt32(); //sender is the new master + m.masterId = GetIntFromBytes(ReadExact(stream, 4)); //sender is the new master AddMessage(m); break; } diff --git a/Runtime/VelNetPlayer.cs b/Runtime/VelNetPlayer.cs index f4fb524..7369dfd 100644 --- a/Runtime/VelNetPlayer.cs +++ b/Runtime/VelNetPlayer.cs @@ -61,93 +61,81 @@ namespace VelNet /// /// These are generally things that come from the "owner" and should be enacted locally, where appropriate /// - /// Overall message encoding: - /// uint16: numMessages - /// for m in numMessages - /// int32: message size (including message type) + /// Message encoding: /// byte: message type /// byte[]: message + /// + /// The length of the byte[] for message is fixed according to the message type /// public void HandleMessage(VelNetManager.DataMessage m) { using MemoryStream mem = new MemoryStream(m.data); using BinaryReader reader = new BinaryReader(mem); - - ushort numMessages = reader.ReadUInt16(); - for (int i = 0; i < numMessages; i++) + //individual message parameters separated by comma + VelNetManager.MessageType messageType = (VelNetManager.MessageType)reader.ReadByte(); + + switch (messageType) { - //individual message parameters separated by comma - int messageLength = reader.ReadInt32(); - VelNetManager.MessageType messageType = (VelNetManager.MessageType)reader.ReadByte(); - byte[] message = reader.ReadBytes(messageLength-1); - - // make a separate reader to prevent malformed messages from messing us up - using MemoryStream messageMem = new MemoryStream(message); - using BinaryReader messageReader = new BinaryReader(messageMem); - - switch (messageType) + case VelNetManager.MessageType.ObjectSync: // sync update for an object I may own { - case VelNetManager.MessageType.ObjectSync: // 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 = messageReader.ReadString(); - string identifier = messageReader.ReadString(); - string syncMessage = messageReader.ReadString(); - byte[] messageBytes = Convert.FromBase64String(syncMessage); - if (manager.objects.ContainsKey(objectKey)) + if (manager.objects[objectKey].owner == this) { - if (manager.objects[objectKey].owner == this) - { - manager.objects[objectKey].ReceiveBytes(identifier, messageBytes); - } + manager.objects[objectKey].ReceiveBytes(componentIdx, syncMessage); } - - break; } - case VelNetManager.MessageType.TakeOwnership: // I'm trying to take ownership of an object - { - string networkId = messageReader.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 = messageReader.ReadString(); - string prefabName = messageReader.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 = messageReader.ReadString(); - - VelNetManager.NetworkDestroy(networkId); - break; - } - case VelNetManager.MessageType.DeleteSceneObjects: //deleted scene objects - { - int len = messageReader.ReadInt32(); - for (int k = 1; k < len; k++) - { - VelNetManager.NetworkDestroy(messageReader.ReadString()); - } - - break; - } - default: - throw new ArgumentOutOfRangeException(); + 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; + } + default: + throw new ArgumentOutOfRangeException(); } } @@ -165,6 +153,7 @@ namespace VelNet 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); } @@ -176,6 +165,7 @@ namespace VelNet 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); } @@ -190,6 +180,7 @@ namespace VelNet { writer.Write(o); } + VelNetManager.SendToRoom(mem.ToArray()); } @@ -200,7 +191,7 @@ namespace VelNet if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return; // send to all, which will make me delete as well - + using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); writer.Write((byte)VelNetManager.MessageType.Destroy); From 743552bd58c259bf1480bea7143cffe9d0e68857 Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Sun, 30 Jan 2022 17:04:37 -0500 Subject: [PATCH 13/13] converted some magic numbers to message types, added a custom message type, added editor button for scene ids --- Editor/EditorUtils.cs | 48 ++++++++++++++++++++++++++++ Editor/EditorUtils.cs.meta | 11 +++++++ Editor/VelNet.Editor.asmdef | 18 +++++++++++ Editor/VelNet.Editor.asmdef.meta | 7 ++++ Runtime/VelNetManager.cs | 55 +++++++++++++++++++++++++------- Runtime/VelNetPlayer.cs | 8 ++++- 6 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 Editor/EditorUtils.cs create mode 100644 Editor/EditorUtils.cs.meta create mode 100644 Editor/VelNet.Editor.asmdef create mode 100644 Editor/VelNet.Editor.asmdef.meta diff --git a/Editor/EditorUtils.cs b/Editor/EditorUtils.cs new file mode 100644 index 0000000..ab66d21 --- /dev/null +++ b/Editor/EditorUtils.cs @@ -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(); + Dictionary ids = new Dictionary(); + 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 \ No newline at end of file diff --git a/Editor/EditorUtils.cs.meta b/Editor/EditorUtils.cs.meta new file mode 100644 index 0000000..a0118c0 --- /dev/null +++ b/Editor/EditorUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f2f5f489d44f614c96bcf8f493c787d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/VelNet.Editor.asmdef b/Editor/VelNet.Editor.asmdef new file mode 100644 index 0000000..044643a --- /dev/null +++ b/Editor/VelNet.Editor.asmdef @@ -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 +} \ No newline at end of file diff --git a/Editor/VelNet.Editor.asmdef.meta b/Editor/VelNet.Editor.asmdef.meta new file mode 100644 index 0000000..4389fa2 --- /dev/null +++ b/Editor/VelNet.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ae0703a992a8fe347978b1cd2dd2d7a9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index 9234857..0b5174b 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -27,13 +27,14 @@ namespace VelNet MESSAGE_SETGROUP = 6 }; - public enum MessageType + public enum MessageType : byte { ObjectSync, TakeOwnership, Instantiate, Destroy, - DeleteSceneObjects + DeleteSceneObjects, + Custom } public string host; @@ -80,6 +81,7 @@ namespace VelNet public static Action RoomsReceived; public static Action MessageReceived; + public static Action CustomMessageReceived; #endregion @@ -438,7 +440,15 @@ namespace VelNet sceneObjects[i].networkId = -1 + "-" + sceneObjects[i].sceneNetworkId; sceneObjects[i].owner = masterPlayer; sceneObjects[i].isSceneObject = true; // needed for special handling when deleted - objects.Add(sceneObjects[i].networkId, sceneObjects[i]); + + if (objects.ContainsKey(sceneObjects[i].networkId)) + { + Debug.LogError($"Duplicate NetworkID: {sceneObjects[i].networkId} {sceneObjects[i].name} {objects[sceneObjects[i].networkId]}"); + } + else + { + objects.Add(sceneObjects[i].networkId, sceneObjects[i]); + } } } else @@ -528,12 +538,12 @@ namespace VelNet { //read a byte - byte type = (byte)stream.ReadByte(); + MessageSendType type = (MessageSendType)stream.ReadByte(); switch (type) { //login - case 0: + case MessageSendType.MESSAGE_LOGIN: { LoginMessage m = new LoginMessage(); m.userId = GetIntFromBytes(ReadExact(stream, 4)); //not really the sender... @@ -541,7 +551,7 @@ namespace VelNet break; } //rooms - case 1: + case MessageSendType.MESSAGE_GETROOMS: { RoomsMessage m = new RoomsMessage(); m.rooms = new List(); @@ -567,7 +577,7 @@ namespace VelNet break; } //joined - case 2: + case MessageSendType.MESSAGE_JOINROOM: { JoinMessage m = new JoinMessage(); m.userId = GetIntFromBytes(ReadExact(stream, 4)); @@ -578,7 +588,10 @@ namespace VelNet break; } //data - case 3: + case MessageSendType.MESSAGE_OTHERS: + // case MessageSendType.MESSAGE_OTHERS_ORDERED: + // case MessageSendType.MESSAGE_ALL: + // case MessageSendType.MESSAGE_ALL_ORDERED: { DataMessage m = new DataMessage(); m.senderId = GetIntFromBytes(ReadExact(stream, 4)); @@ -588,7 +601,7 @@ namespace VelNet break; } //new master - case 4: + case MessageSendType.MESSAGE_ALL: { ChangeMasterMessage m = new ChangeMasterMessage(); m.masterId = GetIntFromBytes(ReadExact(stream, 4)); //sender is the new master @@ -723,7 +736,7 @@ namespace VelNet byte[] uB = Encoding.UTF8.GetBytes(username); byte[] pB = Encoding.UTF8.GetBytes(password); - writer.Write((byte)0); + writer.Write((byte)MessageSendType.MESSAGE_LOGIN); writer.Write((byte)uB.Length); writer.Write(uB); writer.Write((byte)pB.Length); @@ -747,7 +760,7 @@ namespace VelNet BinaryWriter writer = new BinaryWriter(stream); byte[] R = Encoding.UTF8.GetBytes(roomname); - writer.Write((byte)2); + writer.Write((byte)MessageSendType.MESSAGE_JOINROOM); writer.Write((byte)R.Length); writer.Write(R); SendTcpMessage(stream.ToArray()); @@ -765,6 +778,26 @@ namespace VelNet } } + public static void SendCustomMessage(byte[] message, bool include_self = false, bool reliable = true, bool ordered = false) + { + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + writer.Write((byte)MessageType.Custom); + writer.Write(message.Length); + writer.Write(message); + SendToRoom(mem.ToArray(), include_self, reliable, ordered); + } + + public static void SendCustomMessageToGroup(string group, byte[] message,bool reliable = true) + { + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + writer.Write((byte)MessageType.Custom); + writer.Write(message.Length); + writer.Write(message); + SendToGroup(group, mem.ToArray(), reliable); + } + internal static void SendToRoom(byte[] message, bool include_self = false, bool reliable = true, bool ordered = false) { byte sendType = (byte)MessageSendType.MESSAGE_OTHERS; diff --git a/Runtime/VelNetPlayer.cs b/Runtime/VelNetPlayer.cs index 7369dfd..1b8a3c0 100644 --- a/Runtime/VelNetPlayer.cs +++ b/Runtime/VelNetPlayer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System; using System.IO; using System.Text; +using UnityEngine; namespace VelNet { @@ -131,7 +132,12 @@ namespace VelNet { VelNetManager.NetworkDestroy(reader.ReadString()); } - + break; + } + case VelNetManager.MessageType.Custom: // custom packets + { + int len = reader.ReadInt32(); + VelNetManager.CustomMessageReceived?.Invoke(reader.ReadBytes(len)); break; } default: