1 // Copyright 2001-2019 Crytek GmbH / Crytek Group. All rights reserved.
4 using System
.Collections
.Generic
;
5 using System
.Net
.Sockets
;
6 using System
.Threading
;
8 namespace CryEngine
.Debugger
.Mono
10 static class Constants
12 public const int DefaultPort
= 4600;
13 public const int DefaultBuffer
= 4096;
14 public const int ReconnectionTime
= 3000;
15 public const int ConnectionAttempts
= 25;
18 public enum MessageType
25 public class LogMessage
27 public MessageType Type { get; set; }
28 public string Message { get; set; }
30 public LogMessage(MessageType type
, string message
)
37 interface IRemoteConsoleClientListener
39 void OnLogMessage(LogMessage message
);
40 void OnAutoCompleteDone(List
<string> autoCompleteList
);
42 void OnDisconnected();
45 interface IRemoteConsoleClient
49 void SetServer(string ip
);
50 void ExecuteConsoleCommand(string command
);
51 void ExecuteGameplayCommand(string command
);
52 void SetListener(IRemoteConsoleClientListener listener
);
56 class RemoteConsoleClientListenerState
58 public IRemoteConsoleClientListener Listener { get; set; }
= null;
59 public bool Connected { get; set; }
= false;
60 public bool AutoCompleteSent { get; set; }
= false;
62 public void SetListener(IRemoteConsoleClientListener listener
)
70 AutoCompleteSent
= false;
74 class RemoteConsole
: IRemoteConsoleClient
, IDisposable
76 private enum ConsoleEventType
101 Strobo_CallstackStart
,
107 private class CommandEvent
109 public ConsoleEventType Type { get; set; }
110 public string Command { get; set; }
112 public CommandEvent(ConsoleEventType type
, string command
)
119 private Thread _consoleThread
= null;
120 private TcpClient _clientSocket
= null;
121 private byte[] _inStream
= new byte[Constants
.DefaultBuffer
];
123 private string _server
= "";
124 private Queue
<CommandEvent
> _commands
= new Queue
<CommandEvent
>();
125 private Queue
<string> _autoComplete
= new Queue
<string>();
126 private Queue
<LogMessage
> _messages
= new Queue
<LogMessage
>();
128 private RemoteConsoleClientListenerState _listener
= new RemoteConsoleClientListenerState();
130 private object _locker
= new object();
131 private object _clientLocker
= new object();
133 private volatile bool _running
= false;
134 private volatile bool _stopping
= false;
135 private volatile bool _isConnected
= false;
136 private volatile bool _autoCompleteIsDone
= false;
137 private volatile bool _resetConnection
= false;
138 private int _ticks
= 0;
139 private int _connectionAttempts
= 0;
141 public int Port { get; private set; }
143 public RemoteConsole()
145 _clientSocket
= null;
146 Port
= Constants
.DefaultPort
;
149 public RemoteConsole(int port
)
151 _clientSocket
= null;
155 public bool SetPort(string portText
)
158 if((!_isConnected
|| _resetConnection
) && int.TryParse(portText
, out port
))
171 _consoleThread
= new Thread(RemoteConsoleLoop
);
172 _consoleThread
.Name
= nameof(RemoteConsoleLoop
);
173 _consoleThread
.Start();
185 if (_clientSocket
!= null)
187 _clientSocket
.Close();
190 while (_stopping
&& _consoleThread
.IsAlive
)
194 _consoleThread
= null;
198 public void SetServer(string ip
)
200 if (_listener
.Listener
!= null)
202 _listener
.Listener
.OnDisconnected();
203 _listener
.Connected
= false;
209 _autoCompleteIsDone
= false;
211 _resetConnection
= true;
212 _connectionAttempts
= 0;
216 public void ExecuteConsoleCommand(string command
)
220 _commands
.Enqueue(new CommandEvent(ConsoleEventType
.ConsoleCommand
, command
));
224 public void ExecuteGameplayCommand(string command
)
228 _commands
.Enqueue(new CommandEvent(ConsoleEventType
.GameplayEvent
, command
));
232 public void SetListener(IRemoteConsoleClientListener listener
)
236 _listener
.SetListener(listener
);
240 public void PumpEvents()
242 List
<LogMessage
> messages
= null;
243 List
<string> autoComplete
= null;
244 bool sendConn
= false;
246 IRemoteConsoleClientListener l
= null;
249 l
= _listener
.Listener
;
250 messages
= new List
<LogMessage
>(_messages
);
252 if (_autoCompleteIsDone
&& !_listener
.AutoCompleteSent
)
254 autoComplete
= new List
<string>(_autoComplete
);
255 _autoComplete
.Clear();
256 _listener
.AutoCompleteSent
= true;
258 if (_isConnected
!= _listener
.Connected
&& !_resetConnection
)
260 _listener
.Connected
= _isConnected
;
262 isConn
= _isConnected
;
278 if (messages
!= null)
280 foreach (var message
in messages
)
282 l
.OnLogMessage(message
);
285 if (autoComplete
!= null)
287 l
.OnAutoCompleteDone(autoComplete
);
292 private void RemoteConsoleLoop()
296 if(!_resetConnection
&& !_isConnected
&& _connectionAttempts
>= Constants
.ConnectionAttempts
)
298 AddLogMessage(MessageType
.Message
, string.Format("Unable to connect after {0} connection attemtps.", _connectionAttempts
));
302 if (!_isConnected
|| _resetConnection
)
305 if (_ticks
++ % Constants
.ReconnectionTime
== 0)
307 _connectionAttempts
++;
310 if (_clientSocket
!= null && _clientSocket
.Connected
)
312 _clientSocket
.GetStream().Close();
313 _clientSocket
.Close();
314 _clientSocket
.Dispose();
316 _clientSocket
= new TcpClient();
320 _clientSocket
.Connect(_server
, Port
);
321 NetworkStream stream
= _clientSocket
.GetStream();
322 stream
.ReadTimeout
= 3000;
323 stream
.WriteTimeout
= 3000;
329 // Prevent log-spam if connecting failed but there are still connection attempts left.
330 if(!(ex
is SocketException
) && _connectionAttempts
< Constants
.ConnectionAttempts
)
332 AddLogMessage(MessageType
.Message
, ex
.Message
);
336 catch (System
.Exception
){ }
340 _isConnected
= _clientSocket
.Client
!= null ? _clientSocket
.Connected
: false;
343 _autoCompleteIsDone
= false;
347 // Reset the connection attempts if reconnecting was succesful.
348 _connectionAttempts
= 0;
351 if (_resetConnection
)
353 _resetConnection
= false;
360 if(_clientSocket
.Available
> 0)
362 _isConnected
= ProcessClient();
365 // Pump the events immediately since they will be logged thread-safe later on.
370 string message
= string.Format("Disconnecting the remote console because it ran into the following exception: {0}{1}{2}", ex
.Message
, Environment
.NewLine
, ex
.StackTrace
);
371 AddLogMessage(MessageType
.Error
, message
);
380 private bool ReadData(ref ConsoleEventType id
, ref string data
)
384 NetworkStream stream
= _clientSocket
.GetStream();
388 ret
+= stream
.Read(_inStream
, ret
, Constants
.DefaultBuffer
- ret
);
394 if (_inStream
[ret
- 1] == '\0')
399 string returndata
= System
.Text
.Encoding
.ASCII
.GetString(_inStream
);
400 id
= (ConsoleEventType
)(returndata
[0] - '0');
401 int index
= returndata
.IndexOf('\0');
402 data
= returndata
.Substring(1, index
- 1);
411 private bool SendData(ConsoleEventType id
, string data
= "")
413 char cid
= (char)((char)id
+ '0');
420 byte[] outStream
= System
.Text
.Encoding
.ASCII
.GetBytes(msg
);
421 NetworkStream stream
= _clientSocket
.GetStream();
422 stream
.Write(outStream
, 0, outStream
.Length
);
432 private void AddLogMessage(MessageType type
, string message
)
436 _messages
.Enqueue(new LogMessage(type
, message
));
440 private void AddAutoCompleteItem(string item
)
444 _autoComplete
.Enqueue(item
);
448 private bool GetCommand(ref CommandEvent command
)
453 if (_commands
.Count
> 0)
455 command
= _commands
.Dequeue();
462 private void AutoCompleteDone()
466 _autoCompleteIsDone
= true;
470 private void ClearCommands()
478 private bool ProcessClient()
480 ConsoleEventType eventType
= ConsoleEventType
.Noop
;
482 if (!ReadData(ref eventType
, ref data
))
487 case ConsoleEventType
.LogMessage
:
488 AddLogMessage(MessageType
.Message
, data
);
489 return SendData(ConsoleEventType
.Noop
);
490 case ConsoleEventType
.LogWarning
:
491 AddLogMessage(MessageType
.Warning
, data
);
492 return SendData(ConsoleEventType
.Noop
);
493 case ConsoleEventType
.LogError
:
494 AddLogMessage(MessageType
.Error
, data
);
495 return SendData(ConsoleEventType
.Noop
);
496 case ConsoleEventType
.AutoCompleteList
:
497 AddAutoCompleteItem(data
);
498 return SendData(ConsoleEventType
.Noop
);
499 case ConsoleEventType
.AutoCompleteListDone
:
501 return SendData(ConsoleEventType
.Noop
);
502 case ConsoleEventType
.Req
:
503 CommandEvent command
= null;
504 if (GetCommand(ref command
))
505 return SendData(command
.Type
, command
.Command
);
507 return SendData(ConsoleEventType
.Noop
);
509 return SendData(ConsoleEventType
.Noop
);
513 public void Dispose()
516 GC
.SuppressFinalize(this);
519 public void Dispose(bool disposing
)
523 _clientSocket
.Dispose();
524 _clientSocket
= null;