diff --git a/LowLevelMessages.cs b/LowLevelMessages.cs new file mode 100644 index 0000000..a976ddf --- /dev/null +++ b/LowLevelMessages.cs @@ -0,0 +1,35 @@ +using Passer; + +public class LowLevelMessages { + public static Spherical ReceiveSpherical(byte[] data, ref uint ix) { + float horizontal = ReceiveAngle8(data, ref ix); + float vertical = ReceiveAngle8(data, ref ix); + float distance = ReceiveFloat16(data, ref ix); + Spherical v = new(distance, horizontal, vertical); + return v; + } + + public static Quat32 ReceiveQuat32(byte[] data, ref uint ix) { + Quat32 q = new( + (data[ix++] - 128.0F) / 127.0F, + (data[ix++] - 128.0F) / 127.0F, + (data[ix++] - 128.0F) / 127.0F, + data[ix++] / 255.0F); + return q; + } + + public static float ReceiveAngle8(byte[] data, ref uint ix) { + float value = (data[ix++] * 180) / 128.0F; + return value; + } + + public static float ReceiveFloat16(byte[] data, ref uint ix) { + ushort value = (ushort)(data[ix++] << 8 | data[ix++]); + float16 f16 = new(); + f16.SetBinary(value); + float f = f16.toFloat(); + return f; + } + + +} diff --git a/LowLevelMessages.cs.meta b/LowLevelMessages.cs.meta new file mode 100644 index 0000000..9980c38 --- /dev/null +++ b/LowLevelMessages.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 157cf85b523c1f648adc007539f8b736 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Messages.cs b/Messages.cs index 7bbd7bc..eacfea6 100644 --- a/Messages.cs +++ b/Messages.cs @@ -1,120 +1,368 @@ using System.Collections.Generic; +using System.Collections.Concurrent; using System.IO; -using System.Net; using System.Net.Sockets; using System.Threading.Tasks; + namespace Passer.Control { -public class Client { - //public ConnectionMethod connection; - public UdpClient udpClient; - public string ipAddress; - public int port; - public byte networkId; + public class Client { + //public ConnectionMethod connection; + public UdpClient udpClient; + public string ipAddress; + public int port; - public static Client GetClient(string ipAddress, int port) { - foreach (Client c in clients) { - if (c.ipAddress == ipAddress && c.port == port) - return c; - } - return null; - } - static public List clients = new List(); + public byte networkId; - public static Client NewClient() { - Client client = new(); - clients.Add(client); - client.networkId = 0; + public readonly ConcurrentQueue messageQueue = new(); - return client; - } + public static Client GetClient(string ipAddress, int port) { + foreach (Client c in clients) { + if (c.ipAddress == ipAddress && c.port == port) + return c; + } + return null; + } + static public List clients = new List(); - public static Client NewUDPClient(UdpClient udpClient, string ipAddress, int port) { - Client client = NewClient(); - client.ipAddress = null; - client.port = port; - client.udpClient = udpClient; - return client; - } -} - -public class IMessage { - public IMessage() { } - public IMessage(byte[] data) { - Deserialize(data); - } - - public virtual byte[] Serialize() { return null; } - public virtual void Deserialize(byte[] data) { } - - public static void SendMsg(Client client, byte[] data) { - if (client.ipAddress != null) - client.udpClient.Send(data, data.Length, client.ipAddress, client.port); - else - ;//Debug.Log("Unknown client"); - } -} - -#region Client - -class ClientMsg : IMessage { - public const byte length = 2; - public byte clientId; - - public ClientMsg(byte[] data) : base(data) { } - public override void Deserialize(byte[] data) { - base.Deserialize(data); - uint ix = 0; - clientId = data[ix++]; - } - - protected async Task Receive(Stream dataStream, Client client, byte packetSize) { - if (packetSize != ClientMsg.length) { - //Debug.LogWarning($"Client msg size invalid: {packetSize}"); - return false; - } - - byte[] buffer = new byte[packetSize - 1]; // without msgId - int byteCount = dataStream.Read(buffer, 0, packetSize - 1); - while (byteCount < packetSize - 1) { - // not all bytes have been read, wait and try again - await Task.Delay(1); - byteCount += dataStream.Read(buffer, byteCount, packetSize - 1 - byteCount); - } - - ClientMsg msg = new(buffer); - - if (client.networkId == 0) { - client.networkId = (byte)(Client.clients.Count); - NetworkIdMsg.Send(client); - //if (string.IsNullOrEmpty(sceneUrl) == false) - //SendModelUrl(client, sceneUrl); - } - else if (msg.clientId == 0) { - NetworkIdMsg.Send(client); - //if (string.IsNullOrEmpty(sceneUrl) == false) - //SendModelUrl(client, sceneUrl); - } - - return true; - } -} - -#endregion Client - -#region Network Id - -class NetworkIdMsg : IMessage { - public const byte Id = 0xA1; - public const byte length = 2; - - public static void Send(Client client) { - byte[] data = new byte[NetworkIdMsg.length]; - data[0] = NetworkIdMsg.Id; - data[1] = client.networkId; - SendMsg(client, data); - } -} - -#endregion Network Id + public static Client NewClient() { + Client client = new(); + clients.Add(client); + client.networkId = 0; + + return client; + } + + public static Client NewUDPClient(UdpClient udpClient, string ipAddress, int port) { + Client client = NewClient(); + client.ipAddress = null; + client.port = port; + client.udpClient = udpClient; + return client; + } + } + + public class IMessage { + public IMessage() { } + public IMessage(byte[] data) { + Deserialize(data); + } + + public virtual byte[] Serialize() { return null; } + public virtual void Deserialize(byte[] data) { } + + public static bool SendMsg(Client client, IMessage msg) { + return SendMsg(client, msg.Serialize()); + } + public static bool SendMsg(Client client, byte[] data) { + if (client == null || client.ipAddress == null) + return false; + + client.udpClient.Send(data, data.Length, client.ipAddress, client.port); + return true; + } + public static async Task Receive(Stream dataStream, byte packetSize) { + byte[] buffer = new byte[packetSize - 1]; // without msgId + int byteCount = dataStream.Read(buffer, 0, packetSize - 1); + while (byteCount < packetSize - 1) { + // not all bytes have been read, wait and try again + await Task.Delay(1); + byteCount += dataStream.Read(buffer, byteCount, packetSize - 1 - byteCount); + } + return buffer; + } + } + + #region Client + + public class ClientMsg : IMessage { + public const byte length = 2; + public byte clientId; + + public ClientMsg(byte[] data) : base(data) { } + public override void Deserialize(byte[] data) { + base.Deserialize(data); + uint ix = 0; + clientId = data[ix++]; + } + + public static async Task Receive(Stream dataStream, Client client, byte packetSize) { + if (packetSize != length) + return false; + + byte[] buffer = await Receive(dataStream, packetSize); + ClientMsg msg = new(buffer); + + if (client.networkId == 0) { + client.networkId = (byte)(Client.clients.Count); + NetworkIdMsg.Send(client); + //if (string.IsNullOrEmpty(sceneUrl) == false) + //SendModelUrl(client, sceneUrl); + } + else if (msg.clientId == 0) { + NetworkIdMsg.Send(client); + //if (string.IsNullOrEmpty(sceneUrl) == false) + //SendModelUrl(client, sceneUrl); + } + + return true; + } + } + + #endregion Client + + #region Network Id + + public class NetworkIdMsg : IMessage { + public const byte Id = 0xA1; + public const byte length = 2; + + public static bool Send(Client client) { + byte[] data = new byte[NetworkIdMsg.length]; + data[0] = NetworkIdMsg.Id; + data[1] = client.networkId; + return SendMsg(client, data); + } + } + + #endregion Network Id + + #region Thing + + public class ThingMsg : IMessage { + public const byte length = 4; + public const byte Id = 0x80; + public byte objectId; + public byte objectType; + public byte parentId; + + public ThingMsg(byte[] data) : base(data) { } + public override void Deserialize(byte[] data) { + uint ix = 0; + objectId = data[ix++]; + objectType = data[ix++]; + parentId = data[ix]; + } + + public static bool Send(Client client, Thing obj) { + if (obj == null) + return false; + + byte[] data = new byte[4]; + data[0] = ThingMsg.Id; + data[1] = obj.networkId; + data[2] = obj.thingId; + data[3] = obj.objectType; + data[4] = 0x00; // not supported yet + return SendMsg(client, data); + } + public static async Task Receive(Stream dataStream, Client client, byte packetSize) { + if (packetSize != length) + return false; + + byte[] buffer = await Receive(dataStream, packetSize); + ThingMsg msg = new(buffer); + + client.messageQueue.Enqueue(msg); + return true; + } + } + + #endregion Thing + + #region Name + + public class NameMsg : IMessage { + public byte networkId = 0; + public byte objectId; + public byte len; + public string name; + + public NameMsg(byte[] data) : base(data) { } + public override void Deserialize(byte[] data) { + uint ix = 0; + this.objectId = data[ix++]; + int strlen = data[ix++]; + this.name = System.Text.Encoding.UTF8.GetString(data, (int)ix, strlen); + } + + public static async Task Receive(Stream dataStream, Client client, byte packetSize) { + byte[] buffer = await Receive(dataStream, packetSize); + NameMsg msg = new(buffer); + + client.messageQueue.Enqueue(msg); + return true; + } + } + + #endregion + + #region Model URL + + public class ModelUrlMsg : IMessage { + public const byte Id = 0x90; // (144) Model URL + public byte objectId; + public Spherical position; + public float scale; + public string url; + + public ModelUrlMsg(string url, float scale = 1) { + this.url = url; + this.scale = scale; + this.position = Spherical.zero; + } + public ModelUrlMsg(byte[] data) : base(data) { } + + public override byte[] Serialize() { + byte[] data = new byte[this.url.Length + 9]; + data[0] = ModelUrlMsg.Id; + data[1] = 0x00; // Thing Id + // data[2]..[5] == position 0, 0, 0 + data[6] = 0x3C; // Dummy float16 value 1 + data[7] = 0x00; + + data[8] = (byte)url.Length; + for (int ix = 0; ix < this.url.Length; ix++) + data[9 + ix] = (byte)url[ix]; + return data; + } + public override void Deserialize(byte[] data) { + uint ix = 0; + this.objectId = data[ix++]; + this.position = LowLevelMessages.ReceiveSpherical(data, ref ix); + this.scale = LowLevelMessages.ReceiveFloat16(data, ref ix); + int strlen = data[ix++]; + url = System.Text.Encoding.UTF8.GetString(data, (int)ix, strlen); + } + + public static bool Send(Client client, string modelUrl) { + ModelUrlMsg msg = new(modelUrl); + return SendMsg(client, msg); + } + public static async Task Receive(Stream dataStream, Client client, byte packetSize) { + byte[] buffer = await Receive(dataStream, packetSize); + ModelUrlMsg msg = new(buffer); + client.messageQueue.Enqueue(msg); + return true; + } + } + + #endregion Model URL + + #region Pose + + public class PoseMsg : IMessage { + public const byte length = 3 + 4 + 4; + public byte thingId; + public byte poseType; + + public Spherical position; + public Quat32 orientation; + + public PoseMsg(byte[] data) : base(data) { } + + public override void Deserialize(byte[] data) { + uint ix = 0; + thingId = data[ix++]; + poseType = data[ix++]; + + //if ((poseType & Pose_Position) != 0) + position = LowLevelMessages.ReceiveSpherical(data, ref ix); + //if ((poseType & Pose_Orientation) != 0) { + orientation = LowLevelMessages.ReceiveQuat32(data, ref ix); + } + + public static async Task Receive(Stream dataStream, Client client, byte packetSize) { + if (packetSize != length) + return false; + + byte[] buffer = await Receive(dataStream, packetSize); + PoseMsg msg = new(buffer); + + client.messageQueue.Enqueue(msg); + return true; + + } + } + + #endregion Pose + + #region Bytes + + public class BytesMsg : IMessage { + public const byte Id = 0xB1; + public byte networkId; + public byte thingId; + public byte[] bytes; + + public BytesMsg(byte[] data) : base(data) { } + public BytesMsg(byte networkId, byte thingId, byte[] bytes) : base() { + this.networkId = networkId; + this.thingId = thingId; + this.bytes = bytes; + } + public override byte[] Serialize() { + byte[] buffer = new byte[4 + this.bytes.Length]; + int ix = 0; + buffer[ix++] = BytesMsg.Id; + buffer[ix++] = this.networkId; + buffer[ix++] = this.thingId; + buffer[ix++] = (byte)bytes.Length; + foreach (byte b in bytes) + buffer[ix++] = b; + + return buffer; + } + public override void Deserialize(byte[] data) { + //this.bytes = data; + uint ix = 0; + this.thingId = data[ix++]; + this.bytes = new byte[data.Length - ix]; + for (uint bytesIx = 0; ix < data.Length; ix++, bytesIx++) + this.bytes[bytesIx] = data[ix]; + } + + + public static void Send(Client client, byte thingId, byte[] bytes) { + BytesMsg msg = new(client.networkId, thingId, bytes); + SendMsg(client, msg); + } + + // received bytes + public static async Task Receive(Stream dataStream, Client client, byte packetSize) { + byte[] buffer = await Receive(dataStream, packetSize); + BytesMsg msg = new(buffer); + client.messageQueue.Enqueue(msg); + return true; + } + } + + #endregion Bytes + + #region Destroy + + public class DestroyMsg : IMessage { + public const byte length = 2; + public byte objectId; + + public DestroyMsg(byte[] data) : base(data) { } + + public override void Deserialize(byte[] data) { + objectId = data[0]; + } + + public static async Task Receive(Stream dataStream, Client client, byte packetSize) { + if (packetSize != length) + return false; + + byte[] buffer = await Receive(dataStream, packetSize); + DestroyMsg msg = new(buffer); + + client.messageQueue.Enqueue(msg); + return true; + } + } + + + #endregion Destroy } diff --git a/Messages.cs.meta b/Messages.cs.meta new file mode 100644 index 0000000..60016bd --- /dev/null +++ b/Messages.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3f26f22a5422a44997b39a1844ab2ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Quat32.cs b/Quat32.cs new file mode 100644 index 0000000..dc6cbc4 --- /dev/null +++ b/Quat32.cs @@ -0,0 +1,15 @@ +namespace Passer { + public class Quat32 { + public float x; + public float y; + public float z; + public float w; + + public Quat32(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + } +} \ No newline at end of file diff --git a/Quat32.cs.meta b/Quat32.cs.meta new file mode 100644 index 0000000..c3f81a4 --- /dev/null +++ b/Quat32.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96aa3cf18eaeb574d9265704d68000da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..f345b71 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3b79b5b373e9ced4abe72eb4d2f83c6a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Spherical.cs b/Spherical.cs new file mode 100644 index 0000000..3ecb663 --- /dev/null +++ b/Spherical.cs @@ -0,0 +1,15 @@ +namespace Passer { + public class Spherical { + public float distance; + public float horizontal; + public float vertical; + + public static Spherical zero = new(0, 0, 0); + + public Spherical(float distance, float horizontal, float vertical) { + this.distance = distance; + this.horizontal = horizontal; + this.vertical = vertical; + } + } +} \ No newline at end of file diff --git a/Spherical.cs.meta b/Spherical.cs.meta new file mode 100644 index 0000000..5ba10b9 --- /dev/null +++ b/Spherical.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33f05ee51ff6f7042b3e22723bcfc4f5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: