diff --git a/Game/Game.vcxproj b/Game/Game.vcxproj index f112a2b..dc46a10 100644 --- a/Game/Game.vcxproj +++ b/Game/Game.vcxproj @@ -48,10 +48,9 @@ true - $(SolutionDir)Juliet\include\;$(SolutionDir)Game;$(IncludePath) - $(SolutionDir)\bin\$(Platform)\$(Configuration)\;$(LibraryPath) $(SolutionDir)\bin\$(Platform)\$(Configuration)\ $(SolutionDir)Intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(ProjectName) false @@ -66,10 +65,15 @@ true pch.h stdcpp20 + $(SolutionDir)Juliet\include\;$(SolutionDir)Game; Windows true + Juliet.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + $(OutDir)$(TargetName)_$(Random).pdb @@ -83,24 +87,21 @@ NDEBUG;CPPDYNAMICLIBRARYTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true pch.h + $(SolutionDir)Juliet\include\;$(SolutionDir)Game; Windows true true true + $(SolutionDir)\bin\$(Platform)\$(Configuration)\ + Juliet.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) - + - - - {1bbc0b92-e4d8-4838-974b-439c5c501e82} - Juliet - - diff --git a/Game/dllmain.cpp b/Game/dllmain.cpp deleted file mode 100644 index 97e8886..0000000 --- a/Game/dllmain.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include // TODO: remove because our dll should not be platform dependant - -#include -#include - -// Test code -namespace Game -{ - struct Door - { - DECLARE_ENTITY() - bool IsOpened; - }; - DEFINE_ENTITY(Door); - - struct Rock - { - DECLARE_ENTITY() - int Health; - }; - DEFINE_ENTITY(Rock); - -} // namespace Game - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) -{ - using namespace Game; - - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - { - // Entity Use case - InitEntityManager(); - auto& manager = GetEntityManager(); - Door* door = MakeEntity(manager, 10.0f, 2.0f); - door->IsOpened = true; - - Entity* ent = door->Base; - Door* stillDoor = DownCast(ent); - Assert(door == stillDoor); - - Rock* rock = MakeEntity(manager, 1.f, 2.f); - rock->Health = 100; - Assert(door->Base != rock->Base); - - printf("Door is %s\n", door->IsOpened ? "Opened" : "Closed"); - printf("Rock has %d health points\n", rock->Health); - - // Have to manually free for now because im not using arenas or anything - free(door->Base); - free(door); - free(rock->Base); - free(rock); - - break; - } - - case DLL_THREAD_ATTACH: break; - case DLL_THREAD_DETACH: break; - case DLL_PROCESS_DETACH: break; - } - - return TRUE; -} diff --git a/Game/game.cpp b/Game/game.cpp new file mode 100644 index 0000000..182b068 --- /dev/null +++ b/Game/game.cpp @@ -0,0 +1,69 @@ +#include +#include // TODO: remove because our dll should not be platform dependant +#undef min +#undef max + +#include +#include +#include +#include +#include + +// Test code +namespace Game +{ + struct Door + { + DECLARE_ENTITY() + bool IsOpened; + }; + DEFINE_ENTITY(Door); + + struct Rock + { + DECLARE_ENTITY() + int Health; + }; + DEFINE_ENTITY(Rock); + +} // namespace Game + +using namespace Juliet; + +extern "C" __declspec(dllexport) void __cdecl GameInit() +{ + using namespace Game; + + // Entity Use case + InitEntityManager(); + auto& manager = GetEntityManager(); + Door* door = MakeEntity(manager, 10.0f, 2.0f); + door->IsOpened = true; + + Entity* ent = door->Base; + Door* stillDoor = DownCast(ent); + Assert(door == stillDoor); + + Rock* rock = MakeEntity(manager, 1.f, 2.f); + rock->Health = 100; + Assert(door->Base != rock->Base); + + printf("Door is %s\n", door->IsOpened ? "Opened" : "Closed"); + printf("Rock has %d health points\n", rock->Health); + + // Have to manually free for now because im not using arenas or anything + free(door->Base); + free(door); + free(rock->Base); + free(rock); +} + +extern "C" __declspec(dllexport) void __cdecl GameShutdown() +{ + printf("Shutting down game...\n"); +} + +extern "C" __declspec(dllexport) void __cdecl GameUpdate(float deltaTime) +{ + printf("Updating game...\n"); +} diff --git a/Juliet.sln b/Juliet.sln index 00d6d7b..7f6d579 100644 --- a/Juliet.sln +++ b/Juliet.sln @@ -11,6 +11,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JulietApp", "JulietApp\Juli EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Game", "Game\Game.vcxproj", "{B7B12DCC-1A69-4371-A9FE-D6E7671497B0}" + ProjectSection(ProjectDependencies) = postProject + {1BBC0B92-E4D8-4838-974B-439C5C501E82} = {1BBC0B92-E4D8-4838-974B-439C5C501E82} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Juliet/Juliet.vcxproj b/Juliet/Juliet.vcxproj index 2b134ec..fc6b95e 100644 --- a/Juliet/Juliet.vcxproj +++ b/Juliet/Juliet.vcxproj @@ -113,16 +113,16 @@ - - - - - - - - - - + + + + + + + + + + @@ -152,6 +152,8 @@ + + @@ -168,69 +170,20 @@ - - MultiThreadedDebugDll - Disabled - true - NoListing - W:\Classified\Juliet\Intermediate\Juliet\x64\Debug\ - false - W:\Classified\Juliet\Intermediate\Juliet\x64\Debug\ - Default - Default - Column - false - false - false - NotSet - Fast - Default - false - stdcpp20 - Default - W:\Classified\Juliet\Intermediate\Juliet\x64\Debug\ - false - Neither - W:\Classified\Juliet\Intermediate\Juliet\x64\Debug\ - Cdecl - Use - pch.h - W:\Classified\Juliet\Intermediate\Juliet\x64\Debug\Juliet.pch - false - false - false - false - false - W:\Classified\Juliet\Intermediate\Juliet\x64\Debug\ - true - true - false - Default - W:\Classified\Juliet\Intermediate\Juliet\x64\Debug\Juliet.tlog\ - true - false - Level3 - W:\Classified\Juliet\Intermediate\Juliet\x64\Debug\ - ProgramDatabase - false - false - true - _DEBUG;JULIET_EXPORT;JULIET_WIN32;_WINDLL;_UNICODE;UNICODE; - false - true - true - Default - --target=amd64-pc-windows-msvc - + + + + + diff --git a/Juliet/include/Core/Common/CoreTypes.h b/Juliet/include/Core/Common/CoreTypes.h index 85d34de..ddad9b3 100644 --- a/Juliet/include/Core/Common/CoreTypes.h +++ b/Juliet/include/Core/Common/CoreTypes.h @@ -24,6 +24,14 @@ struct ByteBuffer size_t Size; }; +template +struct StringBuffer +{ + CharType* Data; + size_t Size; +}; +#define StringBufferParam(str) { (str), sizeof(str) } + using FunctionPtr = auto (*)(void) -> void; // Limits diff --git a/Juliet/include/Core/HAL/Display/Display.h b/Juliet/include/Core/HAL/Display/Display.h index 0c80edc..2f6f543 100644 --- a/Juliet/include/Core/HAL/Display/Display.h +++ b/Juliet/include/Core/HAL/Display/Display.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include namespace Juliet diff --git a/Juliet/include/Core/DynLib/DynamicLibrary.h b/Juliet/include/Core/HAL/DynLib/DynamicLibrary.h similarity index 81% rename from Juliet/include/Core/DynLib/DynamicLibrary.h rename to Juliet/include/Core/HAL/DynLib/DynamicLibrary.h index 894fbc0..7d6e10e 100644 --- a/Juliet/include/Core/DynLib/DynamicLibrary.h +++ b/Juliet/include/Core/HAL/DynLib/DynamicLibrary.h @@ -1,12 +1,10 @@ #pragma once -#include +#include namespace Juliet { - struct DynamicLibrary - { - }; + struct DynamicLibrary; extern JULIET_API DynamicLibrary* LoadDynamicLibrary(const char* filename); extern JULIET_API FunctionPtr LoadFunction(NonNullPtr lib, const char* functionName); diff --git a/Juliet/include/Core/HAL/Filesystem/Filesystem.h b/Juliet/include/Core/HAL/Filesystem/Filesystem.h new file mode 100644 index 0000000..0da2d87 --- /dev/null +++ b/Juliet/include/Core/HAL/Filesystem/Filesystem.h @@ -0,0 +1,7 @@ +#pragma once + +namespace Juliet +{ + // Returns the path to the application directory + extern JULIET_API char * GetBasePath(); +} \ No newline at end of file diff --git a/Juliet/include/Core/HotReload/HotReload.h b/Juliet/include/Core/HotReload/HotReload.h new file mode 100644 index 0000000..b61447c --- /dev/null +++ b/Juliet/include/Core/HotReload/HotReload.h @@ -0,0 +1,36 @@ +#pragma once + +namespace Juliet +{ + // Fwd Declare + struct DynamicLibrary; + + struct HotReloadCode + { + StringBuffer DLLFullPath; + StringBuffer LockFullPath; + StringBuffer TransientDLLName; + + uint64 LastWriteTime; + + DynamicLibrary* Dll; + + void** Functions; + const char** FunctionNames; + uint32 FunctionCount; + + uint32 UniqueID; + + bool IsValid : 1; + }; + + extern JULIET_API void InitHotReloadCode(HotReloadCode& code, StringBuffer dllName, + StringBuffer transientDllName, StringBuffer lockFilename); + extern JULIET_API void ShutdownHotReloadCode(HotReloadCode& code); + + extern JULIET_API void LoadCode(HotReloadCode& code); + extern JULIET_API void UnloadCode(HotReloadCode& code); + + extern JULIET_API void ReloadCode(HotReloadCode& code); + extern JULIET_API bool ShouldReloadCode(const HotReloadCode& code); +} // namespace Juliet diff --git a/Juliet/include/Core/Logging/LogManager.h b/Juliet/include/Core/Logging/LogManager.h index 9fc0e7f..2655932 100644 --- a/Juliet/include/Core/Logging/LogManager.h +++ b/Juliet/include/Core/Logging/LogManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include // TODO : Juliet strings #include diff --git a/Juliet/include/Core/Memory/Allocator.h b/Juliet/include/Core/Memory/Allocator.h index a1708bc..2501d54 100644 --- a/Juliet/include/Core/Memory/Allocator.h +++ b/Juliet/include/Core/Memory/Allocator.h @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace Juliet { @@ -21,8 +21,10 @@ namespace Juliet template void SafeFree(Type*& memory) { - Assert(memory); - free(memory); - memory = nullptr; + if (memory) + { + free(memory); + memory = nullptr; + } } } // namespace Juliet diff --git a/Juliet/include/Core/Thread/Thread.h b/Juliet/include/Core/Thread/Thread.h index 6fbc158..9950085 100644 --- a/Juliet/include/Core/Thread/Thread.h +++ b/Juliet/include/Core/Thread/Thread.h @@ -5,4 +5,13 @@ namespace Juliet { using Thread = std::thread; + + // TODO : Proper wait + inline void wait_ms(int milliseconds) + { + clock_t start_time = clock(); + while (clock() < start_time + milliseconds) + { + } + } } diff --git a/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp b/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp index 228f9c7..2712249 100644 --- a/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp +++ b/Juliet/src/Core/HAL/Display/Win32/Win32Window.cpp @@ -34,7 +34,7 @@ namespace Juliet::Win32 ReleaseDC(state->Handle, state->HDC); DestroyWindow(state->Handle); - Free(state); + SafeFree(state); } window->State = nullptr; } diff --git a/Juliet/src/Core/DynLib/Win32/DynamicLibrary.cpp b/Juliet/src/Core/HAL/DynLib/Win32/DynamicLibrary.cpp similarity index 89% rename from Juliet/src/Core/DynLib/Win32/DynamicLibrary.cpp rename to Juliet/src/Core/HAL/DynLib/Win32/DynamicLibrary.cpp index 9367bfd..c01bf41 100644 --- a/Juliet/src/Core/DynLib/Win32/DynamicLibrary.cpp +++ b/Juliet/src/Core/HAL/DynLib/Win32/DynamicLibrary.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include namespace Juliet @@ -10,7 +10,7 @@ namespace Juliet // TODO : Move into string file // Use portable code + pass the memory array into parameter and not use new // This is from http://www.rohitab.com/discuss/topic/41257-char-to-lpcwstr/ - wchar_t* UTF8ToWideChar(const char* utf8) + static wchar_t* UTF8ToWideChar(const char* utf8) { wchar_t* w; @@ -60,7 +60,7 @@ namespace Juliet FunctionPtr LoadFunction(NonNullPtr lib, const char* functionName) { - auto function = (FunctionPtr)GetProcAddress(reinterpret_cast(lib.Get()), functionName); + auto function = reinterpret_cast(GetProcAddress(reinterpret_cast(lib.Get()), functionName)); if (!function) { Log(LogLevel::Error, LogCategory::Core, "Failed loading %s", functionName); diff --git a/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp b/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp new file mode 100644 index 0000000..c1c07cf --- /dev/null +++ b/Juliet/src/Core/HAL/Filesystem/Filesystem.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include +#include +#include + +namespace Juliet +{ + namespace + { + char* CachedBasePath = nullptr; + } + char* GetBasePath() + { + if (CachedBasePath == nullptr) + { + CachedBasePath = Platform::GetBasePath(); + } + return CachedBasePath; + } + + void InitFilesystem() {} + + void ShutdownFilesystem() + { + if (CachedBasePath != nullptr) + { + SafeFree(CachedBasePath); + } + } +} // namespace Juliet diff --git a/Juliet/src/Core/HAL/Filesystem/Filesystem_Platform.h b/Juliet/src/Core/HAL/Filesystem/Filesystem_Platform.h new file mode 100644 index 0000000..7f2fd12 --- /dev/null +++ b/Juliet/src/Core/HAL/Filesystem/Filesystem_Platform.h @@ -0,0 +1,6 @@ +#pragma once + +namespace Juliet::Platform +{ + extern char* GetBasePath(); +} \ No newline at end of file diff --git a/Juliet/src/Core/HAL/Filesystem/Filesystem_Private.h b/Juliet/src/Core/HAL/Filesystem/Filesystem_Private.h new file mode 100644 index 0000000..8a599bd --- /dev/null +++ b/Juliet/src/Core/HAL/Filesystem/Filesystem_Private.h @@ -0,0 +1,7 @@ +#pragma once + +namespace Juliet +{ + extern void InitFilesystem(); + extern void ShutdownFilesystem(); +} \ No newline at end of file diff --git a/Juliet/src/Core/HAL/Filesystem/Win32/Win32Filesystem.cpp b/Juliet/src/Core/HAL/Filesystem/Win32/Win32Filesystem.cpp new file mode 100644 index 0000000..70c83f3 --- /dev/null +++ b/Juliet/src/Core/HAL/Filesystem/Win32/Win32Filesystem.cpp @@ -0,0 +1,90 @@ +#include + +#include +#include +#include + +namespace Juliet::Platform +{ + namespace + { + // TODO : Move into string file + // Use portable code + pass the memory array into parameter and not use new + // From: https://stackoverflow.com/questions/215963/how-do-you-properly-use-widechartomultibyte + char* WideCharToUTF8(wchar_t* wcharStr) + { + char* result = nullptr; + size_t length = WideCharToMultiByte(CP_UTF8, 0, wcharStr, -1, nullptr, 0, nullptr, nullptr); + if (length <= 0) + { + return nullptr; + } + + result = new char[length]; + if (!result) + { + return nullptr; + } + + if (WideCharToMultiByte(CP_UTF8, 0, wcharStr, -1, result, length, nullptr, nullptr) <= 0) + { + delete[] result; + return nullptr; + } + + return result; + } + } // namespace + + char* GetBasePath() + { + // Allocate a buffer that could fit the module size. + // Max Path is a good start but could be bigger if the path include long path prefix + StringBuffer buffer{ .Data = nullptr, .Size = MAX_PATH }; + buffer.Data = static_cast(Calloc(MAX_PATH, sizeof(WCHAR))); + if (buffer.Data == nullptr) + { + return nullptr; + } + + size_t moduleFilenameLength = 0; + while (true) + { + moduleFilenameLength = GetModuleFileNameW(nullptr, buffer.Data, static_cast(buffer.Size)); + + // If the module filename length is bigger than the buffer size, we need to reallocate a bigger buffer + if (moduleFilenameLength >= buffer.Size - 1) + { + buffer.Size *= 2; + buffer.Data = static_cast(Realloc(buffer.Data, buffer.Size * sizeof(WCHAR))); + } + else + { + break; + } + } + + if (moduleFilenameLength == 0) + { + SafeFree(buffer.Data); + Log(LogLevel::Error, LogCategory::Core, "Filesystem: Cannot locate executable path"); + } + + size_t idx = 0; + for (idx = moduleFilenameLength - 1; idx > 0; --idx) + { + if (buffer.Data[idx] == '\\') + { + break; + } + } + + Assert(idx > 0 && "Path is not absolute!"); + buffer.Data[idx + 1] = '\0'; // Chop chop + + // TODO: Add utils to Convert to/from UTF8W + char* basePath = WideCharToUTF8(buffer.Data); + SafeFree(buffer.Data); + return basePath; + } +} // namespace Juliet::Platform diff --git a/Juliet/src/Core/HotReload/HotReload.cpp b/Juliet/src/Core/HotReload/HotReload.cpp new file mode 100644 index 0000000..0f02b95 --- /dev/null +++ b/Juliet/src/Core/HotReload/HotReload.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include +#include +#include + +#define MAX_TRIES 100 + +namespace Juliet +{ + void InitHotReloadCode(HotReloadCode& code, StringBuffer dllName, + StringBuffer transientDllName, StringBuffer lockFilename) + { + // Get the app base path and build the dll path from there. + const char* basePath = GetBasePath(); + size_t basePathLength = strlen(basePath); + + // Assign Transient dll path + code.TransientDLLName = transientDllName; + + // First allocate all the full path. + // TODO: Add path composition into filesystem + string format + string builder + const size_t dllFullPathLength = basePathLength + dllName.Size; + code.DLLFullPath.Data = static_cast(Calloc(dllFullPathLength, sizeof(char))); + int writtenSize = snprintf(code.DLLFullPath.Data, dllFullPathLength, "%s%s", basePath, dllName.Data); + if (writtenSize < static_cast(dllFullPathLength) - 1) + { + SafeFree(code.DLLFullPath.Data); + Log(LogLevel::Error, LogCategory::Core, "Cannot create DLL Full Path"); + return; + } + code.DLLFullPath.Size = writtenSize + 1; + + // Lock filename path + const size_t lockPathLength = basePathLength + lockFilename.Size; + code.LockFullPath.Data = static_cast(Calloc(lockPathLength, sizeof(char))); + writtenSize = snprintf(code.LockFullPath.Data, lockPathLength, "%s%s", basePath, lockFilename.Data); + if (writtenSize < static_cast(lockPathLength) - 1) + { + code.LockFullPath.Size = 0; + SafeFree(code.LockFullPath.Data); + Log(LogLevel::Error, LogCategory::Core, "Cannot create lock file full path"); + return; + } + code.LockFullPath.Size = writtenSize + 1; + + LoadCode(code); + } + + void ShutdownHotReloadCode(HotReloadCode& code) + { + UnloadCode(code); + + code.DLLFullPath.Size = 0; + SafeFree(code.DLLFullPath.Data); + code.LockFullPath.Size = 0; + SafeFree(code.LockFullPath.Data); + } + + void ReloadCode(HotReloadCode& code) + { + UnloadCode(code); + for (uint32 tryItr = 0; !code.IsValid && tryItr < MAX_TRIES; ++tryItr) + { + LoadCode(code); + wait_ms(100); + } + } +} // namespace Juliet diff --git a/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp b/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp new file mode 100644 index 0000000..af1992d --- /dev/null +++ b/Juliet/src/Core/HotReload/Win32/Win32HotReload.cpp @@ -0,0 +1,134 @@ +#include + +#include +#include +#include +#include +#include + +#define MAX_ATTEMPT 256 +#define MAX_DLL_ID 32767 +#define TEMP_DLL_BUFFER_SIZE_FOR_ID 6 // MAX_DLL_ID + _ + +namespace Juliet +{ + namespace + { + FILETIME GetLastWriteTime(const char* filename) + { + FILETIME lastWriteTime = {}; + + WIN32_FILE_ATTRIBUTE_DATA Data; + if (GetFileAttributesExA(filename, GetFileExInfoStandard, &Data)) + { + lastWriteTime = Data.ftLastWriteTime; + } + + return lastWriteTime; + } + + } // namespace + + void LoadCode(HotReloadCode& code) + { + // TODO : Create and use a TransientAllocator + // Create temp dll name + + char* lockFilename = code.LockFullPath.Data; + WIN32_FILE_ATTRIBUTE_DATA Ignored; + if (!GetFileAttributesExA(lockFilename, GetFileExInfoStandard, &Ignored)) + { + const char* dllName = code.DLLFullPath.Data; + + FILETIME lastWriteTime = GetLastWriteTime(dllName); + ULARGE_INTEGER result{ .LowPart = lastWriteTime.dwLowDateTime, .HighPart = lastWriteTime.dwHighDateTime }; + code.LastWriteTime = result.QuadPart; + + // Create filename for the temp dll until we find a valid id. + // This is not infinite, in a big session we could reach the 128 attempts and fail... + // We'll see for better later. + + // Get the app base path and build the dll path from there. + const char* basePath = GetBasePath(); + size_t basePathLength = strlen(basePath); + + StringBuffer tempDllPath = {}; + const size_t tempDllMaxBufferSize = basePathLength + code.TransientDLLName.Size + TEMP_DLL_BUFFER_SIZE_FOR_ID; + tempDllPath.Data = static_cast(Calloc(tempDllMaxBufferSize, sizeof(char))); + + char idToStr[TEMP_DLL_BUFFER_SIZE_FOR_ID]; + for (uint32 attempt = 0; attempt < MAX_ATTEMPT; ++attempt) + { + // int to char + int idLength = snprintf(idToStr, sizeof(idToStr), "%u", code.UniqueID); + + int writtenSize = snprintf(tempDllPath.Data, tempDllMaxBufferSize, "%s%u_%s", basePath, code.UniqueID, + code.TransientDLLName.Data); + if (writtenSize < static_cast(basePathLength + idLength + code.TransientDLLName.Size)) + { + SafeFree(tempDllPath.Data); + Log(LogLevel::Error, LogCategory::Core, "Cannot create temp full path"); + return; + } + tempDllPath.Size = writtenSize + 1; + + if (++code.UniqueID >= MAX_DLL_ID) + { + code.UniqueID = 0; + } + if (CopyFileA(dllName, tempDllPath.Data, false)) + { + break; + } + } + + code.Dll = LoadDynamicLibrary(tempDllPath.Data); + if (code.Dll) + { + code.IsValid = true; + for (size_t FunctionIndex = 0; FunctionIndex < code.FunctionCount; ++FunctionIndex) + { + if (auto function = reinterpret_cast(LoadFunction(code.Dll, code.FunctionNames[FunctionIndex]))) + { + code.Functions[FunctionIndex] = function; + } + else + { + code.IsValid = false; + } + } + } + + SafeFree(tempDllPath.Data); + tempDllPath = {}; + } + + if (!code.IsValid) + { + UnloadCode(code); + } + } + + void UnloadCode(HotReloadCode& code) + { + code.IsValid = false; + UnloadDynamicLibrary(code.Dll); + code.Dll = nullptr; + + code.LastWriteTime = 0; + ZeroDynArray(code.FunctionCount, code.Functions); + } + + bool ShouldReloadCode(const HotReloadCode& code) + { + ULARGE_INTEGER largeInt = {}; + FILETIME codeLastWriteTime = {}; + largeInt.QuadPart = code.LastWriteTime; + codeLastWriteTime.dwHighDateTime = largeInt.HighPart; + codeLastWriteTime.dwLowDateTime = largeInt.LowPart; + + FILETIME lastWriteTime = GetLastWriteTime(code.DLLFullPath.Data); + int compare = CompareFileTime(&lastWriteTime, &codeLastWriteTime); + return compare != 0; + } +} // namespace Juliet diff --git a/Juliet/src/Core/Juliet.cpp b/Juliet/src/Core/Juliet.cpp index b1d9066..4c53513 100644 --- a/Juliet/src/Core/Juliet.cpp +++ b/Juliet/src/Core/Juliet.cpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace Juliet @@ -40,6 +41,10 @@ namespace Juliet void JulietInit(JulietInit_Flags flags) { + // Mandatory systems + InitFilesystem(); + + // Optional systems if ((flags | JulietInit_Flags::Display) != JulietInit_Flags::None) { InitializeDisplaySystem(); @@ -55,6 +60,8 @@ namespace Juliet DecrementSystemRefCount(JulietInit_Flags::Display); ShutdownDisplaySystem(); } + + ShutdownFilesystem(); } } // namespace Juliet diff --git a/Juliet/src/Engine/Engine.cpp b/Juliet/src/Engine/Engine.cpp index 62b5355..969f7c4 100644 --- a/Juliet/src/Engine/Engine.cpp +++ b/Juliet/src/Engine/Engine.cpp @@ -37,22 +37,11 @@ namespace Juliet EngineInstance.Application = nullptr; } - // Todo: proper fixed tick - void wait_ms(int milliseconds) - { - clock_t start_time = clock(); - while (clock() < start_time + milliseconds) - { - } - } - void RunEngine() { while (EngineInstance.Application->IsRunning()) { EngineInstance.Application->Update(); - - wait_ms(16); } } } // namespace Juliet diff --git a/Juliet/src/Graphics/D3D12/DX12CommandList.cpp b/Juliet/src/Graphics/D3D12/DX12CommandList.cpp index 685f57f..51c2eff 100644 --- a/Juliet/src/Graphics/D3D12/DX12CommandList.cpp +++ b/Juliet/src/Graphics/D3D12/DX12CommandList.cpp @@ -392,7 +392,7 @@ namespace Juliet::D3D12 } } - Free(commandList->PresentDatas); + SafeFree(commandList->PresentDatas); Free(commandList.Get()); } diff --git a/Juliet/src/Graphics/D3D12/DX12GraphicsDevice.cpp b/Juliet/src/Graphics/D3D12/DX12GraphicsDevice.cpp index b76106a..ea8a874 100644 --- a/Juliet/src/Graphics/D3D12/DX12GraphicsDevice.cpp +++ b/Juliet/src/Graphics/D3D12/DX12GraphicsDevice.cpp @@ -1,7 +1,7 @@ #include -#include -#include +#include +#include #include #include #include @@ -200,10 +200,10 @@ namespace Juliet::D3D12 } // Clean allocations - Free(driver->AvailableCommandLists); - Free(driver->SubmittedCommandLists); - //Free(driver->WindowData); // TODO Should free the vector of WindowData, but we have only one for now - Free(driver->AvailableFences); + SafeFree(driver->AvailableCommandLists); + SafeFree(driver->SubmittedCommandLists); + // Free(driver->WindowData); // TODO Should free the vector of WindowData, but we have only one for now + SafeFree(driver->AvailableFences); if (driver->IndirectDrawCommandSignature) { @@ -310,7 +310,7 @@ namespace Juliet::D3D12 Internal::DestroySwapChain(d3d12Driver, d3d12Driver->WindowData); - Free(d3d12Driver->WindowData); + SafeFree(d3d12Driver->WindowData); d3d12Driver->WindowData = nullptr; } diff --git a/JulietApp/EditorMain.cpp b/JulietApp/EditorMain.cpp deleted file mode 100644 index 9cad5e0..0000000 --- a/JulietApp/EditorMain.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "EditorMain.h" diff --git a/JulietApp/EditorMain.h b/JulietApp/EditorMain.h deleted file mode 100644 index f363f78..0000000 --- a/JulietApp/EditorMain.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -class EditorMain -{ -}; - diff --git a/JulietApp/JulietApp.vcxproj b/JulietApp/JulietApp.vcxproj index 9c2aff7..3e3db5d 100644 --- a/JulietApp/JulietApp.vcxproj +++ b/JulietApp/JulietApp.vcxproj @@ -11,21 +11,70 @@ - - - - - - - - {b7b12dcc-1a69-4371-a9fe-d6e7671497b0} - Game - {1bbc0b92-e4d8-4838-974b-439c5c501e82} Juliet + + + + + + MultiThreadedDebugDll + Disabled + true + NoListing + W:\Classified\Juliet\Intermediate\JulietApp\x64\Debug\ + false + W:\Classified\Juliet\Intermediate\JulietApp\x64\Debug\ + Default + Default + Column + false + false + false + NotSet + Fast + Default + false + stdcpp20 + Default + W:\Classified\Juliet\Intermediate\JulietApp\x64\Debug\ + false + Neither + W:\Classified\Juliet\Intermediate\JulietApp\x64\Debug\ + Cdecl + NotUsing + stdafx.h + W:\Classified\Juliet\Intermediate\JulietApp\x64\Debug\JulietApp.pch + false + false + false + false + false + W:\Classified\Juliet\Intermediate\JulietApp\x64\Debug\ + true + true + false + Default + W:\Classified\Juliet\Intermediate\JulietApp\x64\Debug\JulietApp.tlog\ + true + false + Level3 + W:\Classified\Juliet\Intermediate\JulietApp\x64\Debug\ + ProgramDatabase + false + false + true + _DEBUG;JULIET_WIN32;_UNICODE;UNICODE; + false + true + true + Default + --target=amd64-pc-windows-msvc + + 17.0 Win32Proj diff --git a/JulietApp/Editor/EditorMain_win32.cpp b/JulietApp/main.cpp similarity index 63% rename from JulietApp/Editor/EditorMain_win32.cpp rename to JulietApp/main.cpp index 6b5c458..4ea920a 100644 --- a/JulietApp/Editor/EditorMain_win32.cpp +++ b/JulietApp/main.cpp @@ -1,31 +1,52 @@ -#include +#include #include #include +#include #include +#include #include #include #include -#include - -#include - -// TODO : Replace with message box from framework + call main and not winmain + subsystem -#include #include +#include #include #include +#include +#include + +// TODO : Replace with message box from framework + call main and not winmain + subsystem // TODO : Think how to do the draw pipeline. // Ex: Expose a Draw method ? // Store a graphics context ? // For now. Put everything in Update down below. // Should split update from draw, update should have a != timestep than graphics (60fps or more) + +// TODO : Remove main.h. Useless +// May be remove that Application class, useless too. + using namespace Juliet; -void Win32EditorApplication::Init() +namespace { - Log(LogLevel::Message, LogCategory::Editor, "Initializing Editor Application..."); + using GameInit_t = void (*)(void); + using GameShutdown_t = void (*)(void); + using GameUpdate_t = void (*)(float deltaTime); + struct GameFunctionTable + { + GameInit_t Init = nullptr; + GameShutdown_t Shutdown = nullptr; + GameUpdate_t Update = nullptr; + } Game; + const char* GameFunctionTable[] = { "GameInit", "GameShutdown", "GameUpdate" }; +} // namespace + +void JulietApplication::Init() +{ + Log(LogLevel::Message, LogCategory::Editor, "Initializing Juliet Application..."); + + Log(LogLevel::Message, LogCategory::Editor, "%s", GetBasePath()); GraphicsConfig config; GraphicsDevice = CreateGraphicsDevice(config); @@ -37,18 +58,25 @@ void Win32EditorApplication::Init() if (Running) { AttachToWindow(GraphicsDevice, MainWindow); - Game = LoadDynamicLibrary("Game.dll"); + //Game = LoadDynamicLibrary("Game.dll"); + + GameCode.Functions = reinterpret_cast(&Game); + GameCode.FunctionCount = ArraySize(GameFunctionTable); + GameCode.FunctionNames = GameFunctionTable; + InitHotReloadCode(GameCode, StringBufferParam("Game.dll"),StringBufferParam("Game_Temp.dll"), StringBufferParam("lock.tmp")); + if ((Running = GameCode.IsValid)) + { + Game.Init(); + } } } -void Win32EditorApplication::Shutdown() +void JulietApplication::Shutdown() { - Log(LogLevel::Message, LogCategory::Editor, "Shutdown Editor Application..."); + Log(LogLevel::Message, LogCategory::Editor, "Shutting down Juliet Application..."); - if (Game) - { - UnloadDynamicLibrary(Game); - } + Game.Shutdown(); + ShutdownHotReloadCode(GameCode); if (MainWindow && GraphicsDevice) { @@ -64,9 +92,11 @@ void Win32EditorApplication::Shutdown() { DestroyGraphicsDevice(GraphicsDevice); } + + Log(LogLevel::Message, LogCategory::Editor, "Juliet App shutdown Completed"); } -void Win32EditorApplication::Update() +void JulietApplication::Update() { SystemEvent evt; while (GetEvent(evt)) @@ -80,6 +110,13 @@ void Win32EditorApplication::Update() } } + Game.Update(0.0f); + + if (ShouldReloadCode(GameCode)) + { + ReloadCode(GameCode); + } + // Draw here for now // 1) Acquire a Command Buffer CommandList* cmdList = AcquireCommandList(GraphicsDevice, QueueType::Graphics); @@ -114,24 +151,24 @@ void Win32EditorApplication::Update() SubmitCommandLists(cmdList); } -bool Win32EditorApplication::IsRunning() +bool JulietApplication::IsRunning() { return Running; } namespace { - Win32EditorApplication EditorApplication; + JulietApplication EditorApplication; } -Win32EditorApplication& GetEditorApplication() +JulietApplication& GetEditorApplication() { return EditorApplication; } int main(int argc, char** argv) { - CreateMutex(0, false, L"Local\\Juliet.Editor"); + CreateMutex(0, false, L"Local\\Juliet.App"); if (GetLastError() == ERROR_ALREADY_EXISTS) { MessageBox(nullptr, L"An instance of Juliet is already running.", L"Juliet", MB_OK | MB_ICONEXCLAMATION); diff --git a/JulietApp/Editor/EditorMain_win32.h b/JulietApp/main.h similarity index 66% rename from JulietApp/Editor/EditorMain_win32.h rename to JulietApp/main.h index 444e586..6b7728b 100644 --- a/JulietApp/Editor/EditorMain_win32.h +++ b/JulietApp/main.h @@ -1,8 +1,9 @@ #pragma once #include -#include #include +#include +#include namespace Juliet { @@ -10,7 +11,7 @@ namespace Juliet struct Window; } // namespace Juliet -class Win32EditorApplication : public Juliet::IApplication +class JulietApplication : public Juliet::IApplication { protected: void Init() override; @@ -21,9 +22,9 @@ class Win32EditorApplication : public Juliet::IApplication private: Juliet::Window* MainWindow = {}; Juliet::GraphicsDevice* GraphicsDevice = {}; - Juliet::DynamicLibrary* Game = {}; + Juliet::HotReloadCode GameCode = {}; bool Running = false; }; -Win32EditorApplication& GetEditorApplication(); +JulietApplication& GetEditorApplication(); diff --git a/ShellGameBuild.lnk b/ShellGameBuild.lnk new file mode 100644 index 0000000..e21c4f7 Binary files /dev/null and b/ShellGameBuild.lnk differ diff --git a/misc/build_game.bat b/misc/build_game.bat new file mode 100644 index 0000000..521ca27 --- /dev/null +++ b/misc/build_game.bat @@ -0,0 +1,16 @@ +@echo off + +del /s ..\Game*.pdb > NUL 2> NUL + +pushd %~dp0\..\bin\x64\Debug +echo WAITING FOR PDB > lock.tmp +popd + +pushd %~dp0 +msbuild -p:Random=%random% /p:SolutionDir=..\ ..\Game\Game.vcxproj +popd + +pushd %~dp0\..\bin\x64\Debug +del lock.tmp +popd + diff --git a/misc/shell.bat b/misc/shell.bat new file mode 100644 index 0000000..0a7a8a8 --- /dev/null +++ b/misc/shell.bat @@ -0,0 +1,8 @@ +@echo off + +pushd . +call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 +popd + +set path=W:\Classified\Juliet\misc;%path% +set _NO_DEBUG_HEAP=1 \ No newline at end of file