turanszkij said:
The reason I want to avoid having to parse windows messages, because I am developing an engine, and want a single interface point that the user needs to call
This may or may not be bad design for some reasons, especially the Window Handling. How do you support Window Configuration like FullScreen, Windowed, Border or Borderless mode? How is the user able to react on Window resizing or a layout change for example when the user decides to move the Game from one Desktop to another?
All I can tell you is that you have to work with OS Messages, this is how the OS is designed to work. You can't for example handle UPnP (a Controller/ Gamepad was attached) without notifications from the OS and I'm 99% sure that this is the fact also for raw input. If you have a game loop (I suppose you have and hide it behind the game.Run() function), why not implement the WM_INPUT message by default and feed your input system with the data?
As I described above, you anyways have to maintain a buffer for your input states and where the data is produced shouldn't matter as long as the user is able to obtain them in order and in-time. Or else my question, why hide something behind a game.Run() function if you are inconsitent in what you hide behind?
This is for sure a case of all-or-nothing.
Engines like Urho3D offer a macro for this btw. and so did I too in ours'. My macro takes 3 functions, the main function as same as a prepare and cleanup one called automatically by the code
#ifdef WINDOWS
#include <Windows/WindowsPlatform.h>
#define __app_entry(initialize, main, shutdown) \
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int showCmd) \
{ \
initialize(); \
assert(&Drough::MainAllocator::Allocator()); \
\
Drough::Array<const char*> args; \
Drough::GetArguments(cmdLine, args); \
\
int ret_code = main(args); \
if(!args.Empty()) args.~Array(); \
\
shutdown(); \
\
return ret_code; \
}
#define __app_entry_no_args(initialize, main, shutdown) \
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int showCmd) \
{ \
initialize(); \
assert(&Drough::MainAllocator::Allocator()); \
\
int ret_code = main(); \
shutdown(); \
\
return ret_code; \
}
#elif defined(ANDROID)
#define __app_entry(prepare, main, cleanup) \
extern "C" int SDL_main(int argc, char** argv); \
int32 SDL_main(int argc, char** argv) \
{ \
initialize(); \
assert(&Drough::MainAllocator::Allocator()); \
\
Drough::Array<const char*> args; \
Drough::GetArguments(argc, argv, args); \
if(!args.Empty()) args.~Array(); \
\
int ret_code = main(args); \
shutdown(); \
\
return ret_code; \
}
#define __app_entry_no_args(prepare, main, cleanup) \
extern "C" int SDL_main(int argc, char** argv); \
int32 SDL_main(int argc, char** argv) \
{ \
initialize(); \
assert(&Drough::MainAllocator::Allocator()); \
\
int ret_code = main(); \
shutdown(); \
\
return ret_code; \
}
#else
#define __app_entry(prepare, main, cleanup) \
int32 main(int argc, char** argv) \
{ \
initialize(); \
assert(&Drough::MainAllocator::Allocator()); \
\
Drough::Array<const char*> args; \
Drough::GetArguments(argc, argv, args); \
if(!args.Empty()) args.~Array(); \
\
int ret_code = main(args); \
shutdown(); \
\
return ret_code; \
}
#define __app_entry_no_args(prepare, main, cleanup) \
int32 main(int argc, char** argv) \
{ \
initialize(); \
assert(&Drough::MainAllocator::Allocator()); \
\
int ret_code = main(args); \
shutdown(); \
\
return ret_code; \
}
#endif
And our message loop lookes like this. It is very simple and short (I don't use WM_DEVICECHANGED here but this usually belongs to UPnP)
bool MessageLoop(MSG& msg)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
switch(msg.message)
{
default:
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
break;
case WM_INPUT:
{
void* device = Input::Process((void*)msg.lParam, *buffer);
if(device == Input::GetKeyboard().Handle()) Input::Process(Input::GetKeyboard(), *buffer, Dispatcher::Handler<EventChannel::Default, void (ControlStats::Stat)>());
}
break;
case WM_DEVICECHANGE: break;
case WM_CLOSE:
case WM_DESTROY:
{
FRM_LOG("Window closed");
running = false;
}
break;
}
return true;
}
else return false;
}
This can for example be hidden (and I already did so) behind an Engine::Start() or game.Run() function without problems