1
0
forked from 0ad/0ad

Moves Atlas UI to main thread while engine loop runs in new thread.

Fixes Atlas compatibility with OS X (wxOSX/Cocoa requires the UI to run
in the main thread). Fixes #500.
Fixes Windows shutdown to close COM library properly (and WMI).

This was SVN commit r10299.
This commit is contained in:
historic_bruno 2011-09-20 22:49:02 +00:00
parent e66a0c6dac
commit 08b4d96cf2
4 changed files with 94 additions and 57 deletions

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2010 Wildfire Games
/* Copyright (c) 2011 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -42,6 +42,8 @@ _COM_SMARTPTR_TYPEDEF(IEnumWbemClassObject, __uuidof(IEnumWbemClassObject));
static ModuleInitState initState;
static bool didInitCOM = false;
static Status Init()
{
HRESULT hr;
@ -49,6 +51,12 @@ static Status Init()
hr = CoInitialize(0);
ENSURE(hr == S_OK || hr == S_FALSE); // S_FALSE => already initialized
// balance calls to CoInitialize and CoUninitialize
if (hr == S_FALSE)
CoUninitialize();
else if (hr == S_OK)
didInitCOM = true;
hr = CoInitializeSecurity(0, -1, 0, 0, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE, 0);
if(FAILED(hr))
WARN_RETURN(ERR::_2);
@ -75,8 +83,18 @@ static void Shutdown()
{
pSvc->Release();
// note: don't shut down COM because other modules may still be using it.
//CoUninitialize();
if (didInitCOM)
{
/* From MSDN documentation: A thread must call CoUninitialize once for each successful call
* it has made to the CoInitialize or CoInitializeEx function, including any call that returns
* S_FALSE. Only the CoUninitialize call corresponding to the CoInitialize or CoInitializeEx
* call that initialized the library can close it.
*
* So it should be perfectly safe to call this, since it balances out the CoInitialize in Init
*/
CoUninitialize();
didInitCOM = false;
}
}
void wmi_Shutdown()

View File

@ -459,6 +459,9 @@ Status sys_pick_directory(OsPath& path)
return INFO::OK;
}
// Balance call to CoInitialize, which must have been successful
CoUninitialize();
WARN_RETURN(StatusFromWin());
}

View File

@ -102,6 +102,10 @@
#define MUST_INIT_X11 0
#endif
#if OS_WIN
extern void wmi_Shutdown();
#endif
#include <iostream>
ERROR_GROUP(System);
@ -683,6 +687,12 @@ void Shutdown(int UNUSED(flags))
delete &g_Profiler;
delete &g_ProfileViewer;
TIMER_END(L"shutdown misc");
#if OS_WIN
TIMER_BEGIN(L"shutdown wmi");
wmi_Shutdown();
TIMER_END(L"shutdown wmi");
#endif
}
#if OS_UNIX

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -73,21 +73,6 @@ static InputProcessor g_Input;
static GameLoopState state;
GameLoopState* g_GameLoop = &state;
static void* LaunchWindow(void* data)
{
debug_SetThreadName("atlas_window");
const wchar_t* windowName = reinterpret_cast<const wchar_t*>(data);
Atlas_StartWindow(windowName);
return NULL;
}
// Work out which Atlas window to launch, given the command-line arguments
static const wchar_t* FindWindowName(const CmdLineArgs& UNUSED(args))
{
return L"ScenarioEditor";
// (This is a bit pointless - there's no choice since we've deleted the ActorViewer)
}
static ErrorReactionInternal AtlasDisplayError(const wchar_t* text, size_t flags)
{
@ -114,47 +99,20 @@ static void RendererIncrementalLoad()
while (more && timer_Time() - startTime < maxTime);
}
bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll)
static void* RunEngine(void *data)
{
// Load required symbols from the DLL
try
{
dll.LoadSymbol("Atlas_StartWindow", Atlas_StartWindow);
dll.LoadSymbol("Atlas_SetMessagePasser", Atlas_SetMessagePasser);
dll.LoadSymbol("Atlas_SetDataDirectory", Atlas_SetDataDirectory);
dll.LoadSymbol("Atlas_GLSetCurrent", Atlas_GLSetCurrent);
dll.LoadSymbol("Atlas_GLSwapBuffers", Atlas_GLSwapBuffers);
dll.LoadSymbol("Atlas_NotifyEndOfFrame", Atlas_NotifyEndOfFrame);
dll.LoadSymbol("Atlas_DisplayError", Atlas_DisplayError);
dll.LoadSymbol("Atlas_ReportError", Atlas_ReportError);
dll.LoadSymbol("ShareableMalloc", ShareableMallocFptr);
dll.LoadSymbol("ShareableFree", ShareableFreeFptr);
}
catch (PSERROR_DllLoader&)
{
debug_warn(L"Failed to initialise DLL");
return false;
}
debug_SetThreadName("engine_thread");
// Construct a message passer for communicating with Atlas
MessagePasserImpl msgPasser;
AtlasMessage::g_MessagePasser = &msgPasser;
// Set new main thread so that all the thread-safety checks pass
ThreadUtil::SetMainThread();
// Pass our message handler to Atlas
Atlas_SetMessagePasser(&msgPasser);
const CmdLineArgs args = *reinterpret_cast<const CmdLineArgs*>(data);
// Tell Atlas the location of the data directory
const Paths paths(args);
Atlas_SetDataDirectory(paths.RData().string().c_str());
MessagePasserImpl* msgPasser = (MessagePasserImpl*)AtlasMessage::g_MessagePasser;
// Register all the handlers for message which might be passed back
RegisterHandlers();
// Create a new thread, and launch the Atlas window inside that thread
const wchar_t* windowName = FindWindowName(args);
pthread_t uiThread;
pthread_create(&uiThread, NULL, LaunchWindow, reinterpret_cast<void*>(const_cast<wchar_t*>(windowName)));
// Override ah_display_error to pass all errors to the Atlas UI
AppHooks hooks = {0};
hooks.display_error = AtlasDisplayError;
@ -199,7 +157,7 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll)
{
IMessage* msg;
while ((msg = msgPasser.Retrieve()) != NULL)
while ((msg = msgPasser->Retrieve()) != NULL)
{
recent_activity = true;
@ -294,7 +252,7 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll)
// (TODO: This should probably be done with something like semaphores)
Atlas_NotifyEndOfFrame(); // (TODO: rename to NotifyEndOfQuiteShortProcessingPeriodSoPleaseSendMeNewMessages or something)
SDL_Delay(50);
if (!msgPasser.IsEmpty())
if (!msgPasser->IsEmpty())
break;
time = timer_Time();
}
@ -306,10 +264,58 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll)
}
}
return NULL;
}
bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll)
{
// Load required symbols from the DLL
try
{
dll.LoadSymbol("Atlas_StartWindow", Atlas_StartWindow);
dll.LoadSymbol("Atlas_SetMessagePasser", Atlas_SetMessagePasser);
dll.LoadSymbol("Atlas_SetDataDirectory", Atlas_SetDataDirectory);
dll.LoadSymbol("Atlas_GLSetCurrent", Atlas_GLSetCurrent);
dll.LoadSymbol("Atlas_GLSwapBuffers", Atlas_GLSwapBuffers);
dll.LoadSymbol("Atlas_NotifyEndOfFrame", Atlas_NotifyEndOfFrame);
dll.LoadSymbol("Atlas_DisplayError", Atlas_DisplayError);
dll.LoadSymbol("Atlas_ReportError", Atlas_ReportError);
dll.LoadSymbol("ShareableMalloc", ShareableMallocFptr);
dll.LoadSymbol("ShareableFree", ShareableFreeFptr);
}
catch (PSERROR_DllLoader&)
{
debug_warn(L"Failed to initialise DLL");
return false;
}
// Construct a message passer for communicating with Atlas
// (here so that it's scope lasts beyond the game thread)
MessagePasserImpl msgPasser;
AtlasMessage::g_MessagePasser = &msgPasser;
// Pass our message handler to Atlas
Atlas_SetMessagePasser(&msgPasser);
// Tell Atlas the location of the data directory
const Paths paths(args);
Atlas_SetDataDirectory(paths.RData().string().c_str());
// run the engine loop in a new thread
pthread_t engineThread;
pthread_create(&engineThread, NULL, RunEngine, reinterpret_cast<void*>(const_cast<CmdLineArgs*>(&args)));
// start Atlas UI on main thread
// (required for wxOSX/Cocoa compatbility as some parts of the API aren't thread-safe)
Atlas_StartWindow(L"ScenarioEditor");
// Wait for the engine to exit
pthread_join(engineThread, NULL);
// TODO: delete all remaining messages, to avoid memory leak warnings
// Wait for the UI to exit
pthread_join(uiThread, NULL);
// Restore main thread
ThreadUtil::SetMainThread();
// Clean up
View::DestroyViews();