Index: source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp +++ source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp @@ -675,6 +675,7 @@ void ScenarioEditor::OnTimer(wxTimerEvent&) { UpdateTool(m_ToolManager); + POST_MESSAGE(RenderLoop, ); } void ScenarioEditor::OnIdle(wxIdleEvent&) { Index: source/tools/atlas/GameInterface/GameLoop.cpp =================================================================== --- source/tools/atlas/GameInterface/GameLoop.cpp +++ source/tools/atlas/GameInterface/GameLoop.cpp @@ -29,7 +29,6 @@ #include "InputProcessor.h" #include "graphics/TextureManager.h" -#include "gui/GUIManager.h" #include "lib/app_hooks.h" #include "lib/external_libraries/libsdl.h" #include "lib/timer.h" @@ -101,175 +100,6 @@ 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(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); - } - 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 - 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) { // Load required symbols from the DLL @@ -307,16 +137,21 @@ // Tell Atlas the location of the user config directory Atlas_SetConfigDirectory(paths.Config().string().c_str()); - // Run the engine loop in a new thread - std::thread engineThread = std::thread(RunEngine, std::ref(args)); + RegisterHandlers(); + + // 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; // Start Atlas UI on main thread // (required for wxOSX/Cocoa compatibility - see http://trac.wildfiregames.com/ticket/500) Atlas_StartWindow(L"ScenarioEditor"); - // Wait for the engine to exit - engineThread.join(); - // TODO: delete all remaining messages, to avoid memory leak warnings // Restore main thread Index: source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp =================================================================== --- source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp +++ source/tools/atlas/GameInterface/Handlers/GraphicsSetupHandlers.cpp @@ -22,9 +22,11 @@ #include "../CommandProc.h" #include "../ActorViewer.h" #include "../View.h" +#include "../InputProcessor.h" #include "graphics/GameView.h" #include "graphics/ObjectManager.h" +#include "gui/GUIManager.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "maths/MathUtil.h" @@ -35,6 +37,8 @@ #include "ps/GameSetup/GameSetup.h" #include "renderer/Renderer.h" +static InputProcessor g_Input; + namespace AtlasMessage { // see comment in GameLoop.cpp about ah_display_error before using INIT_HAVE_DISPLAY_ERROR @@ -192,6 +196,41 @@ #endif } +MESSAGEHANDLER(RenderLoop) +{ + { + 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 + g_AtlasGameLoop->realFrameLength = realFrameLength; + } + + g_Input.ProcessInput(g_AtlasGameLoop); + + 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(); + + g_AtlasGameLoop->view->Update(g_AtlasGameLoop->realFrameLength); + + g_AtlasGameLoop->view->Render(); + + if (CProfileManager::IsInitialised()) + g_Profiler.Frame(); +} + ////////////////////////////////////////////////////////////////////////// MESSAGEHANDLER(RenderStyle) Index: source/tools/atlas/GameInterface/MessagePasser.h =================================================================== --- source/tools/atlas/GameInterface/MessagePasser.h +++ source/tools/atlas/GameInterface/MessagePasser.h @@ -34,8 +34,6 @@ virtual void Add(IMessage*) = 0; // takes ownership of IMessage object - virtual IMessage* Retrieve() = 0; - virtual void Query(QueryMessage*, void(*timeoutCallback)()) = 0; // blocks; caller retains ownership of QueryMessage object }; Index: source/tools/atlas/GameInterface/MessagePasserImpl.h =================================================================== --- source/tools/atlas/GameInterface/MessagePasserImpl.h +++ source/tools/atlas/GameInterface/MessagePasserImpl.h @@ -33,7 +33,6 @@ MessagePasserImpl(); ~MessagePasserImpl(); virtual void Add(AtlasMessage::IMessage* msg); - virtual AtlasMessage::IMessage* Retrieve(); virtual void Query(AtlasMessage::QueryMessage* qry, void(*timeoutCallback)()); bool IsEmpty(); Index: source/tools/atlas/GameInterface/MessagePasserImpl.cpp =================================================================== --- source/tools/atlas/GameInterface/MessagePasserImpl.cpp +++ source/tools/atlas/GameInterface/MessagePasserImpl.cpp @@ -21,6 +21,7 @@ #include "MessagePasserImpl.h" #include "Messages.h" +#include "Handlers/MessageHandler.h" #include "lib/timer.h" #include "lib/rand.h" @@ -86,101 +87,42 @@ if (m_Trace) debug_printf("%8.3f add message: %s\n", timer_Time(), msg->GetName()); + msgHandlers::const_iterator it = GetMsgHandlers().find(msg->GetName()); + if (it != GetMsgHandlers().end()) { - std::lock_guard lock(m_Mutex); - m_Queue.push(msg); + it->second(msg); } -} - -IMessage* MessagePasserImpl::Retrieve() -{ - // (It should be fairly easy to use a more efficient thread-safe queue, - // since there's only one thread adding items and one thread consuming; - // but it's not worthwhile yet.) - - IMessage* msg = NULL; - + else { - std::lock_guard lock(m_Mutex); - if (! m_Queue.empty()) - { - msg = m_Queue.front(); - m_Queue.pop(); - } + 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)", msg->GetName()); } - - if (m_Trace && msg) - debug_printf("%8.3f retrieved message: %s\n", timer_Time(), msg->GetName()); - - return msg; + // Delete the object - we took ownership of it. + AtlasMessage::ShareableDelete(msg); } -void MessagePasserImpl::Query(QueryMessage* qry, void(* UNUSED(timeoutCallback) )()) +void MessagePasserImpl::Query(QueryMessage* msg, void(* UNUSED(timeoutCallback) )()) { - ENSURE(qry); - ENSURE(qry->GetType() == IMessage::Query); + ENSURE(msg); + ENSURE(msg->GetType() == IMessage::Query); if (m_Trace) - debug_printf("%8.3f add query: %s\n", timer_Time(), qry->GetName()); - - // Set the semaphore, so we can block until the query has been handled - qry->m_Semaphore = static_cast(m_Semaphore); + debug_printf("%8.3f add query: %s\n", timer_Time(), msg->GetName()); + msgHandlers::const_iterator it = GetMsgHandlers().find(msg->GetName()); + if (it != GetMsgHandlers().end()) { - std::lock_guard lock(m_Mutex); - m_Queue.push(qry); + it->second(msg); } - - // Wait until the query handler has handled the query and called sem_post: - - - // The following code was necessary to avoid deadlock, but it still breaks - // in some cases (e.g. when Atlas issues a query before its event loop starts - // running) and doesn't seem to be the simplest possible solution. - // So currently we're trying to not do anything like that at all, and - // just stop the game making windows (which is what seems (from experience) to - // deadlock things) by overriding ah_display_error. Hopefully it'll work like - // that, and the redundant code below/elsewhere can be removed, but it's - // left in here in case it needs to be reinserted in the future to make it - // work. - // (See http://www.wildfiregames.com/forum/index.php?s=&showtopic=10236&view=findpost&p=174617) - -// // At least on Win32, it is necessary for the UI thread to run its event -// // loop to avoid deadlocking the system (particularly when the game -// // tries to show a dialog box); so timeoutCallback is called whenever we -// // think it's necessary for that to happen. -// -// #if OS_WIN -// // On Win32, use MsgWaitForMultipleObjects, which waits on the semaphore -// // but is also interrupted by incoming Windows-messages. -// // while (0 != (err = sem_msgwait_np(psem))) -// -// while (0 != (err = sem_wait(psem))) -// #else -// // TODO: On non-Win32, I have no idea whether the same problem exists; but -// // it might do, so call the callback every few seconds just in case it helps. -// struct timespec abs_timeout; -// clock_gettime(CLOCK_REALTIME, &abs_timeout); -// abs_timeout.tv_sec += 2; -// while (0 != (err = sem_timedwait(psem, &abs_timeout))) -// #endif - - while (0 != sem_wait(m_Semaphore)) + else { - // If timed out, call callback and try again -// if (errno == ETIMEDOUT) -// timeoutCallback(); -// else - // Keep retrying while EINTR, but other errors are probably fatal - if (errno != EINTR) - { - debug_warn(L"Semaphore wait failed"); - return; // (leaks the semaphore) - } + 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)", msg->GetName()); } - - // Clean up - qry->m_Semaphore = NULL; } bool MessagePasserImpl::IsEmpty() Index: source/tools/atlas/GameInterface/Messages.h =================================================================== --- source/tools/atlas/GameInterface/Messages.h +++ source/tools/atlas/GameInterface/Messages.h @@ -148,6 +148,8 @@ ((int, height)) ); +MESSAGE(RenderLoop, ); + ////////////////////////////////////////////////////////////////////////// // Messages for map panel