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": [