From a967795b5867298a8863acbb31fa0ec5c20733f1 Mon Sep 17 00:00:00 2001 From: Kyle Johnsen Date: Mon, 3 Jan 2022 15:58:58 -0500 Subject: [PATCH] new server that handles master clients; new client that handles additional objects in scene, with some progress towards instantiation and deleting --- TestVelGameServer/Assets/NetworkObject.cs | 11 ++ .../Assets/NetworkObject.cs.meta | 11 ++ TestVelGameServer/Assets/Scenes/test.unity | 166 +++++++++++++++++- TestVelGameServer/Assets/SyncTransform.cs | 68 +++++++ .../Assets/SyncTransform.cs.meta | 11 ++ .../Assets/VelGameServer/NetworkManager.cs | 57 +++++- .../Assets/VelGameServer/NetworkPlayer.cs | 79 +++++++++ .../Assets/VelGameServer/VelCommsNetwork.cs | 3 +- server_basics.txt | 1 + threaded_server.py | 88 ++++++---- 10 files changed, 453 insertions(+), 42 deletions(-) create mode 100644 TestVelGameServer/Assets/NetworkObject.cs create mode 100644 TestVelGameServer/Assets/NetworkObject.cs.meta create mode 100644 TestVelGameServer/Assets/SyncTransform.cs create mode 100644 TestVelGameServer/Assets/SyncTransform.cs.meta diff --git a/TestVelGameServer/Assets/NetworkObject.cs b/TestVelGameServer/Assets/NetworkObject.cs new file mode 100644 index 0000000..085ebab --- /dev/null +++ b/TestVelGameServer/Assets/NetworkObject.cs @@ -0,0 +1,11 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public abstract class NetworkObject: MonoBehaviour +{ + public NetworkPlayer owner; + public string networkId; + public abstract byte[] getSyncMessage(); //local owner asks for this and sends it periodically + public abstract void handleSyncMessage(byte[] message); //remote owner will call this +} diff --git a/TestVelGameServer/Assets/NetworkObject.cs.meta b/TestVelGameServer/Assets/NetworkObject.cs.meta new file mode 100644 index 0000000..6a1a6e9 --- /dev/null +++ b/TestVelGameServer/Assets/NetworkObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd77ffdf919cc444f863d7bf0cda29ea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestVelGameServer/Assets/Scenes/test.unity b/TestVelGameServer/Assets/Scenes/test.unity index 85a848a..71e268e 100644 --- a/TestVelGameServer/Assets/Scenes/test.unity +++ b/TestVelGameServer/Assets/Scenes/test.unity @@ -1150,6 +1150,101 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 711524766} m_CullTransparentMesh: 1 +--- !u!1 &720148908 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 720148909} + - component: {fileID: 720148912} + - component: {fileID: 720148911} + - component: {fileID: 720148910} + m_Layer: 0 + m_Name: Sphere + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &720148909 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 720148908} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1720689858} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!135 &720148910 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 720148908} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &720148911 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 720148908} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &720148912 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 720148908} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} --- !u!1 &720503449 GameObject: m_ObjectHideFlags: 0 @@ -1819,11 +1914,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 03a4d4e1a7fd74c7ab2eccca4ce168db, type: 3} m_Name: m_EditorClassIdentifier: - host: neko.ugavel.com + host: localhost port: 3290 userid: -1 room: playerPrefab: {fileID: 6139051692386484099, guid: d4158ab9c4a204cdbba28d3273fc1fb3, type: 3} + prefabs: [] connected: 0 --- !u!1 &1154194181 GameObject: @@ -2255,6 +2351,7 @@ GameObject: - component: {fileID: 1434745019} - component: {fileID: 1434745022} - component: {fileID: 1434745023} + - component: {fileID: 1434745024} m_Layer: 0 m_Name: Dissonance m_TagString: Untagged @@ -2365,6 +2462,25 @@ MonoBehaviour: _tokens: [] _roomName: Global _useTrigger: 0 +--- !u!114 &1434745024 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1434745018} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 03a4d4e1a7fd74c7ab2eccca4ce168db, type: 3} + m_Name: + m_EditorClassIdentifier: + host: + port: 0 + userid: -1 + room: + playerPrefab: {fileID: 0} + prefabs: [] + connected: 0 --- !u!1 &1484033255 GameObject: m_ObjectHideFlags: 0 @@ -2634,6 +2750,54 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: -20, y: -20} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &1720689856 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1720689858} + - component: {fileID: 1720689857} + m_Layer: 0 + m_Name: TestNetworkedGameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1720689857 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1720689856} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3f1f9b0bbd93a484a987c51f1107ebe5, type: 3} + m_Name: + m_EditorClassIdentifier: + owner: {fileID: 0} + networkId: + targetPosition: {x: 0, y: 0, z: 0} + targetRotation: {x: 0, y: 0, z: 0, w: 0} +--- !u!4 &1720689858 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1720689856} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 720148909} + m_Father: {fileID: 0} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1760805524 GameObject: m_ObjectHideFlags: 0 diff --git a/TestVelGameServer/Assets/SyncTransform.cs b/TestVelGameServer/Assets/SyncTransform.cs new file mode 100644 index 0000000..cdee0b3 --- /dev/null +++ b/TestVelGameServer/Assets/SyncTransform.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using UnityEngine; + +public class SyncTransform : NetworkObject +{ + + public Vector3 targetPosition; + public Quaternion targetRotation; + + + public override byte[] getSyncMessage() + { + float[] data = new float[7]; + for(int i = 0; i < 3; i++) + { + data[i] = transform.position[i]; + data[i + 3] = transform.rotation[i]; + } + data[6] = transform.rotation[3]; + + byte[] toReturn = new byte[sizeof(float) * data.Length]; + Buffer.BlockCopy(data, 0, toReturn,0, toReturn.Length); + return toReturn; + } + + public override void handleSyncMessage(byte[] message) + { + float[] data = new float[7]; + Buffer.BlockCopy(message, 0, data, 0, message.Length); + for(int i = 0; i < 3; i++) + { + targetPosition[i] = data[i]; + targetRotation[i] = data[i + 3]; + } + targetRotation[3] = data[6]; + } + + // Start is called before the first frame update + void Start() + { + StartCoroutine(syncBehavior()); + } + + IEnumerator syncBehavior() + { + while (true) + { + if (owner != null && owner.isLocal) { + owner.syncObject(this); + } + yield return new WaitForSeconds(.1f); + } + } + // Update is called once per frame + void Update() + { + if(owner != null && !owner.isLocal) + { + transform.position = Vector3.Lerp(transform.position, targetPosition, .1f); + transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, .1f); + } + } +} diff --git a/TestVelGameServer/Assets/SyncTransform.cs.meta b/TestVelGameServer/Assets/SyncTransform.cs.meta new file mode 100644 index 0000000..cc91662 --- /dev/null +++ b/TestVelGameServer/Assets/SyncTransform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f1f9b0bbd93a484a987c51f1107ebe5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestVelGameServer/Assets/VelGameServer/NetworkManager.cs b/TestVelGameServer/Assets/VelGameServer/NetworkManager.cs index 03aab04..55efe59 100644 --- a/TestVelGameServer/Assets/VelGameServer/NetworkManager.cs +++ b/TestVelGameServer/Assets/VelGameServer/NetworkManager.cs @@ -22,6 +22,11 @@ public class NetworkManager : MonoBehaviour public Action onJoinedRoom = delegate { }; public Action onPlayerJoined = delegate { }; public Action onPlayerLeft = delegate { }; + + public List prefabs = new List(); + + public Dictionary objects = new Dictionary(); //maintains a list of all known objects on the server (ones that have ids) + NetworkPlayer masterPlayer = null; #endregion // Use this for initialization public class Message @@ -47,9 +52,9 @@ public class NetworkManager : MonoBehaviour receivedMessages.Add(m); } } - private void Update() + private void Update() { - lock(receivedMessages) { + lock(receivedMessages) { //the main thread, which can do Unity stuff foreach(Message m in receivedMessages) { if(m.type == 0) //when you join the server @@ -57,7 +62,7 @@ public class NetworkManager : MonoBehaviour this.userid = m.sender; Debug.Log("joined server"); } - + if (m.type == 2) { //if this message is for me, that means I joined a new room... @@ -104,12 +109,44 @@ public class NetworkManager : MonoBehaviour } } } - if(m.type == 3) + if(m.type == 3) //generic message { players[m.sender]?.handleMessage(m); } + if(m.type == 4) //change master player (this should only happen when the first player joins or if the master player leaves) + { + if (masterPlayer == null) + { + masterPlayer = players[m.sender]; + + //no master player yet, add the scene objects + NetworkObject[] sceneObjects = GameObject.FindObjectsOfType(); //add all local network objects + for (int i = 0; i < sceneObjects.Length; i++) + { + sceneObjects[i].networkId = -1 + "-" + i; + sceneObjects[i].owner = masterPlayer; + 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; + } + + } + messageReceived(m); } receivedMessages.Clear(); @@ -142,7 +179,7 @@ public class NetworkManager : MonoBehaviour Debug.Log("On client connect exception " + e); } } - void handleMessage(string s) + void handleMessage(string s) //this parses messages from the server, and adds them to a queue to be processed on the main thread { Message m = new Message(); string[] sections = s.Split(':'); @@ -192,6 +229,16 @@ public class NetworkManager : MonoBehaviour 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; } diff --git a/TestVelGameServer/Assets/VelGameServer/NetworkPlayer.cs b/TestVelGameServer/Assets/VelGameServer/NetworkPlayer.cs index cecc901..485afa0 100644 --- a/TestVelGameServer/Assets/VelGameServer/NetworkPlayer.cs +++ b/TestVelGameServer/Assets/VelGameServer/NetworkPlayer.cs @@ -14,6 +14,7 @@ public class NetworkPlayer : MonoBehaviour, Dissonance.IDissonancePlayer public NetworkManager manager; Vector3 networkPosition; public bool isLocal = false; + public int lastObjectId=0; //for instantiation public Dissonance.VelCommsNetwork commsNetwork; bool isSpeaking = false; @@ -25,6 +26,7 @@ public class NetworkPlayer : MonoBehaviour, Dissonance.IDissonancePlayer public Quaternion Rotation => transform.rotation; public NetworkPlayerType Type => isLocal?NetworkPlayerType.Local:NetworkPlayerType.Remote; public bool IsTracking => true; + bool isMaster = false; void Start() { @@ -132,6 +134,66 @@ public class NetworkPlayer : MonoBehaviour, Dissonance.IDissonancePlayer } break; } + case "5": //sync update for an object I may own + { + + string objectKey = sections[1]; + string syncMessage = sections[2]; + byte[] messageBytes = Convert.FromBase64String(syncMessage); + if (manager.objects.ContainsKey(objectKey)) + { + if(manager.objects[objectKey].owner == this) + { + manager.objects[objectKey].handleSyncMessage(messageBytes); + } + } + + break; + } + case "6": //I'm trying to take ownership of an object + { + int objectId = int.Parse(sections[1]); + int creatorId = int.Parse(sections[2]); + string objectKey = creatorId + "-" + objectId; + if (manager.objects.ContainsKey(objectKey)) + { + manager.objects[objectKey].owner = this; + + } + + break; + } + case "7": //I'm trying to instantiate an object (sent to everyone) + { + int objectId = int.Parse(sections[1]); + string prefabName = sections[2]; + NetworkObject temp = manager.prefabs.Find((prefab) => prefab.name == prefabName); + if (temp != null) + { + NetworkObject instance = GameObject.Instantiate(temp); + instance.networkId = this.userid + "-" + objectId; + + instance.owner = this; + manager.objects.Add(instance.networkId, instance); + } + + break; + } + case "8": //I'm trying to destroy a gameobject I own (I guess this is sent to everyone) + { + int objectId = int.Parse(sections[1]); + int creatorId = int.Parse(sections[2]); + string objectKey = creatorId + "-" + objectId; + if (manager.objects.ContainsKey(objectKey)) + { + if (manager.objects[objectKey].owner == this) + { + GameObject.Destroy(manager.objects[objectKey].gameObject); + } + manager.objects.Remove(objectKey); + } + break; + } } } @@ -154,4 +216,21 @@ public class NetworkPlayer : MonoBehaviour, Dissonance.IDissonancePlayer manager.sendTo(0, "3," + id+";"); commsNetwork.comms.TrackPlayerPosition(this); } + + public void setAsMasterPlayer() + { + isMaster = true; + //if I'm master, I'm now responsible for updating all scene objects + //FindObjectsOfType(); + } + public void syncObject(NetworkObject obj) + { + byte[] data = obj.getSyncMessage(); + manager.sendTo(0, "5," + obj.networkId + "," + Convert.ToBase64String(data)); + } + + public void instantiateObject(string prefab) + { + + } } diff --git a/TestVelGameServer/Assets/VelGameServer/VelCommsNetwork.cs b/TestVelGameServer/Assets/VelGameServer/VelCommsNetwork.cs index bd41419..b11511f 100644 --- a/TestVelGameServer/Assets/VelGameServer/VelCommsNetwork.cs +++ b/TestVelGameServer/Assets/VelGameServer/VelCommsNetwork.cs @@ -8,6 +8,7 @@ using UnityEngine; namespace Dissonance { + [RequireComponent(typeof(DissonanceComms),typeof(NetworkManager))] public class VelCommsNetwork : MonoBehaviour, ICommsNetwork { public ConnectionStatus Status @@ -39,8 +40,6 @@ namespace Dissonance ConnectionStatus _status = ConnectionStatus.Disconnected; CodecSettings initSettings; public string dissonanceId; - List packets = new List(); - bool loopBackSound = false; public DissonanceComms comms; public NetworkManager manager; NetworkPlayer myPlayer; diff --git a/server_basics.txt b/server_basics.txt index 87cf8aa..106799f 100644 --- a/server_basics.txt +++ b/server_basics.txt @@ -40,6 +40,7 @@ rooms - a list of rooms on the server, with counts 3:user_id:Message\n - message from sender user_id +4:user_id\n - You are the master client Unity-side. diff --git a/threaded_server.py b/threaded_server.py index 1f229b4..9179c2a 100644 --- a/threaded_server.py +++ b/threaded_server.py @@ -46,6 +46,36 @@ def send_client_message(client, message): client.message_lock.release() client.message_ready.set() +def leave_room(client, clientDisconnected=False): + global rooms + global rooms_lock + choseNewMaster = False + newMasterId = -1 + rooms_lock.acquire() + try: + rooms[client.room].clients.remove(client) + if(len(rooms[client.room].clients) == 0): + del rooms[client.room] + elif rooms[client.room].master == client: + rooms[client.room].master = rooms[client.room].clients[0] + newMasterId = rooms[client.room].master.id + choseNewMaster = True + + + except Exception as e: + print("not in room") + rooms_lock.release() + send_room_message(client.room, f"2:{client.id}:\n") #client not in the room anymore + if not clientDisconnected: + send_client_message(client,f"2:{client.id}:\n") #so send again to them + else: + client_lock.acquire() + del client_dict[client.id] #remove the client from the list of clients... + client_lock.release() + if choseNewMaster: + send_room_message(client.room,f"4:{newMasterId}\n") + client.room = "" + def decode_message(client,message): global rooms global rooms_lock @@ -68,50 +98,46 @@ def decode_message(client,message): if messageType == '2' and len(decodedMessage) > 1: #join or create a room - + print("request to join " + decodedMessage[1] + " from " + str(client.id)) roomName = decodedMessage[1] if client.room == roomName: #don't join the same room + print("Client trying to join the same room") pass elif (roomName == '-1') and client.room != '': #can't leave a room if you aren't in one #leave the room - rooms_lock.acquire() - try: - - rooms[client.room].clients.remove(client) - if(len(rooms[client.room].clients) == 0): - del rooms[client.room] - except Exception as e: - print("not in room") - rooms_lock.release() - send_room_message(client.room, f"2:{client.id}:\n") - send_client_message(client,f"2:{client.id}:\n") - client.room = '' + leave_room(client) elif roomName != '': #join or create the room - rooms_lock.acquire() + + if client.room != '': + #leave that room + leave_room(client) + + masterId = -1 + rooms_lock.acquire() + if roomName in rooms: #join the room rooms[roomName].clients.append(client) - + masterId = rooms[roomName].master.id else: - #create the room and join - rooms[roomName] = types.SimpleNamespace(name=roomName,clients=[client],room_lock=threading.Lock()) - - - rooms_lock.release() + #create the room and join it as master + rooms[roomName] = types.SimpleNamespace(name=roomName,clients=[client],master=client,room_lock=threading.Lock()) + masterId = client.id - if (client.room != '') and (client.room != roomName): #client left the previous room - send_room_message(client.room, f"2:{client.id}:{roomName}:\n") + current_clients = rooms[roomName].clients + rooms_lock.release() client.room = roomName #client joins the new room #send a message to the clients new room that they joined! send_room_message(roomName, f"2:{client.id}:{client.room}\n") - for c in rooms[roomName].clients: + + for c in current_clients: #tell that client about all the other clients in the room if c.id != client.id: send_client_message(client,f"2:{c.id}:{client.room}\n") - + send_client_message(client, f"4:{masterId}\n") #tell the client who the master is @@ -157,16 +183,10 @@ def client_read_thread(conn, addr, client): client.message_ready.set() pass #now we can kill the client, removing the client from the rooms - client_lock.acquire() - rooms_lock.acquire() - if client.room != '': - rooms[client.room].clients.remove(client) - if(len(rooms[client.room].clients) == 0): - del rooms[client.room] - del client_dict[client.id] #remove the client from the list of clients... - rooms_lock.release() - client_lock.release() - send_room_message(client.room, f"2:{client.id}:\n") + + + leave_room(client,True) + print("client destroyed") def client_write_thread(conn, addr, client): while client.alive: