Browse Source

Initial commit

James Peret 2 years ago
commit
d1e998fa2c
55 changed files with 1877 additions and 0 deletions
  1. 71 0
      .gitignore
  2. 31 0
      Readme.md
  3. 7 0
      Readme.md.meta
  4. 8 0
      Runtime.meta
  5. 8 0
      Runtime/Client.meta
  6. 59 0
      Runtime/Client/ClientActivityMessages.cs
  7. 11 0
      Runtime/Client/ClientActivityMessages.cs.meta
  8. 103 0
      Runtime/Client/ClientBehaviour.cs
  9. 11 0
      Runtime/Client/ClientBehaviour.cs.meta
  10. 32 0
      Runtime/Client/ClientHandshakeController.cs
  11. 11 0
      Runtime/Client/ClientHandshakeController.cs.meta
  12. 66 0
      Runtime/Client/ClientStatus.cs
  13. 11 0
      Runtime/Client/ClientStatus.cs.meta
  14. 187 0
      Runtime/Client/JobifiedClientBehaviour.cs
  15. 11 0
      Runtime/Client/JobifiedClientBehaviour.cs.meta
  16. 52 0
      Runtime/Client/PeerList.cs
  17. 11 0
      Runtime/Client/PeerList.cs.meta
  18. 29 0
      Runtime/ClientData.cs
  19. 11 0
      Runtime/ClientData.cs.meta
  20. 21 0
      Runtime/KairoEngine.Multiplayer.asmdef
  21. 7 0
      Runtime/KairoEngine.Multiplayer.asmdef.meta
  22. 159 0
      Runtime/NetMsgController.cs
  23. 11 0
      Runtime/NetMsgController.cs.meta
  24. 47 0
      Runtime/NetMsgEvents.cs
  25. 11 0
      Runtime/NetMsgEvents.cs.meta
  26. 13 0
      Runtime/NetOpCode.cs
  27. 11 0
      Runtime/NetOpCode.cs.meta
  28. 21 0
      Runtime/NetworkingEvents.cs
  29. 11 0
      Runtime/NetworkingEvents.cs.meta
  30. 8 0
      Runtime/Server.meta
  31. 56 0
      Runtime/Server/ClientList.cs
  32. 11 0
      Runtime/Server/ClientList.cs.meta
  33. 172 0
      Runtime/Server/JobifiedServerBehaviour.cs
  34. 11 0
      Runtime/Server/JobifiedServerBehaviour.cs.meta
  35. 64 0
      Runtime/Server/ServerActivityMessages.cs
  36. 11 0
      Runtime/Server/ServerActivityMessages.cs.meta
  37. 130 0
      Runtime/Server/ServerBehaviour.cs
  38. 11 0
      Runtime/Server/ServerBehaviour.cs.meta
  39. 38 0
      Runtime/Server/ServerHandshakeController.cs
  40. 11 0
      Runtime/Server/ServerHandshakeController.cs.meta
  41. 71 0
      Runtime/Server/ServerStatus.cs
  42. 11 0
      Runtime/Server/ServerStatus.cs.meta
  43. 8 0
      Runtime/_NetMsgs.meta
  44. 25 0
      Runtime/_NetMsgs/NetChatMsg.cs
  45. 11 0
      Runtime/_NetMsgs/NetChatMsg.cs.meta
  46. 28 0
      Runtime/_NetMsgs/NetClientConnectedMsg.cs
  47. 11 0
      Runtime/_NetMsgs/NetClientConnectedMsg.cs.meta
  48. 28 0
      Runtime/_NetMsgs/NetClientDisconnectedMsg.cs
  49. 11 0
      Runtime/_NetMsgs/NetClientDisconnectedMsg.cs.meta
  50. 25 0
      Runtime/_NetMsgs/NetDisconnectMsg.cs
  51. 11 0
      Runtime/_NetMsgs/NetDisconnectMsg.cs.meta
  52. 27 0
      Runtime/_NetMsgs/NetHandshakeMsg.cs
  53. 11 0
      Runtime/_NetMsgs/NetHandshakeMsg.cs.meta
  54. 23 0
      Runtime/_NetMsgs/NetMsg.cs
  55. 11 0
      Runtime/_NetMsgs/NetMsg.cs.meta

+ 71 - 0
.gitignore

