A game about forced loneliness, made by TACStudios
1/*---------------------------------------------------------------------------------------------
2 * Copyright (c) Microsoft Corporation. All rights reserved.
3 * Licensed under the MIT License. See License.txt in the project root for license information.
4 *--------------------------------------------------------------------------------------------*/
5using System;
6using System.Net;
7using System.Net.Sockets;
8
9namespace Microsoft.Unity.VisualStudio.Editor.Messaging
10{
11 internal class Messager : IDisposable
12 {
13 public event EventHandler<MessageEventArgs> ReceiveMessage;
14 public event EventHandler<ExceptionEventArgs> MessagerException;
15
16 private readonly UdpSocket _socket;
17 private readonly object _disposeLock = new object();
18 private bool _disposed;
19
20#if UNITY_EDITOR_WIN
21 [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
22 private static extern bool SetHandleInformation(IntPtr hObject, HandleFlags dwMask, HandleFlags dwFlags);
23
24 [Flags]
25 private enum HandleFlags: uint
26 {
27 None = 0,
28 Inherit = 1,
29 ProtectFromClose = 2
30 }
31#endif
32
33 protected Messager(int port)
34 {
35 _socket = new UdpSocket();
36 _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false);
37 _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
38
39#if UNITY_EDITOR_WIN
40 // Explicitely disable inheritance for our UDP socket handle
41 // We found that Unity is creating a fork when importing new assets that can clone our socket
42 SetHandleInformation(_socket.Handle, HandleFlags.Inherit, HandleFlags.None);
43#endif
44
45 _socket.Bind(IPAddress.Any, port);
46
47 BeginReceiveMessage();
48 }
49
50 private void BeginReceiveMessage()
51 {
52 var buffer = new byte[UdpSocket.BufferSize];
53 var any = UdpSocket.Any();
54
55 try
56 {
57 lock (_disposeLock)
58 {
59 if (_disposed)
60 return;
61
62 _socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref any, ReceiveMessageCallback, buffer);
63 }
64 }
65 catch (SocketException se)
66 {
67 MessagerException?.Invoke(this, new ExceptionEventArgs(se));
68
69 BeginReceiveMessage();
70 }
71 catch (ObjectDisposedException)
72 {
73 }
74 }
75
76 private void ReceiveMessageCallback(IAsyncResult result)
77 {
78 try
79 {
80 var endPoint = UdpSocket.Any();
81
82 lock (_disposeLock)
83 {
84 if (_disposed)
85 return;
86
87 _socket.EndReceiveFrom(result, ref endPoint);
88 }
89
90 var message = DeserializeMessage(UdpSocket.BufferFor(result));
91 if (message != null)
92 {
93 message.Origin = (IPEndPoint)endPoint;
94
95 if (IsValidTcpMessage(message, out var port, out var bufferSize))
96 {
97 // switch to TCP mode to handle big messages
98 TcpClient.Queue(message.Origin.Address, port, bufferSize, buffer =>
99 {
100 var originalMessage = DeserializeMessage(buffer);
101 originalMessage.Origin = message.Origin;
102 ReceiveMessage?.Invoke(this, new MessageEventArgs(originalMessage));
103 });
104 }
105 else
106 {
107 ReceiveMessage?.Invoke(this, new MessageEventArgs(message));
108 }
109 }
110 }
111 catch (ObjectDisposedException)
112 {
113 return;
114 }
115 catch (Exception e)
116 {
117 RaiseMessagerException(e);
118 }
119
120 BeginReceiveMessage();
121 }
122
123 private static bool IsValidTcpMessage(Message message, out int port, out int bufferSize)
124 {
125 port = 0;
126 bufferSize = 0;
127 if (message.Value == null)
128 return false;
129 if (message.Type != MessageType.Tcp)
130 return false;
131 var parts = message.Value.Split(':');
132 if (parts.Length != 2)
133 return false;
134 if (!int.TryParse(parts[0], out port))
135 return false;
136 return int.TryParse(parts[1], out bufferSize);
137 }
138
139 private void RaiseMessagerException(Exception e)
140 {
141 MessagerException?.Invoke(this, new ExceptionEventArgs(e));
142 }
143
144 private static Message MessageFor(MessageType type, string value)
145 {
146 return new Message { Type = type, Value = value };
147 }
148
149 public void SendMessage(IPEndPoint target, MessageType type, string value = "")
150 {
151 var message = MessageFor(type, value);
152 var buffer = SerializeMessage(message);
153
154 try
155 {
156 lock (_disposeLock)
157 {
158 if (_disposed)
159 return;
160
161 if (buffer.Length >= UdpSocket.BufferSize)
162 {
163 // switch to TCP mode to handle big messages
164 var port = TcpListener.Queue(buffer);
165 if (port > 0)
166 {
167 // success, replace original message with "switch to tcp" marker + port information + buffer length
168 message = MessageFor(MessageType.Tcp, string.Concat(port, ':', buffer.Length));
169 buffer = SerializeMessage(message);
170 }
171 }
172
173 _socket.BeginSendTo(buffer, 0, Math.Min(buffer.Length, UdpSocket.BufferSize), SocketFlags.None, target, SendMessageCallback, null);
174 }
175 }
176 catch (SocketException se)
177 {
178 MessagerException?.Invoke(this, new ExceptionEventArgs(se));
179 }
180 }
181
182 private void SendMessageCallback(IAsyncResult result)
183 {
184 try
185 {
186 lock (_disposeLock)
187 {
188 if (_disposed)
189 return;
190
191 _socket.EndSendTo(result);
192 }
193 }
194 catch (SocketException se)
195 {
196 MessagerException?.Invoke(this, new ExceptionEventArgs(se));
197 }
198 catch (ObjectDisposedException)
199 {
200 }
201 }
202
203 private static byte[] SerializeMessage(Message message)
204 {
205 var serializer = new Serializer();
206 serializer.WriteInt32((int)message.Type);
207 serializer.WriteString(message.Value);
208
209 return serializer.Buffer();
210 }
211
212 private static Message DeserializeMessage(byte[] buffer)
213 {
214 if (buffer.Length < 4)
215 return null;
216
217 var deserializer = new Deserializer(buffer);
218 var type = (MessageType)deserializer.ReadInt32();
219 var value = deserializer.ReadString();
220
221 return new Message { Type = type, Value = value };
222 }
223
224 public static Messager BindTo(int port)
225 {
226 return new Messager(port);
227 }
228
229 public void Dispose()
230 {
231 lock (_disposeLock)
232 {
233 _disposed = true;
234 _socket.Close();
235 }
236 }
237 }
238}