From 9220550dd4d5bcbc51fc0ce9f2f24188fc358dbb Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 26 Sep 2023 17:26:35 -0400 Subject: [PATCH] Day 21 complete --- .gitignore | 2 + .vscode/launch.json | 2 +- HandmadeHero.10x | 10 +- HandmadeHero.vcxproj | 17 +- HandmadeHero.vcxproj.user | 2 +- docs/Day 021.md | 15 ++ project/{ => engine}/engine.cpp | 129 +++++------ project/{ => engine}/engine.hpp | 53 ----- project/engine/win32_engine.cpp | 11 + project/handmade.cpp | 2 +- project/handmade.hpp | 67 ++++++ project/handmade_engine.cpp | 19 ++ project/handmade_win32.cpp | 2 - project/platform/macros.hpp | 6 + project/platform/platform.hpp | 48 ++-- project/platform/platform_engine_api.hpp | 37 ++- project/platform/platform_game_api.hpp | 3 + project/platform/win32.hpp | 21 +- project/platform/win32_platform.cpp | 175 ++++++++++++++- scripts/build.ps1 | 272 ++++++++++++++++------- scripts/handmade.rdbg | Bin 811 -> 787 bytes scripts/helpers/devshell.ps1 | 1 + 22 files changed, 640 insertions(+), 254 deletions(-) create mode 100644 docs/Day 021.md rename project/{ => engine}/engine.cpp (77%) rename project/{ => engine}/engine.hpp (76%) create mode 100644 project/engine/win32_engine.cpp create mode 100644 project/handmade.hpp create mode 100644 project/handmade_engine.cpp create mode 100644 project/platform/platform_game_api.hpp diff --git a/.gitignore b/.gitignore index c29ce1e..c46cc39 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ build **/*.dll data/test.out +data/handmade_engine.symbols +data/handmade_win32.exe diff --git a/.vscode/launch.json b/.vscode/launch.json index f2191e2..30f2117 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "type":"cppvsdbg", "request": "launch", "name" : "Debug handmade win32 msvc", - "program": "${workspaceFolder}/build/handmade_win32.exe", + "program": "${workspaceFolder}/data/handmade_win32.exe", "args": [], "cwd": "${workspaceFolder}/data", "visualizerFile": "${workspaceFolder}/scripts/handmade.natvis" diff --git a/HandmadeHero.10x b/HandmadeHero.10x index b21fa9d..7e5a044 100644 --- a/HandmadeHero.10x +++ b/HandmadeHero.10x @@ -8,16 +8,16 @@ true false false - pwsh -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(WorkspaceDirectory)/scripts/build.ps1 msvc dev optimized - + pwsh -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(WorkspaceDirectory)/scripts/build.ps1 msvc dev debug engine + pwsh -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(WorkspaceDirectory)/scripts/build.ps1 msvc dev debug platform pwsh -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(WorkspaceDirectory)/scripts/clean.ps1 - $(WorkspaceDirectory)/build/handmade_win32.exe + $(WorkspaceDirectory)/data/handmade_win32.exe $(WorkspaceDirectory)/data - $(WorkspaceDirectory)/build/handmade_win32.exe - $(WorkspaceDirectory)/build/handmade_win32.exe + $(WorkspaceDirectory)/data/handmade_win32.exe + $(WorkspaceDirectory)/data/handmade_win32.exe true diff --git a/HandmadeHero.vcxproj b/HandmadeHero.vcxproj index f8703d3..13e3e2c 100644 --- a/HandmadeHero.vcxproj +++ b/HandmadeHero.vcxproj @@ -17,28 +17,43 @@ $(ProjectDir)project;$(IncludePath) $(ProjectDir)data;$(windir)System32;$(LibraryPath) - pwsh -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(ProjectDir)scripts\build.ps1 msvc dev + pwsh -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(ProjectDir)scripts\build.ps1 msvc dev engine pwsh ExecutionPolicy Bypass -NoProfile -NonInteractive -File $(ProjectDir)scripts\clean.ps1 GEN_TIME;Build_Development;Build_Debug;$(NMakePreprocessorDefinitions) $(VC_IncludePath);$(WindowsSDK_IncludePath) + + + + + + + + + + + + + + + diff --git a/HandmadeHero.vcxproj.user b/HandmadeHero.vcxproj.user index 556ee01..977a45d 100644 --- a/HandmadeHero.vcxproj.user +++ b/HandmadeHero.vcxproj.user @@ -6,6 +6,6 @@ $(ProjectDir)data WindowsLocalDebugger - $(ProjectDir)build\handmade_win32.exe + $(ProjectDir)data\handmade_win32.exe \ No newline at end of file diff --git a/docs/Day 021.md b/docs/Day 021.md new file mode 100644 index 0000000..ef9b6a7 --- /dev/null +++ b/docs/Day 021.md @@ -0,0 +1,15 @@ +# Day 21 + +So I learned today the good reason why he doesn't use static variables; So that when he makes this library dynamically loaded we don't lose the state of the game on reload. + +This day was extremely gratifying to get working. + +I went the extra mile than what Casey did and allow for name mangled symbols (for both clang and msvc). +It took a few more steps than his solution but I get to keep the eronomics of namespaces. + +After the linker finishes emitting, I use the build script to parse the .map file and extract the decorated symbols I need to load in the platform layer. Those are exported to a file called `handmade_engine.symbols` and then in the platform layer I load them up using an enum as the lookup table for the line the symbol will be in the file. From there its just loading up the symbol with GetProcAddress! + +![img](https://files.catbox.moe/sdebtc.png) +![img](https://files.catbox.moe/ufkacl.png) +![img](https://files.catbox.moe/zr52au.png) +![img](https://files.catbox.moe/g3f1vv.png) diff --git a/project/engine.cpp b/project/engine/engine.cpp similarity index 77% rename from project/engine.cpp rename to project/engine/engine.cpp index eb297e8..d8a1e58 100644 --- a/project/engine.cpp +++ b/project/engine/engine.cpp @@ -1,15 +1,21 @@ -#include "engine.hpp" //#include "win32.h" +#include "engine.hpp" +#include "platform/platform_engine_api.hpp" +#include "handmade.hpp" NS_ENGINE_BEGIN struct EngineState { - s32 WaveSwitch; s32 WaveToneHz; s32 ToneVolume; s32 XOffset; s32 YOffset; + + b32 RendererPaused; + + f32 SampleWaveSineTime; + b32 SampleWaveSwitch; }; using GetSoundSampleValueFn = s16( EngineState* state, AudioBuffer* sound_buffer ); @@ -28,7 +34,7 @@ square_wave_sample_value( EngineState* state, AudioBuffer* sound_buffer ) internal s16 sine_wave_sample_value( EngineState* state, AudioBuffer* sound_buffer ) { - local_persist f32 time = 0.f; + f32& time = state->SampleWaveSineTime; s32 wave_period = sound_buffer->SamplesPerSecond / state->WaveToneHz; @@ -104,7 +110,6 @@ render_weird_graident(OffscreenBuffer* buffer, u32 x_offset, u32 y_offset ) u8 red = 0; #endif - *pixel++ = u32(red << 16) | u32(green << 8) | blue; } wildcard += 0.5375f; @@ -112,66 +117,55 @@ render_weird_graident(OffscreenBuffer* buffer, u32 x_offset, u32 y_offset ) } } -b32 input_using_analog() +Engine_API +void on_module_reload( Memory* memory, platform::ModuleAPI* platfom_api ) { - return false; + } -void startup() +Engine_API +void startup( Memory* memory, platform::ModuleAPI* platform_api ) { -} - -void shutdown() -{ -} - -// TODO : I rather expose the back_buffer and sound_buffer using getters for access in any function. -void update_and_render( InputState* input, OffscreenBuffer* back_buffer, Memory* memory ) -{ - // Graphics & Input Test - local_persist u32 x_offset = 0; - local_persist u32 y_offset = 0; - - // Wave Sound Test - local_persist bool wave_switch = false; - -#if 0 - if ( input_using_analog() ) - { - // TODO(Ed) : Use analog movement tuning - } - else - { - // TODO(Ed) : Use digital movement tuning - } -#endif - EngineState* state = rcast( EngineState*, memory->Persistent ); + assert( sizeof(EngineState) <= memory->PersistentSize ); - do_once_start - assert( sizeof(EngineState) <= memory->PersistentSize ); + state->ToneVolume = 1000; - state->ToneVolume = 1000; - state->WaveToneHz = 120; + state->XOffset = 0; + state->YOffset = 0; - state->XOffset = 0; - state->YOffset = 0; - state->WaveSwitch = false; + state->SampleWaveSwitch = false; + state->WaveToneHz = 120; + state->SampleWaveSineTime = 0.f; - #if Build_Debug && 0 + state->RendererPaused = false; + + #if Build_Debug && 0 + { + using namespace platform; + + char const* file_path = __FILE__; + Debug_FileContent file_content = platform_api->debug_file_read_content( file_path ); + if ( file_content.Size ) { - using namespace platform; - - char const* file_path = __FILE__; - Debug_FileContent file_content = debug_file_read_content( file_path ); - if ( file_content.Size ) - { - debug_file_write_content( "test.out", file_content.Size, file_content.Data ); - debug_file_free_content( & file_content ); - } + platform_api->debug_file_write_content( "test.out", file_content.Size, file_content.Data ); + platform_api->debug_file_free_content( & file_content ); } - #endif - do_once_end + } + #endif +} + +Engine_API +void shutdown( Memory* memory, platform::ModuleAPI* platform_api ) +{ +} + +Engine_API +// TODO : I rather expose the back_buffer and sound_buffer using getters for access in any function. +void update_and_render( InputState* input, OffscreenBuffer* back_buffer, Memory* memory, platform::ModuleAPI* platform_api ) +{ + EngineState* state = rcast( EngineState*, memory->Persistent ); + assert( sizeof(EngineState) <= memory->PersistentSize ); ControllerState* controller = & input->Controllers[0]; @@ -195,7 +189,6 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, Memory* b32 toggle_wave_tone = false; b32 pause_renderer = false; - local_persist b32 renderer_paused = false; f32 analog_threshold = 0.5f; @@ -255,10 +248,10 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, Memory* toggle_wave_tone |= pressed( keyboard->Space ); } - x_offset += 3 * move_right; - x_offset -= 3 * move_left; - y_offset += 3 * move_down; - y_offset -= 3 * move_up; + state->XOffset += 3 * move_right; + state->XOffset -= 3 * move_left; + state->YOffset += 3 * move_down; + state->YOffset -= 3 * move_up; if ( raise_volume ) { @@ -284,26 +277,27 @@ void update_and_render( InputState* input, OffscreenBuffer* back_buffer, Memory* if ( toggle_wave_tone ) { - state->WaveSwitch ^= true; + state->SampleWaveSwitch ^= true; } - render_weird_graident( back_buffer, x_offset, y_offset ); + render_weird_graident( back_buffer, state->XOffset, state->YOffset ); if ( pause_renderer ) { - if ( renderer_paused ) + if ( state->RendererPaused ) { - platform::set_pause_rendering(false); - renderer_paused = false; + platform_api->debug_set_pause_rendering(false); + state->RendererPaused = false; } else { - platform::set_pause_rendering(true); - renderer_paused = true; + platform_api->debug_set_pause_rendering(true); + state->RendererPaused = true; } } } -void update_audio( AudioBuffer* audio_buffer, Memory* memory ) +Engine_API +void update_audio( AudioBuffer* audio_buffer, Memory* memory, platform::ModuleAPI* platform_api ) { EngineState* state = rcast( EngineState*, memory->Persistent ); do_once_start @@ -311,11 +305,10 @@ void update_audio( AudioBuffer* audio_buffer, Memory* memory ) do_once_end // TODO(Ed) : Allow sample offsets here for more robust platform options - if ( ! state->WaveSwitch ) + if ( ! state->SampleWaveSwitch ) output_sound( state, audio_buffer, sine_wave_sample_value ); else output_sound( state, audio_buffer, square_wave_sample_value ); } NS_ENGINE_END - diff --git a/project/engine.hpp b/project/engine/engine.hpp similarity index 76% rename from project/engine.hpp rename to project/engine/engine.hpp index eff148a..17e6d2d 100644 --- a/project/engine.hpp +++ b/project/engine/engine.hpp @@ -197,56 +197,3 @@ void input_mode_pop( InputMode* mode ); void input_mode_pop( InputMode* mode ); NS_ENGINE_END - -// TODO(Ed) : Move this to handmade game layer later. - -#define NS_HANDMADE_BEGIN namespace handmade { -#define NS_HANDMADE_END } - -NS_HANDMADE_BEGIN - -// We want a 'binding' to have multiple binds to active it (most likely) -struct Actionable -{ - char const* Name; - engine::InputBindCallback* Binds; - s32 NumBinds; - char _PAD_[4]; -}; - -struct ActionableMode -{ - -}; - -/* - Platform Layer: - - Controller : Keyboard & Mouse, XPad, DSPad - - ---VV--- - - Engine Layer: - - InputBinding callbacks (per-game-logic frame basis) - Push/Pop input modes (binding sets) - - ---VV--- - - Game Layer: - - Actionables : Binding Sets where a raw input, or input interpretation leads to an player action. - ActionSet : Actionables.Push/Pop -> Input.Push/Pop ? - Player : Controller, Actionables, ActionSets -*/ - -struct Player -{ - // So far just has an assigned controller. - engine::ControllerState* Controller; - - // Possilby some other stuff in the future. -}; - -NS_HANDMADE_END - diff --git a/project/engine/win32_engine.cpp b/project/engine/win32_engine.cpp new file mode 100644 index 0000000..3f7f7a7 --- /dev/null +++ b/project/engine/win32_engine.cpp @@ -0,0 +1,11 @@ +#include "platform/win32.hpp" +#include "engine.hpp" + +b32 WINAPI DllMain( + HINSTANCE instance, + DWORD reason_for_call, + LPVOID reserved +) +{ + return true; +} diff --git a/project/handmade.cpp b/project/handmade.cpp index 5ae0d3b..9bcbd36 100644 --- a/project/handmade.cpp +++ b/project/handmade.cpp @@ -4,5 +4,5 @@ #pragma once -#include "engine.hpp" +#include "engine/engine.hpp" diff --git a/project/handmade.hpp b/project/handmade.hpp new file mode 100644 index 0000000..f0bd3b6 --- /dev/null +++ b/project/handmade.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "engine/engine.hpp" + +#define NS_HANDMADE_BEGIN namespace handmade { +#define NS_HANDMADE_END } + +NS_HANDMADE_BEGIN + +struct Memory +{ + // Subscection of engine memory for the game to use. + + void* Persistent; + u64 PersistentSize; + + // void* Frame; + // u64 FrameSize; + + void* Transient; + u64 TransientSize; +}; + +// We want a 'binding' to have multiple binds to active it (most likely) +struct Actionable +{ + char const* Name; + engine::InputBindCallback* Binds; + s32 NumBinds; + char _PAD_[4]; +}; + +struct ActionableMode +{ + +}; + +/* + Platform Layer: + + Controller : Keyboard & Mouse, XPad, DSPad + + ---VV--- + + Engine Layer: + + InputBinding callbacks (per-game-logic frame basis) + Push/Pop input modes (binding sets) + + ---VV--- + + Game Layer: + + Actionables : Binding Sets where a raw input, or input interpretation leads to an player action. + ActionSet : Actionables.Push/Pop -> Input.Push/Pop ? + Player : Controller, Actionables, ActionSets +*/ + +struct Player +{ + // So far just has an assigned controller. + engine::ControllerState* Controller; + + // Possilby some other stuff in the future. +}; + +NS_HANDMADE_END diff --git a/project/handmade_engine.cpp b/project/handmade_engine.cpp new file mode 100644 index 0000000..419e824 --- /dev/null +++ b/project/handmade_engine.cpp @@ -0,0 +1,19 @@ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-const-variable" +#pragma clang diagnostic ignored "-Wswitch" +#pragma clang diagnostic ignored "-Wunused-variable" +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wvarargs" +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wunused-but-set-variable" +#endif + +#include "platform/grime.hpp" + +#if Build_Unity +# include "engine/engine.cpp" +# if SYSTEM_WINDOWS +# include "engine/win32_engine.cpp" +# endif +#endif diff --git a/project/handmade_win32.cpp b/project/handmade_win32.cpp index c2d2599..5a56c90 100644 --- a/project/handmade_win32.cpp +++ b/project/handmade_win32.cpp @@ -10,7 +10,5 @@ #endif #if Build_Unity -#include "handmade.cpp" -#include "engine.cpp" #include "platform/win32_platform.cpp" #endif diff --git a/project/platform/macros.hpp b/project/platform/macros.hpp index fe72e29..2223008 100644 --- a/project/platform/macros.hpp +++ b/project/platform/macros.hpp @@ -61,6 +61,12 @@ # define assert( expression ) #endif +#ifdef COMPILER_CLANG +# define compiler_decorated_func_name __PRETTY_NAME__ +#elif defined(COMPILER_MSVC) +# define compiler_decorated_func_name __FUNCDNAME__ +#endif + // TODO(Ed) : Add this sauce later #if 0 #define congrats( message ) diff --git a/project/platform/platform.hpp b/project/platform/platform.hpp index fc5d094..92ac416 100644 --- a/project/platform/platform.hpp +++ b/project/platform/platform.hpp @@ -32,7 +32,11 @@ NS_PLATFORM_BEGIN -#if Build_Debug +// On-Demand platform interface. +// Everything exposed here should be based on a feature a game may want to provide a user +// (Example: Letting the user change the refresh-rate of the monitor or the engine's target frame-rate) + +#if Build_Development /* IMPORTANT : These are not for shipping code - they are blocking and the write isn't protected. */ @@ -44,34 +48,42 @@ struct Debug_FileContent char _PAD_[4]; }; -void debug_file_free_content ( Debug_FileContent* file_content ); -Debug_FileContent debug_file_read_content ( char const* file_path ); -b32 debug_file_write_content( char const* file_path, u32 content_size, void* content_memory ); - -// Allows the engine or game to pause the renderering of any next frames. -// ( Prevents blipping of the black buffer ) -void set_pause_rendering( b32 value ); +using DebugFileFreeContentFn = void ( Debug_FileContent* file_content ); +using DebugFileReadContentFn = Debug_FileContent ( char const* file_path ); +using DebugFileWriteContentFn = b32 ( char const* file_path, u32 content_size, void* content_memory ); +using DebugSetPauseRenderingFn = void (b32 value); #endif -// On-Demand platform interface. -// Everything exposed here should be based on a feature a game may want to provide a user -// (Example: Letting the user change the refresh-rate of the monitor or the engine's target frame-rate) - // TODO(Ed) : Implement this later when settings UI is setup. #pragma region Settings Exposure // Exposing specific properties for user configuration in settings // Returns the current monitor refresh rate. -u32 get_monitor_refresh_rate(); - +using GetMonitorRefreshRateFn = u32(); // Sets the monitor refresh rate -// Must be of the compatiable listing for the monitor the window surface is presenting to. -void set_monitor_refresh_rate( u32 rate_in_hz ); +// Must be of the compatiable listing for the monitor the window surface is presenting to +using SetMonitorRefreshRateFn = void ( u32 rate_in_hz ); -u32 get_engine_frame_target(); +using GetEngineFrameTargetFn = u32 (); +using SetEngineFrameTargetFn = void ( u32 rate_in_hz ); -void set_engine_frame_target( u32 rate_in_hz ); +struct ModuleAPI +{ +#if Build_Development + DebugFileFreeContentFn* debug_file_free_content; + DebugFileReadContentFn* debug_file_read_content; + DebugFileWriteContentFn* debug_file_write_content; + + DebugSetPauseRenderingFn* debug_set_pause_rendering; +#endif + + GetMonitorRefreshRateFn* get_monitor_refresh_rate; + SetMonitorRefreshRateFn* set_monitor_refresh_rate; + + GetEngineFrameTargetFn* get_engine_frame_target; + SetEngineFrameTargetFn* set_engine_frame_target; +}; #pragma endregion Settings Exposure diff --git a/project/platform/platform_engine_api.hpp b/project/platform/platform_engine_api.hpp index 8e67c46..0a0cd41 100644 --- a/project/platform/platform_engine_api.hpp +++ b/project/platform/platform_engine_api.hpp @@ -2,20 +2,47 @@ This represents the API only accessible to the platform layer to fullfill for the engine layer. */ #pragma once -#include "engine.hpp" +#include "engine/engine.hpp" + +#ifndef Engine_API +# define Engine_API +#endif NS_ENGINE_BEGIN -void startup(); -void shutdown(); +using OnModuleRelaodFn = void( Memory* memory, platform::ModuleAPI* platform_api ); +using StartupFn = void( Memory* memory, platform::ModuleAPI* platform_api ); +using ShutdownFn = void( Memory* memory, platform::ModuleAPI* platform_api ); // Needs a contextual reference to four things: // Timing, Input, Bitmap Buffer -void update_and_render( InputState* input, OffscreenBuffer* back_buffer, Memory* memory ); +using UpdateAndRenderFn = void ( InputState* input, OffscreenBuffer* back_buffer, Memory* memory, platform::ModuleAPI* platform_api ); // Audio timing is complicated, processing samples must be done at a different period from the rest of the engine's usual update. // IMPORTANT: This has very tight timing, and cannot be more than a millisecond in execution. // TODO(Ed) : Reduce timing pressure on performance by measuring it or pinging its time. -void update_audio( AudioBuffer* audio_buffer, Memory* memory ); +using UpdateAudioFn = void ( AudioBuffer* audio_buffer, Memory* memory, platform::ModuleAPI* platform_api ); + +struct ModuleAPI +{ + enum : u32 + { + Sym_OnModuleReload, + Sym_Startup, + Sym_Shutdown, + Sym_UpdateAndRender, + Sym_UpdateAudio, + }; + + OnModuleRelaodFn* on_module_reload; + StartupFn* startup; + ShutdownFn* shutdown; + + UpdateAndRenderFn* update_and_render; + UpdateAudioFn* update_audio; + + b32 IsValid; + char _PAD_[4]; +}; NS_ENGINE_END diff --git a/project/platform/platform_game_api.hpp b/project/platform/platform_game_api.hpp new file mode 100644 index 0000000..9186eab --- /dev/null +++ b/project/platform/platform_game_api.hpp @@ -0,0 +1,3 @@ +/* + This represents the API only accessible to the platform layer to fullfill for the game layer. +*/ diff --git a/project/platform/win32.hpp b/project/platform/win32.hpp index 197b3c7..813be72 100644 --- a/project/platform/win32.hpp +++ b/project/platform/win32.hpp @@ -113,6 +113,14 @@ enum XI_State : DWORD XI_PluggedIn = ERROR_SUCCESS, }; + +template< typename ProcSignature > +ProcSignature* get_procedure_from_library( HMODULE library_module, char const* symbol ) +{ + void* address = rcast( void*, GetProcAddress( library_module, symbol ) ); + return rcast( ProcSignature*, address ); +} + #pragma region XInput WIN_LIB_API DWORD WINAPI XInputGetState ( @@ -151,11 +159,14 @@ xinput_load_library_bindings() { HMODULE xinput_lib = LoadLibraryA( XINPUT_DLL_A ); -#pragma warning( push ) -#pragma warning( disable: 4191 ) - xinput_get_state = rcast( XInputGetStateFn*, GetProcAddress( xinput_lib, "XInputGetState" )); - xinput_set_state = rcast( XInputSetStateFn*, GetProcAddress( xinput_lib, "XInputSetState" )); -#pragma warning( pop ) + XInputGetStateFn* get_state = get_procedure_from_library< XInputGetStateFn >( xinput_lib, "XInputGetState" ); + XInputSetStateFn* set_state = get_procedure_from_library< XInputSetStateFn >( xinput_lib, "XInputSetState" ); + + if ( get_state ) + xinput_get_state = get_state; + + if ( set_state ) + xinput_set_state = set_state; } #pragma endregion XInput diff --git a/project/platform/win32_platform.cpp b/project/platform/win32_platform.cpp index b13ee38..dd8f8a1 100644 --- a/project/platform/win32_platform.cpp +++ b/project/platform/win32_platform.cpp @@ -23,7 +23,7 @@ #include "win32.hpp" // Engine layer headers -#include "engine.hpp" +#include "engine/engine.hpp" #include "platform_engine_api.hpp" // Standard-Library stand-ins @@ -216,7 +216,7 @@ b32 debug_file_write_content( char const* file_path, u32 content_size, void* con return true; } -void set_pause_rendering( b32 value ) +void debug_set_pause_rendering( b32 value ) { Pause_Rendering = value; } @@ -425,15 +425,12 @@ init_sound(HWND window_handle, DirectSoundBuffer* sound_buffer ) } // Get direct sound object -#pragma warning( push ) -#pragma warning( disable: 4191 ) - direct_sound_create = rcast( DirectSoundCreateFn*, GetProcAddress( sound_library, "DirectSoundCreate" )); + direct_sound_create = get_procedure_from_library< DirectSoundCreateFn >( sound_library, "DirectSoundCreate" ); if ( ! ensure( direct_sound_create, "Failed to get direct_sound_create_procedure" ) ) { // TOOD : Diagnostic return; } -#pragma warning( pop ) LPDIRECTSOUND direct_sound; if ( ! SUCCEEDED(direct_sound_create( 0, & direct_sound, 0 )) ) @@ -751,6 +748,131 @@ process_pending_window_messages( engine::KeyboardState* keyboard ) } } } + +#pragma region Platfom API +u32 get_monitor_refresh_rate(); + +#pragma endregion Platform API + +// TODO(Ed): This also assumes the symbol name is always within size of the provided buffer, needs to fail if not. +void get_symbol_from_module_table( Debug_FileContent symbol_table, u32 symbol_ID, char* symbol_name ) +{ + struct Token + { + char const* Ptr; + u32 Len; + char _PAD_[4]; + }; + + Token tokens[256] = {}; + s32 idx = 0; + + char const* scanner = rcast( char const*, symbol_table.Data ); + u32 left = symbol_table.Size; + while ( left ) + { + if ( *scanner == '\n' || *scanner == '\r' ) + { + ++ scanner; + -- left; + } + else + { + tokens[idx].Ptr = scanner; + while ( left && *scanner != '\r' && *scanner != '\n' ) + { + -- left; + ++ scanner; + ++ tokens[idx].Len; + } + ++ idx; + } + } + + Token& token = tokens[symbol_ID]; + while ( token.Len -- ) + { + *symbol_name = *token.Ptr; + ++ symbol_name; + ++ token.Ptr; + } + *symbol_name = '\0'; +} + +// Right now they are just the data directory +#define Path_To_Symbol_Tables + +global HMODULE Lib_Handmade_Engine = nullptr; + +engine::ModuleAPI load_engine_module_api() +{ + using ModuleAPI = engine::ModuleAPI; + + // TODO(Ed) : Need proper paything to the dll (not assume is in the base directory). + + CopyFileA( "handmade_engine.dll", "handmade_engine_temp.dll", FALSE ); + + // Engine + Lib_Handmade_Engine = LoadLibraryA( "handmade_engine_temp.dll" ); + if ( ! Lib_Handmade_Engine ) + { + return {}; + } + + constexpr char const* + handmade_engine_symbols = Path_To_Symbol_Tables "handmade_engine.symbols"; + + Debug_FileContent symbol_table = debug_file_read_content( handmade_engine_symbols ); + if ( symbol_table.Size == 0 ) + { + fatal( "Failed to laod symbol table for handmade engine module!" ); + return {}; + } + + // TODO(Ed) : Clean this up later when Casey makes strings. (If he doesn't we'll do it) + char symbol_on_module_reload[256]; + char symboL_startup[256]; + char symboL_shutdown[256]; + char symboL_update_and_render[256]; + char symbol_update_audio[256]; + get_symbol_from_module_table( symbol_table, ModuleAPI::Sym_OnModuleReload, symbol_on_module_reload ); + get_symbol_from_module_table( symbol_table, ModuleAPI::Sym_Startup, symboL_startup ); + get_symbol_from_module_table( symbol_table, ModuleAPI::Sym_Shutdown, symboL_shutdown ); + get_symbol_from_module_table( symbol_table, ModuleAPI::Sym_UpdateAndRender, symboL_update_and_render ); + get_symbol_from_module_table( symbol_table, ModuleAPI::Sym_UpdateAudio, symbol_update_audio ); + + debug_file_free_content( & symbol_table ); + + engine::ModuleAPI engine_api {}; + engine_api.on_module_reload = get_procedure_from_library< engine::OnModuleRelaodFn > ( Lib_Handmade_Engine, symbol_on_module_reload ); + engine_api.startup = get_procedure_from_library< engine::StartupFn > ( Lib_Handmade_Engine, symboL_startup ); + engine_api.shutdown = get_procedure_from_library< engine::ShutdownFn > ( Lib_Handmade_Engine, symboL_shutdown ); + engine_api.update_and_render = get_procedure_from_library< engine::UpdateAndRenderFn >( Lib_Handmade_Engine, symboL_update_and_render ); + engine_api.update_audio = get_procedure_from_library< engine::UpdateAudioFn > ( Lib_Handmade_Engine, symbol_update_audio ); + + engine_api.IsValid = + engine_api.on_module_reload + && engine_api.startup + && engine_api.shutdown + && engine_api.update_and_render + && engine_api.update_audio; + if ( engine_api.IsValid ) + { + OutputDebugStringA( "Loaded engine module API\n" ); + } + return engine_api; +} + +void unload_engine_module_api( engine::ModuleAPI* engine_api ) +{ + if ( engine_api->IsValid ) + { + FreeLibrary( Lib_Handmade_Engine ); + *engine_api = {}; + OutputDebugStringA( "Unloaded engine module API\n" ); + } +} + NS_PLATFORM_END int CALLBACK @@ -774,6 +896,22 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho QueryPerformanceFrequency( rcast(LARGE_INTEGER*, & Performance_Counter_Frequency) ); + // Prepare platform API + ModuleAPI platform_api {}; + { + #if Build_Development + platform_api.debug_file_free_content = & debug_file_free_content; + platform_api.debug_file_read_content = & debug_file_read_content; + platform_api.debug_file_write_content = & debug_file_write_content; + + platform_api.debug_set_pause_rendering = & debug_set_pause_rendering; + #endif + platform_api.get_monitor_refresh_rate = nullptr; + platform_api.set_monitor_refresh_rate = nullptr; + platform_api.get_engine_frame_target = nullptr; + platform_api.set_engine_frame_target = nullptr; + } + // Memory engine::Memory engine_memory {}; { @@ -802,6 +940,9 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho } } + // Load engine module + engine::ModuleAPI engine_api = load_engine_module_api(); + WNDCLASSW window_class {}; HWND window_handle = nullptr; { @@ -935,6 +1076,8 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho } } + engine_api.startup( & engine_memory, & platform_api ); + u64 last_frame_clock = timing_get_wall_clock(); u64 last_frame_cycle = __rdtsc(); u64 flip_wall_clock = last_frame_clock; @@ -942,8 +1085,14 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho #if Build_Development u64 startup_cycles = last_frame_cycle - launch_cycle; f32 startup_ms = timing_get_ms_elapsed( launch_clock, last_frame_clock ); + + char text_buffer[256]; + sprintf_s( text_buffer, sizeof(text_buffer), "Startup MS: %f\n", startup_ms ); + OutputDebugStringA( text_buffer ); #endif + u64 module_reload_counter = 0; + Running = true; #if 0 // This tests the play & write cursor update frequency. @@ -960,6 +1109,14 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho #endif while( Running ) { + if ( module_reload_counter > 120 ) + { + unload_engine_module_api( & engine_api ); + engine_api = load_engine_module_api(); + module_reload_counter = 0; + } + ++ module_reload_counter; + process_pending_window_messages( new_keyboard ); // TODO(Ed): Offload polling to these functions later. @@ -1107,7 +1264,7 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho } // Engine's logical iteration and rendering process - engine::update_and_render( & input, rcast(engine::OffscreenBuffer*, & Surface_Back_Buffer.Memory), & engine_memory ); + engine_api.update_and_render( & input, rcast(engine::OffscreenBuffer*, & Surface_Back_Buffer.Memory), & engine_memory, & platform_api ); u64 audio_frame_start = timing_get_wall_clock(); f32 flip_to_audio_ms = timing_get_ms_elapsed( flip_wall_clock, audio_frame_start ); @@ -1194,7 +1351,7 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho sound_buffer.RunningSampleIndex = ds_sound_buffer.RunningSampleIndex; sound_buffer.SamplesPerSecond = ds_sound_buffer.SamplesPerSecond; sound_buffer.Samples = ds_sound_buffer.Samples; - engine::update_audio( & sound_buffer, & engine_memory ); + engine_api.update_audio( & sound_buffer, & engine_memory, & platform_api ); DebugTimeMarker* marker = & debug_markers[ debug_marker_index ]; marker->OutputPlayCusror = ds_play_cursor; @@ -1331,7 +1488,7 @@ WinMain( HINSTANCE instance, HINSTANCE prev_instance, LPSTR commandline, int sho #endif } - engine::shutdown(); + engine_api.shutdown( & engine_memory, & platform_api ); if ( jsl_num_devices > 0 ) { diff --git a/scripts/build.ps1 b/scripts/build.ps1 index d0e33e7..2187381 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -14,6 +14,9 @@ Push-Location $path_root $debug = $null $analysis = $false $dev = $false + $platform = $null + $engine = $null + $game = $null [array] $vendors = @( "clang", "msvc" ) @@ -26,11 +29,14 @@ if ( $args ) { $args | ForEach-Object { "debug" { $debug = $true } "analysis" { $analysis = $true } "dev" { $dev = $true } + "platform" { $platform = $true } + "engine" { $engine = $true } + "game" { $game = $true } } }} #endregion Argument -#region Configuration +#region Toolchain Configuration if ($IsWindows) { # This HandmadeHero implementation is only designed for 64-bit systems & $devshell -arch amd64 @@ -45,7 +51,7 @@ write-host "Building HandmadeHero with $vendor" if ( $dev ) { if ( $debug -eq $null ) { - $debug = $true + # $debug = $true } if ( $optimize -eq $null ) { @@ -118,42 +124,52 @@ function run-linker if ( $vendor -match "clang" ) { # https://clang.llvm.org/docs/ClangCommandLineReference.html - $flag_all_c = '/TC' - $flag_all_cpp = '/TP' - $flag_compile = '-c' - $flag_color_diagnostics = '-fcolor-diagnostics' - $flag_no_color_diagnostics = '-fno-color-diagnostics' - $flag_debug = '-g' - $flag_debug_codeview = '-gcodeview' - $flag_define = '-D' - $flag_exceptions_disabled = '-fno-exceptions' - $flag_preprocess = '-E' - $flag_include = '-I' - $flag_library = '-l' - $flag_library_path = '-L' - $flag_linker = '-Wl,' - $flag_link_mapfile = '-Map' - $flag_link_win_subsystem_console = '/SUBSYSTEM:CONSOLE' - $flag_link_win_subsystem_windows = '/SUBSYSTEM:WINDOWS' - $flag_link_win_machine_32 = '/MACHINE:X86' - $flag_link_win_machine_64 = '/MACHINE:X64' - $flag_link_win_debug = '/DEBUG' - $flag_link_win_pdb = '/PDB:' - $flag_link_win_path_output = '/OUT:' - $flag_no_optimization = '-O0' - $flag_optimize_fast = '-O2' - $flag_optimize_size = '-O1' - $flag_optimize_intrinsics = '-Oi' - $flag_path_output = '-o' - $flag_preprocess_non_intergrated = '-no-integrated-cpp' - $flag_profiling_debug = '-fdebug-info-for-profiling' - $flag_set_stack_size = '-stack=' - $flag_syntax_only = '-fsyntax-only' - $flag_target_arch = '-target' - $flag_wall = '-Wall' - $flag_warning = '-W' - $flag_warnings_as_errors = '-Werror' - $flag_win_nologo = '/nologo' + $flag_all_c = '/TC' + $flag_all_cpp = '/TP' + $flag_compile = '-c' + $flag_color_diagnostics = '-fcolor-diagnostics' + $flag_no_color_diagnostics = '-fno-color-diagnostics' + $flag_debug = '-g' + $flag_debug_codeview = '-gcodeview' + $flag_define = '-D' + $flag_exceptions_disabled = '-fno-exceptions' + $flag_preprocess = '-E' + $flag_include = '-I' + $flag_section_data = '-fdata-sections' + $flag_section_functions = '-ffunction-sections' + $flag_library = '-l' + $flag_library_path = '-L' + $flag_linker = '-Wl,' + if ( $IsWindows ) { + $flag_link_dll = '/DLL' + $flag_link_mapfile = '/MAP:' + $flag_link_optimize_references = '/OPT:REF' + } + if ( $IsLinux ) { + $flag_link_mapfile = '--Map=' + $flag_link_optimize_references = '--gc-sections' + } + $flag_link_win_subsystem_console = '/SUBSYSTEM:CONSOLE' + $flag_link_win_subsystem_windows = '/SUBSYSTEM:WINDOWS' + $flag_link_win_machine_32 = '/MACHINE:X86' + $flag_link_win_machine_64 = '/MACHINE:X64' + $flag_link_win_debug = '/DEBUG' + $flag_link_win_pdb = '/PDB:' + $flag_link_win_path_output = '/OUT:' + $flag_no_optimization = '-O0' + $flag_optimize_fast = '-O2' + $flag_optimize_size = '-O1' + $flag_optimize_intrinsics = '-Oi' + $flag_path_output = '-o' + $flag_preprocess_non_intergrated = '-no-integrated-cpp' + $flag_profiling_debug = '-fdebug-info-for-profiling' + $flag_set_stack_size = '-stack=' + $flag_syntax_only = '-fsyntax-only' + $flag_target_arch = '-target' + $flag_wall = '-Wall' + $flag_warning = '-W' + $flag_warnings_as_errors = '-Werror' + $flag_win_nologo = '/nologo' $ignore_warning_ms_include = 'no-microsoft-include' $ignore_warning_return_type_c_linkage = 'no-return-type-c-linkage' @@ -175,12 +191,12 @@ if ( $vendor -match "clang" ) function build-simple { - param( [array]$includes, [array]$compiler_args, [array]$linker_args, [string]$unit, [string]$executable ) + param( [array]$includes, [array]$compiler_args, [array]$linker_args, [string]$unit, [string]$binary ) Write-Host "build-simple: clang" - $object = $executable -replace '\.exe', '.obj' - $pdb = $executable -replace '\.exe', '.pdb' - $map = $executable -replace '\.exe', '.map' + $object = $binary -replace '\.exe', '.obj' + $pdb = $binary -replace '\.exe', '.pdb' + $map = $binary -replace '\.exe', '.map' $compiler_args += @( $flag_no_color_diagnostics, @@ -188,6 +204,8 @@ if ( $vendor -match "clang" ) $flag_target_arch, $target_arch, $flag_wall, $flag_preprocess_non_intergrated, + $flag_section_data, + $flag_section_functions, ( $flag_path_output + $object ) ) if ( $optimized ) { @@ -214,12 +232,12 @@ if ( $vendor -match "clang" ) $linker_args += @( $flag_link_win_machine_64, - $( $flag_link_win_path_output + $executable ) + $( $flag_link_win_path_output + $binary ) ) if ( $debug ) { $linker_args += $flag_link_win_debug $linker_args += $flag_link_win_pdb + $pdb - # $linker_args += $flag_link_mapfile + $map + $linker_args += $flag_link_mapfile + $map } $libraries | ForEach-Object { @@ -227,7 +245,7 @@ if ( $vendor -match "clang" ) } $linker_args += $object - run-linker $linker $executable $linker_args + run-linker $linker $binary $linker_args # $compiler_args += $unit # $linker_args | ForEach-Object { @@ -256,6 +274,7 @@ if ( $vendor -match "msvc" ) $flag_dll = '/LD' $flag_dll_debug = '/LDd' $flag_linker = '/link' + $flag_link_dll = '/DLL' $flag_link_mapfile = '/MAP:' $flag_link_optimize_references = '/OPT:REF' $flag_link_win_debug = '/DEBUG' @@ -287,12 +306,12 @@ if ( $vendor -match "msvc" ) # This works because this project uses a single unit to build function build-simple { - param( [array]$includes, [array]$compiler_args, [array]$linker_args, [string]$unit, [string]$executable ) + param( [array]$includes, [array]$compiler_args, [array]$linker_args, [string]$unit, [string]$binary ) Write-Host "build-simple: msvc" - $object = $executable -replace '\.exe', '.obj' - $pdb = $executable -replace '\.exe', '.pdb' - $map = $executable -replace '\.exe', '.map' + $object = $binary -replace '\.(exe|dll)$', '.obj' + $pdb = $binary -replace '\.(exe|dll)$', '.pdb' + $map = $binary -replace '\.(exe|dll)$', '.map' $compiler_args += @( $flag_nologo, @@ -336,18 +355,18 @@ if ( $vendor -match "msvc" ) $linker_args += @( $flag_nologo, $flag_link_win_machine_64, - ( $flag_link_win_path_output + $executable ) + ( $flag_link_win_path_output + $binary ) ) if ( $debug ) { $linker_args += $flag_link_win_debug $linker_args += $flag_link_win_pdb + $pdb - # $linker_args += $flag_link_mapfile + $map + $linker_args += $flag_link_mapfile + $map } else { } $linker_args += $object - run-linker $linker $executable $linker_args + run-linker $linker $binary $linker_args # $compiler_args += $unit # $compiler_args += $flag_linker @@ -360,11 +379,14 @@ if ( $vendor -match "msvc" ) } #endregion Configuration +#region Building $path_project = Join-Path $path_root 'project' $path_build = Join-Path $path_root 'build' +$path_data = Join-Path $path_root 'data' $path_deps = Join-Path $path_project 'dependencies' $path_gen = Join-Path $path_project 'gen' $path_platform = Join-Path $path_project 'platform' +$path_engine = Join-Path $path_project 'engine' $update_deps = Join-Path $PSScriptRoot 'update_deps.ps1' @@ -376,21 +398,21 @@ if ( (Test-Path $path_deps) -eq $false ) { & $update_deps } -$includes = @( - $path_project, - $path_gen, - # $path_deps, - $path_platform -) -$compiler_args = @() -$compiler_args += ( $flag_define + 'GEN_TIME' ) - -$linker_args = @( - $flag_link_win_subsystem_console -) - #region Handmade Generate if ( $false ) { + $includes = @( + $path_project, + $path_gen, + # $path_deps, + $path_platform + ) + $compiler_args = @() + $compiler_args += ( $flag_define + 'GEN_TIME' ) + + $linker_args = @( + $flag_link_win_subsystem_console + ) + $unit = Join-Path $path_gen 'handmade_gen.cpp' $executable = Join-Path $path_build 'handmade_gen.exe' @@ -408,10 +430,7 @@ if ( $false ) { #region Handmade Runtime $includes = @( - $path_project, - $path_gen, - $path_deps, - $path_platform + $path_project ) # Microsoft @@ -423,9 +442,6 @@ $lib_winmm = 'Winmm.lib' # Github $lib_jsl = Join-Path $path_deps 'JoyShockLibrary/x64/JoyShockLibrary.lib' -$unit = Join-Path $path_project 'handmade_win32.cpp' -$executable = Join-Path $path_build 'handmade_win32.exe' - $stack_size = 1024 * 1024 * 4 $compiler_args = @( @@ -436,9 +452,6 @@ $compiler_args = @( $flag_warnings_as_errors $flag_optimize_intrinsics - ($flag_define + 'Build_DLL=0' ) - - # For now this script only supports unity builds... (for the full binary) ($flag_define + 'Build_Unity=1' ) ) @@ -449,19 +462,108 @@ else { $compiler_args += ( $flag_define + 'Build_Development=0' ) } -$linker_args = @( - $lib_gdi32, - # $lib_xinput, - $lib_user32, - $lib_winmm, +if ( $engine ) +{ + $engine_compiler_args = $compiler_args + $engine_compiler_args += ($flag_define + 'Build_DLL=1' ) - $lib_jsl, + if ( $vendor -eq 'msvc' ) + { + $engine_compiler_args += ($flag_define + 'Engine_API=__declspec(dllexport)') + } + if ( $vendor -eq 'clang' ) + { + $engine_compiler_args += ($flag_define + 'Engine_API=__attribute__((visibility("default")))') + } - $flag_link_win_subsystem_windows - $flag_link_optimize_references -) + $linker_args = @( + $flag_link_dll, + $flag_link_optimize_references + ) -build-simple $includes $compiler_args $linker_args $unit $executable + $unit = Join-Path $path_project 'handmade_engine.cpp' + $dynamic_library = Join-Path $path_build 'handmade_engine.dll' + + build-simple $includes $engine_compiler_args $linker_args $unit $dynamic_library + + if ( Test-Path $dynamic_library ) + { + $data_path = Join-Path $path_data 'handmade_engine.dll' + move-item $dynamic_library $data_path -Force + + # We need to generate the symbol table so that we can lookup the symbols we need when loading/reloading the library at runtime. + # This is done by sifting through the emitter.map file from the linker for the base symbol names + # and mapping them to their found decorated name + + $engine_symbols = @( + 'on_module_reload', + 'startup', + 'shutdown', + 'update_and_render', + 'update_audio' + ) + + $path_engine_map = Join-Path $path_build 'handmade_engine.map' + + $engine_symbol_table = @() + $engine_symbol_list = @() + Get-Content -Path $path_engine_map | ForEach-Object { + # Split each line by whitespace + $tokens = $_ -split '\s+', 3 + + # Extract only the decorated name using regex for both MSVC and Clang conventions + $decoratedName = ($tokens[2] -match '(\?[\w@]+|_Z[\w@]+)' ) ? $matches[1] : $null + + # Check each regular name against the current line + foreach ($name in $engine_symbols) { + if ($decoratedName -like "*$name*") { + $engine_symbol_table += $name + ', ' + $decoratedName + $engine_symbol_list += $decoratedName + $engine_symbols = $engine_symbols -ne $name # Remove the found symbol from the array + } + } + } + + write-host "Engine Symbol Table:" -ForegroundColor Green + $engine_symbol_table | ForEach-Object { + write-host "`t$_" -ForegroundColor Green + } + + # Write the symbol table to a file + $path_engine_symbols = Join-Path $path_data 'handmade_engine.symbols' + $engine_symbol_list | Out-File -Path $path_engine_symbols + } +} + +if ( $platform ) +{ + $platform_compiler_args = $compiler_args + $platform_compiler_args += ($flag_define + 'Build_DLL=0' ) + + $linker_args = @( + $lib_gdi32, + # $lib_xinput, + $lib_user32, + $lib_winmm, + + $lib_jsl, + + $flag_link_win_subsystem_windows + $flag_link_optimize_references + ) + + $unit = Join-Path $path_project 'handmade_win32.cpp' + $executable = Join-Path $path_build 'handmade_win32.exe' + + build-simple $includes $platform_compiler_args $linker_args $unit $executable + + if ( Test-Path $executable ) + { + $data_path = Join-Path $path_data 'handmade_win32.exe' + move-item $executable $data_path -Force + } +} #endregion Handmade Runtime Pop-Location +#endregion Building diff --git a/scripts/handmade.rdbg b/scripts/handmade.rdbg index 64c7312e5ac9e39d61064b357475f152ab490c91..31f860a87f79ae16bd47df2d9a2504faa2097770 100644 GIT binary patch delta 77 zcmZ3@HkoZg93$hzctgJO%sgYG_=23olC=Dy+>K`|7=;)a7#O&KI5jUlGcPqhu^@AD bA(O)7OeOV7nFiT;wD6<#