· 4 years ago · Jun 17, 2021, 05:20 PM
1#include "pch.h"
2
3#include "SpriteBatch.h"
4#include "SpriteFont.h"
5#include "WICTextureLoader.h"
6#include "SimpleMath.h"
7
8namespace ClayEngine
9{
10 using String = std::wstring;
11
12 inline void WriteLine(String message)
13 {
14 std::wcout << L"[" << std::setfill(L'0') << std::setw(8) << std::this_thread::get_id() << L"] " << message << std::endl;
15 }
16
17 constexpr auto c_ticks_per_second = static_cast<uint64_t>(10'000'000ull);
18 constexpr auto c_target_frame_rate = static_cast<uint32_t>(60ul);
19
20 inline double TicksToSeconds(uint64_t ticks) noexcept { return static_cast<double>(ticks) / c_ticks_per_second; }
21 inline uint64_t SecondsToTicks(double seconds) noexcept { return static_cast<uint64_t>(seconds * c_ticks_per_second); }
22
23 // Foward Declarations
24 struct GameTickerEntryPoint
25 {
26 void operator()(std::future<void> future);
27 };
28
29 template <class Functor>
30 class ThreadGuard
31 {
32 std::thread t;
33 std::promise<void> p;
34
35 public:
36 ThreadGuard()
37 {
38 t = std::thread{ Functor(), std::move(p.get_future()) };
39 }
40 ~ThreadGuard()
41 {
42 p.set_value();
43 if (t.joinable()) t.join();
44 }
45 };
46
47 /// <summary>
48 /// This static class provides pointers to any registered classes in the application
49 /// </summary>
50 class Services
51 {
52 using ServicesMap = std::map<String, uint64_t>;
53
54 inline static ServicesMap services{};
55
56 public:
57 static void AddService(String key, uint64_t addr)
58 {
59 auto it = services.find(key);
60 if (it == services.end())
61 {
62 std::wstringstream ss;
63 ss << L"Adding Service by Key: " << key << L" Addr: 0x" << std::setfill(L'0') << std::setw(12) << std::hex << addr;
64 WriteLine(ss.str());
65
66 services.emplace(key, addr);
67 return;
68 }
69
70 throw std::exception("Service not added due to unique key constraint violation");
71 }
72
73 static void RemoveService(String key)
74 {
75 auto it = services.find(key);
76 if (it != services.end())
77 {
78 services.erase(it);
79 }
80
81 throw std::exception("Service not found for removal");
82 }
83
84 template<typename T, typename... Args>
85 static std::unique_ptr<T> MakeService(String key, Args&&... args)
86 {
87 auto it = services.find(key);
88 if (it == services.end())
89 {
90 auto ptr = std::make_unique<T>(std::forward<Args>(args)...);
91 auto addr = reinterpret_cast<uint64_t>(ptr.get());
92
93 std::wstringstream ss;
94 ss << L"Adding registered class by key \"" << key << L"\" at memory address 0x" << std::setfill(L'0') << std::setw(12) << std::hex << addr;
95 WriteLine(ss.str());
96
97 services.emplace(key, addr);
98 return ptr;
99 }
100
101 throw std::exception("Service not created due to unique key constraint violation");
102 }
103
104 template<typename T>
105 static T* GetService(String key)
106 {
107 auto it = services.find(key);
108 if (it != services.end())
109 {
110 auto ptr = reinterpret_cast<T*>(it->second);
111 return ptr;
112 }
113
114 throw std::exception("Service not found");
115 }
116
117 static bool FindService(String key)
118 {
119 auto it = services.find(key);
120 if (it != services.end())
121 return true;
122 else
123 return false;
124 }
125 };
126
127
128
129
130
131
132 class LexicalAnalyzer
133 {
134 std::string s = {};
135 std::string::const_iterator i = {};
136
137 public:
138 LexicalAnalyzer(std::string input) : s{ input }
139 {
140 i = s.cbegin();
141 }
142 ~LexicalAnalyzer() = default;
143
144 std::string GetNextToken()
145 {
146 return "";
147 }
148
149 bool IsNumber(char ch)
150 {
151 return (ch == '0' || ch == '1' || ch == '2' || ch == '3' || ch == '4' ||
152 ch == '5' || ch == '6' || ch == '7' || ch == '8' || ch == '9');
153 }
154
155 bool IsOperator(char ch)
156 {
157 return (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '>' ||
158 ch == '<' || ch == '=' || ch == '|' || ch == '&' || ch == '{' ||
159 ch == '}' || ch == '[' || ch == ']' || ch == '(' || ch == ')' ||
160 ch == '\'' || ch == '\"' || ch == '`' || ch == '.' || ch == ',');
161 }
162
163 bool IsWhitespace(char ch)
164 {
165 return (ch == ' ' || ch == '\t' || ch == '\n');
166 }
167 };
168
169 class CommandParser
170 {
171 public:
172 enum class Verbs
173 {
174 Invalid,
175 Set,
176 };
177 enum class Nouns
178 {
179 Invalid,
180 ScreenWidth,
181 ScreenHeight,
182 };
183
184 private:
185 Verbs verb = Verbs::Invalid;
186 Nouns noun = Nouns::Invalid;
187 int value = 0;
188
189
190 public:
191 CommandParser() = default;
192 ~CommandParser() = default;
193
194 // Command Parser will execute the command in the string provided
195 void ParseCommand(std::string line)
196 {
197 return;
198 }
199
200 };
201
202
203
204
205
206 namespace Platform
207 {
208 constexpr auto c_max_loadstring = 100;
209
210 constexpr auto c_inputbuffer_size = 1024;
211 constexpr auto c_backbuffer_size = 20;
212
213 constexpr auto c_min_window_width = 320;
214 constexpr auto c_min_window_height = 200;
215 constexpr auto c_default_window_width = 1920;
216 constexpr auto c_default_window_height = 1080;
217
218 constexpr auto c_settings_filename = "clayengine.ini";
219
220 inline bool PlatformStart()
221 {
222 if (AllocConsole())
223 {
224 FILE* file = nullptr;
225 _wfreopen_s(&file, TEXT("CONIN$"), TEXT("r"), stdin);
226 _wfreopen_s(&file, TEXT("CONOUT$"), TEXT("w"), stdout);
227 _wfreopen_s(&file, TEXT("CONOUT$"), TEXT("w"), stderr);
228 WriteLine(TEXT("Allocated default console"));
229 }
230 else return false;
231
232 if (FAILED(CoInitializeEx(nullptr, COINITBASE_MULTITHREADED))) return false;
233 if (!DirectX::XMVerifyCPUSupport()) return false;
234
235 //WSADATA wsad;
236 //if (WSAStartup(MAKEWORD(2, 2), &wsad) != 0) return false;
237
238 return true;
239 }
240
241 inline void PlatformStop()
242 {
243 //WSACleanup();
244
245 CoUninitialize();
246 }
247
248 /// <summary>
249 /// This class provides access to the lines of text in a text file
250 /// </summary>
251 class TextFile
252 {
253 using String = std::string;
254 using Lines = std::vector<String>;
255
256 String name;
257 Lines lines{};
258
259 public:
260 TextFile(String filename) noexcept(false)
261 : name{ filename }
262 {
263 // Open file stream for reading
264 std::ifstream ifs;
265 ifs.open(filename, std::ios::in);
266
267 // Read the lines into a vector and close stream
268 for (std::string line; std::getline(ifs, line);)
269 {
270 lines.push_back(line);
271 }
272
273 ifs.close();
274 }
275 TextFile(TextFile const&) = delete;
276 TextFile& operator=(TextFile const&) = delete;
277 TextFile(TextFile&&) = default;
278 TextFile& operator=(TextFile&&) = default;
279 ~TextFile()
280 {
281 // Open file stream for writing (and truncate target file)
282 std::ofstream ofs;
283 ofs.open(name, std::ios::out | std::ios_base::trunc);
284
285 // Loop lines into the out file
286 std::for_each(lines.begin(), lines.end(), [&](auto& element) { ofs << element << std::endl; });
287 ofs.close();
288 }
289
290 Lines const& GetLines() const { return lines; }
291 };
292 using TextFilePtr = std::unique_ptr<TextFile>;
293
294 /// <summary>
295 /// This class provides an interface to various .ini files where settings are stored
296 ///IDEA: This class has a lot of hard coded parsing, and should be made more resilient
297 /// </summary>
298 class Settings
299 {
300 using Words = std::vector<std::string>;
301
302 enum class SettingsType
303 {
304 Invalid,
305 Video,
306 Network,
307 Control,
308 };
309
310 struct ClayEngineSettings
311 {
312 int Width = 0; // Screen width (in pixels) to request
313 int Height = 0; // Screen height (in pixels) to request
314 char MoveForward = 'e';
315 char MoveLeft = 's';
316 char MoveBackward = 'd';
317 char MoveRight = 'f';
318 };
319
320 TextFilePtr cesf = nullptr;
321 ClayEngineSettings ces = {};
322
323 TextFilePtr tf = nullptr;
324 SettingsType st = SettingsType::Invalid;
325
326 SettingsType ClassifyType(std::string token)
327 {
328 if (token == "[video]") return SettingsType::Video;
329 if (token == "[network]") return SettingsType::Network;
330 if (token == "[control]") return SettingsType::Control;
331 return SettingsType::Invalid;
332 }
333
334 Words GetTokens(std::string line)
335 {
336 std::stringstream ss; ss << line;
337
338 std::string word = {};
339 Words words = {};
340 while (ss >> word)
341 {
342 if (word.empty()) continue;
343
344 words.push_back(std::move(word));
345 }
346
347 return words;
348 }
349
350 public:
351 Settings() noexcept(false)
352 {
353 // Instantiate RAII ini file reader and get the lines reference
354 cesf = std::make_unique<TextFile>(c_settings_filename);
355 auto cesf_lines = cesf->GetLines();
356
357 // Iterate the lines
358 auto cesf_it = cesf_lines.begin();
359 while (cesf_it != cesf_lines.end())
360 {
361 //auto words = GetTokens(cesf_it->c_str());
362
363 // If it's a blank line, just move on...
364 if (cesf_it->c_str()[0] == '\n')
365 {
366 ++cesf_it;
367 continue;
368 }
369
370 // Check if it's a section header (it should be)
371 if (cesf_it->c_str()[0] == '[')
372 {
373 st = ClassifyType(cesf_it->c_str());
374 ++cesf_it;
375 continue;
376 }
377
378 // If the value isn't a section header and we have NOT found a valid section type, that's an error
379 if (st == SettingsType::Invalid) throw std::exception("Encountered unknown settings type tag");
380
381 std::string key = {};
382 std::string val = {};
383 size_t d = 0;
384
385 // Parse the strings for their values by section header to determine which tag we populate
386 switch (st)
387 {
388 case SettingsType::Video:
389 d = cesf_it->find('=');
390 key = cesf_it->substr(0, d);
391 val = cesf_it->substr(d + 1);
392
393 if (key == "default_width")
394 {
395 ces.Width = std::stoi(val);
396 ++cesf_it;
397 continue;
398 }
399
400 if (key == "default_height")
401 {
402 ces.Height = std::stoi(val);
403 ++cesf_it;
404 continue;
405 }
406
407 throw std::exception("Encountered unknown window settings tag");
408
409 default:
410 throw std::exception("Encountered unhandled switch case");
411 }
412 }
413 }
414 Settings(Settings const&) = delete;
415 Settings& operator=(Settings const&) = delete;
416 Settings(Settings&&) = default;
417 Settings& operator=(Settings&&) = default;
418 ~Settings() = default;
419
420 // ClayEngine.ini accessors
421 ClayEngineSettings const& GetWindowSettings() const { return ces; }
422 };
423 using SettingsPtr = std::unique_ptr<Settings>;
424
425 /// <summary>
426 /// The window class maintains the various system handles for the Win32 window
427 /// </summary>
428 class WindowClass
429 {
430 HINSTANCE instance_handle;
431 HWND window_handle;
432 HACCEL table_handle;
433
434 WCHAR title_string[c_max_loadstring];
435 WCHAR class_string[c_max_loadstring];
436
437 RECT size;
438
439 ATOM MakeWindowClass(WNDPROC fn)
440 {
441 WNDCLASSEXW wcex{};
442 wcex.cbSize = sizeof(WNDCLASSEX);
443 wcex.style = CS_HREDRAW | CS_VREDRAW;
444 wcex.hInstance = instance_handle;
445 wcex.lpfnWndProc = fn;
446 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
447 wcex.lpszClassName = class_string;
448
449 return RegisterClassExW(&wcex);
450 }
451 BOOL MakeWindowInstance(int flags)
452 {
453 window_handle = CreateWindowW(class_string, title_string, WS_OVERLAPPEDWINDOW,
454 CW_USEDEFAULT, 0, GetWindowWidth(), GetWindowHeight(), nullptr, nullptr, instance_handle, nullptr);
455
456 if (!window_handle) return FALSE;
457
458 ShowWindow(window_handle, flags);
459 ShowCursor(true);
460
461 UpdateWindow(window_handle);
462 GetClientRect(window_handle, &size);
463
464 return TRUE;
465 }
466
467 public:
468 WindowClass(HINSTANCE instance, int flags, WNDPROC fn, RECT rect)
469 : instance_handle{ instance }
470 {
471 size = rect;
472
473 LoadStringW(instance_handle, IDS_APP_TITLE, title_string, c_max_loadstring);
474 LoadStringW(instance_handle, IDC_PROTOTYPECLIENT, class_string, c_max_loadstring);
475
476 MakeWindowClass(fn);
477 if (!MakeWindowInstance(flags)) throw std::exception("Failed to CreateWindowExW");
478
479 }
480 ~WindowClass() = default;
481
482 void UpdateWindowSize() { GetClientRect(window_handle, &size); }
483 const RECT& GetWindowSize() const { return size; }
484 const int GetWindowWidth() const { return size.right - size.left; }
485 const int GetWindowHeight() const { return size.bottom - size.top; }
486 const HWND GetWindowHandle() const { return window_handle; }
487
488 // Message Handlers
489 void OnWindowResized() {}
490 void OnWindowMoved() {}
491 void OnActivated() {}
492 void OnDeactivated() {}
493 void OnSuspended() {}
494 void OnResumed() {}
495 };
496 using WindowClassPtr = std::unique_ptr<WindowClass>;
497
498 /// <summary>
499 /// Handles buffering keyboard input for strings
500 /// </summary>
501 template <size_t Size>
502 class StringBuffer
503 {
504 static_assert(Size >= 0);
505 static_assert(Size <= c_inputbuffer_size);
506
507 using type = std::array<wchar_t, Size>;
508
509 std::mutex buffer_mtx = {};
510 type buffer = {};
511
512 size_t carat = 0;
513 size_t end = 0;
514
515 bool overwrite = false;
516
517 public:
518 StringBuffer() = default;
519
520 ~StringBuffer() = default;
521
522 void MoveCarat(const size_t offset)
523 {
524 buffer_mtx.lock();
525 auto v = carat + offset;
526
527 if (v >= c_inputbuffer_size)
528 {
529 carat = c_inputbuffer_size;
530 }
531 else if (v < 0)
532 {
533 carat = 0;
534 }
535 else if (v >= end)
536 {
537 carat = end;
538 }
539 else
540 {
541 carat = v;
542 }
543 buffer_mtx.unlock();
544 }
545
546 const bool GetOverwrite() const
547 {
548 buffer_mtx.unlock();
549 auto v = overwrite;
550 buffer_mtx.unlock();
551
552 return v;
553 }
554
555 void SetOverwrite(const bool value)
556 {
557 buffer_mtx.lock();
558 overwrite = value;
559 buffer_mtx.unlock();
560 }
561
562 void ToggleOverwrite()
563 {
564 buffer_mtx.lock();
565 overwrite = !overwrite;
566 buffer_mtx.unlock();
567 }
568
569 void InsertChar(const wchar_t character)
570 {
571 buffer_mtx.lock();
572 if (carat >= c_inputbuffer_size) //TODO: What to do with full buffer?
573 {
574 buffer_mtx.unlock();
575 return;
576 }
577
578 if (carat == end) // Normal condition
579 {
580 buffer[carat] = character;
581
582 ++carat;
583 ++end;
584 }
585 if (carat < end)
586 {
587 if (overwrite)
588 {
589 // Replace character in array with character provided
590 buffer[carat] = character;
591
592 ++carat;
593 }
594 else
595 {
596 // Push values between carat and end forward one... Check 1024 bounds
597
598 auto v = end + 1;
599
600 if (v >= c_inputbuffer_size)
601 {
602 buffer_mtx.unlock();
603 return;
604 }
605
606 for (; v > carat; --v)
607 {
608 buffer[v] = buffer[v - 1];
609 }
610 buffer[carat] = character;
611
612 ++carat;
613 ++end;
614 }
615 }
616 buffer_mtx.unlock();
617 }
618
619 void RemovePrev()
620 {
621 buffer_mtx.lock();
622 if (carat == 0) // We're at the beginning of the line, can't backspace
623 {
624 buffer_mtx.unlock();
625 return;
626 }
627
628 if (carat == end)
629 {
630 --carat;
631
632 buffer[carat] = 0;
633
634 --end;
635 }
636 else
637 {
638 --carat;
639 auto v = carat;
640
641 for (;v < end;++v)
642 {
643 buffer[v] = buffer[v + 1ull];
644 }
645 buffer[v] = 0;
646
647 --end;
648 }
649 buffer_mtx.unlock();
650 }
651
652 void RemoveNext()
653 {
654 buffer_mtx.lock();
655 if (end == 0) // There's no text to delete
656 {
657 buffer_mtx.unlock();
658 return;
659 }
660
661 if (end >= c_inputbuffer_size) // We're at the end of the line already, can't delete
662 {
663 buffer_mtx.unlock();
664 return;
665 }
666
667 auto v = carat;
668
669 for (;v < end; ++v)
670 {
671 buffer[v] = buffer[v + 1ull];
672 }
673 buffer[v] = 0;
674
675 --end;
676 buffer_mtx.unlock();
677 }
678
679 void Clear()
680 {
681 buffer_mtx.lock();
682 buffer.fill(0);
683
684 carat = 0;
685 end = 0;
686 buffer_mtx.unlock();
687 }
688
689 const String GetString()
690 {
691 buffer_mtx.lock();
692 auto ret = String{ buffer.data() };
693 buffer_mtx.unlock();
694
695 return ret;
696 }
697
698 void SetString(const String& string)
699 {
700 buffer_mtx.lock();
701 buffer.fill(0);
702 auto hr = memcpy_s(&buffer, sizeof(wchar_t) * string.length(), string.c_str(), sizeof(wchar_t) * string.length());
703
704 //TODO: Some additional elegance here would store the carat location and go back to it on subsequent scrolls
705 if (carat > string.length()) carat = string.length() + 1;
706 if (end > string.length()) end = string.length() + 1;
707
708 buffer_mtx.unlock();
709 }
710 };
711 using InputBuffer = StringBuffer<c_inputbuffer_size>;
712 using InputBufferPtr = std::unique_ptr<InputBuffer>;
713
714 /// <summary>
715 /// Provides an array of strings with rc-like scrollback functionality
716 /// Intended to provide short term storage for an input buffer
717 /// </summary>
718 template<size_t Size>
719 class StringArray
720 {
721 static_assert(Size >= 0);
722 static_assert(Size <= c_backbuffer_size);
723
724 using type = std::array<String, Size>;
725
726 std::mutex buffer_mtx = {};
727 type buffer = {};
728
729 size_t carat = 0;
730 size_t end = 0;
731
732 public:
733 StringArray() = default;
734 ~StringArray() = default;
735
736 void InsertString(const String& string)
737 {
738 if (carat >= c_backbuffer_size)
739 {
740 for (size_t i = 1; i <= c_backbuffer_size ; ++i)
741 {
742 buffer[i - 1] = buffer[i];
743 }
744
745 buffer[carat - 1] = string;
746 return;
747 }
748
749 if (carat < end) throw std::exception("You are trying to insert during an update");
750
751 if (carat == end)
752 {
753 buffer[carat] = string;
754 ++carat;
755 ++end;
756 }
757 }
758
759 void UpdateString(const String& string)
760 {
761 if (carat <= 0) throw std::exception("Cannot update string on empty set");
762
763 if (carat == end) throw std::exception("You are trying to update during an insert");
764
765 buffer[carat - 1] = string;
766 carat = end; //IDEA: For now, just move us back to the end of the buffer, in the future, do insert-in-the-middle logic
767 }
768
769 const String MoveCarat(const size_t offset)
770 {
771 auto v = carat + offset;
772
773 if (v <= 0) throw std::exception("Cannot move carat on empty set");
774
775 if (v >= c_backbuffer_size)
776 {
777 carat = c_backbuffer_size;
778 }
779 else if (v < 1)
780 {
781 carat = 1;
782 }
783 else if (v > end)
784 {
785 carat = end;
786 }
787 else
788 {
789 carat = v;
790 }
791
792 return buffer[carat - 1];
793 }
794
795 const bool IsInScrollback() const
796 {
797 return carat < end;
798 }
799 };
800 using BackBuffer = StringArray<c_backbuffer_size>;
801 using BackBufferPtr = std::unique_ptr<BackBuffer>;
802
803 /// <summary>
804 /// Input system is the top level API for handling user input events
805 /// </summary>
806 class InputSystem
807 {
808 //IDEA: It might make sense to have this class have a state related to input mode
809 // Or we could have this class collect input into a buffer regardless and just not use it
810 // when it's not needed? Would that be wasting resources tracking key presses as a string
811 // when we're just in move mode and most people are using WASD?
812
813 InputBufferPtr inputbuffer = nullptr;
814 BackBufferPtr backbuffer = nullptr;
815
816 bool capital = false;
817 bool shift = false;
818 bool control = false;
819 bool alt = false;
820
821 public:
822 InputSystem() noexcept
823 {
824 inputbuffer = std::make_unique<InputBuffer>();
825 backbuffer = std::make_unique<BackBuffer>();
826 capital = 0x01 & GetKeyState(VK_CAPITAL);
827 }
828 ~InputSystem() = default;
829
830 void OnMouseEvent()
831 {
832 // Updates Mouse state
833 }
834
835 void OnKeyDown(WPARAM wParam, LPARAM lParam)
836 {
837 SHORT lshift = 0;
838 SHORT rshift = 0;
839 SHORT lcontrol = 0;
840 SHORT rcontrol = 0;
841 SHORT lmenu = 0;
842 SHORT rmenu = 0;
843
844 switch (wParam)
845 {
846 case VK_CAPITAL:
847 capital = 0x01 & GetKeyState(VK_CAPITAL);
848 break;
849 case VK_SHIFT:
850 lshift = GetKeyState(VK_LSHIFT);
851 rshift = GetKeyState(VK_RSHIFT);
852
853 if (0x8000 & lshift) capital = 0x01 & lshift;
854 if (0x8000 & rshift) capital = 0x01 & rshift;
855
856 shift = (0x8000 & lshift) | (0x8000 & rshift);
857 break;
858 case VK_CONTROL:
859 lcontrol = GetKeyState(VK_LCONTROL);
860 rcontrol = GetKeyState(VK_RCONTROL);
861
862 control = (0x8000 & lcontrol) | (0x8000 & rcontrol);
863 break;
864 case VK_MENU:
865 lmenu = GetKeyState(VK_LMENU);
866 rmenu = GetKeyState(VK_RMENU);
867
868 alt = (0x8000 & lmenu) | (0x8000 & rmenu);
869 break;
870 case VK_INSERT: // Process the INS key
871 inputbuffer->ToggleOverwrite(); // lParam contains some state data we could use here
872 break;
873 case VK_DELETE: // Process the DEL key
874 inputbuffer->RemoveNext();
875 break;
876 case VK_HOME: // Process the HOME key
877 if (control)
878 {
879 backbuffer->MoveCarat(-(c_backbuffer_size));
880 }
881 else
882 {
883 inputbuffer->MoveCarat(-(c_inputbuffer_size));
884 }
885 break;
886 case VK_END: // Process the END key
887 if (control)
888 {
889 backbuffer->MoveCarat(c_backbuffer_size);
890 }
891 else
892 {
893 inputbuffer->MoveCarat(c_inputbuffer_size);
894 }
895 break;
896 case VK_UP: // Process the UP ARROW key
897 if (backbuffer->IsInScrollback())
898 {
899 backbuffer->UpdateString(inputbuffer->GetString()); //TODO: Handle logic for when already in scrollback
900 }
901 else
902 {
903 backbuffer->InsertString(inputbuffer->GetString()); //TODO: Handle logic for when already in scrollback
904 }
905 inputbuffer->SetString(backbuffer->MoveCarat(-1));
906 break;
907 case VK_DOWN: // Process the DOWN ARROW key
908 if (backbuffer->IsInScrollback())
909 {
910 backbuffer->UpdateString(inputbuffer->GetString());
911 }
912 else
913 {
914 backbuffer->InsertString(inputbuffer->GetString());
915 }
916 inputbuffer->SetString(backbuffer->MoveCarat(1));
917 break;
918 case VK_LEFT: // Process the LEFT ARROW key
919 if (control) {} //TODO: Move to next word or symbol
920 inputbuffer->MoveCarat(-1);
921 break;
922 case VK_RIGHT: // Process the RIGHT ARROW key
923 if (control) {} //TODO: Move to beginning of current or previous word or symbol
924 inputbuffer->MoveCarat(1);
925 break;
926 case VK_F1: // Process the F1 key
927 case VK_NUMLOCK: // Toggle run default for many...
928 case VK_SNAPSHOT: // Screenshot tool...
929 case VK_SCROLL: // Toggle behavior of chat history versus mousewheel scrolling
930 case VK_PAUSE: // Used for miscellaneous characters; it can vary by keyboard
931 case VK_OEM_1: // For the US standard keyboard, the ';:' key
932 case VK_OEM_2: // For the US standard keyboard, the '/?' key
933 case VK_OEM_3: // For the US standard keyboard, the '`~' key
934 //TODO: Toggle Console
935 case VK_OEM_4: // For the US standard keyboard, the '[{' key
936 case VK_OEM_5: // For the US standard keyboard, the '\|' key
937 case VK_OEM_6: // For the US standard keyboard, the ']}' key
938 case VK_OEM_7: // For the US standard keyboard, the ''"' key
939 default:
940 break;
941 }
942 } // Updates Keyboard state
943
944 void OnKeyUp(WPARAM wParam, LPARAM lParam)
945 {
946 SHORT lshift = 0;
947 SHORT rshift = 0;
948 SHORT controls = 0;
949 SHORT menus = 0;
950
951 switch (wParam)
952 {
953 case VK_SHIFT:
954 lshift = GetKeyState(VK_LSHIFT);
955 rshift = GetKeyState(VK_RSHIFT);
956
957 shift = ((0x8000 & lshift) | (0x8000 & rshift));
958 break;
959 case VK_CONTROL:
960 control = 0x8000 & GetKeyState(VK_CONTROL);
961 break;
962 case VK_MENU:
963 alt = 0x8000 & GetKeyState(VK_MENU);
964 break;
965 default:
966 break;
967 }
968 }
969
970 void OnChar(WPARAM wParam, LPARAM lParam)
971 {
972 switch (wParam)
973 {
974 case 0x08: // Process a backspace
975 inputbuffer->RemovePrev();
976 break;
977 case 0x0A: // Process a linefeed. (Shift-Enter)
978 //backbuffer->InsertString(inputbuffer->GetString());
979 //inputbuffer->Clear();
980 break;
981 case 0x1B: // Process an escape
982 PostQuitMessage(0);
983 break;
984 case 0x09:
985 // Process a tab.
986 break;
987 case 0x0D: // Process a carriage return
988 if (backbuffer->IsInScrollback())
989 {
990 backbuffer->UpdateString(inputbuffer->GetString());
991 }
992 else
993 {
994 backbuffer->InsertString(inputbuffer->GetString());
995 }
996 inputbuffer->Clear();
997 break;
998 default: // Process displayable characters
999 inputbuffer->InsertChar(static_cast<wchar_t>(wParam));
1000 break;
1001 }
1002 } // Processes character input (dependent on input state)
1003
1004 // In addition to collecting and handling buffers, this system should also allow other systems to register for callback events on various key presses.
1005 //void* RegisterKeyCallback(std::function<void> fn, uint64_t key)
1006 //{
1007
1008 //}
1009 };
1010 using InputSystemPtr = std::unique_ptr<InputSystem>;
1011 }
1012
1013 namespace Graphics
1014 {
1015 using DevicePtr = Microsoft::WRL::ComPtr<ID3D11Device>;
1016 using ContextPtr = Microsoft::WRL::ComPtr<ID3D11DeviceContext1>;
1017
1018 /// <summary>
1019 /// This represents the COM device resources that provide device and context services to the pipeline.
1020 /// </summary>
1021 class DX11Resources
1022 {
1023 UINT creation_flags = 0;
1024 D3D_FEATURE_LEVEL feature_level = {};
1025
1026 DevicePtr dev = nullptr;
1027 ContextPtr ctx = nullptr;
1028
1029 public:
1030 DX11Resources(UINT flags = 0)
1031 :creation_flags{ flags }
1032 {
1033#ifdef _DEBUG
1034 creation_flags |= D3D11_CREATE_DEVICE_DEBUG;
1035#endif
1036 // TODO: Modify for supported Direct3D feature levels
1037 static const D3D_FEATURE_LEVEL feature_levels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
1038 D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1, };
1039
1040 Microsoft::WRL::ComPtr<ID3D11Device> device;
1041 Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
1042 DX::ThrowIfFailed(D3D11CreateDevice(
1043 nullptr, // default adapter
1044 D3D_DRIVER_TYPE_HARDWARE,
1045 nullptr,
1046 creation_flags,
1047 feature_levels,
1048 _countof(feature_levels),
1049 D3D11_SDK_VERSION,
1050 device.ReleaseAndGetAddressOf(),
1051 &feature_level,
1052 context.ReleaseAndGetAddressOf()
1053 ));
1054
1055#ifndef NDEBUG
1056 Microsoft::WRL::ComPtr<ID3D11Debug> debug_device;
1057 if (SUCCEEDED(device.As(&debug_device)))
1058 {
1059 Microsoft::WRL::ComPtr<ID3D11InfoQueue> info_queue;
1060 if (SUCCEEDED(debug_device.As(&info_queue)))
1061 {
1062#ifdef _DEBUG
1063 info_queue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true);
1064 info_queue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true);
1065#endif
1066
1067 D3D11_INFO_QUEUE_FILTER filter = {};
1068 D3D11_MESSAGE_ID hide[] = {
1069 D3D11_MESSAGE_ID_SETPRIVATEDATA_CHANGINGPARAMS,
1070 };
1071
1072 filter.DenyList.NumIDs = _countof(hide);
1073 filter.DenyList.pIDList = hide;
1074 }
1075 }
1076#endif
1077 DX::ThrowIfFailed(device.As(&dev));
1078 DX::ThrowIfFailed(context.As(&ctx));
1079 }
1080 ~DX11Resources() = default;
1081
1082 UINT GetCreationFlags() { return creation_flags; }
1083 D3D_FEATURE_LEVEL GetFeatureLevel() { return feature_level; }
1084 DevicePtr GetDevice() { return dev; }
1085 ContextPtr GetContext() { return ctx; }
1086 };
1087 using DX11ResourcesPtr = std::unique_ptr<DX11Resources>;
1088
1089 using SwapChainPtr = Microsoft::WRL::ComPtr<IDXGISwapChain1>;
1090 using RenderTargetViewPtr = Microsoft::WRL::ComPtr<ID3D11RenderTargetView>;
1091 using DepthStencilViewPtr = Microsoft::WRL::ComPtr<ID3D11DepthStencilView>;
1092
1093 /// <summary>
1094 /// This class owns the various swapchain buffers, in this implementation we have
1095 /// a simple render target view and depth stencil for texture and UV map.
1096 /// </summary>
1097 class DX11Pipeline
1098 {
1099 bool device_lost = true;
1100
1101 DevicePtr device = nullptr;
1102 ContextPtr context = nullptr;
1103
1104 SwapChainPtr swapchain = nullptr;
1105
1106 RenderTargetViewPtr rendertarget = nullptr;
1107 DepthStencilViewPtr depthstencil = nullptr;
1108
1109 public:
1110 DX11Pipeline();
1111 ~DX11Pipeline() = default;
1112
1113 SwapChainPtr GetSwapchain() { return swapchain; }
1114 RenderTargetViewPtr GetRTV() { return rendertarget; }
1115 DepthStencilViewPtr GetDSV() { return depthstencil; }
1116 };
1117 using DX11PipelinePtr = std::unique_ptr<DX11Pipeline>;
1118
1119 /// <summary>
1120 /// API entry point for the graphical pipeline and GPU resources
1121 /// </summary>
1122 class RenderSystem
1123 {
1124 bool device_lost = true;
1125
1126 DX11ResourcesPtr res = nullptr;
1127 DX11PipelinePtr pipe = nullptr;
1128
1129 ClayEngine::Platform::WindowClass* wnd = nullptr;
1130
1131 public:
1132 RenderSystem()
1133 {
1134 wnd = Services::GetService<ClayEngine::Platform::WindowClass>(TEXT("WindowClass"));
1135 }
1136 ~RenderSystem();
1137
1138 void StartRenderSystem();
1139 void StopRenderSystem();
1140 void RestartRenderSystem();
1141
1142 void Clear();
1143 void Present();
1144
1145 bool IsDeviceLost() { return device_lost; }
1146 void OnDeviceLost() { device_lost = true; }
1147 };
1148 using RenderSystemPtr = std::unique_ptr<RenderSystem>;
1149
1150 using TexturePtr = Microsoft::WRL::ComPtr<ID3D11ShaderResourceView>;
1151 using ResourcePtr = Microsoft::WRL::ComPtr<ID3D11Resource>;
1152 using SpriteBatchPtr = DirectX::SpriteBatch*;
1153 using SpriteFontPtr = DirectX::SpriteFont*;
1154 using Rect = DirectX::SimpleMath::Rectangle;
1155 using Frames = std::vector<RECT>;
1156
1157 inline bool ResourceIsTexture(TexturePtr texture) noexcept
1158 {
1159 if (!texture) return false;
1160
1161 ResourcePtr res;
1162 texture->GetResource(res.GetAddressOf());
1163
1164 D3D11_RESOURCE_DIMENSION dim;
1165 res->GetType(&dim);
1166
1167 return (dim == D3D11_RESOURCE_DIMENSION_TEXTURE2D);
1168 }
1169
1170 //IDEA: We need to use Lambda expressions for the callbacks, this will need to be re-thought
1171
1172 /// <summary>
1173 /// Raw pointer interface for calling Update/Draw on Sprite objects.
1174 /// </summary>
1175 struct ISprite
1176 {
1177 virtual ISprite* Clone() const = 0;
1178 virtual void Update(float elapsedTime) = 0;
1179 virtual void Draw(SpriteBatchPtr spriteBatch) = 0;
1180
1181 virtual ~ISprite() = default;
1182 };
1183
1184 /// <summary>
1185 /// Base Sprite class that provides simple drawing functions for 2D graphics
1186 /// </summary>
1187 class SpriteBase
1188 {
1189 protected:
1190 bool is_active;
1191 DirectX::SimpleMath::Vector2 position;
1192 DirectX::SimpleMath::Vector2 origin;
1193 DirectX::SimpleMath::Vector2 offset;
1194 DirectX::SimpleMath::Vector2 scale;
1195 float rotation;
1196 float depth;
1197 DirectX::SimpleMath::Vector4 alpha;
1198
1199 public:
1200 SpriteBase();
1201 virtual ~SpriteBase() = default;
1202
1203 virtual void SetActive(bool active);
1204
1205 void SetPosition(float x, float y);
1206 void SetPosition(DirectX::SimpleMath::Vector2 position);
1207 void SetOrigin(float x, float y);
1208 void SetOrigin(DirectX::SimpleMath::Vector2 origin);
1209 void SetOffset(float x, float y);
1210 void SetOffset(DirectX::SimpleMath::Vector2 offset);
1211 void SetScale(float scale);
1212 void SetScale(float x, float y);
1213 void SetScale(DirectX::SimpleMath::Vector2 scale);
1214 void SetRotation(float rotation);
1215 void SetDepth(float depth);
1216 void SetAlpha(float alpha);
1217 };
1218
1219 /// <summary>
1220 /// A realized class of SpriteBase that implements a standard sprite animation strip
1221 /// with an optional static frame
1222 /// </summary>
1223 class SpriteStrip : public SpriteBase, ISprite
1224 {
1225 TexturePtr texture{};
1226 Rect source{};
1227 Frames frames{};
1228
1229 double frame_elapsed_time;
1230 uint32_t current_frame;
1231 double time_per_frame;
1232 uint32_t frame_count;
1233 bool has_static_frame;
1234
1235 bool is_paused;
1236 bool has_static;
1237
1238 public:
1239 SpriteStrip(TexturePtr texturePtr, Rect sourceRect, uint32_t frameCount, bool hasStatic);
1240
1241 virtual void SetActive(bool active) override;
1242
1243 void SetPaused(bool isPaused);
1244 void SetStatic(bool hasStatic);
1245 void SetFramerate(float fps);
1246
1247 virtual ISprite* Clone() const override { return new SpriteStrip(*this); }
1248 virtual void Update(float elapsedTime) override;
1249 virtual void Draw(SpriteBatchPtr spriteBatch) override;
1250 };
1251
1252 /// <summary>
1253 /// A realized class of SpriteBase that implements drawing text on the screen
1254 /// </summary>
1255 class SpriteString : public SpriteBase, ISprite
1256 {
1257 SpriteFontPtr font = {};
1258 String string = {};
1259
1260 public:
1261 SpriteString(SpriteFontPtr spriteFont, String initialString);
1262
1263 void SetFont(SpriteFontPtr fontPtr);
1264 void SetString(String newString);
1265
1266 virtual ISprite* Clone() const override { return new SpriteString(*this); }
1267 virtual void Update(float elapsedTime) override;
1268 virtual void Draw(SpriteBatchPtr spriteBatch) override;
1269 };
1270
1271 /// <summary>
1272 /// Responsible for providing pointers to SpriteFont resources for drawing graphical text
1273 /// </summary>
1274 class FontRegistry
1275 {
1276 // Ok, so, we're loading values in from text file here rather than these enums...
1277 // We'll store the value we pull out of the text file for the key
1278 // and perhaps use the enum for size? Maybe have a struct string,int,ptr?
1279 // Consider alignment, this is operationaly a font atlas...
1280
1281 enum class FontFace { Fixed, Serif, SansSerif };
1282 enum class FontSize { Small, Medium, Large, ExtraLarge };
1283
1284 using SpriteFontPtr = std::unique_ptr<DirectX::SpriteFont>;
1285 using SpriteFontRaw = DirectX::SpriteFont*;
1286
1287 using FixedFonts = std::map<FontRegistry::FontSize, SpriteFontPtr>;
1288 using SerifFonts = std::map<FontRegistry::FontSize, SpriteFontPtr>;
1289 using SansSerifFonts = std::map<FontRegistry::FontSize, SpriteFontPtr>;
1290
1291 FixedFonts fixed_fonts;
1292 SerifFonts serif_fonts;
1293 SansSerifFonts sans_fonts;
1294
1295 DevicePtr dev = nullptr;
1296
1297 public:
1298 FontRegistry()
1299 {
1300 dev = Services::GetService<DX11Resources>(TEXT("DX11Resources"))->GetDevice();
1301 }
1302 ~FontRegistry() = default;
1303
1304 void AddFont(FontFace face, FontSize size, String path)
1305 {
1306 switch (face)
1307 {
1308 case FontFace::Fixed:
1309 fixed_fonts.emplace(size, std::make_unique<DirectX::SpriteFont>(dev.Get(), path.c_str()));
1310 break;
1311 case FontFace::Serif:
1312 serif_fonts.emplace(size, std::make_unique<DirectX::SpriteFont>(dev.Get(), path.c_str()));
1313 break;
1314 case FontFace::SansSerif:
1315 sans_fonts.emplace(size, std::make_unique<DirectX::SpriteFont>(dev.Get(), path.c_str()));
1316 break;
1317 }
1318 throw;
1319 }
1320 SpriteFontRaw GetFontPtr(FontFace face, FontSize size)
1321 {
1322 switch (face)
1323 {
1324 case FontFace::Fixed:
1325 return fixed_fonts.at(size).get();
1326 case FontFace::Serif:
1327 return serif_fonts.at(size).get();
1328 case FontFace::SansSerif:
1329 return sans_fonts.at(size).get();
1330 }
1331 throw;
1332 }
1333
1334 };
1335 using FontRegistryPtr = std::unique_ptr<FontRegistry>;
1336
1337 /// <summary>
1338 /// Responsible for providing pointers to Sprite resources for drawing 2D graphics
1339 /// </summary>
1340 class TextureRegistry
1341 {
1342 using TexturePtr = Microsoft::WRL::ComPtr<ID3D11ShaderResourceView>;
1343 using TextureRaw = ID3D11ShaderResourceView*;
1344
1345 using String = std::wstring;
1346 using Textures = std::map<String, TexturePtr>;
1347
1348 Textures textures;
1349
1350 DevicePtr dev;
1351
1352 public:
1353 TextureRegistry()
1354 {
1355 dev = Services::GetService<DX11Resources>(TEXT("DX11Resources"))->GetDevice();
1356 }
1357 ~TextureRegistry() = default;
1358
1359 void AddTexture(String key, String path)
1360 {
1361 Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> texture;
1362 auto hr = DirectX::CreateWICTextureFromFile(dev.Get(), path.c_str(), nullptr, texture.ReleaseAndGetAddressOf());
1363 if (!FAILED(hr))
1364 textures.emplace(key, texture);
1365
1366 throw;
1367 }
1368 TextureRaw GetTexturePtr(String key)
1369 {
1370 auto it = textures.find(key);
1371 if (it != textures.end())
1372 return textures.at(key).Get();
1373
1374 throw;
1375 }
1376 };
1377 using TextureRegistryPtr = std::unique_ptr<TextureRegistry>;
1378
1379 /// <summary>
1380 /// API entry point for the font and 2D texture resources
1381 /// </summary>
1382 class ContentSystem
1383 {
1384 FontRegistryPtr fonts;
1385 TextureRegistryPtr textures;
1386
1387 public:
1388 ContentSystem()
1389 {
1390 fonts = std::make_unique<FontRegistry>();
1391 textures = std::make_unique<TextureRegistry>();
1392
1393 // Using Platform, get a list of resources from disk
1394
1395 // At this point, we need to get from the filesystem a list of font and texture files
1396 // that we're going to load, we then need to queue that list up for a worker thread
1397 // to asynchronously load all of the content.
1398 // We should play some kind of loading screen during this time, so it might be useful to have
1399 // some very fast initial graphics loaded for a progress bar, for example. A font and a texture
1400 // hard coded to be added right after instantiation, for example.
1401
1402 }
1403 ContentSystem(ContentSystem const&) = delete;
1404 ContentSystem& operator=(ContentSystem const&) = delete;
1405 ContentSystem(ContentSystem&&) = default;
1406 ContentSystem& operator=(ContentSystem&&) = default;
1407 ~ContentSystem()
1408 {
1409 textures.reset();
1410 fonts.reset();
1411 }
1412 };
1413 using ContentSystemPtr = std::unique_ptr<ContentSystem>;
1414 }
1415
1416 /// <summary>
1417 /// Timer class provides interval timing information for Update calls
1418 /// </summary>
1419 class Timer
1420 {
1421 using TimePoint = std::chrono::steady_clock::time_point;
1422 using TimeSpan = std::chrono::steady_clock::duration;
1423 using Clock = std::chrono::steady_clock;
1424 using Seconds = std::chrono::seconds;
1425 using Microseconds = std::chrono::microseconds; // 1'000'000
1426
1427 TimePoint last_time_point = {};
1428 TimeSpan frames_time_span = {};
1429 TimeSpan total_time_span = {};
1430 TimeSpan update_time_span = {};
1431
1432 uint32_t frames_per_second = 0;
1433 uint32_t frames_this_second = 0;
1434
1435 bool is_fixed_update = false;
1436 uint32_t target_frame_rate = c_target_frame_rate;
1437 TimeSpan target_update_time = Seconds(1 / target_frame_rate);
1438
1439 public:
1440 Timer()
1441 {
1442 total_time_span = TimeSpan::zero();
1443
1444 ResetTimer();
1445 }
1446 ~Timer() = default;
1447
1448 void ResetTimer()
1449 {
1450 last_time_point = Clock::now();
1451
1452 frames_per_second = 0;
1453 frames_this_second = 0;
1454
1455 frames_time_span = TimeSpan::zero();
1456 update_time_span = TimeSpan::zero();
1457 }
1458
1459 //IDEA: Should this be a std::function<void> fn type callback instead? This is called with a Lambda in the template.
1460 //template <typename T>
1461 //void Tick(const T& tick)
1462 void Tick()
1463 {
1464 auto now_time_point = Clock::now();
1465 auto delta_time_span = now_time_point - last_time_point;
1466 last_time_point = now_time_point;
1467
1468 total_time_span += delta_time_span;
1469 update_time_span += delta_time_span;
1470 frames_time_span += delta_time_span;
1471 ++frames_this_second;
1472
1473 if (frames_time_span >= Seconds(1))
1474 {
1475 frames_per_second = frames_this_second;
1476 frames_this_second = 0;
1477 frames_time_span %= Seconds(1);
1478 }
1479
1480 if (is_fixed_update)
1481 {
1482 if (update_time_span >= target_update_time)
1483 {
1484 update_time_span %= target_update_time;
1485 //tick();
1486 }
1487 }
1488 else
1489 {
1490 update_time_span = TimeSpan::zero();
1491 //tick();
1492 }
1493 }
1494
1495 double GetElapsedSeconds() const noexcept { return TicksToSeconds(frames_time_span.count()); }
1496 double GetTotalSeconds() const noexcept { return TicksToSeconds(total_time_span.count()); }
1497 uint64_t GetElapsedTicks() const noexcept { return static_cast<uint64_t>(frames_time_span.count()); }
1498 uint64_t GetTotalTicks() const noexcept { return static_cast<uint64_t>(total_time_span.count()); }
1499
1500 uint32_t GetFrameCount() const noexcept { return frames_this_second; }
1501 uint32_t GetFramesPerSecond() const noexcept { return frames_per_second; }
1502
1503 void SetFixedUpdate(bool isFixed) noexcept { is_fixed_update = isFixed; }
1504 void SetTargetFramerate(uint32_t framerate) noexcept { target_update_time = Seconds{ 1 / framerate }; }
1505 };
1506 using TimerPtr = std::unique_ptr<Timer>;
1507
1508 /// <summary>
1509 /// This functor is meant to call the core game loop Update/Draw
1510 /// when the engine core switches to any kind of scene rendering mode
1511 /// </summary>
1512 class GameTicker
1513 {
1514 using Callback = std::function<void(float)>; //IDEA: Probably remove the elapsed time at some point...
1515 using Callbacks = std::vector<Callback>;
1516
1517 Callbacks update_callbacks = {};
1518 std::mutex update_mutex = {};
1519
1520 Callbacks draw_callbacks = {};
1521 std::mutex draw_mutex = {};
1522
1523 TimerPtr timer = nullptr;
1524
1525 bool is_ticker_running = false;
1526
1527 ClayEngine::Graphics::RenderSystem* rs = nullptr;
1528
1529 std::thread t;
1530 std::promise<void> p{};
1531
1532 public:
1533 GameTicker()
1534 {
1535 timer = std::make_unique<Timer>();
1536 }
1537 ~GameTicker()
1538 {
1539 timer.reset();
1540 }
1541
1542 void StartTicker()
1543 {
1544 if (!is_ticker_running)
1545 {
1546 t = std::thread{ GameTickerEntryPoint(), std::move(p.get_future()) };
1547 }
1548
1549 rs = Services::GetService<ClayEngine::Graphics::RenderSystem>(TEXT("RenderSystem"));
1550 }
1551
1552 void StopTicker()
1553 {
1554 if (is_ticker_running)
1555 {
1556 p.set_value();
1557 if (t.joinable()) t.join();
1558
1559 is_ticker_running = false;
1560 }
1561 }
1562
1563 void Tick()
1564 {
1565 is_ticker_running = true;
1566
1567 //timer->Tick([&]() { Update(timer); });
1568 timer->Tick();
1569 auto elapsed = static_cast<float>(timer->GetElapsedSeconds()); //IDEA: We probably don't need this, the elements can get their own reference to the timer and get what time they need...
1570
1571 update_mutex.lock();
1572 std::for_each(update_callbacks.begin(), update_callbacks.end(), [&](Callback element) { element(elapsed); });
1573 update_mutex.unlock();
1574
1575 //Clear
1576 rs->Clear();
1577
1578 draw_mutex.lock();
1579 std::for_each(draw_callbacks.begin(), draw_callbacks.end(), [&](Callback element) { element(elapsed); });
1580 draw_mutex.unlock();
1581
1582 //Present
1583 rs->Present();
1584 }
1585
1586 void AddUpdateCallback(Callback fn)
1587 {
1588 update_mutex.lock();
1589 update_callbacks.push_back(fn);
1590 update_mutex.unlock();
1591 }
1592
1593 void AddDrawCallback(Callback fn)
1594 {
1595 draw_mutex.lock();
1596 draw_callbacks.push_back(fn);
1597 draw_mutex.unlock();
1598 }
1599
1600 void ClearUpdateCallbacks()
1601 {
1602 update_mutex.lock();
1603 update_callbacks.clear();
1604 update_mutex.unlock();
1605 }
1606
1607 void ClearDrawCallbacks()
1608 {
1609 draw_mutex.lock();
1610 draw_callbacks.clear();
1611 draw_mutex.unlock();
1612 }
1613
1614 const Timer& GetTimer()
1615 {
1616 return *timer;
1617 }
1618 };
1619 using GameTickerPtr = std::unique_ptr<GameTicker>;
1620
1621 /// <summary>
1622 /// This is the core application state handler responsible for informing various
1623 /// system modules of what they should be doing at any given time. We also handle
1624 /// the logic for when and how to shift from one state to the next here.
1625 ///
1626 /// Note this will not dictate the scene that the engine is running, that state
1627 /// will be managed separately from the application state. This is strictly
1628 /// for managing the state of the application system modules.
1629 /// </summary>
1630 class EngineCore
1631 {
1632 public:
1633 //IDEA: It may be necessary at some point to maintain multiple states, consider hex bit flags
1634 enum class CoreState
1635 {
1636 Default,
1637 Initializing,
1638 LocalReady,
1639 LocalStarting,
1640 LocalRunning,
1641 LocalStopping,
1642 ContentLoading,
1643 ContentUnloading,
1644 RemoteConnecting,
1645 RemoteStarting,
1646 RemoteRunning,
1647 RemoteAwait,
1648 RemoteStopping,
1649 RemoteDisconnecting,
1650 RemoteError,
1651 RemoteReconnecting,
1652 RemoteContentLoading,
1653 RemoteContentUnloading,
1654 };
1655
1656 private:
1657 CoreState state = CoreState::Default;
1658
1659 CoreState loading_next_state = CoreState::Default;
1660 CoreState unloading_next_state = CoreState::Default;
1661
1662 GameTickerPtr game_ticker = nullptr;
1663 ClayEngine::Graphics::RenderSystemPtr render_system = nullptr;
1664 ClayEngine::Graphics::ContentSystemPtr content_system = nullptr;
1665
1666 public:
1667 EngineCore()
1668 {
1669 game_ticker = Services::MakeService<GameTicker>(TEXT("GameTicker"));
1670
1671 // network_system = Services::MakeService<ClayEngine::Network::NetworkClientSystem>(TEXT("NetworkClientSystem"));
1672 // network_system->StartCompletionPort();
1673 }
1674 ~EngineCore()
1675 {
1676 content_system.reset();
1677
1678 //render_system->StopRenderSystem();
1679 render_system.reset();
1680
1681 game_ticker->StopTicker();
1682 game_ticker.reset();
1683 }
1684
1685 void OnStateChange()
1686 {
1687 switch (state)
1688 {
1689 case CoreState::Default: // The platform has been initialized, begin loading rendering and content systems
1690 {
1691 render_system = Services::MakeService<ClayEngine::Graphics::RenderSystem>(TEXT("RenderSystem"));
1692 render_system->StartRenderSystem();
1693
1694 game_ticker->StartTicker();
1695
1696 content_system = Services::MakeService<ClayEngine::Graphics::ContentSystem>(TEXT("ContentSystem"));
1697
1698 // Set State Initializing
1699 }
1700 case CoreState::Initializing: // Load rendering and content systems, set up simple game loop and display a graphical loading screen
1701 {
1702 // MakeService GameStateManager
1703
1704 //TODO: State loop to display loading and debugging content, load basic 2D UI content
1705
1706 // Set State LocalReady
1707 }
1708 case CoreState::LocalReady: // The game can render a scene, load scene manager and wait for scene
1709 {
1710 // MakeService SceneManager
1711
1712 // Set Scene "MainMenu"
1713
1714 // Set State LocalStarting
1715 }
1716 case CoreState::LocalStarting: // Scene manager loads the Main Menu scene by default, or any other scene as defined.
1717 {
1718 // Set LoadingNextState LocalRunning
1719
1720 // Set State ContentLoading
1721 }
1722 case CoreState::LocalRunning: // Game is rendering a scene
1723 {
1724 // Wait for exit signal
1725
1726 // Set State LocalStopping
1727 }
1728 case CoreState::LocalStopping: // Scene manager may cache, save, clear buffers and free content
1729 {
1730 // Cleanup GameStateManager, SceneManager
1731 // Commit and flush caches
1732 // Save game state
1733 // Clear buffers
1734
1735 // Set State ContentUnloading
1736 }
1737 case CoreState::ContentLoading: // Scene manager will obtain a list of resources to be loaded for the scene, return state will be set
1738 {
1739 // Load Content for SceneManager
1740 }
1741 case CoreState::ContentUnloading: // Scene manager will free resources held by content manager
1742 {
1743 // Free Content for SceneManager
1744 }
1745 case CoreState::RemoteConnecting:
1746 case CoreState::RemoteStarting:
1747 case CoreState::RemoteRunning:
1748 case CoreState::RemoteAwait:
1749 case CoreState::RemoteStopping:
1750 case CoreState::RemoteDisconnecting:
1751 case CoreState::RemoteError:
1752 case CoreState::RemoteReconnecting:
1753 case CoreState::RemoteContentLoading:
1754 case CoreState::RemoteContentUnloading:
1755 default:
1756 break;
1757 }
1758 }
1759 };
1760 using EngineCorePtr = std::unique_ptr<EngineCore>;
1761}
1762
1763/// <summary>
1764/// Thread entry point that pumps update/draw loop
1765/// </summary>
1766/// <param name="future"></param>
1767void ClayEngine::GameTickerEntryPoint::operator()(std::future<void> future)
1768{
1769 // Initialize the thread thread "global" variables here
1770 auto ticker = Services::GetService<GameTicker>(TEXT("GameTicker"));
1771
1772
1773 // Thread main loop, thread guard will signal for loop break on shutdown
1774 while (future.wait_for(std::chrono::seconds(0)) == std::future_status::timeout)
1775 {
1776 if (ticker) ticker->Tick();
1777 }
1778}
1779
1780#pragma region Sprite Implementation
1781ClayEngine::Graphics::SpriteBase::SpriteBase()
1782 : is_active{ false }, position{ 0.f, 0.f }, origin{ 0.f, 0.f }, offset{ 0.f, 0.f }, scale{ 1.f }, rotation{ 0.f }, depth{ 0.f }, alpha{ DirectX::SimpleMath::Vector4(1.f) }
1783{}
1784
1785void ClayEngine::Graphics::SpriteBase::SetActive(bool active) { is_active = active; }
1786
1787void ClayEngine::Graphics::SpriteBase::SetPosition(float x, float y) { position = DirectX::SimpleMath::Vector2{ x, y }; }
1788
1789void ClayEngine::Graphics::SpriteBase::SetPosition(DirectX::SimpleMath::Vector2 _position) { position = _position; }
1790
1791void ClayEngine::Graphics::SpriteBase::SetOrigin(float x, float y) { origin = DirectX::SimpleMath::Vector2{ x, y }; }
1792
1793void ClayEngine::Graphics::SpriteBase::SetOrigin(DirectX::SimpleMath::Vector2 _origin) { origin = _origin; }
1794
1795void ClayEngine::Graphics::SpriteBase::SetOffset(float x, float y) { offset = DirectX::SimpleMath::Vector2{ x, y }; }
1796
1797void ClayEngine::Graphics::SpriteBase::SetOffset(DirectX::SimpleMath::Vector2 _offset) { offset = _offset; }
1798
1799void ClayEngine::Graphics::SpriteBase::SetScale(float _scale) { scale = DirectX::SimpleMath::Vector2{ _scale, _scale }; }
1800
1801void ClayEngine::Graphics::SpriteBase::SetScale(float x, float y) { scale = DirectX::SimpleMath::Vector2{ x, y }; }
1802
1803void ClayEngine::Graphics::SpriteBase::SetScale(DirectX::SimpleMath::Vector2 _scale) { scale = _scale; }
1804
1805void ClayEngine::Graphics::SpriteBase::SetRotation(float _rotation) { rotation = _rotation; }
1806
1807void ClayEngine::Graphics::SpriteBase::SetDepth(float _depth) { depth = _depth; }
1808
1809void ClayEngine::Graphics::SpriteBase::SetAlpha(float _alpha) { alpha = DirectX::SimpleMath::Vector4{ 0.f, 0.f, 0.f, _alpha }; }
1810
1811ClayEngine::Graphics::SpriteStrip::SpriteStrip(TexturePtr texturePtr, Rect sourceRect, uint32_t frameCount, bool hasStatic)
1812 : texture{ texturePtr }, source{ sourceRect }, frame_count{ frameCount }, is_paused{ true }, has_static{ hasStatic }
1813{
1814 assert(ResourceIsTexture(texturePtr));
1815
1816 frame_elapsed_time = 0.f;
1817 current_frame = 0;
1818 time_per_frame = 0.1f; // Hard coded 10 FPS
1819
1820 for (auto i = 0u; i <= frameCount; ++i)
1821 {
1822 RECT r;
1823 r.left = source.x + source.width * i;
1824 r.top = source.y;
1825 r.right = r.left + source.width;
1826 r.bottom = r.top + source.height;
1827
1828 if (i < frame_count || (i == frameCount && hasStatic))
1829 {
1830 frames.push_back(r);
1831 }
1832 }
1833}
1834
1835void ClayEngine::Graphics::SpriteStrip::SetActive(bool isActive)
1836{
1837 is_active = isActive;
1838}
1839
1840void ClayEngine::Graphics::SpriteStrip::SetPaused(bool isPaused)
1841{
1842 is_paused = isPaused;
1843}
1844
1845void ClayEngine::Graphics::SpriteStrip::SetStatic(bool hasStatic)
1846{
1847 has_static = hasStatic;
1848}
1849
1850void ClayEngine::Graphics::SpriteStrip::SetFramerate(float fps)
1851{
1852 time_per_frame = 1.f / fps;
1853}
1854
1855void ClayEngine::Graphics::SpriteStrip::Update(float elapsedTime)
1856{
1857 if (!is_active) return;
1858
1859 if (has_static)
1860 {
1861 current_frame = has_static_frame ? frame_count : 0;
1862 return;
1863 }
1864
1865 if (is_active && !is_paused)
1866 {
1867 frame_elapsed_time += elapsedTime;
1868
1869 if (frame_elapsed_time >= time_per_frame)
1870 {
1871 current_frame = (current_frame + 1) % frame_count;
1872 frame_elapsed_time = 0.f;
1873 }
1874 }
1875}
1876
1877void ClayEngine::Graphics::SpriteStrip::Draw(SpriteBatchPtr spriteBatch)
1878{
1879 assert(spriteBatch);
1880
1881 if (is_active)
1882 {
1883 spriteBatch->Draw(
1884 texture.Get(),
1885 position,
1886 &frames[current_frame],
1887 alpha,
1888 rotation,
1889 offset,
1890 scale,
1891 DirectX::SpriteEffects::SpriteEffects_None,
1892 depth
1893 );
1894 }
1895
1896}
1897
1898ClayEngine::Graphics::SpriteString::SpriteString(SpriteFontPtr spriteFont, String initialString)
1899 : font{ spriteFont }, string{ initialString }
1900{}
1901
1902void ClayEngine::Graphics::SpriteString::SetFont(SpriteFontPtr fontPtr)
1903{
1904 font = fontPtr;
1905}
1906
1907void ClayEngine::Graphics::SpriteString::SetString(String newString)
1908{
1909 string = newString;
1910}
1911
1912void ClayEngine::Graphics::SpriteString::Update(float elapsedTime)
1913{
1914 UNREFERENCED_PARAMETER(elapsedTime);
1915
1916 if (!is_active) return;
1917
1918 //m_origin = (m_font->MeasureString(m_string.c_str()) * 0.5F);
1919}
1920
1921void ClayEngine::Graphics::SpriteString::Draw(SpriteBatchPtr spriteBatch)
1922{
1923 assert(spriteBatch);
1924
1925 if (is_active)
1926 {
1927 font->DrawString(
1928 spriteBatch,
1929 string.c_str(),
1930 position,
1931 DirectX::Colors::White,
1932 rotation,
1933 origin,
1934 scale,
1935 DirectX::SpriteEffects::SpriteEffects_None,
1936 depth
1937 );
1938 }
1939}
1940#pragma endregion
1941
1942#pragma region RenderSystem Implementation
1943ClayEngine::Graphics::RenderSystem::~RenderSystem()
1944{
1945 pipe.reset();
1946 res.reset();
1947}
1948
1949void ClayEngine::Graphics::RenderSystem::StartRenderSystem()
1950{
1951 res = Services::MakeService<DX11Resources>(TEXT("DX11Resources"));
1952 pipe = Services::MakeService<DX11Pipeline>(TEXT("DX11Pipeline"));
1953}
1954
1955void ClayEngine::Graphics::RenderSystem::StopRenderSystem()
1956{
1957 if (pipe)
1958 {
1959 if (Services::FindService(TEXT("DX11Pipeline")))
1960 {
1961 Services::RemoveService(TEXT("DX11Pipeline"));
1962 }
1963 pipe.reset();
1964 }
1965 if (res)
1966 {
1967 if (Services::FindService(TEXT("DX11Resources")))
1968 {
1969 Services::RemoveService(TEXT("DX11Resources"));
1970 }
1971 res.reset();
1972 }
1973}
1974
1975void ClayEngine::Graphics::RenderSystem::RestartRenderSystem()
1976{
1977 //IDEA: Consider a "restart" count tracker to keep this from restarting forever...
1978 //IDEA:: Services::GetService<ContentSystem>(TEXT("ContentSystem"))->NotifyRenderSystemReset();
1979
1980 StopRenderSystem();
1981
1982 StartRenderSystem();
1983}
1984
1985void ClayEngine::Graphics::RenderSystem::Clear()
1986{
1987 auto ctx = res->GetContext();
1988
1989 ctx->ClearRenderTargetView(pipe->GetRTV().Get(), DirectX::Colors::CornflowerBlue);
1990 ctx->ClearDepthStencilView(pipe->GetDSV().Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.f, 0);
1991
1992 ctx->OMSetRenderTargets(1, pipe->GetRTV().GetAddressOf(), pipe->GetDSV().Get());
1993
1994 CD3D11_VIEWPORT viewport(0.0f, 0.0f, static_cast<float>(wnd->GetWindowWidth()), static_cast<float>(wnd->GetWindowHeight()));
1995 ctx->RSSetViewports(1, &viewport);
1996}
1997
1998void ClayEngine::Graphics::RenderSystem::Present()
1999{
2000 auto hr = pipe->GetSwapchain()->Present(1, 0);
2001
2002 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
2003 {
2004 device_lost = true;
2005 }
2006 else
2007 {
2008 DX::ThrowIfFailed(hr); //IDEA: This can be handled by a sentinel service and caught instead of thrown out to the OS
2009 }
2010}
2011
2012ClayEngine::Graphics::DX11Pipeline::DX11Pipeline()
2013{
2014 using Microsoft::WRL::ComPtr;
2015
2016 device = Services::GetService<DX11Resources>(TEXT("DX11Resources"))->GetDevice();
2017 context = Services::GetService<DX11Resources>(TEXT("DX11Resources"))->GetContext();
2018 auto rect = Services::GetService<ClayEngine::Platform::WindowClass>(TEXT("WindowClass"))->GetWindowSize();
2019 auto hwnd = Services::GetService<ClayEngine::Platform::WindowClass>(TEXT("WindowClass"))->GetWindowHandle();
2020
2021 // Clear the previous window size specific context.
2022 ID3D11RenderTargetView* views[] = { nullptr };
2023 context->OMSetRenderTargets(_countof(views), views, nullptr);
2024 rendertarget.Reset();
2025 depthstencil.Reset();
2026 context->Flush();
2027
2028 const UINT backbuffer_width = static_cast<UINT>(rect.right - rect.left);
2029 const UINT backbuffer_height = static_cast<UINT>(rect.bottom - rect.top);
2030 const UINT backbuffer_count = 2;
2031
2032 const DXGI_FORMAT rendertarget_format = DXGI_FORMAT_B8G8R8A8_UNORM;
2033 const DXGI_FORMAT depthstencil_format = DXGI_FORMAT_D24_UNORM_S8_UINT;
2034
2035 CD3D11_TEXTURE2D_DESC depthstencil_desc{ depthstencil_format, backbuffer_width, backbuffer_height, 1, 1, D3D11_BIND_DEPTH_STENCIL };
2036
2037 ComPtr<ID3D11Texture2D> rendertarget_buffer;
2038 ComPtr<ID3D11Texture2D> depthstencil_buffer;
2039
2040 if (swapchain)
2041 {
2042 HRESULT hr = swapchain->ResizeBuffers(backbuffer_count, backbuffer_width, backbuffer_height, rendertarget_format, 0);
2043
2044 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
2045 {
2046 Services::GetService<RenderSystem>(TEXT("RenderSystem"))->RestartRenderSystem();
2047
2048 return;
2049 }
2050 else
2051 {
2052 DX::ThrowIfFailed(hr);
2053 }
2054 }
2055 else
2056 {
2057 ComPtr<IDXGIDevice1> dxgi_device;
2058 DX::ThrowIfFailed(device.As(&dxgi_device));
2059
2060 ComPtr<IDXGIAdapter> dxgi_adapter;
2061 DX::ThrowIfFailed(dxgi_device->GetAdapter(dxgi_adapter.GetAddressOf()));
2062
2063 ComPtr<IDXGIFactory2> dxgi_factory;
2064 DX::ThrowIfFailed(dxgi_adapter->GetParent(IID_PPV_ARGS(dxgi_factory.GetAddressOf())));
2065
2066 // Create a descriptor for the swap chain.
2067 DXGI_SWAP_CHAIN_DESC1 swapchain_desc = {};
2068 swapchain_desc.Width = backbuffer_width;
2069 swapchain_desc.Height = backbuffer_height;
2070 swapchain_desc.Format = rendertarget_format;
2071 swapchain_desc.SampleDesc.Count = 1;
2072 swapchain_desc.SampleDesc.Quality = 0;
2073 swapchain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
2074 swapchain_desc.BufferCount = backbuffer_count;
2075
2076 DXGI_SWAP_CHAIN_FULLSCREEN_DESC swapchain_desc_fs = {};
2077 swapchain_desc_fs.Windowed = TRUE;
2078
2079 // Create a SwapChain from a Win32 window.
2080 DX::ThrowIfFailed(dxgi_factory->CreateSwapChainForHwnd(device.Get(), hwnd,
2081 &swapchain_desc, &swapchain_desc_fs, nullptr, swapchain.ReleaseAndGetAddressOf()));
2082
2083 // This template does not support exclusive fullscreen mode and prevents DXGI from responding to the ALT+ENTER shortcut.
2084 DX::ThrowIfFailed(dxgi_factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER));
2085 }
2086
2087 // Obtain the backbuffer for this window which will be the final 3D rendertarget.
2088 DX::ThrowIfFailed(swapchain->GetBuffer(0, IID_PPV_ARGS(rendertarget_buffer.GetAddressOf())));
2089
2090 // Create a view interface on the rendertarget to use on bind.
2091 DX::ThrowIfFailed(device->CreateRenderTargetView(rendertarget_buffer.Get(), nullptr, rendertarget.ReleaseAndGetAddressOf()));
2092
2093 // Allocate a 2-D surface as the depth/stencil buffer and
2094 // create a DepthStencil view on this surface to use on bind.
2095 DX::ThrowIfFailed(device->CreateTexture2D(&depthstencil_desc, nullptr, depthstencil_buffer.GetAddressOf()));
2096
2097 CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
2098 DX::ThrowIfFailed(device->CreateDepthStencilView(depthstencil_buffer.Get(), &depthStencilViewDesc, depthstencil.ReleaseAndGetAddressOf()));
2099}
2100#pragma endregion
2101
2102LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
2103
2104namespace
2105{
2106 ClayEngine::Platform::SettingsPtr g_settings = nullptr;
2107 ClayEngine::Platform::WindowClassPtr g_window = nullptr;
2108 ClayEngine::Platform::InputSystemPtr g_input = nullptr;
2109 ClayEngine::EngineCorePtr g_core = nullptr;
2110}
2111
2112int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd)
2113{
2114 UNREFERENCED_PARAMETER(hPrevInstance);
2115 UNREFERENCED_PARAMETER(lpCmdLine);
2116
2117 using namespace ClayEngine;
2118 using namespace ClayEngine::Platform;
2119
2120 if (!PlatformStart()) return -1;
2121
2122 try
2123 {
2124 g_settings = std::make_unique<Settings>();
2125 }
2126 catch (std::exception ex)
2127 {
2128 std::cout << ex.what();
2129 return -2;
2130 }
2131
2132 auto set = g_settings->GetWindowSettings();
2133 if (set.Width < c_min_window_width) set.Width = c_default_window_width;
2134 if (set.Height < c_min_window_height) set.Height = c_default_window_height;
2135
2136 try
2137 {
2138 g_window = Services::MakeService<WindowClass>(TEXT("WindowClass"), hInstance, nShowCmd, WndProc, RECT{ 0, 0, set.Width, set.Height });
2139 }
2140 catch (std::exception ex)
2141 {
2142 std::cout << ex.what();
2143 return -3;
2144 }
2145
2146 g_input = Services::MakeService<InputSystem>(TEXT("InputSystem"));
2147 g_core = Services::MakeService<EngineCore>(TEXT("EngineCore"));
2148 g_core->OnStateChange(); // Kicks off the first "tick"
2149
2150
2151 MSG msg = {};
2152 while (msg.message != WM_QUIT)
2153 {
2154 if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
2155 {
2156 TranslateMessage(&msg);
2157 DispatchMessage(&msg);
2158 }
2159 }
2160
2161 PlatformStop();
2162
2163 return static_cast<int>(msg.wParam);
2164}
2165
2166LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
2167{
2168 switch (message)
2169 {
2170 case WM_DESTROY:
2171 PostQuitMessage(0);
2172 break;
2173 case WM_GETMINMAXINFO:
2174 if (lParam)
2175 {
2176 auto info = reinterpret_cast<MINMAXINFO*>(lParam);
2177 info->ptMinTrackSize.x = ClayEngine::Platform::c_min_window_width;
2178 info->ptMinTrackSize.y = ClayEngine::Platform::c_min_window_height;
2179 }
2180 break;
2181 case WM_MENUCHAR: // Supress the menu
2182 return MAKELRESULT(0, MNC_CLOSE);
2183 case WM_PAINT:
2184 {
2185 PAINTSTRUCT ps;
2186 (void)BeginPaint(hWnd, &ps);
2187 // HDC hdc = BeginPaint(hWnd, &ps);
2188 // TODO: Add any drawing code that uses hdc here...
2189 EndPaint(hWnd, &ps);
2190 }
2191 break;
2192 case WM_SYSKEYUP:
2193 case WM_KEYUP:
2194 g_input->OnKeyUp(wParam, lParam);
2195 break;
2196 case WM_SYSKEYDOWN:
2197 case WM_KEYDOWN:
2198 g_input->OnKeyDown(wParam, lParam);
2199 break;
2200 case WM_CHAR:
2201 g_input->OnChar(wParam, lParam);
2202 break;
2203 case WM_INPUT:
2204 case WM_MOUSEMOVE:
2205 case WM_LBUTTONDOWN:
2206 case WM_LBUTTONUP:
2207 case WM_RBUTTONDOWN:
2208 case WM_RBUTTONUP:
2209 case WM_MBUTTONDOWN:
2210 case WM_MBUTTONUP:
2211 case WM_MOUSEWHEEL:
2212 case WM_XBUTTONDOWN:
2213 case WM_XBUTTONUP:
2214 case WM_MOUSEHOVER:
2215 {
2216 //TODO: Send to InputSystem
2217 g_input->OnMouseEvent();
2218 }
2219 break;
2220 case WM_ACTIVATEAPP:
2221 case WM_POWERBROADCAST:
2222 case WM_ENTERSIZEMOVE:
2223 case WM_EXITSIZEMOVE:
2224 case WM_SIZE:
2225 case WM_MOVE:
2226 {
2227 //TODO: Send to WindowClass
2228 g_window->OnDeactivated();
2229 g_window->OnSuspended();
2230 g_window->OnActivated();
2231 g_window->OnResumed(); //TODO: Send signal to InputSystem to test hardware key lock state
2232 g_window->OnWindowMoved();
2233 g_window->OnWindowResized();
2234 }
2235 break;
2236 }
2237
2238 return DefWindowProc(hWnd, message, wParam, lParam);
2239}
2240