Index: ps/trunk/source/ps/GameSetup/HWDetect.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/HWDetect.cpp (nonexistent) +++ ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 8663) @@ -0,0 +1,93 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "scripting/ScriptingHost.h" +#include "scriptinterface/ScriptInterface.h" + +#include "lib/ogl.h" +#include "lib/utf8.h" +#include "lib/sysdep/cpu.h" +#include "lib/sysdep/gfx.h" +#include "lib/sysdep/os_cpu.h" +#include "ps/CLogger.h" +#include "ps/Filesystem.h" +#include "ps/VideoMode.h" + +void RunHardwareDetection() +{ + ScriptInterface& scriptInterface = g_ScriptingHost.GetScriptInterface(); + + // Load the detection script: + + const wchar_t* scriptName = L"hwdetect/hwdetect.js"; + CVFSFile file; + if (file.Load(g_VFS, scriptName) != PSRETURN_OK) + { + LOGERROR(L"Failed to load hardware detection script"); + return; + } + + LibError err; // ignore encoding errors + std::wstring code = wstring_from_utf8(file.GetAsString(), &err); + + scriptInterface.LoadScript(scriptName, code); + + // Collect all the settings we'll pass to the script: + + CScriptValRooted settings; + scriptInterface.Eval("({})", settings); + + scriptInterface.SetProperty(settings.get(), "os_unix", OS_UNIX, false); + scriptInterface.SetProperty(settings.get(), "os_linux", OS_LINUX, false); + scriptInterface.SetProperty(settings.get(), "os_macosx", OS_MACOSX, false); + scriptInterface.SetProperty(settings.get(), "os_win", OS_WIN, false); + + scriptInterface.SetProperty(settings.get(), "gfx_card", std::wstring(gfx_card), false); + scriptInterface.SetProperty(settings.get(), "gfx_drv_ver", std::wstring(gfx_drv_ver), false); + scriptInterface.SetProperty(settings.get(), "gfx_mem", gfx_mem, false); + + scriptInterface.SetProperty(settings.get(), "gl_vendor", std::string((const char*)glGetString(GL_VENDOR)), false); + scriptInterface.SetProperty(settings.get(), "gl_renderer", std::string((const char*)glGetString(GL_RENDERER)), false); + scriptInterface.SetProperty(settings.get(), "gl_version", std::string((const char*)glGetString(GL_VERSION)), false); + + const char* exts = ogl_ExtensionString(); + if (!exts) exts = ""; + scriptInterface.SetProperty(settings.get(), "gl_extensions", std::string(exts), false); + + scriptInterface.SetProperty(settings.get(), "video_xres", g_VideoMode.GetXRes(), false); + scriptInterface.SetProperty(settings.get(), "video_yres", g_VideoMode.GetYRes(), false); + scriptInterface.SetProperty(settings.get(), "video_bpp", g_VideoMode.GetBPP(), false); + + struct utsname un; + uname(&un); + scriptInterface.SetProperty(settings.get(), "uname_sysname", std::string(un.sysname), false); + scriptInterface.SetProperty(settings.get(), "uname_release", std::string(un.release), false); + scriptInterface.SetProperty(settings.get(), "uname_version", std::string(un.version), false); + scriptInterface.SetProperty(settings.get(), "uname_machine", std::string(un.machine), false); + + scriptInterface.SetProperty(settings.get(), "cpu_identifier", std::string(cpu_IdentifierString()), false); + scriptInterface.SetProperty(settings.get(), "cpu_frequency", os_cpu_ClockFrequency(), false); + + scriptInterface.SetProperty(settings.get(), "ram_total", (int)os_cpu_MemorySize(), false); + scriptInterface.SetProperty(settings.get(), "ram_free", (int)os_cpu_MemoryAvailable(), false); + + // Run the detection script: + + scriptInterface.CallFunctionVoid(scriptInterface.GetGlobalObject(), "RunHardwareDetection", settings); +} Property changes on: ps/trunk/source/ps/GameSetup/HWDetect.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 8662) +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 8663) @@ -1,950 +1,953 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "lib/app_hooks.h" #include "lib/input.h" #include "lib/lockfree.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/utf8.h" #include "lib/external_libraries/sdl.h" #include "lib/file/common/file_stats.h" #include "lib/res/h_mgr.h" #include "lib/res/graphics/cursor.h" #include "lib/res/sound/snd_mgr.h" #include "lib/sysdep/cursor.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/gfx.h" #include "lib/tex/tex.h" #if OS_WIN #include "lib/sysdep/os/win/wversion.h" #endif #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Font.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Loader.h" #include "ps/Overlay.h" #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MapReader.h" #include "graphics/MaterialManager.h" #include "graphics/ParticleEngine.h" #include "graphics/TerrainTextureManager.h" #include "renderer/Renderer.h" #include "renderer/VertexBufferManager.h" #include "maths/MathUtil.h" #include "simulation2/Simulation2.h" #include "scripting/ScriptingHost.h" #include "scripting/ScriptGlue.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptStats.h" #include "maths/scripting/JSInterface_Vector3D.h" #include "ps/scripting/JSInterface_Console.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/ScriptFunctions.h" #include "sound/JSI_Sound.h" #include "network/NetServer.h" #include "network/NetClient.h" #include "ps/Pyrogenesis.h" // psSetLogDir #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/GameSetup.h" #include "ps/GameSetup/Paths.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/CmdLineArgs.h" +#include "ps/GameSetup/HWDetect.h" #if !(OS_WIN || OS_MACOSX) // assume all other platforms use X11 for wxWidgets #define MUST_INIT_X11 1 #include #else #define MUST_INIT_X11 0 #endif #include ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); #define LOG_CATEGORY L"gamesetup" bool g_DoRenderGui = true; static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code static void SetTextureQuality(int quality) { int q_flags; GLint filter; retry: // keep this in sync with SANE_TEX_QUALITY_DEFAULT switch(quality) { // worst quality case 0: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_NEAREST; break; // [perf] add bilinear filtering case 1: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] no longer reduce resolution case 2: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] add mipmaps case 3: q_flags = OGL_TEX_HALF_BPP; filter = GL_NEAREST_MIPMAP_LINEAR; break; // [perf] better filtering case 4: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [vmem] no longer reduce bpp case SANE_TEX_QUALITY_DEFAULT: q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [perf] add anisotropy case 6: // TODO: add anisotropic filtering q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // invalid default: debug_warn(L"SetTextureQuality: invalid quality"); quality = SANE_TEX_QUALITY_DEFAULT; // careful: recursion doesn't work and we don't want to duplicate // the "sane" default values. goto retry; } ogl_tex_set_defaults(q_flags, filter); } //---------------------------------------------------------------------------- // GUI integration //---------------------------------------------------------------------------- // display progress / description in loading screen void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) { g_ScriptingHost.GetScriptInterface().SetGlobal("g_Progress", percent, true); g_ScriptingHost.GetScriptInterface().SetGlobal("g_LoadDescription", pending_task, true); g_GUI->SendEventToAll("progress"); } void Render() { ogl_WarnIfError(); CStr skystring = "255 0 255"; CFG_GET_USER_VAL("skycolor", String, skystring); CColor skycol; GUI::ParseString(skystring, skycol); g_Renderer.SetClearColor(skycol.AsSColor4ub()); // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->Render(); } ogl_WarnIfError(); // set up overlay mode glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); ogl_WarnIfError(); // Temp GUI message GeeTODO PROFILE_START("render gui"); if(g_DoRenderGui) g_GUI->Draw(); PROFILE_END("render gui"); ogl_WarnIfError(); // Particle Engine Updating CParticleEngine::GetInstance()->UpdateEmitters(); ogl_WarnIfError(); // Text: // Use the GL_ALPHA texture as the alpha channel with a flat colouring glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Added -- glEnable(GL_TEXTURE_2D); // -- GL glLoadIdentity(); PROFILE_START("render console"); g_Console->Render(); PROFILE_END("render console"); ogl_WarnIfError(); PROFILE_START("render logger"); g_Logger->Render(); PROFILE_END("render logger"); ogl_WarnIfError(); // Profile information PROFILE_START("render profiling"); g_ProfileViewer.RenderProfile(); PROFILE_END("render profiling"); ogl_WarnIfError(); // Draw the cursor (or set the Windows cursor, on Windows) CStrW cursorName = g_CursorName; if (cursorName.empty()) { cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y); } else { if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y) < 0) LOGWARNING(L"Failed to draw cursor '%ls'", cursorName.c_str()); } // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); g_Renderer.EndFrame(); ogl_WarnIfError(); } static void RegisterJavascriptInterfaces() { // maths JSI_Vector3D::init(); // graphics CGameView::ScriptingInit(); // renderer CRenderer::ScriptingInit(); // sound JSI_Sound::ScriptingInit(); // ps JSI_Console::init(); // GUI CGUI::ScriptingInit(); GuiScriptingInit(g_ScriptingHost.GetScriptInterface()); } static void InitScripting() { TIMER(L"InitScripting"); // Create the scripting host. This needs to be done before the GUI is created. // [7ms] new ScriptingHost; RegisterJavascriptInterfaces(); } #if 0 // disabled because the file cache doesn't work (http://trac.wildfiregames.com/ticket/611) static size_t OperatingSystemFootprint() { #if OS_WIN switch(wversion_Number()) { case WVERSION_2K: case WVERSION_XP: return 150; case WVERSION_XP64: return 200; default: // don't warn about newer Windows versions case WVERSION_VISTA: return 300; case WVERSION_7: return 250; } #else return 200; #endif } static size_t ChooseCacheSize() { // (all sizes in MiB) const size_t total = os_cpu_MemorySize(); const size_t available = os_cpu_MemoryAvailable(); debug_assert(total >= available); const size_t inUse = total-available; size_t os = OperatingSystemFootprint(); debug_assert(total >= os/2); size_t apps = (inUse > os)? (inUse - os) : 0; size_t game = 300; size_t cache = 200; // plenty of memory if(os + apps + game + cache < total) { cache = total - os - apps - game; } else // below min-spec { // assume kernel+other apps will be swapped out os = 9*os/10; apps = 2*apps/3; game = 3*game/4; if(os + apps + game + cache < total) cache = total - os - apps - game; else { cache = 50; debug_printf(L"Warning: memory size (%d MiB, %d used) is rather low\n", total, available); } } // total data is currently about 500 MiB, and not all of it // is used at once, so don't use more than that. // (this also ensures the byte count will fit in size_t and // avoids using up too much address space) cache = std::min(cache, (size_t)500); debug_printf(L"Cache: %d (total: %d; available: %d)\n", cache, total, available); return cache*MiB; } #else static size_t ChooseCacheSize() { return 32*MiB; } #endif ErrorReaction psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags)) { // If we're fullscreen, then sometimes (at least on some particular drivers on Linux) // displaying the error dialog hangs the desktop since the dialog box is behind the // fullscreen window. So we just force the game to windowed mode before displaying the dialog. // (But only if we're in the main thread, and not if we're being reentrant.) if (ThreadUtil::IsMainThread()) { static bool reentering = false; if (!reentering) { reentering = true; g_VideoMode.SetFullscreen(false); reentering = false; } } // We don't actually implement the error display here, so return appropriately return ER_NOT_IMPLEMENTED; } static void InitVfs(const CmdLineArgs& args) { TIMER(L"InitVfs"); const Paths paths(args); fs::wpath logs(paths.Logs()); CreateDirectories(logs, 0700); psSetLogDir(logs); // desired location for crashlog is now known. update AppHooks ASAP // (particularly before the following error-prone operations): AppHooks hooks = {0}; hooks.bundle_logs = psBundleLogs; hooks.get_log_dir = psLogDir; hooks.display_error = psDisplayError; app_hooks_update(&hooks); const size_t cacheSize = ChooseCacheSize(); g_VFS = CreateVfs(cacheSize); g_VFS->Mount(L"screenshots/", paths.Data()/L"screenshots/"); const fs::wpath readonlyConfig = paths.RData()/L"config/"; g_VFS->Mount(L"config/", readonlyConfig); if(readonlyConfig != paths.Config()) g_VFS->Mount(L"config/", paths.Config()); g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads) std::vector mods = args.GetMultiple("mod"); mods.push_back("public"); if(!args.Has("onlyPublicFiles")) mods.push_back("internal"); fs::wpath modArchivePath(paths.Cache()/L"mods"); fs::wpath modLoosePath(paths.RData()/L"mods"); for (size_t i = 0; i < mods.size(); ++i) { size_t priority = i; size_t flags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_MUST_EXIST; std::wstring modName (wstring_from_utf8(mods[i])); g_VFS->Mount(L"", AddSlash(modLoosePath/modName), flags, priority); g_VFS->Mount(L"", AddSlash(modArchivePath/modName), flags, priority); } // note: don't bother with g_VFS->TextRepresentation - directories // haven't yet been populated and are empty. } static void InitPs(bool setup_gui, const CStrW& gui_page, CScriptVal initData) { { // console TIMER(L"ps_console"); g_Console->UpdateScreenSize(g_xres, g_yres); // Calculate and store the line spacing CFont font(CONSOLE_FONT); g_Console->m_iFontHeight = font.GetLineSpacing(); g_Console->m_iFontWidth = font.GetCharacterWidth(L'C'); g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth); // Offset by an arbitrary amount, to make it fit more nicely g_Console->m_iFontOffset = 7; } // hotkeys { TIMER(L"ps_lang_hotkeys"); LoadHotkeys(); } if (!setup_gui) { // We do actually need *some* kind of GUI loaded, so use the // (currently empty) Atlas one g_GUI->SwitchPage(L"page_atlas.xml", initData); return; } // GUI uses VFS, so this must come after VFS init. g_GUI->SwitchPage(gui_page, initData); // Warn nicely about missing S3TC support if (!ogl_tex_has_s3tc()) { #if !(OS_WIN || OS_MACOSX) bool isMesa = true; #else bool isMesa = false; #endif g_GUI->GetScriptInterface().CallFunctionVoid(OBJECT_TO_JSVAL(g_GUI->GetScriptObject()), "s3tcWarning", isMesa); } } static void InitInput() { SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // register input handlers // This stack is constructed so the first added, will be the last // one called. This is important, because each of the handlers // has the potential to block events to go further down // in the chain. I.e. the last one in the list added, is the // only handler that can block all messages before they are // processed. in_add_handler(game_view_handler); in_add_handler(conInputHandler); in_add_handler(CProfileViewer::InputThunk); in_add_handler(HotkeyInputHandler); // gui_handler needs to be registered after (i.e. called before!) the // hotkey handler so that input boxes can be typed in without // setting off hotkeys. in_add_handler(gui_handler); // must be registered after (called before) the GUI which relies on these globals in_add_handler(GlobalsInputHandler); } static void ShutdownPs() { SAFE_DELETE(g_GUI); SAFE_DELETE(g_Console); // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y); } static void InitRenderer() { TIMER(L"InitRenderer"); if(g_NoGLS3TC) ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE); if(g_NoGLAutoMipmap) ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE); // create renderer new CRenderer; // set renderer options from command line options - NOVBO must be set before opening the renderer g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); g_Renderer.SetOptionBool(CRenderer::OPT_NOFRAMEBUFFEROBJECT,g_NoGLFramebufferObject); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_FANCYWATER,g_FancyWater); g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); // create terrain related stuff new CTerrainTextureManager; // create the material manager new CMaterialManager; g_Renderer.Open(g_xres,g_yres); // Setup lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. g_Renderer.SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; g_Renderer.SetViewport(vp); ColorActivateFastImpl(); } static void InitSDL() { if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOG(CLogger::Error, LOG_CATEGORY, L"SDL library initialization failed: %hs", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); SDL_EnableUNICODE(1); } static void ShutdownSDL() { SDL_Quit(); sys_cursor_reset(); } void EndGame() { SAFE_DELETE(g_NetServer); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } void Shutdown(int UNUSED(flags)) { EndGame(); ShutdownPs(); // Must delete g_GUI before g_ScriptingHost in_reset_handlers(); // destroy actor related stuff TIMER_BEGIN(L"shutdown actor stuff"); delete &g_MaterialManager; TIMER_END(L"shutdown actor stuff"); // destroy terrain related stuff TIMER_BEGIN(L"shutdown TexMan"); delete &g_TexMan; TIMER_END(L"shutdown TexMan"); // destroy renderer TIMER_BEGIN(L"shutdown Renderer"); delete &g_Renderer; g_VBMan.Shutdown(); TIMER_END(L"shutdown Renderer"); tex_codec_unregister_all(); TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); TIMER_END(L"shutdown SDL"); g_VideoMode.Shutdown(); TIMER_BEGIN(L"shutdown ScriptingHost"); delete &g_ScriptingHost; TIMER_END(L"shutdown ScriptingHost"); TIMER_BEGIN(L"shutdown ConfigDB"); delete &g_ConfigDB; TIMER_END(L"shutdown ConfigDB"); // resource // first shut down all resource owners, and then the handle manager. TIMER_BEGIN(L"resource modules"); snd_shutdown(); g_VFS.reset(); // this forcibly frees all open handles (thus preventing real leaks), // and makes further access to h_mgr impossible. h_mgr_shutdown(); file_stats_dump(); TIMER_END(L"resource modules"); TIMER_BEGIN(L"shutdown misc"); timer_DisplayClientTotals(); CNetHost::Deinitialize(); SAFE_DELETE(g_ScriptStatsTable); // should be last, since the above use them SAFE_DELETE(g_Logger); delete &g_Profiler; delete &g_ProfileViewer; TIMER_END(L"shutdown misc"); } void EarlyInit() { // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(232647); ThreadUtil::SetMainThread(); debug_SetThreadName("main"); // add all debug_printf "tags" that we are interested in: debug_filter_add(L"TIMER"); debug_filter_add(L"HRT"); cpu_ConfigureFloatingPoint(); timer_LatchStartTime(); // Because we do GL calls from a secondary thread, Xlib needs to // be told to support multiple threads safely. // This is needed for Atlas, but we have to call it before any other // Xlib functions (e.g. the ones used when drawing the main menu // before launching Atlas) #if MUST_INIT_X11 int status = XInitThreads(); if (status == 0) debug_printf(L"Error enabling thread-safety via XInitThreads\n"); #endif // Initialise the low-quality rand function srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! } static bool Autostart(const CmdLineArgs& args); void Init(const CmdLineArgs& args, int flags) { h_mgr_init(); // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. InitVfs(args); // This must come after VFS init, which sets the current directory // (required for finding our output log files). g_Logger = new CLogger; // Special command-line mode to dump the entity schemas instead of running the game. // (This must be done after loading VFS etc, but should be done before wasting time // on anything else.) if (args.Has("dumpSchema")) { CSimulation2 sim(NULL, NULL); sim.LoadDefaultScripts(); std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc); f << sim.GenerateSchema(); std::cout << "Generated entity.rng\n"; exit(0); } // override ah_translate with our i18n code. AppHooks hooks = {0}; hooks.translate = psTranslate; hooks.translate_free = psTranslateFree; app_hooks_update(&hooks); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) g_Console = new CConsole(); CNetHost::Initialize(); new CProfileViewer; new CProfileManager; // before any script code g_ScriptStatsTable = new CScriptStatsTable; g_ProfileViewer.AddRootTable(g_ScriptStatsTable); InitScripting(); // before GUI // g_ConfigDB, command line args, globals CONFIG_Init(args); } void InitGraphics(const CmdLineArgs& args, int flags) { const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0; if(setup_vmode) { InitSDL(); if (!g_VideoMode.InitSDL()) throw PSERROR_System_VmodeFailed(); // abort startup SDL_WM_SetCaption("0 A.D.", "0 A.D."); } - tex_codec_register_all(); - - const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file - SetTextureQuality(quality); - // needed by ogl_tex to detect broken gfx card/driver combos, // but takes a while due to WMI startup, so make it optional. if(!g_Quickstart) gfx_detect(); + RunHardwareDetection(); + + tex_codec_register_all(); + + const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file + SetTextureQuality(quality); + ogl_WarnIfError(); if(!g_Quickstart) { WriteSystemInfo(); // note: no longer vfs_display here. it's dog-slow due to unbuffered // file output and very rarely needed. } if(g_DisableAudio) { // speed up startup by disabling all sound // (OpenAL init will be skipped). // must be called before first snd_open. snd_disable(true); } g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface()); // (must come after SetVideoMode, since it calls ogl_Init) const char* missing = ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", NULL); if(missing) { wchar_t buf[500]; swprintf_s(buf, ARRAY_SIZE(buf), L"The %hs extension doesn't appear to be available on your computer." L" The game may still work, though - you are welcome to try at your own risk." L" If not or it doesn't look right, upgrade your graphics card.", missing ); DEBUG_DISPLAY_ERROR(buf); // TODO: i18n } if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar")) { DEBUG_DISPLAY_ERROR( L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer." L" Shadows are not available and overall graphics quality might suffer." L" You are advised to try installing newer drivers and/or upgrade your graphics card."); g_Shadows = false; } ogl_WarnIfError(); InitRenderer(); InitInput(); ogl_WarnIfError(); if (!Autostart(args)) { const bool setup_gui = ((flags & INIT_NO_GUI) == 0); InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID); } } void RenderGui(bool RenderingState) { g_DoRenderGui = RenderingState; } static bool Autostart(const CmdLineArgs& args) { /* * Handle various command-line options, for quick testing of various features: * -autostart=mapname -- single-player * -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players * -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1 */ CStr autostartMap = args.Get("autostart"); if (autostartMap.empty()) return false; g_Game = new CGame(); ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); CScriptValRooted attrs; scriptInterface.Eval("({})", attrs); scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false); scriptInterface.SetProperty(attrs.get(), "map", std::string(autostartMap), false); CScriptValRooted mpInitData; g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData); g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs", CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())), false); if (args.Has("autostart-host")) { InitPs(true, L"page_loading.xml", mpInitData.get()); size_t maxPlayers = 2; if (args.Has("autostart-players")) maxPlayers = args.Get("autostart-players").ToUInt(); g_NetServer = new CNetServer(maxPlayers); g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface); bool ok = g_NetServer->SetupConnection(); debug_assert(ok); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc g_NetClient->SetupConnection("127.0.0.1"); } else if (args.Has("autostart-client")) { InitPs(true, L"page_loading.xml", mpInitData.get()); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc CStr ip = "127.0.0.1"; if (args.Has("autostart-ip")) ip = args.Get("autostart-ip"); bool ok = g_NetClient->SetupConnection(ip); debug_assert(ok); } else { g_Game->SetPlayerID(1); g_Game->StartGame(attrs); LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); debug_assert(ret == PSRETURN_OK); InitPs(true, L"page_session.xml", JSVAL_VOID); } return true; } Index: ps/trunk/source/ps/GameSetup/HWDetect.h =================================================================== --- ps/trunk/source/ps/GameSetup/HWDetect.h (nonexistent) +++ ps/trunk/source/ps/GameSetup/HWDetect.h (revision 8663) @@ -0,0 +1,28 @@ +/* Copyright (C) 2010 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_HWDETECT +#define INCLUDED_HWDETECT + +/** + * Runs hardware-detection script to adjust default config settings + * and/or emit warnings depending on the user's system configuration. + * This must only be called after ogl_Init. + */ +void RunHardwareDetection(); + +#endif // INCLUDED_HWDETECT Property changes on: ps/trunk/source/ps/GameSetup/HWDetect.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp =================================================================== --- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 8662) +++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 8663) @@ -1,426 +1,433 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "scriptinterface/ScriptInterface.h" #include "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/MapReader.h" #include "gui/GUIManager.h" #include "lib/timer.h" #include "lib/sysdep/sysdep.h" #include "maths/FixedVector3D.h" #include "network/NetClient.h" #include "network/NetServer.h" #include "network/NetTurnManager.h" #include "ps/CLogger.h" #include "ps/CConsole.h" #include "ps/Errors.h" #include "ps/Game.h" #include "ps/Hotkey.h" #include "ps/Overlay.h" #include "ps/Pyrogenesis.h" #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/Config.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpCommandQueue.h" #include "simulation2/components/ICmpGuiInterface.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/helpers/Selection.h" #include "js/jsapi.h" /* * This file defines a set of functions that are available to GUI scripts, to allow * interaction with the rest of the engine. * Functions are exposed to scripts within the global object 'Engine', so * scripts should call "Engine.FunctionName(...)" etc. */ extern void restart_mainloop_in_atlas(); // from main.cpp namespace { CScriptVal GetActiveGui(void* UNUSED(cbdata)) { return OBJECT_TO_JSVAL(g_GUI->GetScriptObject()); } void PushGuiPage(void* UNUSED(cbdata), std::wstring name, CScriptVal initData) { g_GUI->PushPage(name, initData); } void SwitchGuiPage(void* UNUSED(cbdata), std::wstring name, CScriptVal initData) { g_GUI->SwitchPage(name, initData); } void PopGuiPage(void* UNUSED(cbdata)) { g_GUI->PopPage(); } CScriptVal GuiInterfaceCall(void* cbdata, std::wstring name, CScriptVal data) { CGUIManager* guiManager = static_cast (cbdata); if (!g_Game) return JSVAL_VOID; CSimulation2* sim = g_Game->GetSimulation2(); debug_assert(sim); CmpPtr gui(*sim, SYSTEM_ENTITY); if (gui.null()) return JSVAL_VOID; int player = -1; if (g_Game) player = g_Game->GetPlayerID(); CScriptValRooted arg (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), data.get())); CScriptVal ret (gui->ScriptCall(player, name, arg.get())); return guiManager->GetScriptInterface().CloneValueFromOtherContext(sim->GetScriptInterface(), ret.get()); } void PostNetworkCommand(void* cbdata, CScriptVal cmd) { CGUIManager* guiManager = static_cast (cbdata); if (!g_Game) return; CSimulation2* sim = g_Game->GetSimulation2(); debug_assert(sim); CmpPtr queue(*sim, SYSTEM_ENTITY); if (queue.null()) return; jsval cmd2 = sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), cmd.get()); queue->PostNetworkCommand(cmd2); } std::vector PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y) { return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID()); } std::vector PickFriendlyEntitiesInRect(void* UNUSED(cbdata), int x0, int y0, int x1, int y1, int player) { return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player); } std::vector PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool onScreenOnly) { return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), onScreenOnly); } CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y) { CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, true); return CFixedVector3D(fixed::FromFloat(pos.X), fixed::FromFloat(pos.Y), fixed::FromFloat(pos.Z)); } std::wstring SetCursor(void* UNUSED(cbdata), std::wstring name) { std::wstring old = g_CursorName; g_CursorName = name; return old; } int GetPlayerID(void* UNUSED(cbdata)) { if (g_Game) return g_Game->GetPlayerID(); return -1; } std::wstring GetDefaultPlayerName(void* UNUSED(cbdata)) { // TODO: this should come from a config file or something std::wstring name = sys_get_user_name(); if (name.empty()) name = L"anonymous"; return name; } void StartNetworkGame(void* UNUSED(cbdata)) { debug_assert(g_NetServer); g_NetServer->StartGame(); } void StartGame(void* cbdata, CScriptVal attribs, int playerID) { CGUIManager* guiManager = static_cast (cbdata); debug_assert(!g_NetServer); debug_assert(!g_NetClient); debug_assert(!g_Game); g_Game = new CGame(); // Convert from GUI script context to sim script context CSimulation2* sim = g_Game->GetSimulation2(); CScriptValRooted gameAttribs (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(guiManager->GetScriptInterface(), attribs.get())); g_Game->SetPlayerID(playerID); g_Game->StartGame(gameAttribs); } void SetNetworkGameAttributes(void* cbdata, CScriptVal attribs) { CGUIManager* guiManager = static_cast (cbdata); debug_assert(g_NetServer); g_NetServer->UpdateGameAttributes(attribs, guiManager->GetScriptInterface()); } void StartNetworkHost(void* cbdata, std::wstring playerName) { CGUIManager* guiManager = static_cast (cbdata); debug_assert(!g_NetClient); debug_assert(!g_NetServer); debug_assert(!g_Game); g_NetServer = new CNetServer(); if (!g_NetServer->SetupConnection()) { guiManager->GetScriptInterface().ReportError("Failed to start server"); SAFE_DELETE(g_NetServer); return; } g_Game = new CGame(); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); if (!g_NetClient->SetupConnection("127.0.0.1")) { guiManager->GetScriptInterface().ReportError("Failed to connect to server"); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } } void StartNetworkJoin(void* cbdata, std::wstring playerName, std::string serverAddress) { CGUIManager* guiManager = static_cast (cbdata); debug_assert(!g_NetClient); debug_assert(!g_NetServer); debug_assert(!g_Game); g_Game = new CGame(); g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(playerName); if (!g_NetClient->SetupConnection(serverAddress)) { guiManager->GetScriptInterface().ReportError("Failed to connect to server"); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } } void DisconnectNetworkGame(void* UNUSED(cbdata)) { // TODO: we ought to do async reliable disconnections SAFE_DELETE(g_NetServer); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } CScriptVal PollNetworkClient(void* cbdata) { CGUIManager* guiManager = static_cast (cbdata); if (!g_NetClient) return CScriptVal(); CScriptValRooted poll = g_NetClient->GuiPoll(); // Convert from net client context to GUI script context return guiManager->GetScriptInterface().CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), poll.get()); } void AssignNetworkPlayer(void* UNUSED(cbdata), int playerID, std::string guid) { debug_assert(g_NetServer); g_NetServer->AssignPlayer(playerID, guid); } void SendNetworkChat(void* UNUSED(cbdata), std::wstring message) { debug_assert(g_NetClient); g_NetClient->SendChatMessage(message); } void OpenURL(void* UNUSED(cbdata), std::string url) { sys_open_url(url); } void RestartInAtlas(void* UNUSED(cbdata)) { restart_mainloop_in_atlas(); } bool AtlasIsAvailable(void* UNUSED(cbdata)) { return ATLAS_IsAvailable(); } CScriptVal LoadMapSettings(void* cbdata, std::wstring pathname) { CGUIManager* guiManager = static_cast (cbdata); CMapSummaryReader reader; if (reader.LoadMap(VfsPath(pathname + L".xml")) != PSRETURN_OK) return CScriptVal(); return reader.GetMapSettings(guiManager->GetScriptInterface()).get(); } /** * Start / stop camera following mode * @param entityid unit id to follow. If zero, stop following mode */ void CameraFollow(void* UNUSED(cbdata), entity_id_t entityid) { if (g_Game && g_Game->GetView()) g_Game->GetView()->CameraFollow(entityid, false); } void CameraFollowFPS(void* UNUSED(cbdata), entity_id_t entityid) { if (g_Game && g_Game->GetView()) g_Game->GetView()->CameraFollow(entityid, true); } bool HotkeyIsPressed_(void* UNUSED(cbdata), std::string hotkeyName) { return HotkeyIsPressed(hotkeyName); } +void DisplayErrorDialog(void* UNUSED(cbdata), std::wstring msg) +{ + debug_DisplayError(msg.c_str(), DE_NO_DEBUG_INFO, NULL, NULL, NULL, 0, NULL, NULL); +} + + void SetSimRate(void* UNUSED(cbdata), float rate) { g_Game->SetSimRate(rate); } void SetTurnLength(void* UNUSED(cbdata), int length) { if (g_NetServer) g_NetServer->SetTurnLength(length); else LOGERROR(L"Only network host can change turn length"); } // Focus the game camera on a given position. void SetCameraTarget(void* UNUSED(cbdata), float x, float y, float z) { g_Game->GetView()->ResetCameraTarget(CVector3D(x, y, z)); } // Deliberately cause the game to crash. // Currently implemented via access violation (read of address 0). // Useful for testing the crashlog/stack trace code. int Crash(void* UNUSED(cbdata)) { debug_printf(L"Crashing at user's request.\n"); return *(int*)0; } void DebugWarn(void* UNUSED(cbdata)) { debug_warn(L"Warning at user's request."); } // Force a JS garbage collection cycle to take place immediately. // Writes an indication of how long this took to the console. void ForceGC(void* cbdata) { CGUIManager* guiManager = static_cast (cbdata); double time = timer_Time(); JS_GC(guiManager->GetScriptInterface().GetContext()); time = timer_Time() - time; g_Console->InsertMessage(L"Garbage collection completed in: %f", time); } } // namespace void GuiScriptingInit(ScriptInterface& scriptInterface) { // GUI manager functions: scriptInterface.RegisterFunction("GetActiveGui"); scriptInterface.RegisterFunction("PushGuiPage"); scriptInterface.RegisterFunction("SwitchGuiPage"); scriptInterface.RegisterFunction("PopGuiPage"); // Simulation<->GUI interface functions: scriptInterface.RegisterFunction("GuiInterfaceCall"); scriptInterface.RegisterFunction("PostNetworkCommand"); // Entity picking scriptInterface.RegisterFunction, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint"); scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect"); scriptInterface.RegisterFunction, std::string, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities"); scriptInterface.RegisterFunction("GetTerrainAtPoint"); // Network / game setup functions scriptInterface.RegisterFunction("StartNetworkGame"); scriptInterface.RegisterFunction("StartGame"); scriptInterface.RegisterFunction("StartNetworkHost"); scriptInterface.RegisterFunction("StartNetworkJoin"); scriptInterface.RegisterFunction("DisconnectNetworkGame"); scriptInterface.RegisterFunction("PollNetworkClient"); scriptInterface.RegisterFunction("SetNetworkGameAttributes"); scriptInterface.RegisterFunction("AssignNetworkPlayer"); scriptInterface.RegisterFunction("SendNetworkChat"); // Misc functions scriptInterface.RegisterFunction("SetCursor"); scriptInterface.RegisterFunction("GetPlayerID"); scriptInterface.RegisterFunction("GetDefaultPlayerName"); scriptInterface.RegisterFunction("OpenURL"); scriptInterface.RegisterFunction("RestartInAtlas"); scriptInterface.RegisterFunction("AtlasIsAvailable"); scriptInterface.RegisterFunction("LoadMapSettings"); scriptInterface.RegisterFunction("CameraFollow"); scriptInterface.RegisterFunction("CameraFollowFPS"); scriptInterface.RegisterFunction("HotkeyIsPressed"); + scriptInterface.RegisterFunction("DisplayErrorDialog"); // Development/debugging functions scriptInterface.RegisterFunction("SetSimRate"); scriptInterface.RegisterFunction("SetTurnLength"); scriptInterface.RegisterFunction("SetCameraTarget"); scriptInterface.RegisterFunction("Crash"); scriptInterface.RegisterFunction("DebugWarn"); scriptInterface.RegisterFunction("ForceGC"); } Index: ps/trunk/source/lib/debug.h =================================================================== --- ps/trunk/source/lib/debug.h (revision 8662) +++ ps/trunk/source/lib/debug.h (revision 8663) @@ -1,547 +1,553 @@ /* Copyright (c) 2010 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * platform-independent debug support code. */ #ifndef INCLUDED_DEBUG #define INCLUDED_DEBUG // this module provides platform-independent debug facilities, useful for // diagnosing and reporting program errors. // - a symbol engine provides access to compiler-generated debug information and // can also give a stack trace including local variables; // - our more powerful assert() replacement gives a stack trace so // that the underlying problem becomes apparent; // - the output routines make for platform-independent logging and // crashlogs with "last-known activity" reporting. #include "lib/lib_errors.h" #include "lib/code_generation.h" /** * trigger a breakpoint when reached/"called". * if defined as a macro, the debugger can break directly into the * target function instead of one frame below it as with a conventional * call-based implementation. **/ #if MSC_VERSION # define debug_break __debugbreak // intrinsic "function" #else extern void debug_break(); #endif // for widening non-literals (e.g. __FILE__) // note: C99 says __func__ is a magic *variable*, and GCC doesn't allow // widening it via preprocessor. #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) //----------------------------------------------------------------------------- // output //----------------------------------------------------------------------------- /** * write a formatted string to the debug channel, subject to filtering * (see below). implemented via debug_puts - see performance note there. * * @param fmt Format string and varargs; see printf. **/ LIB_API void debug_printf(const wchar_t* fmt, ...) WPRINTF_ARGS(1); /** * translates and displays the given strings in a dialog. * this is typically only used when debug_DisplayError has failed or * is unavailable because that function is much more capable. * implemented via sys_display_msg; see documentation there. **/ LIB_API void debug_DisplayMessage(const wchar_t* caption, const wchar_t* msg); /// flags to customize debug_DisplayError behavior enum DebugDisplayErrorFlags { /** * disallow the Continue button. used e.g. if an exception is fatal. **/ DE_NO_CONTINUE = 1, /** * enable the Suppress button. set automatically by debug_DisplayError if * it receives a non-NULL suppress pointer. a flag is necessary because * the sys_display_error interface doesn't get that pointer. * rationale for automatic setting: this may prevent someone from * forgetting to specify it, and disabling Suppress despite having * passed a non-NULL pointer doesn't make much sense. **/ DE_ALLOW_SUPPRESS = 2, /** * do not trigger a breakpoint inside debug_DisplayError; caller * will take care of this if ER_BREAK is returned. this is so that the * debugger can jump directly into the offending function. **/ - DE_MANUAL_BREAK = 4 + DE_MANUAL_BREAK = 4, + + /** + * display just the given message; do not add any information about the + * call stack, do not write crashlogs, etc. + */ + DE_NO_DEBUG_INFO = 8 }; /** * a bool that is reasonably certain to be set atomically. * we cannot assume support for OpenMP (requires GCC 4.2) or C++0x, * so we'll have to resort to intptr_t, cpu_CAS and COMPILER_FENCE. **/ typedef volatile intptr_t atomic_bool; /** * value for suppress flag once set by debug_DisplayError. * rationale: this value is fairly distinctive and helps when * debugging the symbol engine. * initial value is 0 rather that another constant; this avoids * allocating .rdata space. **/ const atomic_bool DEBUG_SUPPRESS = 0xAB; /** * choices offered by the shared error dialog **/ enum ErrorReaction { /** * ignore, continue as if nothing happened. * note: value doesn't start at 0 because that is interpreted as a * DialogBoxParam failure. **/ ER_CONTINUE = 1, /** * trigger breakpoint, i.e. enter debugger. * only returned if DE_MANUAL_BREAK was passed; otherwise, * debug_DisplayError will trigger a breakpoint itself. **/ ER_BREAK, /** * ignore and do not report again. * note: non-persistent; only applicable during this program run. * acted on by debug_DisplayError; never returned to caller. **/ ER_SUPPRESS, /** * exit the program immediately. * acted on by debug_DisplayError; never returned to caller. **/ ER_EXIT, /** * special return value for the display_error app hook stub to indicate * that it has done nothing and that the normal sys_display_error * implementation should be called instead. * acted on by debug_DisplayError; never returned to caller. **/ ER_NOT_IMPLEMENTED }; /** * display an error dialog with a message and stack trace. * * @param description text to show. * @param flags: see DebugDisplayErrorFlags. * @param context, lastFuncToSkip: see debug_DumpStack. * @param file, line, func: location of the error (typically passed as * WIDEN(__FILE__), __LINE__, __func__ from a macro) * @param suppress pointer to a caller-allocated flag that can be used to * suppress this error. if NULL, this functionality is skipped and the * "Suppress" dialog button will be disabled. * note: this flag is read and written exclusively here; caller only * provides the storage. values: see DEBUG_SUPPRESS above. * @return ErrorReaction (user's choice: continue running or stop?) **/ LIB_API ErrorReaction debug_DisplayError(const wchar_t* description, size_t flags, void* context, const wchar_t* lastFuncToSkip, const wchar_t* file, int line, const char* func, atomic_bool* suppress); /** * convenience version, in case the advanced parameters aren't needed. * macro instead of providing overload/default values for C compatibility. **/ #define DEBUG_DISPLAY_ERROR(text) debug_DisplayError(text, 0, 0, L"debug_DisplayError", WIDEN(__FILE__),__LINE__,__func__, 0) // // filtering // /** * debug output is very useful, but "too much of a good thing can kill you". * we don't want to require different LOGn() macros that are enabled * depending on "debug level", because changing that entails lengthy * compiles and it's too coarse-grained. instead, we require all * strings to start with "tag_string|" (exact case and no quotes; * the alphanumeric-only \ identifies output type). * they are then subject to filtering: only if the tag has been * "added" via debug_filter_add is the appendant string displayed. * * this approach is easiest to implement and is fine because we control * all logging code. LIMODS falls from consideration since it's not * portable and too complex. * * notes: * - filter changes only affect subsequent debug_*printf calls; * output that didn't pass the filter is permanently discarded. * - strings not starting with a tag are always displayed. * - debug_filter_* can be called at any time and from the debugger, * but are not reentrant. * * in future, allow output with the given tag to proceed. * no effect if already added. **/ LIB_API void debug_filter_add(const wchar_t* tag); /** * in future, discard output with the given tag. * no effect if not currently added. **/ LIB_API void debug_filter_remove(const wchar_t* tag); /** * clear all filter state; equivalent to debug_filter_remove for * each tag that was debug_filter_add-ed. **/ LIB_API void debug_filter_clear(); /** * indicate if the given text would be printed. * useful for a series of debug_printfs - avoids needing to add a tag to * each of their format strings. **/ LIB_API bool debug_filter_allows(const wchar_t* text); /** * write an error description and all logs into crashlog.txt * (in unicode format). * * @param text description of the error (including stack trace); * typically generated by debug_BuildErrorMessage. * * @return LibError; ERR::REENTERED if reentered via recursion or * multithreading (not allowed since an infinite loop may result). **/ LIB_API LibError debug_WriteCrashlog(const wchar_t* text); //----------------------------------------------------------------------------- // debug_assert //----------------------------------------------------------------------------- /** * make sure the expression \ evaluates to non-zero. used to validate * invariants in the program during development and thus gives a * very helpful warning if something isn't going as expected. * sprinkle these liberally throughout your code! * * recommended use is debug_assert(expression && "descriptive string") - * the string can pass more information about the problem on to whomever * is seeing the error. * * rationale: we call this "debug_assert" instead of "assert" for the * following reasons: * - consistency (everything here is prefixed with debug_) and * - to avoid inadvertent use of the much less helpful built-in CRT assert. * if we were to override assert, it would be difficult to tell whether * user source has included \ (possibly indirectly via other * headers) and thereby stomped on our definition. **/ #define debug_assert(expr) \ STMT(\ static atomic_bool suppress__;\ if(!(expr))\ {\ switch(debug_OnAssertionFailure(WIDEN(#expr), &suppress__, WIDEN(__FILE__), __LINE__, __func__))\ {\ case ER_BREAK:\ debug_break();\ break;\ default:\ break;\ }\ }\ ) /** * show a dialog to make sure unexpected states in the program are noticed. * this is less error-prone than "debug_assert(0 && "text");" and avoids * "conditional expression is constant" warnings. we'd really like to * completely eliminate the problem; replacing 0 literals with LIB_API * volatile variables fools VC7 but isn't guaranteed to be free of overhead. * we therefore just squelch the warning (unfortunately non-portable). * this duplicates the code from debug_assert to avoid compiler warnings about * constant conditions. * * if being able to suppress the warning is desirable (e.g. for self-tests), * then use DEBUG_WARN_ERR instead. **/ #define debug_warn(expr) \ STMT(\ static atomic_bool suppress__;\ switch(debug_OnAssertionFailure(expr, &suppress__, WIDEN(__FILE__), __LINE__, __func__))\ {\ case ER_BREAK:\ debug_break();\ break;\ default:\ break;\ }\ ) /** * if (LibError)err indicates an function failed, display the error dialog. * used by CHECK_ERR et al., which wrap function calls and automatically * warn user and return to caller. **/ #define DEBUG_WARN_ERR(err)\ STMT(\ static atomic_bool suppress__;\ switch(debug_OnError(err, &suppress__, WIDEN(__FILE__), __LINE__, __func__))\ {\ case ER_BREAK:\ debug_break();\ break;\ default:\ break;\ }\ ) /** * called when a debug_assert fails; * notifies the user via debug_DisplayError. * * @param assert_expr the expression that failed; typically passed as * \#expr in the assert macro. * @param suppress see debug_DisplayError. * @param file, line source file name and line number of the spot that failed * @param func name of the function containing it * @return ErrorReaction (user's choice: continue running or stop?) **/ LIB_API ErrorReaction debug_OnAssertionFailure(const wchar_t* assert_expr, atomic_bool* suppress, const wchar_t* file, int line, const char* func); /** * called when a DEBUG_WARN_ERR indicates an error occurred; * notifies the user via debug_DisplayError. * * @param err LibError value indicating the error that occurred * @param suppress see debug_DisplayError. * @param file, line source file name and line number of the spot that failed * @param func name of the function containing it * @return ErrorReaction (user's choice: continue running or stop?) **/ LIB_API ErrorReaction debug_OnError(LibError err, atomic_bool* suppress, const wchar_t* file, int line, const char* func); /** * suppress (prevent from showing) the error dialog from subsequent * debug_OnError for the given LibError. * * rationale: for edge cases in some functions, warnings are raised in * addition to returning an error code. self-tests deliberately trigger * these cases and check for the latter but shouldn't cause the former. * we therefore need to squelch them. * * @param err the LibError to skip. * * note: only one concurrent skip request is allowed; call * debug_StopSkippingErrors before the next debug_SkipErrors. */ LIB_API void debug_SkipErrors(LibError err); LIB_API size_t debug_StopSkippingErrors(); //----------------------------------------------------------------------------- // symbol access //----------------------------------------------------------------------------- namespace ERR { const LibError SYM_NO_STACK_FRAMES_FOUND = -100400; const LibError SYM_UNRETRIEVABLE_STATIC = -100401; const LibError SYM_UNRETRIEVABLE = -100402; const LibError SYM_TYPE_INFO_UNAVAILABLE = -100403; const LibError SYM_INTERNAL_ERROR = -100404; const LibError SYM_UNSUPPORTED = -100405; const LibError SYM_CHILD_NOT_FOUND = -100406; // this limit is to prevent infinite recursion. const LibError SYM_NESTING_LIMIT = -100407; // this limit is to prevent large symbols (e.g. arrays or linked lists) // from taking up all available output space. const LibError SYM_SINGLE_SYMBOL_LIMIT = -100408; } namespace INFO { // one of the dump_sym* functions decided not to output anything at // all (e.g. for member functions in UDTs - we don't want those). // therefore, skip any post-symbol formatting (e.g. ) as well. const LibError SYM_SUPPRESS_OUTPUT = +100409; } /** * Maximum number of characters (including trailing \\0) written to * user's buffers by debug_ResolveSymbol. **/ const size_t DBG_SYMBOL_LEN = 1000; const size_t DBG_FILE_LEN = 100; /** * read and return symbol information for the given address. * * NOTE: the PDB implementation is rather slow (~500us). * * @param ptr_of_interest address of symbol (e.g. function, variable) * @param sym_name optional out; size >= DBG_SYMBOL_LEN chars; * receives symbol name returned via debug info. * @param file optional out; size >= DBG_FILE_LEN chars; receives * base name only (no path; see rationale in wdbg_sym) of * source file containing the symbol. * @param line optional out; receives source file line number of symbol. * * note: all of the output parameters are optional; we pass back as much * information as is available and desired. * @return LibError; INFO::OK iff any information was successfully * retrieved and stored. **/ LIB_API LibError debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line); /** * write a complete stack trace (including values of local variables) into * the specified buffer. * * @param buf Target buffer. * @param maxChars Max chars of buffer (should be several thousand). * @param context Platform-specific representation of execution state * (e.g. Win32 CONTEXT). if not NULL, tracing starts there; this is useful * for exceptions. Otherwise, tracing starts from the current call stack. * @param lastFuncToSkip Is used for omitting error-reporting functions like * debug_OnAssertionFailure from the stack trace. It is either 0 (skip nothing) or * a substring of a function's name (this allows platform-independent * matching of stdcall-decorated names). * Rationale: this is safer than specifying a fixed number of frames, * which can be incorrect due to inlining. * @return LibError; ERR::REENTERED if reentered via recursion or * multithreading (not allowed since static data is used). **/ LIB_API LibError debug_DumpStack(wchar_t* buf, size_t maxChars, void* context, const wchar_t* lastFuncToSkip); //----------------------------------------------------------------------------- // helper functions (used by implementation) //----------------------------------------------------------------------------- /** * [system-dependent] write a string to the debug channel. * this can be quite slow (~1 ms)! On Windows, it uses OutputDebugString * (entails context switch), otherwise stdout+fflush (waits for IO). **/ LIB_API void debug_puts(const wchar_t* text); /** * return the caller of a certain function on the call stack. * * this function is useful for recording (partial) stack traces for * memory allocation tracking, etc. * * @param context, lastFuncToSkip - see debug_DumpStack * @return address of the caller **/ LIB_API void* debug_GetCaller(void* context, const wchar_t* lastFuncToSkip); /** * check if a pointer appears to be totally invalid. * * this check is not authoritative (the pointer may be "valid" but incorrect) * but can be used to filter out obviously wrong values in a portable manner. * * @param p pointer * @return 1 if totally bogus, otherwise 0. **/ LIB_API int debug_IsPointerBogus(const void* p); /// does the given pointer appear to point to code? LIB_API bool debug_IsCodePointer(void* p); /// does the given pointer appear to point to the stack? LIB_API bool debug_IsStackPointer(void* p); /** * inform the debugger of the current thread's name. * * (threads are easier to keep apart when they are identified by * name rather than TID.) **/ LIB_API void debug_SetThreadName(const char* name); /** * holds memory for an error message. **/ struct ErrorMessageMem { // rationale: // - error messages with stack traces require a good deal of memory // (hundreds of KB). static buffers of that size are undesirable. // - the heap may be corrupted, so don't use malloc. allocator.h's // page_aligned_malloc (implemented via mmap) should be safe. // - alloca is a bit iffy (the stack may be maxed out), non-portable and // complicates the code because it can't be allocated by a subroutine. // - this method is probably slow, but error messages aren't built often. // if necessary, first try malloc and use mmap if that fails. void* pa_mem; }; /** * free memory from the error message. * * @param emm ErrorMessageMem* **/ LIB_API void debug_FreeErrorMessage(ErrorMessageMem* emm); /** * build a string describing the given error. * * this is a helper function used by debug_DumpStack and is made available * so that the self-test doesn't have to display the error dialog. * * @param description: general description of the problem. * @param fn_only filename (no path) of source file that triggered the error. * @param line, func: exact position of the error. * @param context, lastFuncToSkip: see debug_DumpStack. * @param emm memory for the error message. caller should allocate * stack memory and set alloc_buf*; if not, there will be no * fallback in case heap alloc fails. should be freed via * debug_FreeErrorMessage when no longer needed. **/ LIB_API const wchar_t* debug_BuildErrorMessage(const wchar_t* description, const wchar_t* fn_only, int line, const char* func, void* context, const wchar_t* lastFuncToSkip, ErrorMessageMem* emm); #endif // #ifndef INCLUDED_DEBUG Index: ps/trunk/source/lib/sysdep/os/unix/unix.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/unix/unix.cpp (revision 8662) +++ ps/trunk/source/lib/sysdep/os/unix/unix.cpp (revision 8663) @@ -1,374 +1,380 @@ /* Copyright (c) 2010 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "precompiled.h" #include #include #include #include "lib/external_libraries/sdl.h" #include "lib/utf8.h" #include "lib/sysdep/sysdep.h" #include "lib/sysdep/cursor.h" #include "udbg.h" #include #define GNU_SOURCE #include #include #if OS_MACOSX #define URL_OPEN_COMMAND "open" #else #define URL_OPEN_COMMAND "xdg-open" #endif // these are basic POSIX-compatible backends for the sysdep.h functions. // Win32 has better versions which override these. void sys_display_msg(const wchar_t* caption, const wchar_t* msg) { fprintf(stderr, "%ls: %ls\n", caption, msg); // must not use fwprintf, since stderr is byte-oriented } static ErrorReaction try_gui_display_error(const wchar_t* text, bool manual_break, bool allow_suppress, bool no_continue) { pid_t cpid = fork(); if(cpid == -1) return ER_NOT_IMPLEMENTED; if(cpid == 0) { // This is the child process // Set ASCII charset, to avoid font warnings from xmessage setenv("LC_ALL", "C", 1); LibError err; // ignore UTF-8 errors std::string message = utf8_from_wstring(text, &err); // Replace CRLF->LF boost::algorithm::replace_all(message, "\r\n", "\n"); + // TODO: we ought to wrap the text if it's very long, + // since xmessage doesn't do that and it'll get clamped + // to the screen width + const char* cmd = "/usr/bin/xmessage"; char buttons[256] = ""; const char* defaultButton = "Exit"; if(!no_continue) { strcat_s(buttons, sizeof(buttons), "Continue:100,"); defaultButton = "Continue"; } if(allow_suppress) strcat_s(buttons, sizeof(buttons), "Suppress:101,"); strcat_s(buttons, sizeof(buttons), "Break:102,Debugger:103,Exit:104"); // Since execv wants non-const strings, we strdup them here // and don't care about the memory leak char* const argv[] = { strdup(cmd), + strdup("-geometry"), strdup("x500"), // set height so the box will always be very visible + strdup("-title"), strdup("0 A.D. message"), // TODO: maybe shouldn't hard-code app name strdup("-buttons"), buttons, strdup("-default"), strdup(defaultButton), strdup(message.c_str()), NULL }; execv(cmd, argv); // If exec returns, it failed //fprintf(stderr, "Error running %s: %d\n", cmd, errno); exit(-1); } // This is the parent process int status = 0; waitpid(cpid, &status, 0); // If it didn't exist successfully, fall back to the non-GUI prompt if(!WIFEXITED(status)) return ER_NOT_IMPLEMENTED; switch(WEXITSTATUS(status)) { case 103: // Debugger udbg_launch_debugger(); //-fallthrough case 102: // Break if(manual_break) return ER_BREAK; debug_break(); return ER_CONTINUE; case 100: // Continue if(!no_continue) return ER_CONTINUE; // continue isn't allowed, so this was invalid input. return ER_NOT_IMPLEMENTED; case 101: // Suppress if(allow_suppress) return ER_SUPPRESS; // suppress isn't allowed, so this was invalid input. return ER_NOT_IMPLEMENTED; case 104: // Exit abort(); return ER_EXIT; // placebo; never reached } // Unexpected return value - fall back to the non-GUI prompt return ER_NOT_IMPLEMENTED; } ErrorReaction sys_display_error(const wchar_t* text, size_t flags) { printf("%ls\n\n", text); const bool manual_break = (flags & DE_MANUAL_BREAK ) != 0; const bool allow_suppress = (flags & DE_ALLOW_SUPPRESS) != 0; const bool no_continue = (flags & DE_NO_CONTINUE ) != 0; // Try the GUI prompt if possible ErrorReaction ret = try_gui_display_error(text, manual_break, allow_suppress, no_continue); if (ret != ER_NOT_IMPLEMENTED) return ret; // Otherwise fall back to the terminal-based input // Loop until valid input given: for(;;) { if(!no_continue) printf("(C)ontinue, "); if(allow_suppress) printf("(S)uppress, "); printf("(B)reak, Launch (D)ebugger, or (E)xit?\n"); // TODO Should have some kind of timeout here.. in case you're unable to // access the controlling terminal (As might be the case if launched // from an xterm and in full-screen mode) int c = getchar(); // note: don't use tolower because it'll choke on EOF switch(c) { case EOF: case 'd': case 'D': udbg_launch_debugger(); //-fallthrough case 'b': case 'B': if(manual_break) return ER_BREAK; debug_break(); return ER_CONTINUE; case 'c': case 'C': if(!no_continue) return ER_CONTINUE; // continue isn't allowed, so this was invalid input. loop again. break; case 's': case 'S': if(allow_suppress) return ER_SUPPRESS; // suppress isn't allowed, so this was invalid input. loop again. break; case 'e': case 'E': abort(); return ER_EXIT; // placebo; never reached } } } LibError sys_error_description_r(int err, wchar_t* buf, size_t max_chars) { UNUSED2(err); UNUSED2(buf); UNUSED2(max_chars); // don't need to do anything: lib/errors.cpp already queries // libc's strerror(). if we ever end up needing translation of // e.g. Qt or X errors, that'd go here. return ERR::FAIL; } // stub for sys_cursor_create - we don't need to implement this (SDL/X11 only // has monochrome cursors so we need to use the software cursor anyways) // note: do not return ERR_NOT_IMPLEMENTED or similar because that // would result in WARN_ERRs. LibError sys_cursor_create(size_t w, size_t h, void* bgra_img, size_t hx, size_t hy, sys_cursor* cursor) { UNUSED2(w); UNUSED2(h); UNUSED2(hx); UNUSED2(hy); UNUSED2(bgra_img); *cursor = 0; return INFO::OK; } // creates an empty cursor LibError sys_cursor_create_empty(sys_cursor* cursor) { /* bitmap for a fully transparent cursor */ u8 data[] = {0}; u8 mask[] = {0}; // size 8x1 (cursor size must be a whole number of bytes ^^) // hotspot at 0,0 // SDL will make its own copies of data and mask *cursor = SDL_CreateCursor(data, mask, 8, 1, 0, 0); return cursor ? INFO::OK : ERR::FAIL; } SDL_Cursor *defaultCursor=NULL; // replaces the current system cursor with the one indicated. need only be // called once per cursor; pass 0 to restore the default. LibError sys_cursor_set(sys_cursor cursor) { // Gaah, SDL doesn't have a good API for setting the default cursor // SetCursor(NULL) just /repaints/ the cursor (well, obviously! or...) if(!defaultCursor) defaultCursor = SDL_GetCursor(); // restore default cursor. if(!cursor) SDL_SetCursor(defaultCursor); SDL_SetCursor((SDL_Cursor *)cursor); return INFO::OK; } // destroys the indicated cursor and frees its resources. if it is // currently the system cursor, the default cursor is restored first. LibError sys_cursor_free(sys_cursor cursor) { // bail now to prevent potential confusion below; there's nothing to do. if(!cursor) return INFO::OK; // if the cursor being freed is active, restore the default cursor // (just for safety). if (SDL_GetCursor() == (SDL_Cursor *)cursor) WARN_ERR(sys_cursor_set(NULL)); SDL_FreeCursor((SDL_Cursor *)cursor); return INFO::OK; } LibError sys_cursor_reset() { defaultCursor = NULL; return INFO::OK; } // note: just use the sector size: Linux aio doesn't really care about // the alignment of buffers/lengths/offsets, so we'll just pick a // sane value and not bother scanning all drives. size_t sys_max_sector_size() { // users may call us more than once, so cache the results. static size_t cached_sector_size; if(!cached_sector_size) cached_sector_size = sysconf(_SC_PAGE_SIZE); return cached_sector_size; } std::wstring sys_get_user_name() { // Prefer LOGNAME, fall back on getlogin const char* logname = getenv("LOGNAME"); if (logname && strcmp(logname, "") != 0) return std::wstring(logname, logname + strlen(logname)); // TODO: maybe we should do locale conversion? char buf[256]; if (getlogin_r(buf, ARRAY_SIZE(buf)) == 0) return std::wstring(buf, buf + strlen(buf)); return L""; } LibError sys_generate_random_bytes(u8* buf, size_t count) { FILE* f = fopen("/dev/urandom", "rb"); if (!f) WARN_RETURN(ERR::FAIL); while (count) { size_t numread = fread(buf, 1, count, f); if (numread == 0) WARN_RETURN(ERR::FAIL); buf += numread; count -= numread; } fclose(f); return INFO::OK; } LibError sys_open_url(const std::string& url) { pid_t pid = fork(); if (pid < 0) { debug_warn(L"Fork failed"); return ERR::FAIL; } else if (pid == 0) { // we are the child execlp(URL_OPEN_COMMAND, URL_OPEN_COMMAND, url.c_str(), (const char*)NULL); debug_printf(L"Failed to run '" URL_OPEN_COMMAND "' command\n"); // We can't call exit() because that'll try to free resources which were the parent's, // so just abort here abort(); } else { // we are the parent // TODO: maybe we should wait for the child and make sure it succeeded return INFO::OK; } } Index: ps/trunk/source/lib/debug.cpp =================================================================== --- ps/trunk/source/lib/debug.cpp (revision 8662) +++ ps/trunk/source/lib/debug.cpp (revision 8663) @@ -1,536 +1,545 @@ /* Copyright (c) 2010 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * platform-independent debug support code. */ #include "precompiled.h" #include "lib/debug.h" #include #include #include #include "lib/app_hooks.h" #include "lib/path_util.h" #include "lib/allocators/allocators.h" // page_aligned_alloc #include "lib/fnv_hash.h" #include "lib/sysdep/cpu.h" // cpu_CAS #include "lib/sysdep/sysdep.h" #if OS_WIN #include "lib/sysdep/os/win/wdbg_heap.h" #endif ERROR_ASSOCIATE(ERR::SYM_NO_STACK_FRAMES_FOUND, L"No stack frames found", -1); ERROR_ASSOCIATE(ERR::SYM_UNRETRIEVABLE_STATIC, L"Value unretrievable (stored in external module)", -1); ERROR_ASSOCIATE(ERR::SYM_UNRETRIEVABLE, L"Value unretrievable", -1); ERROR_ASSOCIATE(ERR::SYM_TYPE_INFO_UNAVAILABLE, L"Error getting type_info", -1); ERROR_ASSOCIATE(ERR::SYM_INTERNAL_ERROR, L"Exception raised while processing a symbol", -1); ERROR_ASSOCIATE(ERR::SYM_UNSUPPORTED, L"Symbol type not (fully) supported", -1); ERROR_ASSOCIATE(ERR::SYM_CHILD_NOT_FOUND, L"Symbol does not have the given child", -1); ERROR_ASSOCIATE(ERR::SYM_NESTING_LIMIT, L"Symbol nesting too deep or infinite recursion", -1); ERROR_ASSOCIATE(ERR::SYM_SINGLE_SYMBOL_LIMIT, L"Symbol has produced too much output", -1); ERROR_ASSOCIATE(INFO::SYM_SUPPRESS_OUTPUT, L"Symbol was suppressed", -1); // need to shoehorn printf-style variable params into // the OutputDebugString call. // - don't want to split into multiple calls - would add newlines to output. // - fixing Win32 _vsnprintf to return # characters that would be written, // as required by C99, looks difficult and unnecessary. if any other code // needs that, implement GNU vasprintf. // - fixed size buffers aren't nice, but much simpler than vasprintf-style // allocate+expand_until_it_fits. these calls are for quick debug output, // not loads of data, anyway. // rationale: static data instead of std::set to allow setting at any time. // we store FNV hash of tag strings for fast comparison; collisions are // extremely unlikely and can only result in displaying more/less text. static const size_t MAX_TAGS = 20; static u32 tags[MAX_TAGS]; static size_t num_tags; void debug_filter_add(const wchar_t* tag) { const u32 hash = fnv_hash(tag, wcslen(tag)*sizeof(tag[0])); // make sure it isn't already in the list for(size_t i = 0; i < MAX_TAGS; i++) if(tags[i] == hash) return; // too many already? if(num_tags == MAX_TAGS) { debug_assert(0); // increase MAX_TAGS return; } tags[num_tags++] = hash; } void debug_filter_remove(const wchar_t* tag) { const u32 hash = fnv_hash(tag, wcslen(tag)*sizeof(tag[0])); for(size_t i = 0; i < MAX_TAGS; i++) { if(tags[i] == hash) // found it { // replace with last element (avoid holes) tags[i] = tags[MAX_TAGS-1]; num_tags--; // can only happen once, so we're done. return; } } } void debug_filter_clear() { std::fill(tags, tags+MAX_TAGS, 0); } bool debug_filter_allows(const wchar_t* text) { size_t i; for(i = 0; ; i++) { // no | found => no tag => should always be displayed if(text[i] == ' ' || text[i] == '\0') return true; if(text[i] == '|' && i != 0) break; } const u32 hash = fnv_hash(text, i*sizeof(text[0])); // check if entry allowing this tag is found for(i = 0; i < MAX_TAGS; i++) if(tags[i] == hash) return true; return false; } #undef debug_printf // allowing #defining it out void debug_printf(const wchar_t* fmt, ...) { wchar_t buf[4096]; va_list ap; va_start(ap, fmt); const int numChars = vswprintf_s(buf, ARRAY_SIZE(buf), fmt, ap); debug_assert(numChars >= 0); va_end(ap); if(debug_filter_allows(buf)) debug_puts(buf); } //----------------------------------------------------------------------------- LibError debug_WriteCrashlog(const wchar_t* text) { // (avoid infinite recursion and/or reentering this function if it // fails/reports an error) enum State { IDLE, BUSY, FAILED }; // note: the initial state is IDLE. we rely on zero-init because // initializing local static objects from constants may happen when // this is first called, which isn't thread-safe. (see C++ 6.7.4) cassert(IDLE == 0); static volatile intptr_t state; if(!cpu_CAS(&state, IDLE, BUSY)) return ERR::REENTERED; // NOWARN FILE* f; fs::wpath pathname = ah_get_log_dir()/L"crashlog.txt"; errno_t err = _wfopen_s(&f, pathname.string().c_str(), L"w"); if(err != 0) { state = FAILED; // must come before DEBUG_DISPLAY_ERROR DEBUG_DISPLAY_ERROR(L"Unable to open crashlog.txt for writing (please ensure the log directory is writable)"); return ERR::FAIL; // NOWARN (the above text is more helpful than a generic error code) } fputwc(0xFEFF, f); // BOM fwprintf(f, L"%ls\n", text); fwprintf(f, L"\n\n====================================\n\n"); // allow user to bundle whatever information they want ah_bundle_logs(f); fclose(f); state = IDLE; return INFO::OK; } //----------------------------------------------------------------------------- // error message //----------------------------------------------------------------------------- // (NB: this may appear obscene, but deep stack traces have been // observed to take up > 256 KiB) static const size_t messageSize = 512*KiB; void debug_FreeErrorMessage(ErrorMessageMem* emm) { page_aligned_free(emm->pa_mem, messageSize); } // a stream with printf-style varargs and the possibility of // writing directly to the output buffer. class PrintfWriter { public: PrintfWriter(wchar_t* buf, size_t maxChars) : m_pos(buf), m_charsLeft(maxChars) { } bool operator()(const wchar_t* fmt, ...) WPRINTF_ARGS(2) { va_list ap; va_start(ap, fmt); const int len = vswprintf_s(m_pos, m_charsLeft, fmt, ap); va_end(ap); if(len < 0) return false; m_pos += len; m_charsLeft -= len; return true; } wchar_t* Position() const { return m_pos; } size_t CharsLeft() const { return m_charsLeft; } void CountAddedChars() { const size_t len = wcslen(m_pos); m_pos += len; m_charsLeft -= len; } private: wchar_t* m_pos; size_t m_charsLeft; }; // split out of debug_DisplayError because it's used by the self-test. const wchar_t* debug_BuildErrorMessage( const wchar_t* description, const wchar_t* filename, int line, const char* func, void* context, const wchar_t* lastFuncToSkip, ErrorMessageMem* emm) { // rationale: see ErrorMessageMem emm->pa_mem = page_aligned_alloc(messageSize); wchar_t* const buf = (wchar_t*)emm->pa_mem; if(!buf) return L"(insufficient memory to generate error message)"; PrintfWriter writer(buf, messageSize / sizeof(wchar_t)); // header if(!writer( L"%ls\r\n" L"Location: %ls:%d (%hs)\r\n" L"\r\n" L"Call stack:\r\n" L"\r\n", description, filename, line, func )) { fail: return L"(error while formatting error message)"; } // append stack trace LibError ret = debug_DumpStack(writer.Position(), writer.CharsLeft(), context, lastFuncToSkip); if(ret == ERR::REENTERED) { if(!writer( L"While generating an error report, we encountered a second " L"problem. Please be sure to report both this and the subsequent " L"error messages." )) goto fail; } else if(ret != INFO::OK) { wchar_t description_buf[100] = {'?'}; if(!writer( L"(error while dumping stack: %ls)", error_description_r(ret, description_buf, ARRAY_SIZE(description_buf)) )) goto fail; } else // success { writer.CountAddedChars(); } // append OS error (just in case it happens to be relevant - // it's usually still set from unrelated operations) wchar_t description_buf[100] = L"?"; LibError errno_equiv = LibError_from_errno(false); if(errno_equiv != ERR::FAIL) // meaningful translation error_description_r(errno_equiv, description_buf, ARRAY_SIZE(description_buf)); wchar_t os_error[100] = L"?"; sys_error_description_r(0, os_error, ARRAY_SIZE(os_error)); if(!writer( L"\r\n" L"errno = %d (%ls)\r\n" L"OS error = %ls\r\n", errno, description_buf, os_error )) goto fail; return buf; } //----------------------------------------------------------------------------- // display error messages //----------------------------------------------------------------------------- // translates and displays the given strings in a dialog. // this is typically only used when debug_DisplayError has failed or // is unavailable because that function is much more capable. // implemented via sys_display_msg; see documentation there. void debug_DisplayMessage(const wchar_t* caption, const wchar_t* msg) { sys_display_msg(ah_translate(caption), ah_translate(msg)); } // when an error has come up and user clicks Exit, we don't want any further // errors (e.g. caused by atexit handlers) to come up, possibly causing an // infinite loop. hiding errors isn't good, but we assume that whoever clicked // exit really doesn't want to see any more messages. static atomic_bool isExiting; // this logic is applicable to any type of error. special cases such as // suppressing certain expected WARN_ERRs are done there. static bool ShouldSuppressError(atomic_bool* suppress) { if(isExiting) return true; if(!suppress) return false; if(*suppress == DEBUG_SUPPRESS) return true; return false; } static ErrorReaction CallDisplayError(const wchar_t* text, size_t flags) { // first try app hook implementation ErrorReaction er = ah_display_error(text, flags); // .. it's only a stub: default to normal implementation if(er == ER_NOT_IMPLEMENTED) er = sys_display_error(text, flags); return er; } static ErrorReaction PerformErrorReaction(ErrorReaction er, size_t flags, atomic_bool* suppress) { const bool shouldHandleBreak = (flags & DE_MANUAL_BREAK) == 0; switch(er) { case ER_BREAK: // handle "break" request unless the caller wants to (doing so here // instead of within the dlgproc yields a correct call stack) if(shouldHandleBreak) { debug_break(); er = ER_CONTINUE; } break; case ER_SUPPRESS: (void)cpu_CAS(suppress, 0, DEBUG_SUPPRESS); er = ER_CONTINUE; break; case ER_EXIT: isExiting = 1; // see declaration COMPILER_FENCE; #if OS_WIN // prevent (slow) heap reporting since we're exiting abnormally and // thus probably leaking like a sieve. wdbg_heap_Enable(false); #endif exit(EXIT_FAILURE); } return er; } ErrorReaction debug_DisplayError(const wchar_t* description, size_t flags, void* context, const wchar_t* lastFuncToSkip, const wchar_t* pathname, int line, const char* func, atomic_bool* suppress) { // "suppressing" this error means doing nothing and returning ER_CONTINUE. if(ShouldSuppressError(suppress)) return ER_CONTINUE; // fix up params // .. translate description = ah_translate(description); // .. caller supports a suppress flag; set the corresponding flag so that // the error display implementation enables the Suppress option. if(suppress) flags |= DE_ALLOW_SUPPRESS; + + if(flags & DE_NO_DEBUG_INFO) + { + // in non-debug-info mode, simply display the given description + // and then return immediately + ErrorReaction er = CallDisplayError(description, flags); + return PerformErrorReaction(er, flags, suppress); + } + // .. deal with incomplete file/line info if(!pathname || pathname[0] == '\0') pathname = L"unknown"; if(line <= 0) line = 0; if(!func || func[0] == '\0') func = "?"; // .. _FILE__ evaluates to the full path (albeit without drive letter) // which is rather long. we only display the base name for clarity. const wchar_t* filename = path_name_only(pathname); // display in output window; double-click will navigate to error location. debug_printf(L"%ls(%d): %ls\n", filename, line, description); ErrorMessageMem emm; const wchar_t* text = debug_BuildErrorMessage(description, filename, line, func, context, lastFuncToSkip, &emm); (void)debug_WriteCrashlog(text); ErrorReaction er = CallDisplayError(text, flags); // note: debug_break-ing here to make sure the app doesn't continue // running is no longer necessary. debug_DisplayError now determines our // window handle and is modal. // must happen before PerformErrorReaction because that may exit. debug_FreeErrorMessage(&emm); return PerformErrorReaction(er, flags, suppress); } // is errorToSkip valid? (also guarantees mutual exclusion) enum SkipStatus { INVALID, VALID, BUSY }; static intptr_t skipStatus = INVALID; static LibError errorToSkip; static size_t numSkipped; void debug_SkipErrors(LibError err) { if(cpu_CAS(&skipStatus, INVALID, BUSY)) { errorToSkip = err; numSkipped = 0; COMPILER_FENCE; skipStatus = VALID; // linearization point } else DEBUG_WARN_ERR(ERR::REENTERED); } size_t debug_StopSkippingErrors() { if(cpu_CAS(&skipStatus, VALID, BUSY)) { const size_t ret = numSkipped; COMPILER_FENCE; skipStatus = INVALID; // linearization point return ret; } else { DEBUG_WARN_ERR(ERR::REENTERED); return 0; } } static bool ShouldSkipError(LibError err) { if(cpu_CAS(&skipStatus, VALID, BUSY)) { numSkipped++; const bool ret = (err == errorToSkip); COMPILER_FENCE; skipStatus = VALID; return ret; } return false; } ErrorReaction debug_OnError(LibError err, atomic_bool* suppress, const wchar_t* file, int line, const char* func) { if(ShouldSkipError(err)) return ER_CONTINUE; void* context = 0; const wchar_t* lastFuncToSkip = L"debug_OnError"; wchar_t buf[400]; wchar_t err_buf[200]; error_description_r(err, err_buf, ARRAY_SIZE(err_buf)); swprintf_s(buf, ARRAY_SIZE(buf), L"Function call failed: return value was %ld (%ls)", err, err_buf); return debug_DisplayError(buf, DE_MANUAL_BREAK, context, lastFuncToSkip, file,line,func, suppress); } ErrorReaction debug_OnAssertionFailure(const wchar_t* expr, atomic_bool* suppress, const wchar_t* file, int line, const char* func) { void* context = 0; const std::wstring lastFuncToSkip = L"debug_OnAssertionFailure"; wchar_t buf[400]; swprintf_s(buf, ARRAY_SIZE(buf), L"Assertion failed: \"%ls\"", expr); return debug_DisplayError(buf, DE_MANUAL_BREAK, context, lastFuncToSkip.c_str(), file,line,func, suppress); } Index: ps/trunk/binaries/data/mods/public/hwdetect/hwdetect.js =================================================================== --- ps/trunk/binaries/data/mods/public/hwdetect/hwdetect.js (nonexistent) +++ ps/trunk/binaries/data/mods/public/hwdetect/hwdetect.js (revision 8663) @@ -0,0 +1,107 @@ +/* Copyright (c) 2010 Wildfire Games + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + +This script is for adjusting the game's default settings based on the +user's system configuration details. + +The game engine itself does some detection of capabilities, so it will +enable certain graphical features only when they are supported. +This script is for the messier task of avoiding performance problems +and driver bugs based on experience of particular system configurations. + +*/ + +function RunDetection(settings) +{ + // List of warning strings to display to the user + var warnings = []; + + // TODO: add some mechanism for setting config values + // (overriding default.cfg, but overridden by local.cfg) + + + // Extract all the settings we might use from the argument: + // (This is less error-prone than referring to "settings.foo" directly + // since typos will be caught) + + // OS flags (0 or 1) + var os_unix = settings.os_unix; + var os_linux = settings.os_linux; + var os_macosx = settings.os_macosx; + var os_win = settings.os_win; + + // Should avoid using these, since they're disabled in quickstart mode + var gfx_card = settings.gfx_card; + var gfx_drv_ver = settings.gfx_drv_ver; + var gfx_mem = settings.gfx_mem; + + // Values from glGetString + var gl_vendor = settings.gl_vendor; + var gl_renderer = settings.gl_renderer; + var gl_version = settings.gl_version; + var gl_extensions = settings.gl_extensions.split(" "); // split on spaces + + var video_xres = settings.video_xres; + var video_yres = settings.video_yres; + var video_bpp = settings.video_bpp; + + var uname_sysname = settings.uname_sysname; + var uname_release = settings.uname_release; + var uname_version = settings.uname_version; + var uname_machine = settings.uname_machine; + + var cpu_identifier = settings.cpu_identifier; + var cpu_frequency = settings.cpu_frequency; // -1 if unknown + + var ram_total = settings.ram_total; // megabytes + var ram_free = settings.ram_free; // megabytes + + + // NVIDIA 260.19.* UNIX drivers cause random crashes soon after startup. + // http://www.wildfiregames.com/forum/index.php?showtopic=13668 + // Fixed in 260.19.21: + // "Fixed a race condition in OpenGL that could cause crashes with multithreaded applications." + if (os_unix && gl_version.match(/NVIDIA 260\.19\.(0[0-9]|1[0-9]|20)$/)) + { + warnings.push("You are using 260.19.* series NVIDIA drivers, which may crash the game. Please upgrade to 260.19.21 or later."); + } + + + return { "warnings": warnings }; +} + +global.RunHardwareDetection = function(settings) +{ + //print(uneval(settings)+"\n"); + + var output = RunDetection(settings); + + //print(uneval(output)+"\n"); + + if (output.warnings.length) + { + var msg = output.warnings.join("\n\n"); + Engine.DisplayErrorDialog(msg); + } +}; Property changes on: ps/trunk/binaries/data/mods/public/hwdetect/hwdetect.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property