@@ -0,0 +1,71 @@
+# This .gitignore file should be placed at the root of your Unity project directory
+#
+# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
+#
+/[Ll]ibrary/
+/[Tt]emp/
+/[Oo]bj/
+/[Bb]uild/
+/[Bb]uilds/
+/[Ll]ogs/
+/[Uu]ser[Ss]ettings/
+
+# MemoryCaptures can get excessive in size.
+# They also could contain extremely sensitive data
+/[Mm]emoryCaptures/
+
+# Asset meta data should only be ignored when the corresponding asset is also ignored
+!/[Aa]ssets/**/*.meta
+
+# Uncomment this line if you wish to ignore the asset store tools plugin
+# /[Aa]ssets/AssetStoreTools*
+
+# Autogenerated Jetbrains Rider plugin
+/[Aa]ssets/Plugins/Editor/JetBrains*
+
+# Visual Studio cache directory
+.vs/
+
+# Gradle cache directory
+.gradle/
+
+# Autogenerated VS/MD/Consulo solution and project files
+ExportedObj/
+.consulo/
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.tmp
+*.user
+*.userprefs
+*.pidb
+*.booproj
+*.svd
+*.pdb
+*.mdb
+*.opendb
+*.VC.db
+
+# Unity3D generated meta files
+*.pidb.meta
+*.pdb.meta
+*.mdb.meta
+
+# Unity3D generated file on crash reports
+sysinfo.txt
+
+# Builds
+*.apk
+*.aab
+*.unitypackage
+
+# Crashlytics generated file
+crashlytics-build.properties
+
+# Packed Addressables
+/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
+
+# Temporary auto-generated Android Assets
+/[Aa]ssets/[Ss]treamingAssets/aa.meta
+/[Aa]ssets/[Ss]treamingAssets/aa/*

+ 31 - 0
Readme.md

@@ -0,0 +1,31 @@
+# KairoEngine.Multiplayer
+
+## Messages and Events
+
+Server and clients exchange Net Messages which then become local events that can be subscribed to. The following table show the relations of messages and events.
+
+| Role   | Action      | NetMsg                   | Event                            |
+| ------ | ----------- | ------------------------ | -------------------------------- |
+| server | Start       |                          | PortError                        |
+| server | Start       |                          | Listening                        |
+| server | Stop        |                          | Stopped                          |
+| server | SendData    | NetClientConnectedMsg    | ClientConnected                  |
+| server | SendData    | NetClientDisconnectedMsg | ClientDisconnected               |
+| server | ReceiveData | NetMsg                   | InvalidOpCode                    |
+| server | ReceiveData | NetDisconnectMsg         | SendingDisconnectMessageToClient |
+| server | ReceiveData | NetHandshakeMsg          | HandshakeReceived                |
+| server | SendData    | NetHandshakeMsg          | AcceptingHandshake               |
+| client | Start       |                          | Connecting                       |
+| client | Start       |                          | Connected                        |
+| client | Start       |                          | ErrorConnecting                  |
+| client | Stop        |                          | Disconnected                     |
+| client | Stop        |                          | ServerDisconnected               |
+| client | ReceiveData | NetMsg                   | InvalidOpCode                    |
+| client | SendData    | NetDisconnectMsg         | SendingDisconnectMessageToServer |
+| client | ReceiveData | NetDisconnectMsg         | ServerDisconnect                 |
+| client | SendData    | NetHandshakeMsg          | SendingHandshake                 |
+| client | ReceiveData | NetHandshakeMsg          | HandshakeAccepted                |
+| client | ReceiveData | NetClientConnectedMsg    | PeerConnected                    |
+|        | ReceiveData | NetClientDisconnectedMsg | PeerDisconnected                 |
+
+

+ 7 - 0
Readme.md.meta

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

+ 8 - 0
Runtime.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e9664e5b786948947983b91f1ddc7f4d
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Runtime/Client.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 91c5586f2558e014090c5689968f15c1
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 59 - 0
Runtime/Client/ClientActivityMessages.cs

@@ -0,0 +1,59 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    public class ClientActivityMessages : MonoBehaviour
+    {
+        public ClientBehaviour client;
+        public string activityMessageName = "client";
+
+        private void Awake()
+        {
+            if(client == null) Debug.LogError("Missing ClientBehaviour script in ClientActivityMessages", this.gameObject);
+        }
+        private void Start()
+        {
+            if(client == null) return;
+            GenericEvents.StartListening($"{client.eventStreamName}_Connecting", OnClientEvent);
+            GenericEvents.StartListening($"{client.eventStreamName}_Connected", OnClientEvent);
+            GenericEvents.StartListening($"{client.eventStreamName}_Disconected", OnClientEvent);
+            GenericEvents.StartListening($"{client.eventStreamName}_ServerDisconnected", OnClientEvent);
+            GenericEvents.StartListening($"{client.eventStreamName}_ErrorConnecting", OnClientEvent);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_InvalidOpCode", OnClientReceivedDataEvent);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_SendingDisconnectMessageToServer", OnClientReceivedDataEvent);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_ServerDisconnect", OnClientReceivedDataEvent);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_SendingHandshake", OnClientReceivedDataEvent);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_HandshakeAccepted", OnClientReceivedDataEvent);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_PeerConnected", OnClientReceivedDataEvent);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_PeerDisconnected", OnClientReceivedDataEvent);
+        }
+
+        private void OnDestroy()
+        {
+            if(client == null) return;
+            GenericEvents.StopListening($"{client.eventStreamName}_Connecting", OnClientEvent);
+            GenericEvents.StopListening($"{client.eventStreamName}_Connected", OnClientEvent);
+            GenericEvents.StopListening($"{client.eventStreamName}_Disconected", OnClientEvent);
+            GenericEvents.StopListening($"{client.eventStreamName}_ServerDisconnected", OnClientEvent);
+            GenericEvents.StopListening($"{client.eventStreamName}_ErrorConnecting", OnClientEvent);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_InvalidOpCode", OnClientReceivedDataEvent);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_SendingDisconnectMessageToServer", OnClientReceivedDataEvent);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_ServerDisconnect", OnClientReceivedDataEvent);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_SendingHandshake", OnClientReceivedDataEvent);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_HandshakeAccepted", OnClientReceivedDataEvent);
+        }
+
+        public void OnClientEvent(string text)
+        {
+            ActivityMessageEvents.Send(text, "", client.eventStreamName, activityMessageName, null, 0f, true, false, null, "", "", "");
+        }
+
+        public void OnClientReceivedDataEvent(string text, int clientId, uint code, NetMsg netMsg)
+        {
+            ActivityMessageEvents.Send(text, "", client.eventStreamName, activityMessageName, null, 0f, true, false, null, "", "", "");
+        }
+    }
+}

+ 11 - 0
Runtime/Client/ClientActivityMessages.cs.meta

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

+ 103 - 0
Runtime/Client/ClientBehaviour.cs

@@ -0,0 +1,103 @@
+using UnityEngine;
+using Unity.Networking.Transport;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    public class ClientBehaviour : MonoBehaviour
+    {
+        
+        public string address = "192.168.1.1";
+        public ushort port = 9000;
+        public string playerName = "Player 1";
+        public bool autoConnect = true;
+        public string eventStreamName = "ClientEvents";
+        public bool debug = false;
+
+        public NetworkDriver m_Driver;
+        public NetworkConnection m_Connection;
+        public bool m_Done;
+
+        void Start ()
+        {
+            if(autoConnect) ConnectToServer();
+        }
+
+        public void ConnectToServer()
+        {
+            m_Driver = NetworkDriver.Create();
+            m_Connection = default(NetworkConnection);
+            var endpoint = NetworkEndPoint.Parse(address, port);
+            m_Connection = m_Driver.Connect(endpoint);
+            if(debug) Debug.Log($"Client Connecting to <b>{address}:{port}</b>");
+            GenericEvents.Trigger($"{eventStreamName}_Connecting", $"Connecting to <b>{address}:{port}</b>");
+        }
+
+        public void DisconnectFromServer()
+        {
+            NetDisconnectMsg netDisconnectMsg = new NetDisconnectMsg();
+            SendNetMsgToServer((uint)NetOpCode.Disconnect, netDisconnectMsg);
+            Timer.ExecuteRealTime(500, () => {
+                if(debug) Debug.Log("Client disconnected from server");
+                GenericEvents.Trigger($"{eventStreamName}_Disconected", "Client disconnected from server");
+                m_Connection = default(NetworkConnection);
+                if(m_Driver.IsCreated) m_Driver.Dispose();
+            });
+            
+        }
+
+        public void SendNetMsgToServer(uint code, NetMsg netMsg)
+        {
+            DataStreamWriter writer = new DataStreamWriter();
+            m_Driver.BeginSend(m_Connection, out writer);
+            NetMsgController.Serialize(code, netMsg, ref writer, this);
+            m_Driver.EndSend(writer);
+        }
+
+        public void OnDestroy()
+        {
+            if(m_Driver.IsCreated) m_Driver.Dispose();
+        }
+
+        void Update()
+        {
+            if(!m_Driver.IsCreated) return;
+            m_Driver.ScheduleUpdate().Complete();
+
+            if (!m_Connection.IsCreated)
+            {
+                if (!m_Done)
+                {
+                    if(debug) Debug.Log("Something went wrong during connect");
+                    GenericEvents.Trigger($"{eventStreamName}_ErrorConnecting", "Error connecting to server");
+                    m_Done = true;
+                }
+                return;
+            }
+
+            DataStreamReader stream;
+            NetworkEvent.Type cmd;
+
+            while ((cmd = m_Connection.PopEvent(m_Driver, out stream)) != NetworkEvent.Type.Empty)
+            {
+                if (cmd == NetworkEvent.Type.Connect)
+                {
+                    if(debug) Debug.Log("We are now connected to the server");
+                    GenericEvents.Trigger($"{eventStreamName}_Connected", "Connected to server");
+                }
+                else if (cmd == NetworkEvent.Type.Data)
+                {
+                    //if(debug) Debug.Log("Client received data from the server");
+                    uint code = stream.ReadByte();
+                    NetMsgController.Deserialize(code, ref stream, this);
+                }
+                else if (cmd == NetworkEvent.Type.Disconnect)
+                {
+                    if(debug) Debug.Log("Client got disconnected from server");
+                    GenericEvents.Trigger($"{eventStreamName}_ServerDisconnected", "Client got disconnected from server");
+                    m_Connection = default(NetworkConnection);
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Runtime/Client/ClientBehaviour.cs.meta

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

+ 32 - 0
Runtime/Client/ClientHandshakeController.cs

@@ -0,0 +1,32 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    public class ClientHandshakeController : MonoBehaviour
+    {
+        public ClientBehaviour client;
+        
+        private void Awake()
+        {
+            GenericEvents.StartListening($"{client.eventStreamName}_Connected", OnEvent);
+        }
+
+        private void OnDestroy()
+        {
+            GenericEvents.StopListening($"{client.eventStreamName}_Connected", OnEvent);
+        }
+
+        private void OnEvent(string text)
+        {
+            if(client == null) return;
+            NetHandshakeMsg handshakeMsg = new NetHandshakeMsg();
+            handshakeMsg.playerName = client.playerName;
+            Timer.ExecuteRealTime(100, ()=> {
+                client.SendNetMsgToServer(((uint)NetOpCode.Handshake), handshakeMsg);
+            });
+        }
+    }
+}

+ 11 - 0
Runtime/Client/ClientHandshakeController.cs.meta

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

+ 66 - 0
Runtime/Client/ClientStatus.cs

@@ -0,0 +1,66 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.Multiplayer
+{
+    public enum ClientState
+    {
+        Disconnected,
+        Connecting,
+        Connected,
+    }
+
+    [HideMonoScript]
+    public class ClientStatus : MonoBehaviour
+    {
+        public ClientBehaviour client;
+        [ReadOnly] public ClientState state;
+        [ReadOnly] public int peerCount;
+        [ReadOnly] public float uptime = 0;
+        [ReadOnly] public string serverAddress;
+        [ReadOnly] public int packetsSent;
+        [ReadOnly] public int packetsReceived;
+
+        private void OnEnable()
+        {
+            NetMsgEvents.StartListening($"{client.eventStreamName}_InvalidOpCode", OnReceivePacket);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_SendingDisconnectMessageToServer", OnSentPacket);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_ServerDisconnect", OnReceivePacket);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_SendingHandshake", OnSentPacket);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_HandshakeAccepted", OnReceivePacket);
+        }
+
+        private void OnDisable()
+        {
+            NetMsgEvents.StopListening($"{client.eventStreamName}_InvalidOpCode", OnReceivePacket);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_SendingDisconnectMessageToServer", OnSentPacket);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_ServerDisconnect", OnReceivePacket);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_SendingHandshake", OnSentPacket);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_HandshakeAccepted", OnReceivePacket);
+        }
+
+        private void Update()
+        {
+            if(client.m_Driver.IsCreated == false)
+            {
+                state = ClientState.Disconnected;
+                peerCount = 0;
+                uptime = 0;
+                packetsSent = 0;
+                packetsReceived = 0;
+            }
+            else
+            {
+                state = ClientState.Connected;
+                peerCount = 0;
+                uptime += Time.deltaTime;
+            }
+        }
+
+        private void OnSentPacket(string text, int clientId, uint code, NetMsg netMsg) => packetsSent += 1;
+
+        private void OnReceivePacket(string text, int clientId, uint code, NetMsg netMsg) => packetsReceived += 1;
+    }
+}

+ 11 - 0
Runtime/Client/ClientStatus.cs.meta

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

+ 187 - 0
Runtime/Client/JobifiedClientBehaviour.cs

@@ -0,0 +1,187 @@
+using System.Collections.Generic;
+using UnityEngine;
+using Unity.Jobs;
+using Unity.Collections;
+using Unity.Networking.Transport;
+using Sirenix.OdinInspector;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    [HideMonoScript]
+    public class JobifiedClientBehaviour : MonoBehaviour
+    {
+        [HideInInspector] public NetworkDriver m_Driver;
+        [HideInInspector] public NativeArray<NetworkConnection> m_Connection;
+        [HideInInspector] public NativeArray<byte> m_Done;
+
+        [HideInInspector] public JobHandle ClientJobHandle;
+
+        [HideInInspector] private NativeQueue<JobEvent> m_eventQueue;
+        public string address = "192.168.1.1";
+        public ushort port = 9000;
+        public bool autoConnectOnStart = true;
+        private bool resourceLock = false;
+        private int disconnectNow = 0;
+
+        void Start ()
+        {
+            if(autoConnectOnStart) ConnectToServer();
+        }
+
+        public void ConnectToServer()
+        {
+            resourceLock = true;
+            m_eventQueue = new NativeQueue<JobEvent>(Allocator.Persistent);
+            m_Driver = NetworkDriver.Create();
+            m_Connection = new NativeArray<NetworkConnection>(1, Allocator.Persistent);
+            m_Done = new NativeArray<byte>(1, Allocator.Persistent);
+            var endpoint = NetworkEndPoint.Parse(address, port);
+            m_Connection[0] = m_Driver.Connect(endpoint);
+            GenericEvents.Trigger("ClientEvent", $"Connecting to {address}:{port}");
+        }
+
+        public void DisconnectFromServer()
+        {
+            disconnectNow = 1;
+            //m_Connection[0].Disconnect(m_Driver);
+            //m_Connection[0] = default(NetworkConnection);
+            //resourceLock = false;
+            //Cleanup();
+            //GenericEvents.Trigger("ClientEvent", $"Disconected from server");
+        }
+
+        public void OnDestroy()
+        {
+            Cleanup();
+        }
+        void Update()
+        {
+            if(resourceLock == false) return;
+            ClientJobHandle.Complete();
+            while(m_eventQueue.TryDequeue(out JobEvent jobEvent)) 
+            {
+                TriggerJobEvent(jobEvent);
+            }
+            if (!m_Connection[0].IsCreated && disconnectNow == 1) 
+            {
+                Cleanup();
+                return;
+            }
+            if (!m_Connection[0].IsCreated) return;
+            var job = new ClientUpdateJob
+            {
+                driver = m_Driver,
+                connection = m_Connection,
+                done = m_Done,
+                eventQueue = m_eventQueue.AsParallelWriter(),
+                disconnect = disconnectNow
+            };
+            ClientJobHandle = m_Driver.ScheduleUpdate();
+            ClientJobHandle = job.Schedule(ClientJobHandle);
+        }
+    
+        public virtual void SendToServer(NetMsg msg)
+        {
+            DataStreamWriter writer;
+            m_Driver.BeginSend(m_Connection[0], out writer);
+            msg.Serialize(ref writer);
+            m_Driver.EndSend(writer);
+        }
+
+        private void Cleanup()
+        {
+            if(resourceLock == false) return;
+            ClientJobHandle.Complete();
+            m_Connection.Dispose();
+            m_Driver.Dispose();
+            m_Done.Dispose();
+            m_eventQueue.Dispose();
+            resourceLock = false;
+            disconnectNow = 0;
+        }
+
+        public void TriggerJobEvent(JobEvent jobEvent)
+        {
+            switch (jobEvent.code)
+            {
+                case 1:
+                    GenericEvents.Trigger("ClientEvent", $"Connected to server");
+                    break;
+                case 2:
+                    GenericEvents.Trigger("ClientEvent", $"Disconnected from server");
+                    break;
+                case 3:
+                    GenericEvents.Trigger("ClientEvent", $"Data received from server");
+                    break;
+                case 4:
+                    GenericEvents.Trigger("ClientEvent", $"Error connecting to server");
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    struct ClientUpdateJob : IJob
+    {
+        public NetworkDriver driver;
+        public NativeArray<NetworkConnection> connection;
+        public NativeArray<byte> done;
+        public NativeQueue<JobEvent>.ParallelWriter eventQueue;
+        public int disconnect;
+
+        public void Execute()
+        {
+            if (!connection[0].IsCreated)
+            {
+                if (done[0] != 1)
+                    Debug.Log("Something went wrong during connect");
+                    eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.ErrorConnectingToServer));
+                return;
+            }
+            // Receive Data from connections
+            DataStreamReader stream;
+            NetworkEvent.Type cmd;
+            while ((cmd = connection[0].PopEvent(driver, out stream)) != NetworkEvent.Type.Empty)
+            {
+                if (cmd == NetworkEvent.Type.Connect)
+                {
+                    Debug.Log("We are now connected to the server");
+                    eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.ClientConnected));
+                    uint value = 1;
+                    DataStreamWriter writer = new DataStreamWriter();
+                    var result = driver.BeginSend(connection[0], out writer);
+                    writer.WriteUInt(value);
+                    driver.EndSend(writer);
+                }
+                else if (cmd == NetworkEvent.Type.Data)
+                {
+                    uint value = stream.ReadUInt();
+                    Debug.Log("Got the value = " + value + " back from the server");
+                    eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.ClientReceivedData));
+                    // And finally change the `done[0]` to `1`
+                    done[0] = 1;
+                    //connection[0].Disconnect(driver);
+                    //connection[0] = default(NetworkConnection);
+                    //eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.Disconnected));
+                }
+                else if (cmd == NetworkEvent.Type.Disconnect)
+                {
+                    eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.ClientDisconnected));
+                    Debug.Log("Client got disconnected from server");
+                    connection[0] = default(NetworkConnection);
+                }
+            }
+            // Send Data to connections
+            // Disconnect
+            if(disconnect == 1)
+            {
+                eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.ClientDisconnected));
+                Debug.Log("Client disconnected from server");
+                connection[0].Disconnect(driver);
+                connection[0] = default(NetworkConnection);
+            }
+        }
+    }
+}

+ 11 - 0
Runtime/Client/JobifiedClientBehaviour.cs.meta

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

+ 52 - 0
Runtime/Client/PeerList.cs

@@ -0,0 +1,52 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KairoEngine.Multiplayer
+{
+    public class PeerList : MonoBehaviour
+    {
+        public ClientBehaviour client;
+        public List<ClientData> peers;
+
+        private void OnEnable()
+        {
+            NetMsgEvents.StartListening($"{client.eventStreamName}_PeerConnected", OnPeerConnect);
+            NetMsgEvents.StartListening($"{client.eventStreamName}_PeerDisconnected", OnPeerDisconnect);
+        }
+
+        private void OnDisable()
+        {
+            NetMsgEvents.StopListening($"{client.eventStreamName}_PeerConnected", OnPeerConnect);
+            NetMsgEvents.StopListening($"{client.eventStreamName}_PeerDisconnected", OnPeerDisconnect);
+        }
+
+        private void OnPeerConnect(string text, int clientId, uint code, NetMsg netMsg)
+        {
+            NetClientConnectedMsg msg = (NetClientConnectedMsg)netMsg;
+            for (int i = 0; i < peers.Count; i++)
+            {
+                if(peers[i].playerName == msg.playerName)
+                {
+                    return;
+                }
+            }
+            if(msg.playerName == client.playerName) return;
+            peers.Add(new ClientData(-1, msg.playerName, true));
+            
+        }
+
+        private void OnPeerDisconnect(string text, int clientId, uint code, NetMsg netMsg)
+        {
+            NetClientDisconnectedMsg msg = (NetClientDisconnectedMsg)netMsg;
+            for (int i = 0; i < peers.Count; i++)
+            {
+                if(peers[i].playerName == msg.playerName)
+                {
+                    peers.RemoveAt(i);
+                    return;
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Runtime/Client/PeerList.cs.meta

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

+ 29 - 0
Runtime/ClientData.cs

@@ -0,0 +1,29 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.Multiplayer
+{
+    [System.Serializable]
+    public class ClientData
+    {
+        [FoldoutGroup("@ToString()")] public int connectionId = -1;
+        [FoldoutGroup("@ToString()")] public string playerName = "";
+        [FoldoutGroup("@ToString()")] public bool isConnected = false;
+
+        public ClientData(int connectionId, string playerName, bool isConnected)
+        {
+            this.connectionId = connectionId;
+            this.playerName = playerName;
+            this.isConnected = isConnected;
+        }
+
+        public override string ToString()
+        {
+            string idText = connectionId < 10 ? $"0{connectionId.ToString()}" : $"{connectionId}";
+            if(!isConnected) idText = "XX";
+            return $"{idText} : {playerName}";
+        }
+    }
+}

+ 11 - 0
Runtime/ClientData.cs.meta

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

+ 21 - 0
Runtime/KairoEngine.Multiplayer.asmdef

@@ -0,0 +1,21 @@
+{
+    "name": "KairoEngine.Multiplayer",
+    "rootNamespace": "",
+    "references": [
+        "GUID:7e5ae6a38d1532248b4c890eca668b06",
+        "GUID:8a2eafa29b15f444eb6d74f94a930e1d",
+        "GUID:f2d49d9fa7e7eb3418e39723a7d3b92f",
+        "GUID:e048eeec9bdb9d30448017b829deb3f6",
+        "GUID:560b04d1a97f54a4e82edc0cbbb69285",
+        "GUID:e0cd26848372d4e5c891c569017e11f1"
+    ],
+    "includePlatforms": [],
+    "excludePlatforms": [],
+    "allowUnsafeCode": false,
+    "overrideReferences": false,
+    "precompiledReferences": [],
+    "autoReferenced": true,
+    "defineConstraints": [],
+    "versionDefines": [],
+    "noEngineReferences": false
+}

+ 7 - 0
Runtime/KairoEngine.Multiplayer.asmdef.meta

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

+ 159 - 0
Runtime/NetMsgController.cs

@@ -0,0 +1,159 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Unity.Networking.Transport;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    public static class NetMsgController
+    {
+        public static void Deserialize(uint code, ref DataStreamReader stream, ServerBehaviour server, int clientId = -1) 
+            => Deserialize(code, ref stream, server, null, clientId);
+
+        public static void Deserialize(uint code, ref DataStreamReader stream, ClientBehaviour client, int clientId = -1) 
+            => Deserialize(code, ref stream, null, client, clientId);
+
+        public static void Deserialize(uint code, ref DataStreamReader stream, ServerBehaviour server, ClientBehaviour client, int clientId)
+        {
+            NetOpCode opCode = (NetOpCode)code;
+            switch (opCode)
+            {
+                case NetOpCode.Disconnect:
+                    NetDisconnectMsg netDisconnectMsg = new NetDisconnectMsg();
+                    netDisconnectMsg.Deserialize(ref stream);
+                    if(client != null)
+                    {
+                        string text = $"Server disconnected client";
+                        if(client.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{client.eventStreamName}_ServerDisconnect", text, -1, code, netDisconnectMsg);
+                    }
+                    if(server != null)
+                    {
+                        string text = $"Client is disconnecting";
+                        if(server.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{server.eventStreamName}_ClientDisconnect", text, clientId, code, netDisconnectMsg);
+                    }
+                    break;
+                case NetOpCode.Handshake:
+                    NetHandshakeMsg netHandshakeMsg = new NetHandshakeMsg();
+                    netHandshakeMsg.Deserialize(ref stream);
+                    if(client != null) 
+                    {
+                        string text = $"Server accepted handshake (playerName = {netHandshakeMsg.playerName})";
+                        if(client.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{client.eventStreamName}_HandshakeAccepted", text, -1, code, netHandshakeMsg);
+                    }
+                    if(server != null)  
+                    {
+                        string text = $"Received handshake from client (ID {clientId}, playerName = {netHandshakeMsg.playerName})";
+                        if(server.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{server.eventStreamName}_HandshakeReceived", text, clientId, code, netHandshakeMsg);
+                    }
+                    break;
+                case NetOpCode.ClientConnected:
+                    NetClientConnectedMsg netClientConnectedMsg = new NetClientConnectedMsg();
+                    netClientConnectedMsg.Deserialize(ref stream);
+                    if(client != null)
+                    {
+                        string text = $"New peer connected to server (playerName = {netClientConnectedMsg.playerName})";
+                        if(client.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{client.eventStreamName}_PeerConnected", text, -1, code, netClientConnectedMsg);
+                    }
+                    break;
+                case NetOpCode.ClientDisconnected:
+                    NetClientDisconnectedMsg netClientDisconnectedMsg = new NetClientDisconnectedMsg();
+                    netClientDisconnectedMsg.Deserialize(ref stream);
+                    if(client != null)
+                    {
+                        string text = $"Peer disconnected to server (playerName = {netClientDisconnectedMsg.playerName})";
+                        if(client.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{client.eventStreamName}_PeerDisconnected", text, -1, code, netClientDisconnectedMsg);
+                    }
+                    break;
+                default:
+                    NetMsg netMsg = new NetMsg();
+                    netMsg.Deserialize(ref stream);
+                    string error = $"Received data with invalid NetOpCode {code}";
+                    if(client != null)
+                    {
+                        if(client.debug)  Debug.Log(error);
+                        NetMsgEvents.Trigger($"{client.eventStreamName}_InvalidOpCode", error, -1, code, netMsg);
+                    }
+                    if(server != null)
+                    {
+                        if(server.debug) Debug.Log(error);
+                        NetMsgEvents.Trigger($"{server.eventStreamName}_InvalidOpCode", error, clientId, code, netMsg);
+                    }
+                    break;
+            }
+        }
+
+        public static void Serialize(uint code, NetMsg netMsg, ref DataStreamWriter writer, ServerBehaviour server, int clientId = -1) 
+            => Serialize(code, netMsg, ref writer, server, null, clientId);
+        public static void Serialize(uint code, NetMsg netMsg, ref DataStreamWriter writer, ClientBehaviour client, int clientId = -1 ) 
+            => Serialize(code, netMsg, ref writer, null, client, clientId);
+        public static void Serialize(uint code, NetMsg netMsg, ref DataStreamWriter writer, ServerBehaviour server, ClientBehaviour client, int clientId)
+        {
+            NetOpCode opCode = (NetOpCode)code;
+            switch (opCode)
+            {
+                case NetOpCode.Disconnect:
+                    NetDisconnectMsg netDisconnectMsg = (NetDisconnectMsg)netMsg;
+                    if(client != null) 
+                    {
+                        string text = $"Sending disconnect message to server";
+                        if(client.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{client.eventStreamName}_SendingDisconnectMessageToServer", text, clientId, code, netDisconnectMsg);
+                    }
+                    if(server != null)  
+                    {
+                        string text = $"Sending disconnect message to client (ID {clientId})";
+                        if(server.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{server.eventStreamName}_SendingDisconnectMessageToClient", text, clientId, code, netDisconnectMsg);
+                    }
+                    netDisconnectMsg.Serialize(ref writer);
+                    break;
+                case NetOpCode.Handshake:
+                    NetHandshakeMsg netHandshakeMsg = (NetHandshakeMsg)netMsg;
+                    if(client != null) 
+                    {
+                        string text = $"Sending handshake (playerName = {netHandshakeMsg.playerName})";
+                        if(client.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{client.eventStreamName}_SendingHandshake", text, clientId, code, netHandshakeMsg);
+                    }
+                    if(server != null)  
+                    {
+                        string text = $"Accepting handshake from client (ID {clientId}, playerName = {netHandshakeMsg.playerName})";
+                        if(server.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{server.eventStreamName}_AcceptingHandshake", text, clientId, code, netHandshakeMsg);
+                    }
+                    netHandshakeMsg.Serialize(ref writer);
+                    break;
+                case NetOpCode.ClientConnected:
+                    NetClientConnectedMsg netClientConnectedMsg = (NetClientConnectedMsg)netMsg;
+                    if(server != null)
+                    {
+                        string text = $"Alerting clients of new connection (playerName = {netClientConnectedMsg.playerName})";
+                        if(server.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{server.eventStreamName}_SendingClientConnectedMsg", text, clientId, code, netClientConnectedMsg);
+                    }
+                    netClientConnectedMsg.Serialize(ref writer);
+                    break;
+                case NetOpCode.ClientDisconnected:
+                    NetClientDisconnectedMsg netClientDisconnectedMsg = (NetClientDisconnectedMsg)netMsg;
+                    if(server != null)
+                    {
+                        string text = $"Alerting clients of disconnection (playerName = {netClientDisconnectedMsg.playerName})";
+                        if(server.debug) Debug.Log(text);
+                        NetMsgEvents.Trigger($"{server.eventStreamName}_SendingClientDisconnectedMsg", text, clientId, code, netClientDisconnectedMsg);
+                    }
+                    netClientDisconnectedMsg.Serialize(ref writer);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+}

+ 11 - 0
Runtime/NetMsgController.cs.meta

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

+ 47 - 0
Runtime/NetMsgEvents.cs

@@ -0,0 +1,47 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    public static class NetMsgEvents
+    {
+        public static Dictionary<string, System.Action<string, int, uint, NetMsg>> list = new Dictionary<string, System.Action<string, int, uint, NetMsg>>();
+        
+        public static void StartListening(string title, System.Action<string, int, uint, NetMsg> listener)
+        {
+            System.Action<string, int, uint, NetMsg> action = null;
+            if (list.TryGetValue(title, out action))
+            {
+                action += listener;
+                list[title] = action;
+            }
+            else
+            {
+                action += listener;
+                list.Add(title, action);
+            }
+        }
+
+        public static void StopListening(string title, System.Action<string, int, uint, NetMsg> listener)
+        {
+            System.Action<string, int, uint, NetMsg> action = null;
+            if (list.TryGetValue(title, out action))
+            {
+                action -= listener;
+                list[title] = action;
+            }
+        }
+
+        public static void Trigger(string title, string msg, int clientId, uint code, NetMsg netMsg)
+        {
+            System.Action<string, int, uint, NetMsg> action = null;
+            if (list.TryGetValue(title, out action))
+            {
+                if(action != null) action(msg, clientId, code, netMsg);
+            }
+        }
+    }
+}
+

+ 11 - 0
Runtime/NetMsgEvents.cs.meta

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

+ 13 - 0
Runtime/NetOpCode.cs

@@ -0,0 +1,13 @@
+namespace KairoEngine.Multiplayer
+{
+    public enum NetOpCode
+    {
+        Generic = 0, // NetMsg
+        Disconnect = 1, // NetDisconnectMsg
+        Handshake = 2, // NetHandshakeMsg
+        ClientConnected = 4, // NetClientConnectedMsg
+        ClientDisconnected = 5, // NetClientDisconnectedMsg
+        ChatMessage = 6 //NetChatMsg
+    }
+}
+

+ 11 - 0
Runtime/NetOpCode.cs.meta

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

+ 21 - 0
Runtime/NetworkingEvents.cs

@@ -0,0 +1,21 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KairoEngine.Multiplayer
+{
+    public class NetworkingEvents
+    {
+        public enum Message
+        {
+            ServerStarted = 1,
+            ServerStoped = 2,
+            ClientConnected = 3,
+            ClientDisconnected = 4,
+            ServerReceivedData = 5,
+            ClientReceivedData = 6,
+            ErrorConnectingToServer = 7,
+            ErrorStartingServer = 8
+        }
+    }
+}

+ 11 - 0
Runtime/NetworkingEvents.cs.meta

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

+ 8 - 0
Runtime/Server.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fd33f247eded504468190ea3787262d5
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 56 - 0
Runtime/Server/ClientList.cs

@@ -0,0 +1,56 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    [HideMonoScript]
+    public class ClientList : MonoBehaviour
+    {
+        public ServerBehaviour server;
+        public List<ClientData> clients;
+
+        private void OnEnable()
+        {
+            NetMsgEvents.StartListening($"{server.eventStreamName}_AcceptingHandshake", OnServerAcceptingHandshakeEvent);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_ClientDisconnect", OnServerClientDisconnectEvent);
+        }
+
+        private void OnDisable()
+        {
+            NetMsgEvents.StopListening($"{server.eventStreamName}_AcceptingHandshake", OnServerAcceptingHandshakeEvent);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_ClientDisconnect", OnServerClientDisconnectEvent);
+        }
+
+        private void OnServerAcceptingHandshakeEvent(string text, int clientId, uint code, NetMsg netMsg)
+        {
+            NetHandshakeMsg handshake = (NetHandshakeMsg)netMsg;
+            bool registered = false;
+            for (int i = 0; i < clients.Count; i++)
+            {
+                if(clients[i].playerName == handshake.playerName)
+                {
+                    clients[i].connectionId = clientId;
+                    clients[i].isConnected = true;
+                    registered = true;
+                }
+            }
+            if(!registered) clients.Add(new ClientData(clientId, handshake.playerName, true));
+        }
+
+        private void OnServerClientDisconnectEvent(string text, int clientId, uint code, NetMsg netMsg)
+        {
+            NetDisconnectMsg disconnectMsg = (NetDisconnectMsg)netMsg;
+            for (int i = 0; i < clients.Count; i++)
+            {
+                if(clients[i].connectionId == clientId)
+                {
+                    clients[i].connectionId = -1;
+                    clients[i].isConnected = false;
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Runtime/Server/ClientList.cs.meta

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

+ 172 - 0
Runtime/Server/JobifiedServerBehaviour.cs

@@ -0,0 +1,172 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.Assertions;
+using Unity.Jobs;
+using Unity.Collections;
+using Unity.Networking.Transport;
+using Sirenix.OdinInspector;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    [HideMonoScript]
+    public class JobifiedServerBehaviour : MonoBehaviour
+    {
+        [HideInInspector] public NetworkDriver m_Driver;
+        [HideInInspector] public NativeList<NetworkConnection> m_Connections;
+        public ushort port = 9000;
+        public int players = 4;
+        public bool autoStart = true;
+        private JobHandle ServerJobHandle;
+        [HideInInspector] private NativeQueue<JobEvent> m_eventQueue;
+        private bool resourceLock = false;
+
+        void Start ()
+        {
+            if(autoStart) StartServer();
+        }
+
+        public void StartServer()
+        {
+            resourceLock = true;
+            m_eventQueue = new NativeQueue<JobEvent>(Allocator.Persistent);
+            m_Connections = new NativeList<NetworkConnection>(players, Allocator.Persistent);
+            m_Driver = NetworkDriver.Create();
+            var endpoint = NetworkEndPoint.AnyIpv4;
+            endpoint.Port = port;
+            if (m_Driver.Bind(endpoint) != 0)
+            {
+                Debug.Log($"Failed to bind to port {port}");
+                GenericEvents.Trigger("ServerEvent", $"Failed to bind to port {port}");
+            }
+            else
+            {
+                Debug.Log($"Server listening on port {port}");
+                GenericEvents.Trigger("ServerEvent", $"Server listening on port {port}");
+                m_Driver.Listen();
+            }
+        }
+
+        public void OnDestroy()
+        {
+            // Make sure we run our jobs to completion before exiting.
+            ServerJobHandle.Complete();
+            m_Connections.Dispose();
+            m_Driver.Dispose();
+            m_eventQueue.Dispose();
+        }
+
+        void Update ()
+        {
+            if(resourceLock == false) return;
+            ServerJobHandle.Complete();
+            while(m_eventQueue.TryDequeue(out JobEvent jobEvent)) 
+            {
+                TriggerJobEvent(jobEvent);
+            }
+            var connectionJob = new ServerUpdateConnectionsJob
+            {
+                driver = m_Driver,
+                connections = m_Connections,
+                eventQueue = m_eventQueue.AsParallelWriter()
+            };
+            var serverUpdateJob = new ServerUpdateJob
+            {
+                driver = m_Driver.ToConcurrent(),
+                connections = m_Connections.AsDeferredJobArray(),
+                eventQueue = m_eventQueue.AsParallelWriter(),
+            };
+            ServerJobHandle = m_Driver.ScheduleUpdate();
+            ServerJobHandle = connectionJob.Schedule(ServerJobHandle);
+            ServerJobHandle = serverUpdateJob.Schedule(m_Connections, 1, ServerJobHandle);
+        }
+
+        public void TriggerJobEvent(JobEvent jobEvent)
+        {
+            switch (jobEvent.code)
+            {
+                case 1:
+                    GenericEvents.Trigger("ServerEvent", $"Client connected ");
+                    break;
+                case 2:
+                    GenericEvents.Trigger("ServerEvent", $"Client disconnected");
+                    break;
+                case 3:
+                    GenericEvents.Trigger("ServerEvent", $"Data received from client");
+                    break;
+                case 4:
+                    GenericEvents.Trigger("ServerEvent", $"Error connecting to client");
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    struct ServerUpdateConnectionsJob : IJob
+    {
+        public NetworkDriver driver;
+        public NativeList<NetworkConnection> connections;
+        public NativeQueue<JobEvent>.ParallelWriter eventQueue;
+
+        public void Execute()
+        {
+            // CleanUpConnections
+            for (int i = 0; i < connections.Length; i++)
+            {
+                if (!connections[i].IsCreated)
+                {
+                    connections.RemoveAtSwapBack(i);
+                    --i;
+                }
+            }
+            // AcceptNewConnections
+            NetworkConnection c;
+            while ((c = driver.Accept()) != default(NetworkConnection))
+            {
+                connections.Add(c);
+                Debug.Log("Accepted a connection");
+                eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.ClientConnected));
+            }
+        }
+    }
+
+    struct ServerUpdateJob : IJobParallelForDefer
+    {
+        public NetworkDriver.Concurrent driver;
+        public NativeArray<NetworkConnection> connections;
+
+        public NativeQueue<JobEvent>.ParallelWriter eventQueue;
+
+        public void Execute(int index)
+        {
+            DataStreamReader stream;
+            Assert.IsTrue(connections[index].IsCreated);
+            // Receive data from clients
+            NetworkEvent.Type cmd;
+            while ((cmd = driver.PopEventForConnection(connections[index], out stream)) != NetworkEvent.Type.Empty)
+            {
+                if (cmd == NetworkEvent.Type.Data)
+                {
+                    eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.ServerReceivedData));
+                    uint number = stream.ReadUInt();
+
+                    Debug.Log("Got " + number + " from the Client adding + 2 to it.");
+                    number +=2;
+
+                    DataStreamWriter writer = new DataStreamWriter();
+                    var result = driver.BeginSend(connections[index], out writer);
+                    writer.WriteUInt(number);
+                    driver.EndSend(writer);
+                }
+                else if (cmd == NetworkEvent.Type.Disconnect)
+                {
+                    eventQueue.Enqueue(new JobEvent((int)NetworkingEvents.Message.ClientDisconnected));
+                    Debug.Log("Client disconnected from server");
+                    connections[index] = default(NetworkConnection);
+                }
+            }
+            // Send data to clients
+        }
+    }
+}

+ 11 - 0
Runtime/Server/JobifiedServerBehaviour.cs.meta

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

+ 64 - 0
Runtime/Server/ServerActivityMessages.cs

@@ -0,0 +1,64 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.Core;
+using KairoEngine.Multiplayer;
+
+namespace KairoEngine.Multiplayer
+{
+    public class ServerActivityMessages : MonoBehaviour
+    {
+        public ServerBehaviour server;
+        public string activityMessageName = "server";
+
+        private void Awake()
+        {
+            if(server == null) Debug.LogError("Missing ServerBehaviour script in ServerActivityMessages", this.gameObject);
+        }
+
+        private void Start()
+        {
+            if(server == null) return;
+            GenericEvents.StartListening($"{server.eventStreamName}_PortError", OnServerEvent);
+            GenericEvents.StartListening($"{server.eventStreamName}_Listening", OnServerEvent);
+            GenericEvents.StartListening($"{server.eventStreamName}_Stopped", OnServerEvent);
+            GenericEvents.StartListening($"{server.eventStreamName}_ClientConnected", OnServerClientConnectedEvent);
+            GenericEvents.StartListening($"{server.eventStreamName}_ClientDisconnected", OnServerEvent);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_InvalidOpCode", OnServerReceivedDataEvent);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_SendingDisconnectMessageToClient", OnServerReceivedDataEvent);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_ClientDisconnect", OnServerReceivedDataEvent);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_HandshakeReceived", OnServerReceivedDataEvent);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_AcceptingHandshake", OnServerReceivedDataEvent);
+        }
+
+        private void OnDestroy()
+        {
+            if(server == null) return;
+            GenericEvents.StopListening($"{server.eventStreamName}_PortError", OnServerEvent);
+            GenericEvents.StopListening($"{server.eventStreamName}_Listening", OnServerEvent);
+            GenericEvents.StopListening($"{server.eventStreamName}_Stopped", OnServerEvent);
+            GenericEvents.StopListening($"{server.eventStreamName}_ClientConnected", OnServerClientConnectedEvent);
+            GenericEvents.StopListening($"{server.eventStreamName}_ClientDisconnected", OnServerEvent);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_InvalidOpCode", OnServerReceivedDataEvent);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_SendingDisconnectMessageToClient", OnServerReceivedDataEvent);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_ClientDisconnect", OnServerReceivedDataEvent);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_HandshakeReceived", OnServerReceivedDataEvent);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_AcceptingHandshake", OnServerReceivedDataEvent);
+        }
+
+        public void OnServerEvent(string text)
+        {
+            ActivityMessageEvents.Send(text, "", server.eventStreamName, activityMessageName, null, 0f, true, false, null, "", "", "");
+        }
+
+        public void OnServerReceivedDataEvent(string text, int clientId, uint code, NetMsg netMsg)
+        {
+            ActivityMessageEvents.Send(text, "", server.eventStreamName, activityMessageName, null, 0f, true, false, null, "", "", "");
+        }
+
+        public void OnServerClientConnectedEvent(string text, int connectionId)
+        {
+            ActivityMessageEvents.Send(text, "", server.eventStreamName, activityMessageName, null, 0f, true, false, null, "", "", "");
+        }
+    }
+}

+ 11 - 0
Runtime/Server/ServerActivityMessages.cs.meta

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

+ 130 - 0
Runtime/Server/ServerBehaviour.cs

@@ -0,0 +1,130 @@
+using UnityEngine;
+using UnityEngine.Assertions;
+using Unity.Collections;
+using Unity.Networking.Transport;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    public class ServerBehaviour : MonoBehaviour
+    {
+        public ushort port = 9000;
+        public int players = 4;
+        public bool autoStart = true;
+        public string eventStreamName = "ServerEvents";
+        public bool debug = false;
+        public NetworkDriver m_Driver;
+        public NativeList<NetworkConnection> m_Connections;
+        private bool locked = false;
+
+        void Start ()
+        {
+            if(autoStart) StartServer();
+        }
+
+        public void StartServer()
+        {
+            if(locked) return;
+            m_Driver = NetworkDriver.Create();
+            var endpoint = NetworkEndPoint.AnyIpv4;
+            endpoint.Port = port;
+            if (m_Driver.Bind(endpoint) != 0)
+            {
+                if(debug) Debug.Log($"Failed to bind to port {port}");
+                GenericEvents.Trigger($"{eventStreamName}_PortError", $"Failed to bind to port {port}");
+            }
+            else
+            {
+                m_Driver.Listen();
+                if(debug) Debug.Log($"Server listening on port {port}");
+                GenericEvents.Trigger($"{eventStreamName}_Listening", $"Server listening on port {port}");
+                locked = true;
+            }
+            m_Connections = new NativeList<NetworkConnection>(players, Allocator.Persistent);
+        }
+
+        public void StopServer()
+        {
+            NetDisconnectMsg netDisconnectMsg = new NetDisconnectMsg();
+            SendNetMsgToAllClients((uint)NetOpCode.Disconnect, netDisconnectMsg);
+            Timer.ExecuteRealTime(500, () => {
+                if(debug) Debug.Log($"Server has stopped");
+                GenericEvents.Trigger($"{eventStreamName}_Stopped", $"Server has stopped");
+                m_Driver.Dispose();
+                m_Connections.Dispose();
+                locked = false;
+            });
+        }
+
+        public void SendNetMsgToClient(int id, uint code, NetMsg netMsg)
+        {
+            DataStreamWriter writer = new DataStreamWriter();
+            m_Driver.BeginSend(m_Connections[id], out writer);
+            NetMsgController.Serialize(code, netMsg, ref writer, this, id);
+            m_Driver.EndSend(writer);
+        }
+
+        public void SendNetMsgToAllClients(uint code, NetMsg netMsg)
+        {
+            for (int i = 0; i < m_Connections.Length; i++)
+            {
+                Assert.IsTrue(m_Connections[i].IsCreated);
+                SendNetMsgToClient(i, code, netMsg);
+            }
+        }
+
+        public void OnDestroy()
+        {
+            if(!m_Driver.IsCreated) return;
+            m_Driver.Dispose();
+            m_Connections.Dispose();
+        }
+
+        void Update ()
+        {
+            if(!m_Driver.IsCreated) return;
+            m_Driver.ScheduleUpdate().Complete();
+
+            // CleanUpConnections
+            for (int i = 0; i < m_Connections.Length; i++)
+            {
+                if (!m_Connections[i].IsCreated)
+                {
+                    m_Connections.RemoveAtSwapBack(i);
+                    --i;
+                }
+            }
+            // AcceptNewConnections
+            NetworkConnection c;
+            while ((c = m_Driver.Accept()) != default(NetworkConnection))
+            {
+                m_Connections.Add(c);
+                if(debug) Debug.Log($"Accepted a connection (ID {c.InternalId})");
+                GenericEvents.Trigger($"{eventStreamName}_ClientConnected", $"Accepted a connection (ID {c.InternalId})", c.InternalId);
+            }
+
+            DataStreamReader stream;
+            for (int i = 0; i < m_Connections.Length; i++)
+            {
+                Assert.IsTrue(m_Connections[i].IsCreated);
+
+                NetworkEvent.Type cmd;
+                while ((cmd = m_Driver.PopEventForConnection(m_Connections[i], out stream)) != NetworkEvent.Type.Empty)
+                {
+                    if (cmd == NetworkEvent.Type.Data)
+                    {
+                        //if(debug) Debug.Log("Server received data from client");
+                        uint code = stream.ReadByte();
+                        NetMsgController.Deserialize(code, ref stream, this, i);
+                    }
+                    else if (cmd == NetworkEvent.Type.Disconnect)
+                    {
+                        if(debug) Debug.Log($"Client disconnected from server (ID {m_Connections[i].InternalId})");
+                        GenericEvents.Trigger($"{eventStreamName}_ClientDisconnected", $"Client disconnected from server (ID {m_Connections[i].InternalId})");
+                        m_Connections[i] = default(NetworkConnection);
+                    }
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Runtime/Server/ServerBehaviour.cs.meta

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

+ 38 - 0
Runtime/Server/ServerHandshakeController.cs

@@ -0,0 +1,38 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using KairoEngine.Core;
+
+namespace KairoEngine.Multiplayer
+{
+    public class ServerHandshakeController : MonoBehaviour
+    {
+        public ServerBehaviour server;
+        public string eventStreamName = "ServerEvents";
+        private void Awake()
+        {
+            NetMsgEvents.StartListening($"{eventStreamName}_HandshakeReceived", OnEvent);
+        }
+
+        private void OnDestroy()
+        {
+            NetMsgEvents.StopListening($"{eventStreamName}_HandshakeReceived", OnEvent);
+        }
+
+        private void OnEvent(string text, int clientId, uint code, NetMsg netMsg)
+        {
+            if(server == null) return;
+            // Accept the client handshake
+            NetHandshakeMsg handshakeMsg = (NetHandshakeMsg)netMsg; 
+            Timer.ExecuteRealTime(100, ()=> {
+                server.SendNetMsgToClient(clientId, (uint)NetOpCode.Handshake, handshakeMsg);
+            });
+            // Alert all clients of a new connection
+            Timer.ExecuteRealTime(200, ()=> {
+                NetClientConnectedMsg msg = new NetClientConnectedMsg();
+                msg.playerName = handshakeMsg.playerName;
+                server.SendNetMsgToAllClients((uint)NetOpCode.ClientConnected, msg);
+            });
+        }
+    }
+}

+ 11 - 0
Runtime/Server/ServerHandshakeController.cs.meta

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

+ 71 - 0
Runtime/Server/ServerStatus.cs

@@ -0,0 +1,71 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Sirenix.OdinInspector;
+
+namespace KairoEngine.Multiplayer
+{
+    public enum ServerState
+    {
+        Offline,
+        Online    
+    }
+
+    [HideMonoScript]
+    public class ServerStatus : MonoBehaviour
+    {
+        public ServerBehaviour server;
+        [ReadOnly] public ServerState state;
+        [ReadOnly] public int clientCount;
+        [ReadOnly] public int maxClients;
+        [ReadOnly] public float uptime = 0;
+        [ReadOnly] public string ipAddress;
+        [ReadOnly] public int packetsSent;
+        [ReadOnly] public int packetsReceived;
+
+        private void OnEnable()
+        {
+            NetMsgEvents.StartListening($"{server.eventStreamName}_InvalidOpCode", OnReceivePacket);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_SendingDisconnectMessageToClient", OnSentPacket);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_ClientDisconnect", OnReceivePacket);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_HandshakeReceived", OnReceivePacket);
+            NetMsgEvents.StartListening($"{server.eventStreamName}_AcceptingHandshake", OnSentPacket);
+        }
+
+        private void OnDisable()
+        {
+            NetMsgEvents.StopListening($"{server.eventStreamName}_InvalidOpCode", OnReceivePacket);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_SendingDisconnectMessageToClient", OnSentPacket);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_ClientDisconnect", OnReceivePacket);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_HandshakeReceived", OnReceivePacket);
+            NetMsgEvents.StopListening($"{server.eventStreamName}_AcceptingHandshake", OnSentPacket);
+        }
+
+        private void Update()
+        {
+            if(server.m_Driver.IsCreated == false)
+            {
+                state = ServerState.Offline;
+                clientCount = 0;
+                uptime = 0;
+                packetsSent = 0;
+                packetsReceived = 0;
+            }
+            else
+            {
+                state = ServerState.Online;
+                clientCount = 0;
+                for (int i = 0; i < server.m_Connections.Length; i++)
+                {
+                    if(server.m_Connections[i].IsCreated) clientCount += 1;
+                }
+                uptime += Time.deltaTime;
+            }
+            maxClients = server.players;
+        }
+
+        private void OnSentPacket(string text, int clientId, uint code, NetMsg netMsg) => packetsSent += 1;
+
+        private void OnReceivePacket(string text, int clientId, uint code, NetMsg netMsg) => packetsReceived += 1;
+    }
+}

+ 11 - 0
Runtime/Server/ServerStatus.cs.meta

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

+ 8 - 0
Runtime/_NetMsgs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f87e2371f9c1afe40b199f68c0e66e1c
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 25 - 0
Runtime/_NetMsgs/NetChatMsg.cs

@@ -0,0 +1,25 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Unity.Networking.Transport;
+
+namespace KairoEngine.Multiplayer
+{
+    public class NetChatMsg : NetMsg
+    {
+        public NetChatMsg()
+        {
+            code = NetOpCode.ChatMessage;
+        }
+
+        public override void Serialize(ref DataStreamWriter writer)
+        {
+            writer.WriteByte((byte)code);
+        }
+
+        public override void Deserialize(ref DataStreamReader reader)
+        {
+            
+        }
+    }
+}

+ 11 - 0
Runtime/_NetMsgs/NetChatMsg.cs.meta

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

+ 28 - 0
Runtime/_NetMsgs/NetClientConnectedMsg.cs

@@ -0,0 +1,28 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Unity.Networking.Transport;
+
+namespace KairoEngine.Multiplayer
+{
+    /// <summary>Network message sent from the server to alert clients that a peer has connected.</summary>
+    public class NetClientConnectedMsg : NetMsg
+    {
+        public string playerName = "";
+        public NetClientConnectedMsg()
+        {
+            code = NetOpCode.ClientConnected;
+        }
+
+        public override void Serialize(ref DataStreamWriter writer)
+        {
+            writer.WriteByte((byte)code);
+            writer.WriteFixedString64(playerName);
+        }
+
+        public override void Deserialize(ref DataStreamReader reader)
+        {
+            playerName = reader.ReadFixedString64().ToString();
+        }
+    }
+}

+ 11 - 0
Runtime/_NetMsgs/NetClientConnectedMsg.cs.meta

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

+ 28 - 0
Runtime/_NetMsgs/NetClientDisconnectedMsg.cs

@@ -0,0 +1,28 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Unity.Networking.Transport;
+
+namespace KairoEngine.Multiplayer
+{
+    /// <summary>Network message sent from the server to alert clients that a peer has disconnected.</summary>
+    public class NetClientDisconnectedMsg : NetMsg
+    {
+        public string playerName = "";
+        public NetClientDisconnectedMsg()
+        {
+            code = NetOpCode.ClientDisconnected;
+        }
+
+        public override void Serialize(ref DataStreamWriter writer)
+        {
+            writer.WriteByte((byte)code);
+            writer.WriteFixedString64(playerName);
+        }
+
+        public override void Deserialize(ref DataStreamReader reader)
+        {
+            playerName = reader.ReadFixedString64().ToString();
+        }
+    }
+}

+ 11 - 0
Runtime/_NetMsgs/NetClientDisconnectedMsg.cs.meta

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

+ 25 - 0
Runtime/_NetMsgs/NetDisconnectMsg.cs

@@ -0,0 +1,25 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Unity.Networking.Transport;
+
+namespace KairoEngine.Multiplayer
+{
+    public class NetDisconnectMsg : NetMsg
+    {
+        public NetDisconnectMsg()
+        {
+            code = NetOpCode.Disconnect;
+        }
+
+        public override void Serialize(ref DataStreamWriter writer)
+        {
+            writer.WriteByte((byte)code);
+        }
+
+        public override void Deserialize(ref DataStreamReader reader)
+        {
+            
+        }
+    }
+}

+ 11 - 0
Runtime/_NetMsgs/NetDisconnectMsg.cs.meta

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

+ 27 - 0
Runtime/_NetMsgs/NetHandshakeMsg.cs

@@ -0,0 +1,27 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Unity.Networking.Transport;
+
+namespace KairoEngine.Multiplayer
+{
+    public class NetHandshakeMsg : NetMsg
+    {
+        public string playerName = "";
+        public NetHandshakeMsg()
+        {
+            code = NetOpCode.Handshake;
+        }
+
+        public override void Serialize(ref DataStreamWriter writer)
+        {
+            writer.WriteByte((byte)code);
+            writer.WriteFixedString64(playerName);
+        }
+
+        public override void Deserialize(ref DataStreamReader reader)
+        {
+            playerName = reader.ReadFixedString64().ToString();
+        }
+    }
+}

+ 11 - 0
Runtime/_NetMsgs/NetHandshakeMsg.cs.meta

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

+ 23 - 0
Runtime/_NetMsgs/NetMsg.cs

@@ -0,0 +1,23 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using Unity.Networking.Transport;
+
+namespace KairoEngine.Multiplayer
+{
+    public class NetMsg
+    {
+        public NetOpCode code { set; get;}
+
+        public virtual void Serialize(ref DataStreamWriter writer)
+        {
+            writer.WriteByte((byte)0);
+        }
+
+        public virtual void Deserialize(ref DataStreamReader reader)
+        {
+
+        }
+    }
+}
+

+ 11 - 0
Runtime/_NetMsgs/NetMsg.cs.meta

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