using LZ4Sharp;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
namespace com.rfilkov.kinect
{
///
/// NetClientInterface is a sensor-interface that receives the sensor data over the network instead from a connected device.
///
public class NetClientInterface : DepthSensorBase
{
[Tooltip("Whether to broadcast, in order to find automatically the first available server (local network only).")]
public bool autoServerDiscovery = true;
[Tooltip("Host name or IP address of the network server.")]
public string serverHost = "localhost";
[Tooltip("The base port for all server frame streams (each frame stream listens on separate port).")]
public int serverBasePort = 11000;
[Tooltip("Whether to get color frames from the server, if required by KinectManager.")]
public bool getColorFrames = true;
[Tooltip("Whether to get depth frames from the server, if required by KinectManager.")]
public bool getDepthFrames = true;
[Tooltip("Whether to get infrared frames from the server, if required by KinectManager.")]
public bool getInfraredFrames = true;
[Tooltip("Whether to get body-data frames from the server, if required by KinectManager.")]
public bool getBodyFrames = true;
[Tooltip("Whether to get body-index frames from the server, if required by KinectManager.")]
public bool getBodyIndexFrames = true;
[Tooltip("Whether to get sensor-pose frames from the server, if required by KinectManager.")]
public bool getPoseFrames = true;
[Tooltip("UI-Text to display client status messages.")]
public UnityEngine.UI.Text clientStatusText;
// sensor frame servers
private TcpNetClient controlFrameClient = null;
private TcpNetClient colorFrameClient = null;
private TcpNetClient depthFrameClient = null;
private TcpNetClient infraredFrameClient = null;
private TcpNetClient bodyDataFrameClient = null;
private TcpNetClient bodyIndexFrameClient = null;
private TcpNetClient poseFrameClient = null;
private TcpNetClient depth2colorFrameClient = null;
private TcpNetClient color2depthFrameClient = null;
private TcpNetClient color2infraredFrameClient = null;
private TcpNetClient color2bodyIndexFrameClient = null;
// sensor data
private KinectInterop.SensorData sensorData = null;
// control frame
private KinectInterop.NetSensorData netSensorData = null;
//private ulong lastControlFrameTime = 0;
//private bool bSensorDataMsgSent = false;
private bool bGotNetSensorData = false;
private bool bSetNetSensorData = false;
private bool bInvalidSensorData = false;
private bool bGotDST = false;
private bool bGotCST = false;
// thread sleep time in milliseconds, to wait for server response
private const int THREAD_WAIT_TIME_MS = 100;
// keep-alive
private ulong lastKeepAliveFrameTime = 0;
private const int keepAliveInterval = 20000000; // 2 seconds
// disconnect-reconnect
private ulong latestDataReceivedAt = 0;
private const int disconnectAfter = 300000000; // 10 seconds
private ulong disconnectedAt = 0;
private const int reconnectAfter = 50000000; // 5 seconds
// color image
private byte[] colorImageData = null;
private ulong lastColorImageTime = 0;
// infrared image
private int infraredImageWidth = 0, infraredImageHeight = 0;
private byte[] infraredImageData = null;
private ulong lastInfraredImageTime = 0;
private Texture2D infraredImageTex = null;
// data frame times
private ulong lastControlFrameTime = 0;
private ulong lastColorFrameTime = 0;
private ulong lastDepthFrameTime = 0;
private ulong lastInfraredFrameTime = 0;
private ulong lastBodyDataFrameTime = 0;
private ulong lastBodyIndexFrameTime = 0;
private ulong lastPoseFrameTime = 0;
private ulong lastDepth2colorFrameTime = 0;
private ulong lastColor2depthFrameTime = 0;
private ulong lastColor2infraredFrameTime = 0;
private ulong lastColor2bodyIndexFrameTime = 0;
// body frame
private bool bIgnoreZcoords = false;
// pose frame
private KinectInterop.NetPoseData netPoseData = null;
private object netPoseLock = new object();
// depth2color frame
private int depth2colorImageWidth = 0, depth2colorImageHeight = 0;
private byte[] depth2colorImageData = null;
private ulong lastDepth2ColorImageTime = 0;
// color2infrared frame
private int color2infraredImageWidth = 0, color2infraredImageHeight = 0;
private byte[] color2infraredImageData = null;
private ulong lastColor2InfraredImageTime = 0;
private Texture2D colorInfraredTexture = null;
// decompressors
private ILZ4Decompressor controlFrameDecompressor = null;
private ILZ4Decompressor depthFrameDecompressor = null;
private ILZ4Decompressor bodyIndexFrameDecompressor = null;
private ILZ4Decompressor color2depthFrameDecompressor = null;
private ILZ4Decompressor color2bodyIndexFrameDecompressor = null;
// console buffer
private System.Text.StringBuilder sbConsole = new System.Text.StringBuilder();
// whether the server broadcast response is received
private bool bBroadcastResponseReceived = false;
// frame synchronization
private bool isFrameSyncNeeded = false;
private Dictionary> dictNetMessageData = null;
private List alNetMessageTime = null;
private object syncMessageLock = new object();
// depth sensor settings
[System.Serializable]
public class NetSensorSettings : DepthSensorBase.BaseSensorSettings
{
public string serverHost;
public int serverBasePort;
public bool getBodyIndexFrames;
}
public override KinectInterop.DepthSensorPlatform GetSensorPlatform()
{
return KinectInterop.DepthSensorPlatform.NetSensor;
}
public override System.Type GetSensorSettingsType()
{
return typeof(NetSensorSettings);
}
public override BaseSensorSettings GetSensorSettings(BaseSensorSettings settings)
{
if (settings == null)
{
settings = new NetSensorSettings();
}
NetSensorSettings extSettings = (NetSensorSettings)base.GetSensorSettings(settings);
extSettings.serverHost = serverHost;
extSettings.serverBasePort = serverBasePort;
extSettings.getBodyIndexFrames = getBodyIndexFrames;
return settings;
}
public override void SetSensorSettings(BaseSensorSettings settings)
{
if (settings == null)
return;
base.SetSensorSettings(settings);
NetSensorSettings extSettings = (NetSensorSettings)settings;
serverHost = extSettings.serverHost;
serverBasePort = extSettings.serverBasePort;
getBodyIndexFrames = extSettings.getBodyIndexFrames;
}
public override List GetAvailableSensors()
{
List alSensorInfo = new List();
KinectInterop.SensorDeviceInfo sensorInfo = new KinectInterop.SensorDeviceInfo();
sensorInfo.sensorId = serverHost + ":" + serverBasePort + ", discovery: " + autoServerDiscovery;
sensorInfo.sensorName = "NetSensor";
sensorInfo.sensorCaps = KinectInterop.FrameSource.TypeAll;
if (consoleLogMessages)
Debug.Log(string.Format(" D{0}: {1}, id: {2}", 0, sensorInfo.sensorName, sensorInfo.sensorId));
alSensorInfo.Add(sensorInfo);
return alSensorInfo;
}
public override KinectInterop.SensorData OpenSensor(KinectManager kinectManager, KinectInterop.FrameSource dwFlags, bool bSyncDepthAndColor, bool bSyncBodyAndDepth)
{
// save initial parameters
base.OpenSensor(kinectManager, dwFlags, bSyncDepthAndColor, bSyncBodyAndDepth);
if (deviceStreamingMode == KinectInterop.DeviceStreamingMode.PlayRecording)
{
Debug.LogWarning("Playback selected, but this is not applicable to the network sensor. Ignoring...");
}
//List alSensors = GetAvailableSensors();
//if (deviceIndex >= alSensors.Count)
//{
// Debug.Log(" D" + deviceIndex + " is not available. You can set the device index to -1, to disable it.");
// return null;
//}
sensorData = new KinectInterop.SensorData();
sensorData.sensorIntPlatform = KinectInterop.DepthSensorPlatform.NetSensor;
sensorData.sensorId = "NetSensor" + "_" + serverHost + "_" + serverBasePort;
sensorData.sensorName = "NetSensor";
sensorData.sensorCaps = KinectInterop.FrameSource.TypeAll;
// flip color & depth image vertically
sensorData.colorImageScale = new Vector3(-1f, -1f, 1f);
sensorData.depthImageScale = new Vector3(-1f, -1f, 1f);
sensorData.infraredImageScale = new Vector3(-1f, -1f, 1f);
sensorData.sensorSpaceScale = new Vector3(-1f, -1f, 1f);
sensorData.unitToMeterFactor = 1f;
// depth camera offset & matrix z-flip
sensorRotOffset = new Vector3(0f, 0f, 0f);
sensorRotFlipZ = true;
sensorRotIgnoreY = true;
// color camera data & intrinsics
sensorData.colorImageFormat = TextureFormat.RGB24;
sensorData.colorImageStride = 3; // 3 bytes per pixel
// init network clients
InitNetClients(dwFlags);
// check for frame sync
isFrameSyncNeeded = bSyncDepthAndColor || bSyncBodyAndDepth;
if(isFrameSyncNeeded)
{
dictNetMessageData = new Dictionary>();
alNetMessageTime = new List();
}
if (consoleLogMessages)
Debug.Log("D" + deviceIndex + " NetSensor opened: " + serverHost + ":" + serverBasePort + ", discovery: " + autoServerDiscovery);
return sensorData;
}
public override void CloseSensor(KinectInterop.SensorData sensorData)
{
// close network clients
CloseNetClients();
CleanupSyncMessageData();
// close opened resources
base.CloseSensor(sensorData);
// finish frame sync
if(isFrameSyncNeeded)
{
isFrameSyncNeeded = false;
dictNetMessageData.Clear();
dictNetMessageData = null;
alNetMessageTime.Clear();
alNetMessageTime = null;
}
if (consoleLogMessages)
Debug.Log("D" + deviceIndex + " NetSensor closed: " + serverHost + ":" + serverBasePort + ", discovery: " + autoServerDiscovery);
}
public override bool IsSensorDataValid()
{
if(bInvalidSensorData)
{
// got invalid sensor-data once
return false;
}
// wait for net-cst
long timeStart = System.DateTime.Now.Ticks;
long timeNow = timeStart;
while (!bGotNetSensorData && (timeNow - timeStart) < 50000000) // timeout - 5 seconds
{
Thread.Sleep(THREAD_WAIT_TIME_MS);
timeNow = System.DateTime.Now.Ticks;
}
if (bGotNetSensorData && !bSetNetSensorData && netSensorData != null)
{
SetNetSensorData(netSensorData, sensorData, KinectManager.Instance);
bSetNetSensorData = true;
netSensorData = null;
}
bInvalidSensorData = !bGotNetSensorData;
return bGotNetSensorData;
}
public override bool UpdateSensorData(KinectInterop.SensorData sensorData, KinectManager kinectManager, bool isPlayMode)
{
// client status text
if (sbConsole.Length > 0)
{
lock (sbConsole)
{
if (clientStatusText)
clientStatusText.text = sbConsole.ToString();
sbConsole.Clear();
}
}
// check for server timeout
ulong ulTimeNow = (ulong)System.DateTime.Now.Ticks;
bool isControlClientActive = controlFrameClient != null ? controlFrameClient.IsActive() : false;
latestDataReceivedAt = GetLatestTimestamp();
if(latestDataReceivedAt != 0 && (ulTimeNow - latestDataReceivedAt) >= disconnectAfter && isControlClientActive)
{
Debug.LogWarning("Server timeout detected. Disconnecting...");
disconnectedAt = ulTimeNow;
CloseNetClients();
CleanupSyncMessageData();
return true;
}
// check for server disconnection
if (disconnectedAt == 0 && controlFrameClient != null && !isControlClientActive)
{
Debug.LogError("Server disconnection detected.");
CloseNetClients();
CleanupSyncMessageData();
disconnectedAt = ulTimeNow;
}
// try to reconnect
if(disconnectedAt != 0 && (ulTimeNow - disconnectedAt) >= reconnectAfter)
{
if (consoleLogMessages)
Debug.Log("Start reconnecting...");
CloseNetClients();
CleanupSyncMessageData();
InitNetClients(frameSourceFlags);
return true;
}
// send control messages as needed
UpdateSendControlMessages(ulTimeNow, isControlClientActive, kinectManager);
// color frame
if (colorImageData != null && sensorData.lastColorFrameTime != lastColorImageTime && !isPlayMode)
{
if (sensorData.colorImageTexture == null && sensorData.colorImageWidth > 0 && sensorData.colorImageHeight > 0)
{
sensorData.colorImageTexture = new Texture2D(sensorData.colorImageWidth, sensorData.colorImageHeight, TextureFormat.RGB24, false);
sensorData.colorImageTexture.wrapMode = TextureWrapMode.Clamp;
sensorData.colorImageTexture.filterMode = FilterMode.Point;
}
lock (colorFrameLock)
{
Texture2D colorImageTex2D = (Texture2D)sensorData.colorImageTexture;
if (colorImageTex2D != null)
{
colorImageTex2D.LoadImage(colorImageData);
colorImageTex2D.Apply();
}
sensorData.lastColorFrameTime = currentColorTimestamp = rawColorTimestamp = lastColorImageTime;
//Debug.Log("UpdateColorTimestamp: " + lastColorImageTime);
}
}
// check for depth texture
if((sensorData.depthImageTexture == null && sensorData.depthImage != null &&
kinectManager.getDepthFrames == KinectManager.DepthTextureType.DepthTexture) ||
(sensorData.bodyImageTexture == null && sensorData.bodyIndexImage != null &&
(kinectManager.getBodyFrames == KinectManager.BodyTextureType.UserTexture || kinectManager.getBodyFrames == KinectManager.BodyTextureType.BodyTexture)))
{
KinectInterop.InitSensorData(sensorData, kinectManager);
}
// infrared frame
if(infraredImageData != null && sensorData.lastInfraredImageTime != lastInfraredImageTime && !isPlayMode)
{
if (sensorData.infraredImageTexture == null && infraredImageWidth > 0 && infraredImageHeight > 0)
{
infraredImageTex = new Texture2D(infraredImageWidth, infraredImageHeight, TextureFormat.RGB24, false);
infraredImageTex.wrapMode = TextureWrapMode.Clamp;
infraredImageTex.filterMode = FilterMode.Point;
sensorData.infraredImageTexture = KinectInterop.CreateRenderTexture(sensorData.infraredImageTexture, infraredImageWidth, infraredImageHeight);
}
lock (infraredFrameLock)
{
if (infraredImageTex != null)
{
infraredImageTex.LoadImage(infraredImageData);
infraredImageTex.Apply();
Graphics.Blit(infraredImageTex, sensorData.infraredImageTexture);
}
sensorData.lastInfraredImageTime = lastInfraredImageTime;
//Debug.Log("UpdateInfraredImageTimestamp: " + lastInfraredImageTime);
}
}
// pose frame
if (netPoseData != null && sensorData.lastSensorPoseFrameTime != netPoseData.sensorPoseTime && !isPlayMode)
{
lock(netPoseLock)
{
SetSensorNetPoseData(netPoseData, sensorData, kinectManager);
ApplySensorPoseUpdate(kinectManager);
}
}
// get KM setting for processing of body data
bIgnoreZcoords = kinectManager.ignoreZCoordinates;
// process the other sensor data
return base.UpdateSensorData(sensorData, kinectManager, isPlayMode);
}
// send the control messages as needed
private void UpdateSendControlMessages(ulong ulTimeNow, bool isControlClientActive, KinectManager kinectManager)
{
//// control - get sensor data
//if (!bSensorDataMsgSent && isControlClientActive)
//{
// SendControlMessage(ControlMessageType.GetSensorData);
// bSensorDataMsgSent = true;
// lastKeepAliveFrameTime = ulTimeNow;
//}
// control - set-sensor-data
if (netSensorData != null)
{
SetNetSensorData(netSensorData, sensorData, kinectManager);
bSetNetSensorData = true;
netSensorData = null;
}
// control - keep alive
if ((ulTimeNow - lastKeepAliveFrameTime) >= keepAliveInterval && isControlClientActive)
{
lastKeepAliveFrameTime = ulTimeNow;
SendControlMessage(ControlMessageType.KeepAlive);
}
}
// unprojects plane point into the space
public override Vector3 UnprojectPoint(KinectInterop.CameraIntrinsics intr, Vector2 pixel, float depth)
{
Vector3 point = Vector3.zero;
if (intr == null || depth <= 0f)
return point;
if(sensorPlatform == KinectInterop.DepthSensorPlatform.Kinect4Azure || sensorPlatform == KinectInterop.DepthSensorPlatform.DummyK4A)
{
depth = depth * 1000f;
}
int di = (int)(pixel.x + 0.5f) + (int)(pixel.y + 0.5f) * intr.width;
if (intr.cameraType == 0) // depth
{
if (depth2SpaceTable == null)
{
GetDepthCameraSpaceTable(sensorData);
}
if(depth2SpaceTable != null && di >= 0 && di < depth2SpaceTable.Length)
{
point = depth2SpaceTable[di] * depth;
}
}
else if(intr.cameraType == 1) // color
{
if (color2SpaceTable == null)
{
GetColorCameraSpaceTable(sensorData);
}
if (color2SpaceTable != null && di >= 0 && di < color2SpaceTable.Length)
{
point = color2SpaceTable[di] * depth;
}
}
return point;
}
// projects space point onto a plane
public override Vector2 ProjectPoint(KinectInterop.CameraIntrinsics intr, Vector3 point)
{
if (intr == null || point == Vector3.zero)
return Vector2.zero;
float x = point.x / point.z;
float y = point.y / point.z;
Vector2 pixel = new Vector2(x * intr.fx + intr.ppx, y * intr.fy + intr.ppy);
return pixel;
}
// transforms a point from one space to another
public override Vector3 TransformPoint(KinectInterop.CameraExtrinsics extr, Vector3 point)
{
if (extr == null)
return Vector3.zero;
float toPointX = 0f, toPointY = 0f, toPointZ = 0f;
if (sensorPlatform == KinectInterop.DepthSensorPlatform.RealSense)
{
// RS
toPointX = extr.rotation[0] * point.x + extr.rotation[3] * point.y + extr.rotation[6] * point.z + extr.translation[0];
toPointY = extr.rotation[1] * point.x + extr.rotation[4] * point.y + extr.rotation[7] * point.z + extr.translation[1];
toPointZ = extr.rotation[2] * point.x + extr.rotation[5] * point.y + extr.rotation[8] * point.z + extr.translation[2];
}
else
{
// K4A, K2
toPointX = extr.rotation[0] * point.x + extr.rotation[1] * point.y + extr.rotation[2] * point.z + extr.translation[0];
toPointY = extr.rotation[3] * point.x + extr.rotation[4] * point.y + extr.rotation[5] * point.z + extr.translation[1];
toPointZ = extr.rotation[6] * point.x + extr.rotation[7] * point.y + extr.rotation[8] * point.z + extr.translation[2];
}
return new Vector3(toPointX, toPointY, toPointZ);
}
public override Vector3[] GetDepthCameraSpaceTable(KinectInterop.SensorData sensorData)
{
if (sensorData == null)
return null;
if (depth2SpaceTable == null || depth2SpaceWidth != sensorData.depthImageWidth || depth2SpaceHeight != sensorData.depthImageHeight)
{
depth2SpaceWidth = sensorData.depthImageWidth;
depth2SpaceHeight = sensorData.depthImageHeight;
int depthImageLength = sensorData.depthImageWidth * sensorData.depthImageHeight;
depth2SpaceTable = new Vector3[depthImageLength];
//bNeedDST = true;
SendControlMessage(ControlMessageType.GetDST);
//bDSTsent = true;
}
// wait for net-dst
long timeStart = System.DateTime.Now.Ticks;
long timeNow = timeStart;
while (!bGotDST && (timeNow - timeStart) < 50000000) // timeout - 5 seconds
{
Thread.Sleep(THREAD_WAIT_TIME_MS);
timeNow = System.DateTime.Now.Ticks;
}
if(!bGotDST)
{
Debug.LogWarning("Timed out waiting for net-dst.");
}
return depth2SpaceTable;
}
public override Vector3[] GetColorCameraSpaceTable(KinectInterop.SensorData sensorData)
{
if (sensorData == null)
return null;
if (color2SpaceTable == null || color2SpaceWidth != sensorData.colorImageWidth || color2SpaceHeight != sensorData.colorImageHeight)
{
color2SpaceWidth = sensorData.colorImageWidth;
color2SpaceHeight = sensorData.colorImageHeight;
int colorImageLength = sensorData.colorImageWidth * sensorData.colorImageHeight;
color2SpaceTable = new Vector3[colorImageLength];
//bNeedCST = true;
SendControlMessage(ControlMessageType.GetCST);
//bCSTsent = true;
}
// wait for net-cst
long timeStart = System.DateTime.Now.Ticks;
long timeNow = timeStart;
while (!bGotCST && (timeNow - timeStart) < 50000000) // timeout - 5 seconds
{
Thread.Sleep(THREAD_WAIT_TIME_MS);
timeNow = System.DateTime.Now.Ticks;
}
if (!bGotCST)
{
Debug.LogWarning("Timed out waiting for net-cst.");
}
return color2SpaceTable;
}
// returns the point cloud texture resolution
public override Vector2Int GetPointCloudTexResolution(KinectInterop.SensorData sensorData)
{
Vector2Int texRes = Vector2Int.zero;
// wait for net-cst
long timeStart = System.DateTime.Now.Ticks;
long timeNow = timeStart;
while (texRes == Vector2Int.zero && (timeNow - timeStart) < 50000000) // timeout - 5 seconds
{
switch (pointCloudResolution)
{
case PointCloudResolution.DepthCameraResolution:
texRes = new Vector2Int(sensorData.depthImageWidth, sensorData.depthImageHeight);
break;
case PointCloudResolution.ColorCameraResolution:
texRes = new Vector2Int(sensorData.colorImageWidth, sensorData.colorImageHeight);
break;
}
if(texRes == Vector2Int.zero)
{
Thread.Sleep(THREAD_WAIT_TIME_MS);
}
timeNow = System.DateTime.Now.Ticks;
}
if (texRes == Vector2Int.zero)
{
throw new System.Exception("Unsupported point cloud resolution: " + pointCloudResolution + " or the respective image is not available.");
}
return texRes;
}
// creates the point-cloud vertex shader and its respective buffers, as needed
protected override bool CreatePointCloudVertexShader(KinectInterop.SensorData sensorData)
{
bool bSuccess = base.CreatePointCloudVertexShader(sensorData);
if (pointCloudResolution == PointCloudResolution.ColorCameraResolution && color2depthFrameClient == null)
{
color2depthFrameDecompressor = LZ4DecompressorFactory.CreateNew();
color2depthFrameClient = new TcpNetClient(sbConsole, color2depthFrameDecompressor);
color2depthFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Color2Depth, "tdepth", null);
color2depthFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(Color2DepthFrameReceived);
}
return bSuccess;
}
// disposes the point-cloud vertex shader and its respective buffers
protected override void DisposePointCloudVertexShader(KinectInterop.SensorData sensorData)
{
base.DisposePointCloudVertexShader(sensorData);
if(color2depthFrameClient != null)
{
color2depthFrameClient.ReceivedMessage -= Color2DepthFrameReceived;
color2depthFrameClient.Close();
color2depthFrameClient = null;
}
}
// creates the point-cloud color shader and its respective buffers, as needed
protected override bool CreatePointCloudColorShader(KinectInterop.SensorData sensorData)
{
if (pointCloudResolution == PointCloudResolution.DepthCameraResolution)
{
if (pointCloudAlignedColorTex == null)
{
pointCloudAlignedColorTex = new Texture2D(sensorData.depthImageWidth, sensorData.depthImageHeight, sensorData.colorImageFormat, false);
pointCloudAlignedColorTex.wrapMode = TextureWrapMode.Clamp;
pointCloudAlignedColorTex.filterMode = FilterMode.Point;
}
if (depth2colorFrameClient == null)
{
depth2colorFrameClient = new TcpNetClient(sbConsole, null);
depth2colorFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Depth2Color, "tcolor", null);
depth2colorFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(Depth2ColorFrameReceived);
}
return true;
}
else
{
return base.CreatePointCloudColorShader(sensorData);
}
}
// disposes the point-cloud color shader and its respective buffers
protected override void DisposePointCloudColorShader(KinectInterop.SensorData sensorData)
{
if(depth2colorFrameClient != null)
{
depth2colorFrameClient.ReceivedMessage -= Depth2ColorFrameReceived;
depth2colorFrameClient.Close();
depth2colorFrameClient = null;
pointCloudAlignedColorTex = null;
}
else
{
base.DisposePointCloudColorShader(sensorData);
}
}
// updates the point-cloud color shader with the actual data
protected override bool UpdatePointCloudColorShader(KinectInterop.SensorData sensorData)
{
if (depth2colorFrameClient != null)
{
// depth2color frame
if (depth2colorImageData != null && sensorData.lastDepthCamColorFrameTime != lastDepth2ColorImageTime)
{
if (depth2colorImageWidth > 0 && depth2colorImageHeight > 0)
{
if (pointCloudAlignedColorTex == null || pointCloudAlignedColorTex.width != depth2colorImageWidth || pointCloudAlignedColorTex.height != depth2colorImageHeight)
{
if (pointCloudAlignedColorTex != null)
Destroy(pointCloudAlignedColorTex);
pointCloudAlignedColorTex = new Texture2D(depth2colorImageWidth, depth2colorImageHeight, TextureFormat.RGB24, false);
pointCloudAlignedColorTex.wrapMode = TextureWrapMode.Clamp;
pointCloudAlignedColorTex.filterMode = FilterMode.Point;
}
}
lock (depthCamColorFrameLock)
{
if (pointCloudAlignedColorTex != null || sensorData.depthCamColorImageTexture != null)
{
((Texture2D)pointCloudAlignedColorTex).LoadImage(depth2colorImageData);
((Texture2D)pointCloudAlignedColorTex).Apply();
if (sensorData.depthCamColorImageTexture != null)
{
Graphics.CopyTexture(pointCloudAlignedColorTex, sensorData.depthCamColorImageTexture);
}
if(pointCloudColorTexture != null)
{
Graphics.Blit(pointCloudAlignedColorTex, pointCloudColorTexture);
}
}
sensorData.lastDepthCamColorFrameTime = lastDepthCamColorFrameTime = lastDepth2ColorImageTime;
//Debug.Log("UpdateDepth2ColorImageTimestamp: " + lastDepth2ColorImageTime);
}
if (pointCloudColorTexture != null && pointCloudColorRT != null)
{
Graphics.CopyTexture(pointCloudColorTexture, pointCloudColorRT);
}
}
return true;
}
else
{
return base.UpdatePointCloudColorShader(sensorData);
}
}
// creates the color-cam depth shader and its respective buffers, as needed
protected override bool CreateColorDepthShader(KinectInterop.SensorData sensorData)
{
bool bSuccess = base.CreateColorDepthShader(sensorData);
if (color2depthFrameClient == null)
{
color2depthFrameDecompressor = LZ4DecompressorFactory.CreateNew();
color2depthFrameClient = new TcpNetClient(sbConsole, color2depthFrameDecompressor);
color2depthFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Color2Depth, "tdepth", null);
color2depthFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(Color2DepthFrameReceived);
}
return bSuccess;
}
// disposes the color-cam depth shader and its respective buffers
protected override void DisposeColorDepthShader(KinectInterop.SensorData sensorData)
{
base.DisposeColorDepthShader(sensorData);
if (color2depthFrameClient != null)
{
color2depthFrameClient.ReceivedMessage -= Color2DepthFrameReceived;
color2depthFrameClient.Close();
color2depthFrameClient = null;
}
}
// creates the color-cam infrared shader and its respective buffers, as needed
protected override bool CreateColorInfraredShader(KinectInterop.SensorData sensorData)
{
//bool bSuccess = base.CreateColorInfraredShader(sensorData);
if (color2infraredFrameClient == null)
{
color2infraredFrameClient = new TcpNetClient(sbConsole, null);
color2infraredFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Color2Infrared, "tinfrared", null);
color2infraredFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(Color2InfraredFrameReceived);
}
return true; // bSuccess;
}
// disposes the color-cam infrared shader and its respective buffers
protected override void DisposeColorInfraredShader(KinectInterop.SensorData sensorData)
{
base.DisposeColorInfraredShader(sensorData);
if (color2infraredFrameClient != null)
{
color2infraredFrameClient.ReceivedMessage -= Color2InfraredFrameReceived;
color2infraredFrameClient.Close();
color2infraredFrameClient = null;
}
}
// updates the color-cam infrared shader with the actual data
protected override bool UpdateColorInfraredShader(KinectInterop.SensorData sensorData)
{
if (color2infraredFrameClient != null)
{
// color2infrared frame
if (color2infraredImageData != null && sensorData.lastColorCamInfraredFrameTime != lastColor2InfraredImageTime)
{
if (color2infraredImageWidth > 0 && color2infraredImageHeight > 0)
{
if (sensorData.colorInfraredTexture == null || sensorData.colorInfraredTexture.width != color2infraredImageWidth || sensorData.colorInfraredTexture.height != color2infraredImageHeight)
{
sensorData.colorInfraredTexture = KinectInterop.CreateRenderTexture(sensorData.colorInfraredTexture, color2infraredImageWidth, color2infraredImageHeight, RenderTextureFormat.ARGB32);
//sensorData.colorInfraredTexture.enableRandomWrite = true;
sensorData.colorInfraredTexture.Create();
}
if (colorInfraredTexture == null || colorInfraredTexture.width != color2infraredImageWidth || colorInfraredTexture.height != color2infraredImageHeight)
{
if (colorInfraredTexture != null)
Destroy(colorInfraredTexture);
colorInfraredTexture = new Texture2D(color2infraredImageWidth, color2infraredImageHeight, TextureFormat.RGB24, false);
colorInfraredTexture.wrapMode = TextureWrapMode.Clamp;
colorInfraredTexture.filterMode = FilterMode.Point;
}
}
lock (colorCamInfraredFrameLock)
{
if (colorInfraredTexture != null || sensorData.colorInfraredTexture != null)
{
colorInfraredTexture.LoadImage(color2infraredImageData);
colorInfraredTexture.Apply();
if (sensorData.colorInfraredTexture != null)
{
Graphics.CopyTexture(colorInfraredTexture, sensorData.colorInfraredTexture);
}
}
sensorData.lastColorCamInfraredFrameTime = lastColorCamInfraredFrameTime = lastColor2InfraredImageTime;
//Debug.Log("UpdateColor2InfraredImageTimestamp: " + lastColor2InfraredImageTime);
}
}
return true;
}
else
{
return base.UpdateColorInfraredShader(sensorData);
}
}
// creates the color-cam body-index shader and its respective buffers, as needed
protected override bool CreateColorBodyIndexShader(KinectInterop.SensorData sensorData)
{
bool bSuccess = base.CreateColorBodyIndexShader(sensorData);
if (color2bodyIndexFrameClient == null)
{
color2bodyIndexFrameDecompressor = LZ4DecompressorFactory.CreateNew();
color2bodyIndexFrameClient = new TcpNetClient(sbConsole, color2bodyIndexFrameDecompressor);
color2bodyIndexFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Color2BodyIndex, "tbodyindex", null);
color2bodyIndexFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(Color2BodyIndexFrameReceived);
}
return bSuccess;
}
// disposes the color-cam body-index shader and its respective buffers
protected override void DisposeColorBodyIndexShader(KinectInterop.SensorData sensorData)
{
base.DisposeColorBodyIndexShader(sensorData);
if (color2bodyIndexFrameClient != null)
{
color2bodyIndexFrameClient.ReceivedMessage -= Color2BodyIndexFrameReceived;
color2bodyIndexFrameClient.Close();
color2bodyIndexFrameClient = null;
}
}
// broadcasts to discover the 1st available net-server
private void BroadcastServerDiscovery()
{
UdpClient udpClient = null;
if (bBroadcastResponseReceived)
return;
try
{
//Debug.Log("Auto discovery - looking for net server...");
udpClient = new UdpClient();
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
string sRequestData = "KNS";
byte[] btRequestData = System.Text.Encoding.UTF8.GetBytes(sRequestData);
udpClient.EnableBroadcast = true;
udpClient.Send(btRequestData, btRequestData.Length, new IPEndPoint(IPAddress.Broadcast, serverBasePort));
UdpState state = new UdpState();
state.ep = ep;
state.uc = udpClient;
udpClient.BeginReceive(new System.AsyncCallback(BroadcastServerResponseReceived), state);
// wait for response
// wait for net-cst
long timeStart = System.DateTime.Now.Ticks;
long timeNow = timeStart;
while (!bBroadcastResponseReceived && (timeNow - timeStart) < 50000000) // timeout - 5 seconds
{
Thread.Sleep(THREAD_WAIT_TIME_MS);
timeNow = System.DateTime.Now.Ticks;
}
if(bBroadcastResponseReceived)
{
if (consoleLogMessages)
Debug.Log("Auto discovery - server host: " + serverHost + ", port: " + serverBasePort);
}
else
{
Debug.LogWarning("Timed out waiting for response from the broadcast server.");
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
finally
{
if (udpClient != null)
{
udpClient.Close();
}
}
}
// invoked when a response from the broadcast server gets received
private void BroadcastServerResponseReceived(System.IAsyncResult ar)
{
UdpState state = (UdpState)(ar.AsyncState);
UdpClient uc = state.uc;
IPEndPoint ep = state.ep;
byte[] btReceiveData = uc.EndReceive(ar, ref ep);
string sReceiveData = System.Text.Encoding.UTF8.GetString(btReceiveData);
//Debug.Log("Received response from the broadcast server.");
if(!string.IsNullOrEmpty(sReceiveData))
{
string[] asParts = sReceiveData.Split("|".ToCharArray());
if(asParts.Length >= 3 && asParts[0] == "KNS")
{
serverHost = asParts[1];
int.TryParse(asParts[2], out serverBasePort);
bBroadcastResponseReceived = true;
}
}
}
// initializes the network clients
private void InitNetClients(KinectInterop.FrameSource dwFlags)
{
try
{
if (autoServerDiscovery && !bBroadcastResponseReceived)
{
// discover the 1st available net-server
BroadcastServerDiscovery();
}
// clear params
//bSensorDataMsgSent = false;
bGotNetSensorData = false;
bSetNetSensorData = false;
bInvalidSensorData = false;
lastKeepAliveFrameTime = 0;
latestDataReceivedAt = 0;
disconnectedAt = 0;
lastControlFrameTime = 0;
lastColorFrameTime = 0;
lastDepthFrameTime = 0;
lastInfraredFrameTime = 0;
lastBodyDataFrameTime = 0;
lastBodyIndexFrameTime = 0;
lastPoseFrameTime = 0;
lastDepth2colorFrameTime = 0;
lastColor2depthFrameTime = 0;
lastColor2bodyIndexFrameTime = 0;
// init clients
controlFrameDecompressor = LZ4DecompressorFactory.CreateNew();
controlFrameClient = new TcpNetClient(sbConsole, controlFrameDecompressor);
NetMessageData msgGetData = GetControlMessage(ControlMessageType.GetSensorData);
controlFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Control, "control", msgGetData);
controlFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(ControlFrameReceived);
if ((dwFlags & KinectInterop.FrameSource.TypeColor) != 0 && getColorFrames)
{
colorFrameClient = new TcpNetClient(sbConsole, null);
colorFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Color, "color", null);
colorFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(ColorFrameReceived);
}
if ((dwFlags & KinectInterop.FrameSource.TypeDepth) != 0 && getDepthFrames)
{
depthFrameDecompressor = LZ4DecompressorFactory.CreateNew();
depthFrameClient = new TcpNetClient(sbConsole, depthFrameDecompressor);
depthFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Depth, "depth", null);
depthFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(DepthFrameReceived);
}
if ((dwFlags & KinectInterop.FrameSource.TypeInfrared) != 0 && getInfraredFrames)
{
infraredFrameClient = new TcpNetClient(sbConsole, null);
infraredFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Infrared, "infrared", null);
infraredFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(InfraredFrameReceived);
}
if ((dwFlags & KinectInterop.FrameSource.TypeBody) != 0 && getBodyFrames)
{
bodyDataFrameClient = new TcpNetClient(sbConsole, null);
bodyDataFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.BodyData, "body-data", null);
bodyDataFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(BodyDataFrameReceived);
}
if ((dwFlags & KinectInterop.FrameSource.TypeBodyIndex) != 0 && getBodyIndexFrames)
{
bodyIndexFrameDecompressor = LZ4DecompressorFactory.CreateNew();
bodyIndexFrameClient = new TcpNetClient(sbConsole, bodyIndexFrameDecompressor);
bodyIndexFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.BodyIndex, "body-index", null);
bodyIndexFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(BodyIndexFrameReceived);
}
if ((dwFlags & KinectInterop.FrameSource.TypePose) != 0 && getPoseFrames)
{
poseFrameClient = new TcpNetClient(sbConsole, null);
poseFrameClient.ConnectToServer(serverHost, serverBasePort + (int)NetMessageType.Pose, "pose", null);
poseFrameClient.ReceivedMessage += new ReceivedMessageEventHandler(PoseFrameReceived);
}
}
catch (System.Exception ex)
{
Debug.LogError("Error while initing the net clients.");
Debug.LogException(ex);
}
}
// closes all network clients
private void CloseNetClients()
{
try
{
// dispose coord mapping shaders
DisposePointCloudVertexShader(sensorData);
DisposePointCloudColorShader(sensorData);
DisposeColorDepthShader(sensorData);
DisposeColorInfraredShader(sensorData);
DisposeColorBodyIndexShader(sensorData);
if (controlFrameClient != null)
{
SendControlMessage(ControlMessageType.Disconnect);
controlFrameClient.ReceivedMessage -= ControlFrameReceived;
controlFrameClient.Close();
controlFrameClient = null;
controlFrameDecompressor = null;
}
if (colorFrameClient != null)
{
colorFrameClient.ReceivedMessage -= ColorFrameReceived;
colorFrameClient.Close();
colorFrameClient = null;
}
if (depthFrameClient != null)
{
depthFrameClient.ReceivedMessage -= DepthFrameReceived;
depthFrameClient.Close();
depthFrameClient = null;
depthFrameDecompressor = null;
}
if (infraredFrameClient != null)
{
infraredFrameClient.ReceivedMessage -= InfraredFrameReceived;
infraredFrameClient.Close();
infraredFrameClient = null;
}
if (bodyDataFrameClient != null)
{
bodyDataFrameClient.ReceivedMessage -= BodyDataFrameReceived;
bodyDataFrameClient.Close();
bodyDataFrameClient = null;
}
if (bodyIndexFrameClient != null)
{
bodyIndexFrameClient.ReceivedMessage -= BodyIndexFrameReceived;
bodyIndexFrameClient.Close();
bodyIndexFrameClient = null;
bodyIndexFrameDecompressor = null;
}
if (poseFrameClient != null)
{
poseFrameClient.ReceivedMessage -= PoseFrameReceived;
poseFrameClient.Close();
poseFrameClient = null;
}
if (depth2colorFrameClient != null)
{
depth2colorFrameClient.ReceivedMessage -= Depth2ColorFrameReceived;
depth2colorFrameClient.Close();
depth2colorFrameClient = null;
}
if (color2depthFrameClient != null)
{
color2depthFrameClient.ReceivedMessage -= Color2DepthFrameReceived;
color2depthFrameClient.Close();
color2depthFrameClient = null;
color2depthFrameDecompressor = null;
}
if (color2bodyIndexFrameClient != null)
{
color2bodyIndexFrameClient.ReceivedMessage -= Color2BodyIndexFrameReceived;
color2bodyIndexFrameClient.Close();
color2bodyIndexFrameClient = null;
color2bodyIndexFrameDecompressor = null;
}
}
catch (System.Exception ex)
{
Debug.LogError("Error while closing the net servers.");
Debug.LogException(ex);
}
}
private void ControlFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
// ignore keep-alives from the server
if(args.message.frameData.Length > 2)
{
switch(args.message.encType)
{
case FrameEncodeType.SensorDataJson: // get-sensor-data
CtrlGotNetSensorData(args.message.frameData);
break;
case FrameEncodeType.DSTraw: // get-dst
CtrlGotNetDST(args.message.frameData);
break;
case FrameEncodeType.CSTraw: // get-cst
CtrlGotNetCST(args.message.frameData);
break;
}
}
lastControlFrameTime = (ulong)System.DateTime.Now.Ticks;
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
// process net-sensor-data bytes
private void CtrlGotNetSensorData(byte[] btData)
{
string sNetSensorData = System.Text.Encoding.UTF8.GetString(btData);
//Debug.Log("NetSensorData (" + btData.Length + " bytes): " + sNetSensorData);
netSensorData = JsonUtility.FromJson(sNetSensorData);
//System.IO.File.WriteAllText("NetSensorData.json", sNetSensorData);
bGotNetSensorData = true;
//lastControlFrameTime = args.message.timestamp;
if (consoleLogMessages)
Debug.Log("Got net-sensor-data");
}
// process net-dst bytes
private void CtrlGotNetDST(byte[] btData)
{
if (depth2SpaceTable == null)
{
int depthImageLength = sensorData.depthImageWidth * sensorData.depthImageHeight;
depth2SpaceTable = new Vector3[depthImageLength];
}
KinectInterop.CopyBytes(btData, 1, depth2SpaceTable, 3 * sizeof(float));
//System.IO.File.WriteAllBytes("NetDST.bin", btData);
//lastControlFrameTime = args.message.timestamp;
bGotDST = true;
if (consoleLogMessages)
Debug.Log("Got net-dst");
}
// process net-cst bytes
private void CtrlGotNetCST(byte[] btData)
{
if (color2SpaceTable == null)
{
int colorImageLength = sensorData.colorImageWidth * sensorData.colorImageHeight;
color2SpaceTable = new Vector3[colorImageLength];
}
KinectInterop.CopyBytes(btData, 1, color2SpaceTable, 3 * sizeof(float));
//System.IO.File.WriteAllBytes("NetCST.bin", btData);
//lastControlFrameTime = args.message.timestamp;
bGotCST = true;
if (consoleLogMessages)
Debug.Log("Got net-cst");
}
private void ColorFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (sensorData.colorImageWidth == 0)
{
sensorData.colorImageWidth = args.message.frameWidth;
sensorData.colorImageHeight = args.message.frameHeight;
}
if (isSyncDepthAndColor && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (colorFrameLock)
{
colorImageData = args.message.frameData;
lastColorImageTime = args.message.timestamp;
//Debug.Log("ReceivedColorTimestamp: " + lastColorImageTime);
lastColorFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void DepthFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (sensorData.depthImage == null)
{
sensorData.depthImageWidth = args.message.frameWidth;
sensorData.depthImageHeight = args.message.frameHeight;
rawDepthImage = new ushort[sensorData.depthImageWidth * sensorData.depthImageHeight];
sensorData.depthImage = new ushort[sensorData.depthImageWidth * sensorData.depthImageHeight];
}
if(isFrameSyncNeeded && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (depthFrameLock)
{
KinectInterop.CopyBytes(args.message.frameData, sizeof(byte), rawDepthImage, sizeof(ushort));
rawDepthTimestamp = args.message.timestamp;
//Debug.Log("ReceivedDepthTimestamp: " + rawDepthTimestamp);
lastDepthFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void InfraredFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (infraredImageWidth == 0 || infraredImageHeight == 0)
{
infraredImageWidth = args.message.frameWidth;
infraredImageHeight = args.message.frameHeight;
}
if (isFrameSyncNeeded && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (infraredFrameLock)
{
infraredImageData = args.message.frameData;
lastInfraredImageTime = args.message.timestamp;
//Debug.Log("ReceivedInfraredTimestamp: " + lastInfraredImageTime);
lastInfraredFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void BodyDataFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (sensorData.alTrackedBodies == null)
{
sensorData.alTrackedBodies = new KinectInterop.BodyData[0];
sensorData.trackedBodiesCount = 0;
}
if (isSyncBodyAndDepth && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (bodyTrackerLock)
{
Matrix4x4 sensorToWorld = GetSensorToWorldMatrix();
string sBodyFrameData = System.Text.Encoding.UTF8.GetString(args.message.frameData);
sensorData.trackedBodiesCount = KinectInterop.SetBodyFrameFromCsv(sBodyFrameData, "\t", sensorData, ref sensorData.alTrackedBodies,
ref sensorToWorld, bIgnoreZcoords, out rawBodyTimestamp);
sensorData.lastBodyFrameTime = currentBodyTimestamp = rawBodyTimestamp = args.message.timestamp;
//Debug.Log("ReceivedBodyTimestamp: " + rawBodyTimestamp);
lastBodyDataFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void BodyIndexFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (sensorData.bodyIndexImage == null)
{
//rawBodyIndexImage = new byte[args.message.frameWidth * args.message.frameHeight];
sensorData.bodyIndexImage = new byte[args.message.frameWidth * args.message.frameHeight];
}
if (isSyncBodyAndDepth && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (bodyTrackerLock)
{
rawBodyIndexImage = args.message.frameData;
rawBodyIndexTimestamp = args.message.timestamp;
//Debug.Log("ReceivedBodyIndexTimestamp: " + rawBodyIndexTimestamp);
lastBodyIndexFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void PoseFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
lock(netPoseLock)
{
string sNetPoseData = System.Text.Encoding.UTF8.GetString(args.message.frameData);
netPoseData = JsonUtility.FromJson(sNetPoseData);
lastPoseFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void Depth2ColorFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (depth2colorImageWidth == 0 || depth2colorImageHeight == 0)
{
depth2colorImageWidth = args.message.frameWidth;
depth2colorImageHeight = args.message.frameHeight;
}
if (isSyncDepthAndColor && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (depthCamColorFrameLock)
{
depth2colorImageData = args.message.frameData;
lastDepth2ColorImageTime = args.message.timestamp;
//Debug.Log("ReceivedDepthCamColorTimestamp: " + lastDepth2ColorImageTime);
lastDepth2colorFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void Color2DepthFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (colorCamDepthDataFrame == null || (args.message.frameWidth * args.message.frameHeight) != colorCamDepthDataFrame.Length)
{
colorCamDepthDataFrame = new ushort[args.message.frameWidth * args.message.frameHeight];
}
if (isSyncDepthAndColor && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (colorCamDepthFrameLock)
{
KinectInterop.CopyBytes(args.message.frameData, sizeof(byte), colorCamDepthDataFrame, sizeof(ushort));
lastColorCamDepthFrameTime = args.message.timestamp;
//Debug.Log("ReceivedColorCamDepthTimestamp: " + lastColorCamDepthFrameTime);
lastColor2depthFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void Color2InfraredFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (color2infraredImageWidth == 0 || color2infraredImageHeight == 0)
{
color2infraredImageWidth = args.message.frameWidth;
color2infraredImageHeight = args.message.frameHeight;
}
if (isSyncDepthAndColor && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (colorCamInfraredFrameLock)
{
color2infraredImageData = args.message.frameData;
lastColor2InfraredImageTime = args.message.timestamp;
//Debug.Log("ReceivedColorCamInfraredTimestamp: " + lastColor2InfraredImageTime);
lastColor2infraredFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
private void Color2BodyIndexFrameReceived(object state, ReceivedMessageEventArgs args)
{
if (sensorData == null)
return;
try
{
if (colorCamBodyIndexFrame == null || (args.message.frameWidth * args.message.frameHeight) != colorCamBodyIndexFrame.Length)
{
colorCamBodyIndexFrame = new byte[args.message.frameWidth * args.message.frameHeight];
}
if (isSyncBodyAndDepth && !args.synched)
{
SyncReceivedFrame(args.message);
return;
}
lock (colorCamBodyIndexFrameLock)
{
KinectInterop.CopyBytes(args.message.frameData, sizeof(byte), colorCamBodyIndexFrame, sizeof(byte));
lastColorCamBodyIndexFrameTime = args.message.timestamp;
//Debug.Log("ReceivedColorCamBodyIndexTimestamp: " + lastColorCamBodyIndexFrameTime);
lastColor2bodyIndexFrameTime = (ulong)System.DateTime.Now.Ticks;
}
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
}
// synchronizes the received frame with other frames at the same time
private void SyncReceivedFrame(NetMessageData message)
{
if (dictNetMessageData == null || alNetMessageTime == null)
return;
lock (syncMessageLock)
{
ulong frameTime = message.timestamp / 10;
if (dictNetMessageData.ContainsKey(frameTime))
{
// update the timestamp entry with this type of message
Dictionary dictTimeFrames = dictNetMessageData[frameTime];
dictTimeFrames[message.msgType] = message;
dictNetMessageData[frameTime] = dictTimeFrames;
//Debug.Log(" updated " + frameTime + " - " + message.msgType + " frame, frame-count: " + dictNetMessageData[frameTime].Count + ", missing: " + GetMissingSyncFramesInfo(frameTime, dictTimeFrames));
SyncCheckIfAllFramesReady(frameTime, dictTimeFrames);
}
else
{
// check for timeout of the oldest messages
CheckSyncMessagesTimeout();
// check if the message is before the oldest one in the list
ulong minTime = alNetMessageTime.Count > 0 ? alNetMessageTime[0] : 0;
if(frameTime >= minTime)
{
// create 1st entry for timestamp
Dictionary dictTimeFrames = new Dictionary();
dictTimeFrames[message.msgType] = message;
dictNetMessageData[frameTime] = dictTimeFrames;
alNetMessageTime.Add(frameTime);
//Debug.Log("Added " + frameTime + " - " + message.msgType + " frame, cache-size: " + alNetMessageTime.Count + ", missing: " + GetMissingSyncFramesInfo(frameTime, dictTimeFrames));
if (alNetMessageTime.Count > 1)
{
alNetMessageTime.Sort();
}
while (alNetMessageTime.Count > 20)
{
ulong msgTime = alNetMessageTime[0];
int msgDataCount = 0;
string missingFrames = string.Empty;
if (dictNetMessageData.ContainsKey(msgTime))
{
msgDataCount = dictNetMessageData[msgTime].Count;
missingFrames = GetMissingSyncFramesInfo(msgTime, dictNetMessageData[msgTime]);
dictNetMessageData[msgTime].Clear();
dictNetMessageData.Remove(msgTime);
}
alNetMessageTime.RemoveAt(0);
if (consoleLogMessages)
Debug.LogWarning("Removed entry: " + msgTime + ", frame-count: " + msgDataCount + ", cache-size: " + alNetMessageTime.Count + ", missing: " + missingFrames);
}
}
else
{
//Debug.Log(" skipping " + frameTime + " - " + message.msgType + " frame < " + minTime);
}
}
}
}
// checks for timed out messages in the waiting list (> 5 seconds) and removes them, if found
private void CheckSyncMessagesTimeout()
{
long timeNow = System.DateTime.UtcNow.Ticks;
const long MESSAGE_TIMEOUT = 50000000; // 5 seconds
for (int i = alNetMessageTime.Count - 1; i >= 0; i--)
{
ulong msgTime = alNetMessageTime[i];
if (dictNetMessageData.ContainsKey(msgTime))
{
var dictTimeFrames = dictNetMessageData[msgTime];
var listMessages = dictTimeFrames.Values.ToArray();
foreach (NetMessageData message in listMessages)
{
if ((timeNow - message.timeCreated) > MESSAGE_TIMEOUT)
{
//Debug.Log(" removing timed-out message " + msgTime + "-" + message.msgType + ", duration: " + (timeNow - message.timeCreated));
dictNetMessageData[msgTime].Remove(message.msgType);
}
}
}
if (!dictNetMessageData.ContainsKey(msgTime) || dictNetMessageData[msgTime].Count == 0)
{
if(dictNetMessageData.ContainsKey(msgTime))
dictNetMessageData.Remove(msgTime);
alNetMessageTime.RemoveAt(i);
if (consoleLogMessages)
Debug.LogWarning("Removed timed-out entry: " + msgTime + ", cache-size: " + alNetMessageTime.Count);
}
}
}
// returns the list of missing frames as string
private string GetMissingSyncFramesInfo(ulong frameTime, Dictionary dictTimeFrames)
{
System.Text.StringBuilder sbBuf = new System.Text.StringBuilder();
if (isSyncDepthAndColor && colorFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.Color))
sbBuf.Append("color ");
if (isFrameSyncNeeded && depthFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.Depth))
sbBuf.Append("depth ");
if (isFrameSyncNeeded && infraredFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.Infrared))
sbBuf.Append("ir ");
if (isSyncBodyAndDepth && bodyDataFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.BodyData))
sbBuf.Append("body ");
if (isSyncBodyAndDepth && bodyIndexFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.BodyIndex))
sbBuf.Append("bi ");
if (isSyncDepthAndColor && depth2colorFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.Depth2Color))
sbBuf.Append("d2c ");
if (isSyncDepthAndColor && color2depthFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.Color2Depth))
sbBuf.Append("c2d ");
if (isSyncDepthAndColor && color2infraredFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.Color2Infrared))
sbBuf.Append("c2i ");
if (isSyncBodyAndDepth && color2bodyIndexFrameClient != null && !dictTimeFrames.ContainsKey(NetMessageType.Color2BodyIndex))
sbBuf.Append("c2bi ");
if(sbBuf.Length == 0)
{
sbBuf.Append("");
}
return sbBuf.ToString();
}
// check if all needed frames are ready
private void SyncCheckIfAllFramesReady(ulong frameTime, Dictionary dictTimeFrames)
{
bool bAllFramesPresent = true;
if (isSyncDepthAndColor && colorFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.Color);
if (isFrameSyncNeeded && depthFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.Depth);
if (isFrameSyncNeeded && infraredFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.Infrared);
if (isSyncBodyAndDepth && bodyDataFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.BodyData);
if (isSyncBodyAndDepth && bodyIndexFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.BodyIndex);
if (isSyncDepthAndColor && depth2colorFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.Depth2Color);
if (isSyncDepthAndColor && color2depthFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.Color2Depth);
if (isSyncDepthAndColor && color2infraredFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.Color2Infrared);
if (isSyncBodyAndDepth && color2bodyIndexFrameClient != null)
bAllFramesPresent &= dictTimeFrames.ContainsKey(NetMessageType.Color2BodyIndex);
if (bAllFramesPresent)
{
SyncSendSynchedFrames(frameTime, dictTimeFrames);
}
}
// re-sends the synchronized frames
private void SyncSendSynchedFrames(ulong frameTime, Dictionary dictTimeFrames)
{
//Debug.Log("Resending " + dictTimeFrames.Count + " synched frames at entry: " + frameTime);
foreach (NetMessageData message in dictTimeFrames.Values)
{
ReceivedMessageEventArgs args = new ReceivedMessageEventArgs(message, true);
switch (message.msgType)
{
case NetMessageType.Color:
ColorFrameReceived(this, args);
break;
case NetMessageType.Depth:
DepthFrameReceived(this, args);
break;
case NetMessageType.Infrared:
InfraredFrameReceived(this, args);
break;
case NetMessageType.BodyData:
BodyDataFrameReceived(this, args);
break;
case NetMessageType.BodyIndex:
BodyIndexFrameReceived(this, args);
break;
case NetMessageType.Depth2Color:
Depth2ColorFrameReceived(this, args);
break;
case NetMessageType.Color2Depth:
Color2DepthFrameReceived(this, args);
break;
case NetMessageType.Color2Infrared:
Color2InfraredFrameReceived(this, args);
break;
case NetMessageType.Color2BodyIndex:
Color2BodyIndexFrameReceived(this, args);
break;
default:
Debug.LogError("Unknown message type: " + message.msgType);
break;
}
}
// clean up
if(alNetMessageTime != null && dictNetMessageData != null)
{
while (alNetMessageTime.Count > 0)
{
ulong msgTime = alNetMessageTime[0];
int msgDataCount = 0;
if (dictNetMessageData.ContainsKey(msgTime))
{
msgDataCount = dictNetMessageData[msgTime].Count;
dictNetMessageData[msgTime].Clear();
dictNetMessageData.Remove(msgTime);
}
alNetMessageTime.RemoveAt(0);
//Debug.Log("Synched " + msgTime + ", frame-time: " + frameTime + ", frame-count: " + msgDataCount + ", cache-size: " + alNetMessageTime.Count);
if (msgTime == frameTime)
break;
}
}
}
// cleans up the waiting for sync message data
private void CleanupSyncMessageData()
{
if (alNetMessageTime != null && dictNetMessageData != null)
{
for (int i = alNetMessageTime.Count - 1; i >= 0; i--)
{
ulong msgTime = alNetMessageTime[i];
if (dictNetMessageData.ContainsKey(msgTime))
{
dictNetMessageData[msgTime].Clear();
dictNetMessageData.Remove(msgTime);
}
alNetMessageTime.RemoveAt(i);
}
//if (consoleLogMessages)
// Debug.Log("Cleaned up the sync message data.");
}
}
// constructs and returns the control message
private NetMessageData GetControlMessage(ControlMessageType messageType)
{
ulong ulTimeNow = (ulong)System.DateTime.Now.Ticks;
NetMessageData msg = new NetMessageData(NetMessageType.Control, FrameEncodeType.Raw, 0, 0, ulTimeNow);
byte[] btMsgData = new byte[1];
btMsgData[0] = (byte)messageType;
msg.SetData(btMsgData);
return msg;
}
// sends control message to the server
private void SendControlMessage(ControlMessageType messageType)
{
if(controlFrameClient != null && controlFrameClient.IsActive())
{
NetMessageData msg = GetControlMessage(messageType);
controlFrameClient.SendMessageToAllConnections(msg);
//Debug.Log("Sending ctrl message " + messageType);
}
}
// return the latest timestamp
private ulong GetLatestTimestamp()
{
ulong maxFrameTime = lastControlFrameTime;
if (lastColorFrameTime != 0 && lastColorFrameTime > maxFrameTime)
maxFrameTime = lastColorFrameTime;
if (lastDepthFrameTime != 0 && lastDepthFrameTime > maxFrameTime)
maxFrameTime = lastDepthFrameTime;
if (lastInfraredFrameTime != 0 && lastInfraredFrameTime > maxFrameTime)
maxFrameTime = lastInfraredFrameTime;
if (lastBodyDataFrameTime != 0 && lastBodyDataFrameTime > maxFrameTime)
maxFrameTime = lastBodyDataFrameTime;
if (lastBodyIndexFrameTime != 0 && lastBodyIndexFrameTime > maxFrameTime)
maxFrameTime = lastBodyIndexFrameTime;
if (lastPoseFrameTime != 0 && lastPoseFrameTime > maxFrameTime)
maxFrameTime = lastPoseFrameTime;
if (lastDepth2colorFrameTime != 0 && lastDepth2colorFrameTime > maxFrameTime)
maxFrameTime = lastDepth2colorFrameTime;
if (lastColor2depthFrameTime != 0 && lastColor2depthFrameTime > maxFrameTime)
maxFrameTime = lastColor2depthFrameTime;
if (lastColor2infraredFrameTime != 0 && lastColor2infraredFrameTime > maxFrameTime)
maxFrameTime = lastColor2infraredFrameTime;
if (lastColor2bodyIndexFrameTime != 0 && lastColor2bodyIndexFrameTime > maxFrameTime)
maxFrameTime = lastColor2bodyIndexFrameTime;
return maxFrameTime;
}
}
///
/// TcpNetClient provides network client functionality over tcp streams.
/// This class is based on the excellent work of Andy Wilson and Hrvoje Benko in "Room Alive Toolkit" - https://github.com/microsoft/RoomAliveToolkit
///
internal class TcpNetClient
{
//private const int bufferSize = 10240000;
public int tmpBufferSize = 640000;
private System.Text.StringBuilder sbConsole = null;
private ILZ4Decompressor decompressor = null;
private string streamDesc = string.Empty;
private List connections = new List();
private NetMessageData sendMsgOnConnect = null;
// thread signal
public ManualResetEvent allDoneClient = new ManualResetEvent(false);
public ReceivedMessageEventHandler ReceivedMessage = null;
public bool IsConnected()
{
return (connections.Count > 0);
}
public int GetConnCount()
{
return connections.Count;
}
public bool IsActive()
{
bool bActive = (connections.Count > 0) ? true : false;
foreach (NetConnData conn in connections)
{
if (!conn.isActive)
{
bActive = false;
break;
}
}
return bActive;
}
public bool IsReadyToSend()
{
bool bReady = (connections.Count > 0) ? true : false;
foreach (NetConnData conn in connections)
{
if (!conn.readyToSend)
{
bReady = false;
break;
}
}
return bReady;
}
//public void CloseConnection(int connId)
//{
// foreach (NetConnData conn in connections)
// {
// if (conn.ID == connId)
// {
// //Debug.Log("Closing connection " + conn.ID + " to " + conn.remoteEP);
// LogToConsole("Closing connection to " + streamDesc + " server.");
// conn.isActive = false;
// connections.Remove(conn);
// break;
// }
// }
//}
public void CloseConnectionsTo(string remoteIP)
{
for (int i = connections.Count - 1; i >= 0; i--)
{
NetConnData conn = connections[i];
if (conn.remoteIP == remoteIP)
{
//Debug.Log("Closing connection " + conn.ID + " to " + conn.remoteEP);
LogToConsole("Closing connection to " + streamDesc + " server.");
conn.isActive = false;
connections.Remove(conn);
}
}
}
public void CloseAllConnections()
{
foreach (NetConnData conn in connections)
{
//Debug.Log("Closing connection " + conn.ID + " to " + conn.remoteEP);
LogToConsole("Closing connection to " + streamDesc + " server.");
conn.isActive = false;
}
connections.Clear();
}
public void SendMessageToAllConnections(NetMessageData message)
{
foreach (NetConnData conn in connections)
{
lock (conn.messageQueue)
{
if (conn.messageQueue.Count < NetConnData.MaxMessageQueueLength)
{
conn.messageQueue.Enqueue(message);
}
}
}
}
public void SendMessageToConnection(NetMessageData message, int connId)
{
foreach (NetConnData conn in connections)
{
if (conn.ID == connId)
{
lock (conn.messageQueue)
{
if (conn.messageQueue.Count < NetConnData.MaxMessageQueueLength)
{
conn.messageQueue.Enqueue(message);
}
}
break;
}
}
}
public TcpNetClient(System.Text.StringBuilder sbConsole, ILZ4Decompressor decompressor)
{
this.sbConsole = sbConsole;
this.decompressor = decompressor;
}
public void Close()
{
allDoneClient.Set();
foreach (NetConnData cs in connections)
{
cs.isActive = false;
}
}
public void ConnectToServer(string host, int port, string streamDesc, NetMessageData msgOnConnect)
{
this.streamDesc = streamDesc;
this.sendMsgOnConnect = msgOnConnect;
try
{
TcpClient client = new TcpClient(AddressFamily.InterNetwork);
IPAddress ipAddress = null;
bool isIpAddr = host.Length > 0 && host[0] >= '0' && host[0] <= '9';
if (isIpAddr)
{
ipAddress = IPAddress.Parse(host);
}
else
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(host);
ipAddress = ipHostInfo.AddressList.Where(a => a.AddressFamily == AddressFamily.InterNetwork).First();
}
//Debug.Log("Connecting to " + host + " - " + ipAddress + ":" + port);
LogToConsole("Connecting to " + streamDesc + " server at " + ipAddress + ":" + port);
client.BeginConnect(ipAddress, port, new System.AsyncCallback(HandleNetServerConnected), client);
}
catch (System.Exception ex)
{
//Debug.LogError("Connection to server failed: " + ex.Message);
LogErrorToConsole("Connection to " + streamDesc + " server failed: " + ex.Message);
//Debug.LogException(ex);
}
}
private void HandleNetServerConnected(System.IAsyncResult ar)
{
try
{
NetConnData conn = new NetConnData();
conn.client = (TcpClient)ar.AsyncState;
conn.client.EndConnect(ar);
conn.stream = conn.client.GetStream();
conn.remoteIP = conn.client.Client.RemoteEndPoint.ToString();
int iP = conn.remoteIP.IndexOf(':');
if (iP >= 0)
conn.remoteIP = conn.remoteIP.Substring(0, iP);
connections.Add(conn);
//Debug.Log("Connected to server " + conn.remoteEP);
LogToConsole("Connected to " + streamDesc + " server " + conn.remoteIP);
if(sendMsgOnConnect != null)
{
SendMessageToConnection(sendMsgOnConnect, conn.ID);
}
NetClientProc(conn);
}
catch (System.ObjectDisposedException)
{
// do nothing
}
catch (System.Exception ex)
{
//Debug.LogError("Connection to server failed: " + ex.Message);
LogErrorToConsole("Connection to " + streamDesc + " server failed: " + ex.Message);
//Debug.LogException(ex);
}
}
private void NetClientProc(NetConnData conn)
{
conn.isActive = true;
int bytesReceived = 0;
byte[] bufferTmp = new byte[tmpBufferSize];
try
{
while (conn.client.Connected && conn.isActive)
{
if (conn.readyToSend && conn.stream.CanWrite && conn.messageQueue.Count > 0)
{
byte[] sendBuffer;
lock (conn.messageQueue)
{
sendBuffer = conn.messageQueue.Dequeue().WrapMessage();
conn.readyToSend = false;
}
conn.stream.BeginWrite(sendBuffer, 0, sendBuffer.Length, new System.AsyncCallback(MessageSentToServer), conn);
}
bytesReceived = 0;
while (conn.stream.DataAvailable)
{
conn.messageReceiveTime = (ulong)System.DateTime.Now.Ticks;
bytesReceived = conn.stream.Read(bufferTmp, 0, bufferTmp.Length);
ProcessReceivedData(bufferTmp, bytesReceived, conn);
}
Thread.Sleep(KinectInterop.THREAD_SLEEP_TIME_MS);
}
}
catch(ThreadAbortException)
{
// do nothing
}
catch (System.Exception ex)
{
Debug.LogException(ex);
}
conn.isActive = false;
conn.messageQueue.Clear();
//Debug.Log("Connection " + conn.ID + " to " + conn.remoteEP + " closed.");
LogToConsole("Connection to " + streamDesc + " server closed.");
conn.stream.Close();
conn.client.Close();
if (connections.Contains(conn))
{
connections.Remove(conn);
}
}
private void MessageSentToServer(System.IAsyncResult ar)
{
var conn = (NetConnData)ar.AsyncState;
try
{
conn.stream.EndWrite(ar);
}
catch (System.Exception ex)
{
if(conn.isActive)
{
//Debug.LogError("Exception occured while sending message to server.");
LogErrorToConsole("Error sending to " + streamDesc + " server: " + ex.Message);
//Debug.LogException(ex);
conn.isActive = false;
}
}
conn.readyToSend = true;
}
private void ProcessReceivedData(byte[] buffer, int bytesReceived, NetConnData conn)
{
if (conn.bytesReceived == 0 && bytesReceived > 3) //new message
{
int newMessageSize = System.BitConverter.ToInt32(buffer, 0) + sizeof(int); //prefix is one int
if (newMessageSize != conn.messageSize)
{
conn.messageSize = newMessageSize;
conn.messageBuffer = new byte[newMessageSize];
}
}
int availableLength = conn.messageSize - conn.bytesReceived;
int copyLen = Mathf.Min(availableLength, bytesReceived);
conn.packetCounter++;
System.Array.Copy(buffer, 0, conn.messageBuffer, conn.bytesReceived, copyLen);
conn.bytesReceived += copyLen;
if (conn.bytesReceived == conn.messageSize)
{
conn.ResetCounters();
NetMessageData message = new NetMessageData();
message.SetDecompressor(decompressor);
message.UnwrapMessage(conn.messageBuffer);
//Debug.Log("Received message: " + message.timestamp + " - " + message.msgType + " @ " + System.DateTime.Now.ToString("HH:mm:ss.fff"));
ReceivedMessage?.Invoke(conn, new ReceivedMessageEventArgs(message));
}
if (copyLen != bytesReceived) // process the remainder of the message
{
byte[] newBuffer = new byte[bytesReceived - copyLen];
System.Array.Copy(buffer, copyLen, newBuffer, 0, bytesReceived - copyLen);
ProcessReceivedData(newBuffer, bytesReceived - copyLen, conn);
}
}
// logs message to the console
private void LogToConsole(string sMessage)
{
Debug.Log(sMessage);
lock (sbConsole)
{
sbConsole.Clear();
sbConsole.Append(sMessage); //.AppendLine();
}
}
// logs error message to the console
private void LogErrorToConsole(string sMessage)
{
Debug.LogError(sMessage);
lock (sbConsole)
{
sbConsole.Clear();
sbConsole.Append(sMessage); //.AppendLine();
}
}
// logs error message to the console
private void LogErrorToConsole(System.Exception ex)
{
LogErrorToConsole(ex.Message + "\n" + ex.StackTrace);
}
}
}