Changeset View
Changeset View
Standalone View
Standalone View
source/tools/atlas/GameInterface/GameLoop.cpp
Show All 23 Lines | |||||
#include "SharedMemory.h" | #include "SharedMemory.h" | ||||
#include "Handlers/MessageHandler.h" | #include "Handlers/MessageHandler.h" | ||||
#include "ActorViewer.h" | #include "ActorViewer.h" | ||||
#include "View.h" | #include "View.h" | ||||
#include "InputProcessor.h" | #include "InputProcessor.h" | ||||
#include "graphics/TextureManager.h" | #include "graphics/TextureManager.h" | ||||
#include "gui/GUIManager.h" | |||||
#include "lib/app_hooks.h" | #include "lib/app_hooks.h" | ||||
#include "lib/external_libraries/libsdl.h" | #include "lib/external_libraries/libsdl.h" | ||||
#include "lib/timer.h" | #include "lib/timer.h" | ||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/DllLoader.h" | #include "ps/DllLoader.h" | ||||
#include "ps/Filesystem.h" | #include "ps/Filesystem.h" | ||||
#include "ps/Profile.h" | #include "ps/Profile.h" | ||||
#include "ps/ThreadUtil.h" | #include "ps/ThreadUtil.h" | ||||
Show All 24 Lines | namespace AtlasMessage | ||||
void* (*ShareableMallocFptr)(size_t); | void* (*ShareableMallocFptr)(size_t); | ||||
void (*ShareableFreeFptr)(void*); | void (*ShareableFreeFptr)(void*); | ||||
} | } | ||||
MessagePasser* AtlasMessage::g_MessagePasser = NULL; | MessagePasser* AtlasMessage::g_MessagePasser = NULL; | ||||
static InputProcessor g_Input; | |||||
static GameLoopState state; | static GameLoopState state; | ||||
GameLoopState* g_AtlasGameLoop = &state; | GameLoopState* g_AtlasGameLoop = &state; | ||||
void RendererIncrementalLoad() | |||||
static ErrorReactionInternal AtlasDisplayError(const wchar_t* text, size_t flags) | |||||
{ | |||||
// TODO: after Atlas has been unloaded, don't do this | |||||
Atlas_DisplayError(text, flags); | |||||
return ERI_CONTINUE; | |||||
} | |||||
static void RendererIncrementalLoad() | |||||
{ | { | ||||
// TODO: shouldn't duplicate this code from main.cpp | // TODO: shouldn't duplicate this code from main.cpp | ||||
if (!CRenderer::IsInitialised()) | if (!CRenderer::IsInitialised()) | ||||
return; | return; | ||||
const double maxTime = 0.1f; | const double maxTime = 0.1f; | ||||
double startTime = timer_Time(); | double startTime = timer_Time(); | ||||
bool more; | bool more; | ||||
do { | do { | ||||
more = g_Renderer.GetTextureManager().MakeProgress(); | more = g_Renderer.GetTextureManager().MakeProgress(); | ||||
} | } | ||||
while (more && timer_Time() - startTime < maxTime); | while (more && timer_Time() - startTime < maxTime); | ||||
} | } | ||||
static void RunEngine(const CmdLineArgs& args) | |||||
{ | |||||
debug_SetThreadName("engine_thread"); | |||||
// Set new main thread so that all the thread-safety checks pass | |||||
ThreadUtil::SetMainThread(); | |||||
g_Profiler2.RegisterCurrentThread("atlasmain"); | |||||
MessagePasserImpl* msgPasser = (MessagePasserImpl*)AtlasMessage::g_MessagePasser; | |||||
// Register all the handlers for message which might be passed back | |||||
RegisterHandlers(); | |||||
// Override ah_display_error to pass all errors to the Atlas UI | |||||
// TODO: this doesn't work well because it doesn't pause the game thread | |||||
// and the error box is ugly, so only use it if we fix those issues | |||||
// (use INIT_HAVE_DISPLAY_ERROR init flag to test this) | |||||
AppHooks hooks = {0}; | |||||
hooks.display_error = AtlasDisplayError; | |||||
app_hooks_update(&hooks); | |||||
// Disable the game's cursor rendering | |||||
extern CStrW g_CursorName; | |||||
g_CursorName = L""; | |||||
state.args = args; | |||||
state.running = true; | |||||
state.view = AtlasView::GetView_None(); | |||||
state.glCanvas = NULL; | |||||
double last_activity = timer_Time(); | |||||
while (state.running) | |||||
{ | |||||
bool recent_activity = false; | |||||
////////////////////////////////////////////////////////////////////////// | |||||
// (TODO: Work out why these things have to be in this order (to avoid | |||||
// jumps when starting to move, etc)) | |||||
// Calculate frame length | |||||
{ | |||||
const double time = timer_Time(); | |||||
static double last_time = time; | |||||
const double realFrameLength = time-last_time; | |||||
last_time = time; | |||||
ENSURE(realFrameLength >= 0.0); | |||||
// TODO: filter out big jumps, e.g. when having done a lot of slow | |||||
// processing in the last frame | |||||
state.realFrameLength = realFrameLength; | |||||
} | |||||
// Process the input that was received in the past | |||||
if (g_Input.ProcessInput(&state)) | |||||
recent_activity = true; | |||||
////////////////////////////////////////////////////////////////////////// | |||||
{ | |||||
IMessage* msg; | |||||
while ((msg = msgPasser->Retrieve()) != NULL) | |||||
{ | |||||
recent_activity = true; | |||||
std::string name (msg->GetName()); | |||||
msgHandlers::const_iterator it = GetMsgHandlers().find(name); | |||||
if (it != GetMsgHandlers().end()) | |||||
{ | |||||
it->second(msg); | |||||
} | |||||
else | |||||
{ | |||||
debug_warn(L"Unrecognised message"); | |||||
// CLogger might not be initialised, but this error will be sent | |||||
// to the debug output window anyway so people can still see it | |||||
LOGERROR("Unrecognised message (%s)", name.c_str()); | |||||
} | |||||
if (msg->GetType() == IMessage::Query) | |||||
{ | |||||
// For queries, we need to notify MessagePasserImpl::Query | |||||
// that the query has now been processed. | |||||
sem_post((sem_t*) static_cast<QueryMessage*>(msg)->m_Semaphore); | |||||
// (msg may have been destructed at this point, so don't use it again) | |||||
// It's quite possible that the querier is going to do a tiny | |||||
// bit of processing on the query results and then issue another | |||||
// query, and repeat lots of times in a loop. To avoid slowing | |||||
// that down by rendering between every query, make this | |||||
// thread yield now. | |||||
SDL_Delay(0); | |||||
Stan: You had access to the SDL here, what changed? | |||||
Done Inline ActionsI mean I could still have access but now the code runs on a fixed timer from WxWidgets, which wasn't the case before (as it was threaded). Thus I can't really do this anymore. wraitii: I mean I could still have access but now the code runs on a fixed timer from WxWidgets, which… | |||||
Not Done Inline ActionsI meant using the SDl to have the refreshrate :) Stan: I meant using the SDl to have the refreshrate :) | |||||
Done Inline ActionsI could do that but I'd have to add a specific query. I honestly don't think it's worth the trouble to get 144Hz on blank maps only :p wraitii: I could do that but I'd have to add a specific query. I honestly don't think it's worth the… | |||||
} | |||||
else | |||||
{ | |||||
// For non-queries, we need to delete the object, since we | |||||
// took ownership of it. | |||||
AtlasMessage::ShareableDelete(msg); | |||||
} | |||||
} | |||||
} | |||||
// Exit, if desired | |||||
if (! state.running) | |||||
break; | |||||
////////////////////////////////////////////////////////////////////////// | |||||
// Do per-frame processing: | |||||
ReloadChangedFiles(); | |||||
RendererIncrementalLoad(); | |||||
// Pump SDL events (e.g. hotkeys) | |||||
SDL_Event_ ev; | |||||
while (in_poll_priority_event(&ev)) | |||||
in_dispatch_event(&ev); | |||||
if (g_GUI) | |||||
g_GUI->TickObjects(); | |||||
state.view->Update(state.realFrameLength); | |||||
state.view->Render(); | |||||
if (CProfileManager::IsInitialised()) | |||||
g_Profiler.Frame(); | |||||
double time = timer_Time(); | |||||
if (recent_activity) | |||||
last_activity = time; | |||||
// Be nice to the processor (by sleeping lots) if we're not doing anything | |||||
// useful, and nice to the user (by just yielding to other threads) if we are | |||||
bool yield = (time - last_activity > 0.5); | |||||
// But make sure we aren't doing anything interesting right now, where | |||||
// the user wants to see the screen updating even though they're not | |||||
// interacting with it | |||||
if (state.view->WantsHighFramerate()) | |||||
yield = false; | |||||
if (yield) // if there was no recent activity... | |||||
{ | |||||
double sleepUntil = time + 0.5; // only redraw at 2fps | |||||
StanUnsubmitted Not Done Inline ActionsShould we keep this ? Stan: Should we keep this ? | |||||
wraitiiAuthorUnsubmitted Done Inline ActionsIt's not completely trivial with the new code -> we'd have to modify the timer. I don't think it's worth keeping. wraitii: It's not completely trivial with the new code -> we'd have to modify the timer.
I don't think… | |||||
while (time < sleepUntil) | |||||
{ | |||||
// To minimise latency when the user starts doing stuff, only | |||||
// sleep for a short while, then check if anything's happened, | |||||
// then go back to sleep | |||||
// (TODO: This should probably be done with something like semaphores) | |||||
Atlas_NotifyEndOfFrame(); // (TODO: rename to NotifyEndOfQuiteShortProcessingPeriodSoPleaseSendMeNewMessages or something) | |||||
SDL_Delay(50); | |||||
if (!msgPasser->IsEmpty()) | |||||
break; | |||||
time = timer_Time(); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
Atlas_NotifyEndOfFrame(); | |||||
SDL_Delay(0); | |||||
} | |||||
} | |||||
} | |||||
bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) | bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) | ||||
{ | { | ||||
// Load required symbols from the DLL | // Load required symbols from the DLL | ||||
try | try | ||||
{ | { | ||||
dll.LoadSymbol("Atlas_StartWindow", Atlas_StartWindow); | dll.LoadSymbol("Atlas_StartWindow", Atlas_StartWindow); | ||||
dll.LoadSymbol("Atlas_SetMessagePasser", Atlas_SetMessagePasser); | dll.LoadSymbol("Atlas_SetMessagePasser", Atlas_SetMessagePasser); | ||||
dll.LoadSymbol("Atlas_SetDataDirectory", Atlas_SetDataDirectory); | dll.LoadSymbol("Atlas_SetDataDirectory", Atlas_SetDataDirectory); | ||||
Show All 21 Lines | bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) | ||||
// Tell Atlas the location of the data directory | // Tell Atlas the location of the data directory | ||||
const Paths paths(args); | const Paths paths(args); | ||||
Atlas_SetDataDirectory(paths.RData().string().c_str()); | Atlas_SetDataDirectory(paths.RData().string().c_str()); | ||||
// Tell Atlas the location of the user config directory | // Tell Atlas the location of the user config directory | ||||
Atlas_SetConfigDirectory(paths.Config().string().c_str()); | Atlas_SetConfigDirectory(paths.Config().string().c_str()); | ||||
// Run the engine loop in a new thread | RegisterHandlers(); | ||||
std::thread engineThread = std::thread(RunEngine, std::ref(args)); | |||||
// Disable the game's cursor rendering | |||||
extern CStrW g_CursorName; | |||||
Not Done Inline ActionsBecause we can't use it? Stan: Because we can't use it? | |||||
g_CursorName = L""; | |||||
state.args = args; | |||||
state.running = true; | |||||
state.view = AtlasView::GetView_None(); | |||||
state.glCanvas = NULL; | |||||
// Start Atlas UI on main thread | // Start Atlas UI on main thread | ||||
// (required for wxOSX/Cocoa compatibility - see http://trac.wildfiregames.com/ticket/500) | // (required for wxOSX/Cocoa compatibility - see http://trac.wildfiregames.com/ticket/500) | ||||
Atlas_StartWindow(L"ScenarioEditor"); | Atlas_StartWindow(L"ScenarioEditor"); | ||||
// Wait for the engine to exit | |||||
engineThread.join(); | |||||
// TODO: delete all remaining messages, to avoid memory leak warnings | // TODO: delete all remaining messages, to avoid memory leak warnings | ||||
// Restore main thread | // Restore main thread | ||||
ThreadUtil::SetMainThread(); | ThreadUtil::SetMainThread(); | ||||
// Clean up | // Clean up | ||||
AtlasView::DestroyViews(); | AtlasView::DestroyViews(); | ||||
AtlasMessage::g_MessagePasser = NULL; | AtlasMessage::g_MessagePasser = NULL; | ||||
return true; | return true; | ||||
} | } |
Wildfire Games · Phabricator
You had access to the SDL here, what changed?