Leviathan  0.8.0.0
Leviathan game engine
ConsoleInput.cpp
Go to the documentation of this file.
1 // ------------------------------------ //
2 #include "ConsoleInput.h"
3 
4 #include <stdio.h>
5 using namespace Leviathan;
6 // ------------------------------------ //
7 #ifdef __unix__
8 
9 #include <unistd.h>
10 
11 #endif
12 
13 
15 
16  if(!Initialized)
17  return;
18 
19  if (StdInThread.joinable()) {
20 
21  // If this is true this hasn't been closed properly //
22  LEVIATHAN_ASSERT(!ReadingInput, "ConsoleInput not closed properly");
23 
24  StdInThread.join();
25  }
26 
27  StdInUse = false;
28 }
29 
30 std::atomic<bool> ConsoleInput::StdInUse { false };
31 // ------------------------------------ //
32 bool ConsoleInput::Init(std::function<bool (const std::string&)> callback,
33  bool canopenconsole /*= true*/)
34 {
35  Callback = callback;
36 
37  // Check is already in use //
38  if(StdInUse){
39 
40  DEBUG_BREAK;
41  return false;
42  }
43 
44  const bool oldvalue = StdInUse.exchange(true);
45 
46  if(oldvalue){
47 
48  // Somebody else got it //
49  return false;
50  }
51 
52  // Guaranteed to be the only object that gets here before our destructor //
53  Initialized = true;
54 
55 #ifdef _WIN32
56  // Open a console if not already open //
57  if (GetConsoleWindow() == nullptr && canopenconsole) {
58 
59  // Create a new console //
60  Logger::Get()->Info("Creating a console window for input");
61  CreatedNewConsole = true;
62  CreateConsoleWindow();
63 
64  if (GetConsoleWindow() != nullptr) {
65 
66  Logger::Get()->Info("Leviathan Console");
67  }
68  }
69 #endif // _WIN32
70 
71  if(!PrepareWait()){
72 
73  DEBUG_BREAK;
74  return false;
75  }
76 
77  ReadingInput = true;
78  StdInThread = std::thread(std::bind(&ConsoleInput::WaitForInput, this));
79 
80 
81  return true;
82 }
83 // ------------------------------------ //
84 void ConsoleInput::Release(bool waitquit /*= false*/){
85 
86  if (ReadingInput) {
87 
88  StopWaiting();
89 
90  ReadingInput = false;
91 
92  if(waitquit && StdInThread.joinable())
93  StdInThread.join();
94  }
95 #ifdef _WIN32
96 
97  DestroyConsoleWindow();
98 
99 #endif // _WIN32
100 }
101 // ------------------------------------ //
103 #ifdef _WIN32
104 
105  return GetConsoleWindow() != nullptr;
106 
107 #else
108  if(isatty(fileno(stdin)))
109  return true;
110 
111  return false;
112 #endif
113 }
114 // ------------------------------------ //
115 bool ConsoleInput::OnReceivedInput(const std::string &str){
116 
117  return Callback(str);
118 }
119 // ------------------------------------ //
120 #ifdef _WIN32
121 
122 #include <processenv.h>
123 #include <winbase.h>
124 #include <iostream>
125 
126 
127 void ConsoleInput::CreateConsoleWindow() {
128 
129  constexpr int MAX_CONSOLE_LINES = 500;
130 
131  // Method from http://www.halcyon.com/~ast/dload/guicon.htm
132  // Better method from
133  // http://stackoverflow.com/questions/311955/redirecting-cout-to-a-console-in-windows
134 
135  // Allocate a console for this app
136  if (!AllocConsole()) {
137 
138  LOG_ERROR("ConsoleInput: AllocConsole failed");
139  return;
140  }
141 
142  // set the screen buffer to be big enough to let us scroll text
143  CONSOLE_SCREEN_BUFFER_INFO coninfo;
144  GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
145 
146  coninfo.dwSize.Y = MAX_CONSOLE_LINES;
147 
148  SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
149 
150  // Redirect the CRT standard input, output, and error handles to the console
151  //freopen("CONIN$", "r", stdin);
152  //freopen("CONOUT$", "w", stdout);
153  //freopen("CONOUT$", "w", stderr);
154 
155  FILE* ResultStream;
156 
157  if(freopen_s(&ResultStream, "CONIN$", "r", stdin) != 0)
158  LOG_ERROR("ConsoleInput: freopen failed");
159  if (freopen_s(&ResultStream, "CONOUT$", "w", stdout) != 0)
160  LOG_ERROR("ConsoleInput: freopen failed");
161  if (freopen_s(&ResultStream, "CONOUT$", "w", stderr) != 0)
162  LOG_ERROR("ConsoleInput: freopen failed");
163 
164  // Clear the error state for each of the C++ standard stream
165  // objects. We need to do this, as attempts to access the standard
166  // streams before they refer to a valid target will cause the
167  // iostream objects to enter an error state. In versions of Visual
168  // Studio after 2005, this seems to always occur during startup
169  // regardless of whether anything has been read from or written to
170  // the console or not.
171  std::wcout.clear();
172  std::cout.clear();
173  std::wcerr.clear();
174  std::cerr.clear();
175  std::wcin.clear();
176  std::cin.clear();
177 
178  std::ios::sync_with_stdio();
179 }
180 
181 void ConsoleInput::DestroyConsoleWindow() {
182 
183  if (CreatedNewConsole) {
184 
185  // unredirect output from the console //
186  FILE* ResultStream;
187  freopen_s(&ResultStream, "nul", "r", stdin);
188  freopen_s(&ResultStream, "nul", "w", stdout);
189  freopen_s(&ResultStream, "nul", "w", stderr);
190 
191  CreatedNewConsole = false;
192  while (!FreeConsole()) {
193 
194  Logger::Get()->Error("ConsoleInput: Closing Windows console window failed, retrying");
195  }
196  }
197 }
198 
199 void ConsoleInput::WaitForInput() {
200 
201  char ReadBuffer[200];
202 
203  while (ConsoleInputFile > 0) {
204 
205  DWORD BytesRead;
206  if (!ReadFile(ConsoleInputFile, &ReadBuffer, 199, &BytesRead, NULL)) {
207 
208  // If our read was canceled, quit without a message //
209  if (GetLastError() == ERROR_OPERATION_ABORTED)
210  break;
211 
212  Logger::Get()->Warning("ConsoleInput: Stdin read failed, stopping input thread");
213  break;
214  }
215 
216  if(BytesRead < 1)
217  continue;
218 
219  ReadBuffer[BytesRead] = '\0';
220 
221  OnReceivedInput(std::string(ReadBuffer, BytesRead));
222  }
223 }
224 
226 
227  ConsoleInputFile = CreateFile(TEXT("CONIN$"), GENERIC_READ, FILE_SHARE_READ,
228  NULL, OPEN_EXISTING, NULL, NULL);
229 
230  if (!ConsoleInputFile) {
231 
232  DEBUG_BREAK;
233  return false;
234  }
235 
236  return true;
237 }
238 
240 
241  if(StdInThread.joinable())
242  CancelSynchronousIo(StdInThread.native_handle());
243 
244  if (ConsoleInputFile > 0) {
245 
246  CloseHandle(ConsoleInputFile);
247  ConsoleInputFile = 0;
248  }
249 }
250 
251 #elif defined(__unix__)
252 
253 #include <unistd.h>
254 #include <sys/select.h>
255 
256 void ConsoleInput::WaitForInput() {
257 
258  const auto StdIn = fileno(stdin);
259 
260  char ReadBuffer[200];
261 
262  fd_set rfds;
263 
264  while (true) {
265 
266  FD_ZERO(&rfds);
267  FD_SET(ReadCancelPipe[0], &rfds);
268  FD_SET(StdIn, &rfds);
269 
270  const auto MaxFD = std::max(ReadCancelPipe[0], StdIn) + 1;
271 
272  int SelectRes = select(MaxFD, &rfds, NULL, NULL, NULL);
273 
274  if (SelectRes == -1) {
275 
276  LOG_ERROR("ConsoleInput: select failed, stopping input thread");
277  break;
278  }
279 
280  if(SelectRes == 0)
281  continue;
282 
283  // Quit check //
284  if (FD_ISSET(ReadCancelPipe[0], &rfds)) {
285 
286  // Received quit message //
287  break;
288  }
289 
290  // The other must be valid //
291  LEVIATHAN_ASSERT(FD_ISSET(StdIn, &rfds), "FD set was all empty");
292 
293  // This should not block as it is ready for reading //
294  const auto ReadBytes = read(StdIn, &ReadBuffer, 199);
295 
296  if (ReadBytes == -1) {
297 
298  // Error //
299  LOG_WARNING("ConsoleInput: read on stdin failed, stopping input thread");
300  break;
301  }
302 
303  if (ReadBytes == 0) {
304 
305  // End of std in //
306  LOG_INFO("ConsoleInput: stdin read end reached, stopping input thread");
307  break;
308  }
309 
310  ReadBuffer[ReadBytes] = '\0';
311 
312  OnReceivedInput(std::string(ReadBuffer, ReadBytes));
313  }
314 
315  close(ReadCancelPipe[0]);
316 }
317 
319 
320  if (pipe(ReadCancelPipe) == -1) {
321 
322  LOG_ERROR("ConsoleInput: Failed to create pipe");
323  return false;
324  }
325 
326  return true;
327 }
328 
330 
331  static const char CloseMessage[] = { 'c' };
332 
333  if (write(ReadCancelPipe[1], CloseMessage, 1) != 1) {
334 
335  LOG_ERROR("ConsoleInput: Failed to signal input thread pipe");
336  }
337 
338  // Close the write end, WaitForInput will close the other end
339  close(ReadCancelPipe[1]);
340 }
341 
342 
343 #else
344 #error ConsoleInput not implemented for platform
345 #endif
#define LOG_INFO(x)
Definition: Define.h:92
DLLEXPORT void Info(const std::string &data) override
Definition: Logger.cpp:164
#define LOG_ERROR(x)
Definition: Define.h:94
DLLEXPORT bool Init(std::function< bool(const std::string &)> callback, bool canopenconsole=true)
Starts listening for input from std in.
DLLEXPORT void Release(bool waitquit=false)
virtual bool OnReceivedInput(const std::string &str)
#define LOG_WARNING(x)
Definition: Define.h:93
DLLEXPORT void Warning(const std::string &data) override
Definition: Logger.cpp:190
void StopWaiting()
Stops reading.
#define LEVIATHAN_ASSERT(x, msg)
Definition: Define.h:102
static DLLEXPORT Logger * Get()
Definition: Logger.cpp:106
The access mask controls which registered functions and classes a script sees.
Definition: GameModule.h:12
DLLEXPORT void Error(const std::string &data) override
Definition: Logger.cpp:177
static DLLEXPORT bool IsAttachedToConsole()