#include "bx/filepath.h" #include #include #include #define WIN32_LEAN_AND_MEAN #include #undef min #undef max #include #include #include #include #include "Shared.h" #include "Window.h" #ifdef _MSC_VER constexpr const char* DLLPath = "PuzGame.dll"; constexpr const wchar_t* DLLWatch = L"PuzGame2.dll"; #else constexpr const char* DLLPath = "libPuzGame.dll"; constexpr const wchar_t* DLLWatch = L"libPuzGame2.dll"; #endif namespace { bx::AllocatorI* defaultAllocator = new bx::DefaultAllocator{}; } enum class FileChangeType { DLL, Shader, CompiledShader, }; struct FileWatcherStartup { char DirPath[512]{0}; wchar_t CompName[512]{0}; FileChangeType ChangeType = FileChangeType::Shader; bool Shutdown = false; }; struct FileWatcherData { FileWatcherStartup DLLWatcher; FileWatcherStartup ShaderWatcher; FileWatcherStartup CompiledShaderWatcher; bx::MpScUnboundedQueueT ShaderQueue{defaultAllocator}; bx::MpScUnboundedQueueT DLLQueue{defaultAllocator}; }; struct DevelopmentData { HMODULE GameLib = NULL; FileWatcherData FileWatcher; }; struct EngineData { EngineWindow Window; }; namespace { DevelopmentData DevData; EngineData Engine; SharedData Shared; Startup StartupFunc; Update UpdateFunc; Shutdown ShutdownFunc; } // namespace void FileChangeCheck(HANDLE dirHandle, FileChangeType changeType, const wchar_t* compName = nullptr) { if (dirHandle == NULL) return; uint8_t fileChangeBuffer[1024]{0}; bx::memSet(fileChangeBuffer, 0, sizeof(fileChangeBuffer)); DWORD bytesReturned = 0; if (ReadDirectoryChangesW(dirHandle, fileChangeBuffer, sizeof(fileChangeBuffer), true, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SIZE, &bytesReturned, NULL, NULL)) { uint32_t offset = 0; while (true) { FILE_NOTIFY_INFORMATION* notifyData = reinterpret_cast(&fileChangeBuffer[offset]); if (notifyData->Action == FILE_ACTION_ADDED || notifyData->Action == FILE_ACTION_MODIFIED || notifyData->Action == FILE_ACTION_RENAMED_NEW_NAME) { if (compName == nullptr || compName[0] == 0 || wcscmp(notifyData->FileName, compName) == 0) { FileChangeNotification notif; wcscpy(notif.FileName, notifyData->FileName); if (changeType == FileChangeType::DLL) { DevData.FileWatcher.DLLQueue.push(¬if); } else if (changeType == FileChangeType::Shader) { std::system("shadercompile.bat > shadercompile.log"); Sleep(1000); std::ifstream shaderLogFile("shadercompile.log"); if (shaderLogFile.is_open()) { shaderLogFile.seekg(0, std::ios::end); uint32_t size = bx::min(BX_COUNTOF(Shared.Dev.ShaderLog), shaderLogFile.tellg()); shaderLogFile.seekg(0); shaderLogFile.read(Shared.Dev.ShaderLog, size); shaderLogFile.close(); } } else if (changeType == FileChangeType::CompiledShader) { DevData.FileWatcher.ShaderQueue.push(¬if); } printf("detected file change of type %u!\n", changeType); } } if (notifyData->NextEntryOffset == 0) break; offset += notifyData->NextEntryOffset; } } } HANDLE LoadDirHandle(const char* name) { HANDLE h = CreateFileA(name, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if (h == NULL) { printf("Failed to load dir handle for %s", name); } return h; } unsigned long FileWatcherThread(void* data) { FileWatcherStartup* startupData = reinterpret_cast(data); printf("Starting file watcher of type %u\n", startupData->ChangeType); if (startupData->DirPath[0] == 0) { printf("Invalid file watcher path!\n"); return 1; } HANDLE dirHandle = LoadDirHandle(startupData->DirPath); if (dirHandle == NULL) { printf("Failed to load dir %s for file watcher!\n", startupData->DirPath); return 1; } while (!startupData->Shutdown) { FileChangeCheck(dirHandle, startupData->ChangeType, startupData->CompName); } printf("File watcher thread ended!\n"); return 0; } bool ReloadDLL() { if (DevData.GameLib != NULL) { FreeLibrary(DevData.GameLib); } char exePath[1024]; GetModuleFileName(nullptr, exePath, BX_COUNTOF(exePath)); bx::FilePath exeFilePath{exePath}; bx::FilePath exeDir{exeFilePath.getPath()}; bx::FilePath libPath = exeDir; #ifdef _MSC_VER libPath.join("PuzGame.dll"); #else libPath.join("libPuzGame.dll"); #endif bx::FilePath lib2Path = exeDir; #ifdef _MSC_VER lib2Path.join("PuzGame2.dll"); #else lib2Path.join("libPuzGame2.dll"); #endif if (!CopyFile(lib2Path.getCPtr(), libPath.getCPtr(), false)) { DWORD err = GetLastError(); printf("[%lu] Failed to copy game DLL from %s to %s!\n", err, libPath.getCPtr(), lib2Path.getCPtr()); return false; } HMODULE gameLibReloaded = LoadLibraryEx(DLLPath, NULL, 0); if (gameLibReloaded == NULL) { DWORD err = GetLastError(); printf("[%lu] Failed to load game DLL from %s!\n", err, DLLPath); return false; } DevData.GameLib = gameLibReloaded; #ifdef _MSC_VER Startup StartupReloaded = (Startup)GetProcAddress(DevData.GameLib, "?Setup@Game@@YAXAEAUSharedData@@@Z"); #else Startup StartupReloaded = (Startup)GetProcAddress(DevData.GameLib, "_ZN4Game5SetupER10SharedData"); #endif if (StartupReloaded == NULL) { DWORD err = GetLastError(); printf("[%lu] Failed to load startup function from game DLL!\n", err); return false; } #ifdef _MSC_VER Update UpdateReloaded = (Update)GetProcAddress(DevData.GameLib, "?Update@Game@@YAXXZ"); #else Update UpdateReloaded = (Update)GetProcAddress(DevData.GameLib, "_ZN4Game6UpdateEv"); #endif if (UpdateReloaded == NULL) { printf("Failed to load update function from game DLL!\n"); return false; } #ifdef _MSC_VER Shutdown ShutdownReloaded = (Shutdown)GetProcAddress(DevData.GameLib, "?Shutdown@Game@@YAXXZ"); #else Shutdown ShutdownReloaded = (Shutdown)GetProcAddress(DevData.GameLib, "_ZN4Game8ShutdownEv"); #endif if (ShutdownReloaded == NULL) { printf("Failed to load shutdown function from game DLL\n"); return false; } StartupFunc = StartupReloaded; UpdateFunc = UpdateReloaded; ShutdownFunc = ShutdownReloaded; printf("Loaded Game DLL successfully!\n"); return true; } int main() { char PathBuf[512]{0}; GetCurrentDirectory(sizeof(PathBuf), PathBuf); printf("Current path: %s\n", PathBuf); if (!ReloadDLL()) return 1; Engine.Window.Startup(Shared.Window); if (Shared.Window.Handle == nullptr) { printf("Failed to set up window!\n"); return 1; } DevData.FileWatcher.DLLWatcher.ChangeType = FileChangeType::DLL; DevData.FileWatcher.ShaderWatcher.ChangeType = FileChangeType::Shader; DevData.FileWatcher.CompiledShaderWatcher.ChangeType = FileChangeType::CompiledShader; bx::strCopy(DevData.FileWatcher.DLLWatcher.DirPath, sizeof(DevData.FileWatcher.DLLWatcher.DirPath), "cmake-build"); bx::strCopy( DevData.FileWatcher.ShaderWatcher.DirPath, sizeof(DevData.FileWatcher.ShaderWatcher.DirPath), "game/shaders"); bx::strCopy(DevData.FileWatcher.CompiledShaderWatcher.DirPath, sizeof(DevData.FileWatcher.CompiledShaderWatcher.DirPath), "game/compiled-shaders/dx11"); wcscpy(DevData.FileWatcher.DLLWatcher.CompName, DLLWatch); DWORD fileWatcherThreadId = 0; HANDLE dllThread = CreateThread(NULL, 0, FileWatcherThread, &DevData.FileWatcher.DLLWatcher, 0, &fileWatcherThreadId); HANDLE shaderThread = CreateThread(NULL, 0, FileWatcherThread, &DevData.FileWatcher.ShaderWatcher, 0, &fileWatcherThreadId); HANDLE compiledShaderThread = CreateThread(NULL, 0, FileWatcherThread, &DevData.FileWatcher.CompiledShaderWatcher, 0, &fileWatcherThreadId); Shared.Game.PermanentStorageSize = 1024 * 1024; Shared.Game.PermanentStorage = VirtualAllocEx( GetCurrentProcess(), NULL, Shared.Game.PermanentStorageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); Shared.Game.EntityStorageSize = 1024 * 1024; Shared.Game.EntityStorage = VirtualAllocEx( GetCurrentProcess(), NULL, Shared.Game.EntityStorageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); Shared.Game.TransientStorageSize = 1024 * 1024 * 100; Shared.Game.TransientStorage = VirtualAllocEx( GetCurrentProcess(), NULL, Shared.Game.TransientStorageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); StartupFunc(Shared); bool isRunning = true; while (isRunning) { Engine.Window.Update(Shared.Window); if (Engine.Window.CloseRequested) { isRunning = false; } FileChangeNotification* DLLChange = nullptr; if (DevData.FileWatcher.DLLQueue.pop()) { // Empty queue to avoid multiple reloads while (DevData.FileWatcher.DLLQueue.pop()) { } ShutdownFunc(); ReloadDLL(); StartupFunc(Shared); } FileChangeNotification* CompiledShaderChange = nullptr; while ((CompiledShaderChange = DevData.FileWatcher.ShaderQueue.pop())) { if (Shared.Dev.ChangedShaderCount < BX_COUNTOF(Shared.Dev.ChangedShaders)) { wcscpy(Shared.Dev.ChangedShaders[Shared.Dev.ChangedShaderCount].FileName, CompiledShaderChange->FileName); ++Shared.Dev.ChangedShaderCount; } else { printf("Ran out of shader change buffer!\n"); } } UpdateFunc(); ++Shared.Window.FrameCounter; } ShutdownFunc(); Engine.Window.Shutdown(); DevData.FileWatcher.DLLWatcher.Shutdown = true; DevData.FileWatcher.ShaderWatcher.Shutdown = true; DevData.FileWatcher.CompiledShaderWatcher.Shutdown = true; WaitForSingleObject(dllThread, 100); WaitForSingleObject(shaderThread, 100); WaitForSingleObject(compiledShaderThread, 100); return 0; }