-
Notifications
You must be signed in to change notification settings - Fork 0
/
Client.cs
182 lines (151 loc) · 6.11 KB
/
Client.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
using UnityEngine;
using System;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using ProtoBuf;
namespace Sensation {
public delegate void ClientExceptionDelegate(Exception e);
/**
* The actual transmission over the network is handled in a background thread. Therefore
* any exceptions can't be reported directly and will be reported to any registered
* exception handlers. Be careful, they will NOT be called on the main thread!
**/
public class Client {
private ConcurrentQueue<Message> messageQueue = new ConcurrentQueue<Message>();
private Thread transmitThread;
private EventWaitHandle signal = new AutoResetEvent(false);
private bool shouldStopTransmitting = false;
private readonly object shouldStopTransmittingLock = new object();
private ClientExceptionDelegate exceptionDelegate;
public Profiler profiler;
#region Singleton
// singleton implemenation following http://csharpindepth.com/articles/general/singleton.aspx
public static readonly Client Instance = new Client();
static Client() {}
private Client() {
}
#endregion
/**
* Exception delegates are called on all kind of occasions:
* ArgumentException
* Hostname is an invalid IP
* ArgumentOutOfRangeException
* Hostname was longer than 255 characters
* InvalidOperationException
* The client is not connected anymore
* The network stream is not writable
* ObejctDisposedException
* The client has been closed (actually the underlying TcpClient)
* SocketException
* An error was encountered when resolving the hsotname
* An error occured when accessing the socket
**/
public void AddExceptionDelegate(ClientExceptionDelegate exceptionDelegate) {
if (this.exceptionDelegate == null) {
this.exceptionDelegate = exceptionDelegate;
} else {
this.exceptionDelegate += exceptionDelegate;
}
}
public void Connect(string serverName) {
if (transmitThread != null && transmitThread.IsAlive) {
Debug.Log("Sensation client already connected - disconnect before reconnecting");
return;
}
lock (shouldStopTransmittingLock) {
shouldStopTransmitting = false;
}
transmitThread = new Thread(Transmit);
transmitThread.IsBackground = true; // don't keep the application alive
transmitThread.Start(serverName);
}
public void Disconnect() {
if (transmitThread == null || !transmitThread.IsAlive) {
return;
}
lock (shouldStopTransmittingLock) {
shouldStopTransmitting = true;
}
signal.Set();
transmitThread.Join(2000); // wait for background thread termination, but not too long
messageQueue.Clear();
}
public void SendAsync(Message message) {
lock (shouldStopTransmittingLock) { // this check prevents sensations filling up the queue after disconnecting, which would keep the transmitting thread stuck in the inner while loop
if (shouldStopTransmitting) {
return;
}
}
messageQueue.Enqueue(message);
signal.Set();
}
public void SendAsync(Vibration vibration) {
Message message = new Message();
message.Type = Message.MessageType.Vibration;
message.Vibration = vibration;
SendAsync(message);
}
public void SendAsync(LoadPattern pattern) {
Message message = new Message();
message.Type = Message.MessageType.LoadPattern;
message.LoadPattern = pattern;
SendAsync(message);
}
public void SendAsync(PlayPattern pattern) {
Message message = new Message();
message.Type = Message.MessageType.PlayPattern;
message.PlayPattern = pattern;
SendAsync(message);
}
#region background thread
private void Transmit(object serverName) {
string serverNameString = (string)serverName;
try {
IPAddress[] serverIps = Dns.GetHostEntry(serverNameString).AddressList;
ProcessingLoop(serverIps[0], 10000);
} catch(Exception e) {
if (exceptionDelegate != null) {
exceptionDelegate(e);
}
return;
}
}
private void ProcessingLoop(IPAddress server, int port) {
using (TcpClient client = new TcpClient(server.ToString(), port))
using (NetworkStream networkStream = client.GetStream())
{
while (true) {
lock (shouldStopTransmittingLock) {
if (shouldStopTransmitting) {
break;
}
}
if (!client.Connected) {
throw new InvalidOperationException("Unable to send command - client disconnected");
}
if (!networkStream.CanWrite) {
throw new InvalidOperationException("Can't write to network stream");
}
if (messageQueue.Count > 100) {
Debug.LogWarning("More than 100 sensation messages queued for network transmission: " + messageQueue.Count + " messages");
}
while (messageQueue.Count > 0) {
Message message;
bool messageDequeued = messageQueue.TryDequeue(out message);
if (!messageDequeued || message == null) {
break;
}
if (profiler != null) { // this isn't properly synchronized to spare the lock during normal operations
profiler.Log("send", message.ToString());
}
Serializer.SerializeWithLengthPrefix(networkStream, message, PrefixStyle.Fixed32BigEndian); // this shouldn't throw anything..
}
signal.WaitOne();
}
}
}
#endregion
}
}