-
Notifications
You must be signed in to change notification settings - Fork 0
/
Utilities.cs
272 lines (240 loc) · 8.89 KB
/
Utilities.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
using System;
using System.Net;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using NNanomsg.Protocols;
using System.Diagnostics;
using Script;
//using EventHandler = System.Func<string, bool>;
using EventHandler = System.Func<Script.Event, System.Collections.Generic.Dictionary<string, string>, bool>;
namespace Utilities
{
public class Client
{
protected static bool running = true;
protected String url;
protected bool connected;
protected RequestSocket interaction;
protected SubscribeSocket world;
protected String id;
protected String req_register;
protected String req_access;
protected String req_end;
protected String acknowledged;
protected String denied;
protected Thread listenThread;
public static string RandomString (int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
public Client (String url)
{
this.url = url;
Setup(RandomString(10));
}
~Client ()
{
if (connected)
Disconnect();
}
public void Disconnect ()
{
// Close the Listen thread first so sockets will be free
connected = false;
listenThread.Join();
// Then request that the server remove and cleanup anything
// associated with this connection
Request(req_end);
// Finally dispose of any sockets
world.Dispose();
interaction.Dispose();
}
public void Setup (String id)
{
this.id = id;
this.interaction = new RequestSocket();
this.world = new SubscribeSocket();
this.req_register = "REG " + id;
this.req_access = "ACCESS " + id;
this.req_end = "END " + id;
this.acknowledged = id + "-ACK";
this.denied = id + "-DENIED";
this.connected = false;
this.listenThread = null;
}
protected void Request (String data)
{
interaction.Send(Encoding.UTF8.GetBytes(data));
String reply = Response();
if (reply != acknowledged)
throw new Exception("Cannot register: " + reply);
}
protected string Response ()
{
return Encoding.ASCII.GetString(interaction.Receive());
}
// Block for input from the World server. Checks every 500ms and if
// there is a message then this returns a string of that message.
// Otherwise, the only other value, null, is returned when the thread
// isn't connected anymore.
protected string NextEvent ()
{
byte[] bytes;
while (this.connected) {
//Thread.Sleep(500);
bytes = world.ReceiveImmediate();
if (bytes == null)
continue;
return Encoding.ASCII.GetString(bytes);
}
return null;
}
public void Connect (EventHandler handler)
{
// Request our id from the server.
interaction.Connect(url + ":8889");
Request(req_register);
// Connection must be made before requesting access or otherwise
// we may miss some key events that have already been sent by the
// time we connected.
Console.WriteLine("Accessing...");
world.Subscribe(id);
world.Connect(url + ":8888");
// `Connected` doesn't mean we are actually fully listening. Start
// the listen thread and then wait 500ms before requesting access
this.connected = true;
this.listenThread = new Thread(() => ListenProc(handler));
this.listenThread.Start();
Thread.Sleep(500);
// Request access from the world server.
Request(req_access);
Console.WriteLine("Connected with id " + id);
}
private void ListenProc (EventHandler handler)
{
while (connected) {
string data = NextEvent();
// This basically means EOF
if (data == null)
break;
string[] e = data.Split(" ".ToCharArray());
Event event_type = Event.Default;
Dictionary<string,string> event_data = null;
if (e[0] == id && e[1] == "new") {
// This message has the format:
// <hash> new
// id <id>
// <key0> <value0>
// <key1> <value1>
// ...
// (Not including newlines -- formatted for readability)
// This is building that dictionary to pass to the script
// so it can update from the network.
Debug.Assert(e.Length >= 4);
event_data = new Dictionary<string,string>();
for (int i = 2; i < e.Length; i += 2)
event_data[e[i]] = e[i+1];
}
handler(event_type, event_data);
}
}
}
// A simple container class that has a mutex around its payload
public class SafeContainer
{
protected String data;
protected Mutex mutex;
protected ManualResetEvent dataPlaced;
public SafeContainer()
{
this.data = null;
this.mutex = new Mutex(false);
this.dataPlaced = new ManualResetEvent(false);
}
public void Put (String str)
{
mutex.WaitOne();
this.data = String.Copy(str);
dataPlaced.Set();
mutex.ReleaseMutex();
}
public String Data ()
{
string str;
mutex.WaitOne();
// If the data is null at this point then the Receiving
// threads have worked faster than the Inserting threads.
// We simply need to block until there has been data placed
// for us to read.
if (this.data == null)
{
mutex.ReleaseMutex();
dataPlaced.WaitOne();
dataPlaced.Reset();
mutex.WaitOne();
}
str = String.Copy(this.data);
this.data = null;
mutex.ReleaseMutex();
return str;
}
}
// A channel is a thread-safe queue of messages. It tries to be as
// frictionless as possible by having two locks on the channel -- one for
// inserting messages and another for receiving messages -- and a third
// lock on the messages themselves. This allows for locks that simply
// increment a counter and for messages to be received on any number of
// threads at a time.
public class Channel
{
protected int in_index;
protected int out_index;
protected int buffer_size;
protected SafeContainer[] messages;
protected Mutex input_lock;
protected Mutex output_lock;
public Channel()
{
this.input_lock = new Mutex(false);
this.output_lock = new Mutex(false);
this.in_index = 0;
this.out_index = 0;
this.buffer_size = 4096;
this.messages = new SafeContainer[buffer_size];
for (int i = 0; i < this.messages.Length; i++)
messages[i] = new SafeContainer();
}
public void Insert (String msg)
{
int index;
// Because all of the SafeContainer objects are initialized
// synchronously above then all the locks should be initialized and
// in place. This means we can bump the index and start putting the
// message in place
input_lock.WaitOne();
index = this.in_index;
this.in_index = (this.in_index + 1) % this.buffer_size;
input_lock.ReleaseMutex();
messages[index].Put(msg);
}
public string Receive ()
{
int index;
// Simply incrementing the index will be enough to ensure that the
// next access will go to a different SafeContainer. There's a lock
// on the SafeContainer anyway to enforce this, but the least
// amount of friction on locks is preferred
output_lock.WaitOne();
index = out_index;
this.out_index = (this.out_index + 1) % this.buffer_size;
output_lock.ReleaseMutex();
return String.Copy(messages[index].Data());
}
}
}