· 4 years ago · May 17, 2021, 09:04 PM
1#include "pch.h"
2
3namespace ClayEngine
4{
5 using String = std::wstring;
6
7 inline void WriteLine(String message)
8 {
9 std::wcout << L"[" << std::setfill(L'0') << std::setw(8) << std::this_thread::get_id() << L"] " << message << std::endl;
10 }
11
12 /// <summary>
13 /// This static class provides pointers to any registered classes in the application
14 /// </summary>
15 class Services
16 {
17 using ServicesMap = std::map<String, uint64_t>;
18
19 inline static ServicesMap services{};
20
21 public:
22 static void AddService(String key, uint64_t addr)
23 {
24 auto it = services.find(key);
25 if (it == services.end())
26 {
27 std::wstringstream ss;
28 ss << L"Adding Service by Key: " << key << L" Addr: 0x" << std::setfill(L'0') << std::setw(12) << std::hex << addr;
29 WriteLine(ss.str());
30
31 services.emplace(key, addr);
32 return;
33 }
34
35 throw std::exception("Service not added due to unique key constraint violation");
36 }
37
38 static void RemoveService(String key)
39 {
40 auto it = services.find(key);
41 if (it != services.end())
42 {
43 services.erase(it);
44 }
45
46 throw std::exception("Service not found for removal");
47 }
48
49 template<typename T, typename... Args>
50 static std::unique_ptr<T> MakeService(String key, Args&&... args)
51 {
52 auto it = services.find(key);
53 if (it == services.end())
54 {
55 auto ptr = std::make_unique<T>(std::forward<Args>(args)...);
56 auto addr = reinterpret_cast<uint64_t>(ptr.get());
57
58 std::wstringstream ss;
59 ss << L"Adding registered class by key \"" << key << L"\" at memory address 0x" << std::setfill(L'0') << std::setw(12) << std::hex << addr;
60 WriteLine(ss.str());
61
62 services.emplace(key, addr);
63 return ptr;
64 }
65
66 throw std::exception("Service not created due to unique key constraint violation");
67 }
68
69 template<typename T>
70 static T* GetService(String key)
71 {
72 auto it = services.find(key);
73 if (it != services.end())
74 {
75 auto ptr = reinterpret_cast<T*>(it->second);
76 return ptr;
77 }
78
79 throw std::exception("Service not found");
80 }
81 };
82
83 /// <summary>
84 /// This is the core application state handler responsible for creating and destroying
85 /// local and network sessions as well as loading and unloading required content
86 /// </summary>
87 class EngineCore
88 {
89 public:
90 enum class CoreState
91 {
92 Default,
93 Initializing,
94 LocalReady,
95 LocalStarting,
96 LocalRunning,
97 LocalStopping,
98 ContentLoading,
99 ContentUnloading,
100 RemoteConnecting,
101 RemoteStarting,
102 RemoteRunning,
103 RemoteAwait,
104 RemoteStopping,
105 RemoteDisconnecting,
106 RemoteError,
107 RemoteReconnecting,
108 };
109 private:
110 CoreState state = CoreState::Default;
111 public:
112 EngineCore() noexcept = default;
113 ~EngineCore() = default;
114
115 // Run a local session
116 // Connect to a remote session
117 };
118 using EngineCorePtr = std::unique_ptr<EngineCore>;
119
120 namespace Platform
121 {
122
123 /// <summary>
124 /// The window class maintains the various system handles for the Win32 window
125 /// </summary>
126 constexpr auto c_max_loadstring = 100;
127 class WindowClass
128 {
129 HINSTANCE instance_handle;
130 HWND window_handle;
131 HACCEL table_handle;
132
133 WCHAR title_string[c_max_loadstring];
134 WCHAR class_string[c_max_loadstring];
135
136 RECT size;
137
138 ATOM _Make_window_class(WNDPROC _Func)
139 {
140 WNDCLASSEXW wcex{};
141 wcex.cbSize = sizeof(WNDCLASSEX);
142 wcex.style = CS_HREDRAW | CS_VREDRAW;
143 wcex.hInstance = instance_handle;
144 wcex.lpfnWndProc = _Func;
145 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
146 wcex.lpszClassName = class_string;
147
148 return RegisterClassExW(&wcex);
149 }
150 BOOL _Make_window_instance(int _Flags)
151 {
152 window_handle = CreateWindowW(class_string, title_string, WS_OVERLAPPEDWINDOW,
153 CW_USEDEFAULT, 0, GetWindowWidth(), GetWindowHeight(), nullptr, nullptr, instance_handle, nullptr);
154
155 if (!window_handle) return FALSE;
156
157 ShowWindow(window_handle, _Flags);
158 ShowCursor(true);
159
160 UpdateWindow(window_handle);
161 GetClientRect(window_handle, &size);
162
163 return TRUE;
164 }
165
166 public:
167 WindowClass(HINSTANCE _Instance, int _Flags, WNDPROC _Func, RECT _Size)
168 : instance_handle{ _Instance }
169 {
170 size = _Size;
171
172 LoadStringW(instance_handle, IDS_APP_TITLE, title_string, c_max_loadstring);
173 LoadStringW(instance_handle, IDC_PROTOTYPECLIENT, class_string, c_max_loadstring);
174
175 _Make_window_class(_Func);
176 if (!_Make_window_instance(_Flags)) throw std::exception("Failed to CreateWindowExW");
177
178 }
179 ~WindowClass() = default;
180
181 void UpdateWindowSize() { GetClientRect(window_handle, &size); }
182 const RECT& GetWindowSize() const { return size; }
183 const int GetWindowWidth() const { return size.right - size.left; }
184 const int GetWindowHeight() const { return size.bottom - size.top; }
185
186 // Message Handlers
187 void OnWindowResized() {}
188 void OnWindowMoved() {}
189 void OnActivated() {}
190 void OnDeactivated() {}
191 void OnSuspended() {}
192 void OnResumed() {}
193
194 };
195 using WindowClassPtr = std::unique_ptr<WindowClass>;
196
197 /// <summary>
198 /// Handles buffering keyboard input for strings
199 /// </summary>
200 constexpr auto c_inputbuffer_size = 1024;
201 template<size_t Size>
202 class StringBuffer
203 {
204 static_assert(Size >= 0);
205 static_assert(Size <= c_inputbuffer_size);
206
207 using type = std::array<wchar_t, Size>;
208
209 std::mutex buffer_mtx = {};
210 type buffer = {};
211
212 size_t carat = 0;
213 size_t end = 0;
214
215 bool overwrite = false;
216
217 public:
218 StringBuffer() = default;
219 ~StringBuffer() = default;
220
221 void MoveCarat(const size_t offset)
222 {
223 auto v = carat + offset;
224
225 if (v >= c_inputbuffer_size)
226 {
227 carat = c_inputbuffer_size;
228 }
229 else if (v < 0)
230 {
231 carat = 0;
232 }
233 else if (v >= end)
234 {
235 carat = end;
236 }
237 else
238 {
239 carat = v;
240 }
241 }
242
243 const bool GetOverwrite() const
244 {
245 return overwrite;
246 }
247 void SetOverwrite(const bool value)
248 {
249 overwrite = value;
250 }
251 void ToggleOverwrite()
252 {
253 overwrite = !overwrite;
254 }
255
256 void InsertChar(const wchar_t character)
257 {
258 if (carat >= c_inputbuffer_size) return; //TODO: What to do with full buffer?
259
260 if (carat == end) {} // Normal condition
261 if (carat < end)
262 {
263 if (overwrite)
264 {
265 // Replace character in array with character provided
266 }
267 else
268 {
269 // Push values between carat and end forward one... Check 1024 bounds
270 }
271 }
272
273 buffer_mtx.lock();
274 buffer[carat] = character;
275 buffer_mtx.unlock();
276
277 ++carat;
278 ++end;
279 }
280
281 void RemovePrev()
282 {
283 if (carat == 0) return; // We're at the beginning of the line, can't backspace
284
285 if (carat == end)
286 {
287 --carat;
288
289 buffer_mtx.lock();
290 buffer[carat] = 0;
291 buffer_mtx.unlock();
292
293 --end;
294 }
295 else
296 {
297 --carat;
298 size_t v = carat;
299
300 buffer_mtx.lock();
301 while (v < end)
302 {
303 buffer[v] = buffer[v + 1ull];
304 ++v;
305 }
306 buffer[v] = 0;
307 buffer_mtx.unlock();
308
309 --end;
310 }
311
312 return;
313 }
314
315 void RemoveNext()
316 {
317 if (end == 0) return; // There's no text to delete
318 if (end >= c_inputbuffer_size) return; // We're at the end of the line already, can't delete
319
320 size_t v = carat;
321
322 buffer_mtx.lock();
323 while (v < end)
324 {
325 buffer[v] = buffer[v + 1ull];
326 ++v;
327 }
328
329 buffer[v] = NULL;
330 buffer_mtx.unlock();
331
332 --end;
333 }
334
335 void Clear()
336 {
337 buffer_mtx.lock();
338 buffer.fill(0);
339 buffer_mtx.unlock();
340
341 carat = 0;
342 end = 0;
343 }
344
345 const String GetString()
346 {
347 buffer_mtx.lock();
348 auto ret = String{ buffer.data() };
349 buffer_mtx.unlock();
350
351 return ret;
352 }
353
354 void SetString(const String string)
355 {
356 buffer_mtx.lock();
357 buffer.fill(0);
358 auto hr = memcpy_s(&buffer, sizeof(wchar_t) * string.length(), string.c_str(), sizeof(wchar_t) * string.length());
359
360 //TODO: Some additional elegance here would store the carat location and go back to it on subsequent scrolls
361 if (carat > string.length()) carat = string.length() + 1;
362 if (end > string.length()) end = string.length() + 1;
363
364 buffer_mtx.unlock();
365 }
366 };
367 using InputBuffer = StringBuffer<1024>;
368 using InputBufferPtr = std::unique_ptr<InputBuffer>;
369
370
371 /// <summary>
372 /// Provides an array of strings with rc-like scrollback functionality
373 /// Intended to provide short term storage for an input buffer
374 /// </summary>
375 constexpr auto c_backbuffer_size = 20;
376 template<size_t Size>
377 class StringArray
378 {
379 static_assert(Size >= 0);
380 static_assert(Size <= c_backbuffer_size);
381
382 using type = std::array<String, Size>;
383
384 std::mutex buffer_mtx = {};
385 type buffer = {};
386
387 size_t carat = 0;
388 size_t end = 0;
389
390 public:
391 StringArray() = default;
392 ~StringArray() = default;
393
394 void InsertString(const String string)
395 {
396 if (carat >= c_backbuffer_size)
397 {
398 //TODO: What to do with full buffer?
399 return;
400 }
401
402 if (carat < end) throw std::exception("You are trying to insert during an update");
403
404 if (carat == end)
405 {
406 buffer[carat] = string;
407 ++carat;
408 ++end;
409 }
410 }
411
412 void UpdateString(const String string)
413 {
414 if (carat <= 0) throw std::exception("Cannot update string on empty set");
415
416 if (carat == end) throw std::exception("You are trying to update during an insert");
417
418 buffer[carat - 1] = string;
419 }
420
421 const String MoveCarat(const size_t offset)
422 {
423 auto v = carat + offset;
424
425 if (v <= 0) throw std::exception("Cannot move carat on empty set");
426
427 if (v >= c_backbuffer_size)
428 {
429 carat = c_backbuffer_size;
430 }
431 else if (v < 1)
432 {
433 carat = 1;
434 }
435 else if (v > end)
436 {
437 carat = end;
438 }
439 else
440 {
441 carat = v;
442 }
443
444 return buffer[carat - 1];
445 }
446
447 const bool IsInScrollback() const
448 {
449 return carat < end;
450 }
451 };
452 using BackBuffer = StringArray<20>;
453 using BackBufferPtr = std::unique_ptr<BackBuffer>;
454
455 /// <summary>
456 /// Input system is the top level API for user input management systems
457 /// </summary>
458 class InputSystem
459 {
460 InputBufferPtr inputbuffer = nullptr;
461 BackBufferPtr backbuffer = nullptr;
462
463 bool capital = false;
464 bool shift = false;
465 bool control = false;
466 bool alt = false;
467
468 public:
469 InputSystem() noexcept
470 {
471 inputbuffer = std::make_unique<InputBuffer>();
472 backbuffer = std::make_unique<BackBuffer>();
473 capital = 0x01 & GetKeyState(VK_CAPITAL);
474 }
475 ~InputSystem() = default;
476
477 void OnMouseEvent()
478 {
479 // Updates Mouse state
480 }
481
482 void OnKeyDown(WPARAM wParam, LPARAM lParam)
483 {
484 SHORT lshift = 0;
485 SHORT rshift = 0;
486 SHORT lcontrol = 0;
487 SHORT rcontrol = 0;
488 SHORT lmenu = 0;
489 SHORT rmenu = 0;
490
491 switch (wParam)
492 {
493 case VK_CAPITAL:
494 capital = 0x01 & GetKeyState(VK_CAPITAL);
495 break;
496 case VK_SHIFT:
497 lshift = GetKeyState(VK_LSHIFT);
498 rshift = GetKeyState(VK_RSHIFT);
499
500 if (0x8000 & lshift) capital = 0x01 & lshift;
501 if (0x8000 & rshift) capital = 0x01 & rshift;
502
503 shift = (0x8000 & lshift) | (0x8000 & rshift);
504 break;
505 case VK_CONTROL:
506 lcontrol = GetKeyState(VK_LCONTROL);
507 rcontrol = GetKeyState(VK_RCONTROL);
508
509 control = (0x8000 & lcontrol) | (0x8000 & rcontrol);
510 break;
511 case VK_MENU:
512 lmenu = GetKeyState(VK_LMENU);
513 rmenu = GetKeyState(VK_RMENU);
514
515 alt = (0x8000 & lmenu) | (0x8000 & rmenu);
516 break;
517 case VK_INSERT: // Process the INS key
518 inputbuffer->ToggleOverwrite(); // lParam contains some state data we could use here
519 break;
520 case VK_DELETE: // Process the DEL key
521 inputbuffer->RemoveNext();
522 break;
523 case VK_HOME: // Process the HOME key
524 if (control)
525 {
526 backbuffer->MoveCarat(-(c_backbuffer_size));
527 }
528 else
529 {
530 inputbuffer->MoveCarat(-(c_inputbuffer_size));
531 }
532 break;
533 case VK_END: // Process the END key
534 if (control)
535 {
536 backbuffer->MoveCarat(c_backbuffer_size);
537 }
538 else
539 {
540 inputbuffer->MoveCarat(c_inputbuffer_size);
541 }
542 break;
543 case VK_UP: // Process the UP ARROW key
544 if (backbuffer->IsInScrollback())
545 {
546 backbuffer->UpdateString(inputbuffer->GetString()); //TODO: Handle logic for when already in scrollback
547 }
548 else
549 {
550 backbuffer->InsertString(inputbuffer->GetString()); //TODO: Handle logic for when already in scrollback
551 }
552 inputbuffer->SetString(backbuffer->MoveCarat(-1));
553 break;
554 case VK_DOWN: // Process the DOWN ARROW key
555 if (backbuffer->IsInScrollback())
556 {
557 backbuffer->UpdateString(inputbuffer->GetString());
558 }
559 else
560 {
561 backbuffer->InsertString(inputbuffer->GetString());
562 }
563 inputbuffer->SetString(backbuffer->MoveCarat(1));
564 break;
565 case VK_LEFT: // Process the LEFT ARROW key
566 if (control) {} //TODO: Move to next word or symbol
567 inputbuffer->MoveCarat(-1);
568 break;
569 case VK_RIGHT: // Process the RIGHT ARROW key
570 if (control) {} //TODO: Move to beginning of current or previous word or symbol
571 inputbuffer->MoveCarat(1);
572 break;
573 case VK_F1: // Process the F1 key
574 case VK_NUMLOCK: // Toggle run default for many...
575 case VK_SNAPSHOT: // Screenshot tool...
576 case VK_SCROLL: // Toggle behavior of chat history versus mousewheel scrolling
577 case VK_PAUSE: // Used for miscellaneous characters; it can vary by keyboard
578 case VK_OEM_1: // For the US standard keyboard, the ';:' key
579 case VK_OEM_2: // For the US standard keyboard, the '/?' key
580 case VK_OEM_3: // For the US standard keyboard, the '`~' key
581 //TODO: Toggle Console
582 case VK_OEM_4: // For the US standard keyboard, the '[{' key
583 case VK_OEM_5: // For the US standard keyboard, the '\|' key
584 case VK_OEM_6: // For the US standard keyboard, the ']}' key
585 case VK_OEM_7: // For the US standard keyboard, the ''"' key
586 default:
587 break;
588 }
589 } // Updates Keyboard state
590
591 void OnKeyUp(WPARAM wParam, LPARAM lParam)
592 {
593 SHORT lshift = 0;
594 SHORT rshift = 0;
595 SHORT controls = 0;
596 SHORT menus = 0;
597
598 switch (wParam)
599 {
600 case VK_SHIFT:
601 lshift = GetKeyState(VK_LSHIFT);
602 rshift = GetKeyState(VK_RSHIFT);
603
604 shift = ((0x8000 & lshift) | (0x8000 & rshift));
605 break;
606 case VK_CONTROL:
607 control = 0x8000 & GetKeyState(VK_CONTROL);
608 break;
609 case VK_MENU:
610 alt = 0x8000 & GetKeyState(VK_MENU);
611 break;
612 default:
613 break;
614 }
615 }
616
617 void OnChar(WPARAM wParam, LPARAM lParam)
618 {
619 switch (wParam)
620 {
621 case 0x08: // Process a backspace
622 inputbuffer->RemovePrev();
623 break;
624 case 0x0A: // Process a linefeed. (Shift-Enter)
625 //backbuffer->InsertString(inputbuffer->GetString());
626 //inputbuffer->Clear();
627 break;
628 case 0x1B: // Process an escape
629 PostQuitMessage(0);
630 break;
631 case 0x09:
632 // Process a tab.
633 break;
634 case 0x0D: // Process a carriage return
635 if (backbuffer->IsInScrollback())
636 {
637 backbuffer->UpdateString(inputbuffer->GetString());
638 }
639 else
640 {
641 backbuffer->InsertString(inputbuffer->GetString());
642 }
643 inputbuffer->Clear();
644 break;
645 default: // Process displayable characters
646 inputbuffer->InsertChar(static_cast<wchar_t>(wParam));
647 break;
648 }
649 } // Processes character input (dependent on input state)
650 };
651 using InputSystemPtr = std::unique_ptr<InputSystem>;
652 }
653}
654
655LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
656
657namespace
658{
659 ClayEngine::Platform::WindowClassPtr g_window = nullptr;
660 ClayEngine::Platform::InputSystemPtr g_input = nullptr;
661}
662
663int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
664{
665 UNREFERENCED_PARAMETER(hPrevInstance);
666 UNREFERENCED_PARAMETER(lpCmdLine);
667
668 using namespace ClayEngine;
669 using namespace ClayEngine::Platform;
670
671 if (AllocConsole())
672 {
673 FILE* file = nullptr;
674 _wfreopen_s(&file, TEXT("CONIN$"), TEXT("r"), stdin);
675 _wfreopen_s(&file, TEXT("CONOUT$"), TEXT("w"), stdout);
676 _wfreopen_s(&file, TEXT("CONOUT$"), TEXT("w"), stderr);
677 WriteLine(TEXT("Allocated default console"));
678 }
679
680 if (FAILED(CoInitializeEx(nullptr, COINITBASE_MULTITHREADED))) return -1;
681 if (!DirectX::XMVerifyCPUSupport()) return -1;
682
683 try
684 {
685 g_window = Services::MakeService<WindowClass>(TEXT("WindowClass"), hInstance, nCmdShow, WndProc, RECT{ 0, 0, 1920, 1080 });
686 }
687 catch (std::exception ex)
688 {
689 std::cout << ex.what();
690 return 0;
691 }
692
693 g_input = Services::MakeService<InputSystem>(TEXT("InputSystem"));
694
695 MSG msg{};
696 while (msg.message != WM_QUIT)
697 {
698 if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
699 {
700 TranslateMessage(&msg);
701 DispatchMessage(&msg);
702 }
703 }
704
705 CoUninitialize();
706
707 return static_cast<int>(msg.wParam);
708}
709
710LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
711{
712 switch (message)
713 {
714 case WM_DESTROY:
715 PostQuitMessage(0);
716 break;
717 case WM_GETMINMAXINFO:
718 if (lParam)
719 {
720 auto info = reinterpret_cast<MINMAXINFO*>(lParam);
721 info->ptMinTrackSize.x = 320;
722 info->ptMinTrackSize.y = 200;
723 }
724 break;
725 case WM_MENUCHAR: // Supress the menu
726 return MAKELRESULT(0, MNC_CLOSE);
727 case WM_PAINT:
728 {
729 PAINTSTRUCT ps;
730 (void)BeginPaint(hWnd, &ps);
731 // HDC hdc = BeginPaint(hWnd, &ps);
732 // TODO: Add any drawing code that uses hdc here...
733 EndPaint(hWnd, &ps);
734 }
735 break;
736 case WM_SYSKEYUP:
737 case WM_KEYUP:
738 g_input->OnKeyUp(wParam, lParam);
739 break;
740 case WM_SYSKEYDOWN:
741 case WM_KEYDOWN:
742 g_input->OnKeyDown(wParam, lParam);
743 break;
744 case WM_CHAR:
745 g_input->OnChar(wParam, lParam);
746 break;
747 case WM_INPUT:
748 case WM_MOUSEMOVE:
749 case WM_LBUTTONDOWN:
750 case WM_LBUTTONUP:
751 case WM_RBUTTONDOWN:
752 case WM_RBUTTONUP:
753 case WM_MBUTTONDOWN:
754 case WM_MBUTTONUP:
755 case WM_MOUSEWHEEL:
756 case WM_XBUTTONDOWN:
757 case WM_XBUTTONUP:
758 case WM_MOUSEHOVER:
759 {
760 //TODO: Send to InputSystem
761 g_input->OnMouseEvent();
762 }
763 break;
764 case WM_ACTIVATEAPP:
765 case WM_POWERBROADCAST:
766 case WM_ENTERSIZEMOVE:
767 case WM_EXITSIZEMOVE:
768 case WM_SIZE:
769 case WM_MOVE:
770 {
771 //TODO: Send to WindowClass
772 g_window->OnDeactivated();
773 g_window->OnSuspended();
774 g_window->OnActivated();
775 g_window->OnResumed(); //TODO: Send signal to InputSystem to test hardware key lock state
776 g_window->OnWindowMoved();
777 g_window->OnWindowResized();
778 }
779 break;
780 }
781
782 return DefWindowProc(hWnd, message, wParam, lParam);
783}
784